什么是 MVCC
在Hyperledger Fabric中,MVCC(Multi-Version Concurrency Control,多版本并发控制)是一个重要的机制,用于管理区块链上的数据版本。MVCC的目标是允许多个事务并发执行,而不会导致数据一致性问题。以下是有关Fabric中MVCC问题的详细介绍:
- 多版本数据存储:Fabric使用MVCC来处理世界状态(World State)数据的版本控制。世界状态是指当前区块链状态的一个快照,其中包含了所有已提交的事务的最新状态。MVCC通过在世界状态中维护多个版本的数据来实现。
- 数据版本标识:每个数据项都有一个版本标识,通常是一个递增的数字或时间戳。当执行事务时,它会检查数据项的版本,以确保事务是基于最新的数据状态进行操作的。
- 事务并发处理:MVCC允许多个事务同时对相同的数据进行读取,而不会产生冲突。每个事务都会根据其启动时间戳或其他标识来选择适当版本的数据。这使得多个事务可以并发执行,提高了整个系统的吞吐量。
- 事务提交和版本更新:当事务成功执行并提交时,它将更新相关数据项的版本信息。这样,下一个事务在读取这些数据时就会获得新的版本标识。这确保了对数据的修改是有序且不会产生冲突的。
- 避免冲突和回滚:MVCC有助于避免事务之间的冲突,因为每个事务都基于其读取数据时的版本进行操作。如果两个事务试图同时修改相同的数据项,Fabric将会检测到冲突,并且其中一个事务可能需要回滚。
- 查询历史数据:MVCC还允许查询历史数据,即过去某个时间点的数据状态。这对于审计和追溯数据变更非常有用。
MVCC 冲突
MVCC冲突(Multi-Version Concurrency Control)在Hyperledger Fabric或其他使用MVCC机制的区块链系统中,通常在以下情况下会发生:
- 并发事务修改相同数据项:当两个或多个事务同时试图修改相同的数据项时,可能发生MVCC冲突。这包括多个事务同时尝试写入相同的键值对。
- 读取已被修改的数据:如果一个事务在读取数据时,另一个事务已经修改了该数据,那么在提交时可能会引发MVCC冲突。这是因为读取事务在执行期间依赖的数据版本不再是最新的。
- 并发提交时的版本检查:当两个事务同时提交,并且它们的写集中包含相同的键值对时,MVCC机制会执行版本检查。如果检测到冲突,其中一个事务可能需要回滚,以确保数据的一致性。
- 网络分区和延迟:在分布式系统中,网络分区和延迟也可能导致MVCC冲突。当节点之间的通信出现问题或者由于网络延迟,导致事务在某些节点上的执行时间差异较大时,可能会出现冲突。
- 合约逻辑引发的冲突:智能合约中的业务逻辑可能引发MVCC冲突。例如,一个合约可能先查询某个数据项的值,然后基于该值执行一些逻辑,最后尝试写入新的值。如果在此期间其他事务修改了该数据项,就可能引发冲突。
需要注意的是,MVCC冲突并不是一种错误,而是一个分布式系统中需要处理的正常情况。系统通过检测和解决这些冲突,确保事务的一致性和正确性。在出现冲突时,通常需要有一定的机制来处理,例如回滚事务并重新执行,以确保所有的事务都基于最新的数据状态。
源码分析
在Hyperledger Fabric中,出块前背书节点会对接收到的交易进行验证,最新(2023年12月2日)代码的实现位于core/ledger/kvledger/txmgmt/validation
目录下的validator.go
中,具体实现如下:
// validateKVRead performs mvcc check for a key read during transaction simulation.
// i.e., it checks whether a key/version combination is already updated in the statedb (by an already committed block)
// or in the updates (by a preceding valid transaction in the current block)
func (v *validator) validateKVRead(ns string, kvRead *kvrwset.KVRead, updates *privacyenabledstate.PubUpdateBatch) (bool, error) {
readVersion := rwsetutil.NewVersion(kvRead.Version)
if updates.Exists(ns, kvRead.Key) {
logger.Warnw("Transaction invalidation due to version mismatch, key in readset has been updated in a prior transaction in this block",
"namespace", ns, "key", kvRead.Key, "readVersion", readVersion)
return false, nil
}
committedVersion, err := v.db.GetVersion(ns, kvRead.Key)
if err != nil {
return false, err
}
logger.Debugw("Comparing readset version to committed version",
"namespace", ns, "key", kvRead.Key, "readVersion", readVersion, "committedVersion", committedVersion)
if !version.AreSame(committedVersion, readVersion) {
logger.Warnw("Transaction invalidation due to version mismatch, readset version does not match committed version",
"namespace", ns, "key", kvRead.Key, "readVersion", readVersion, "committedVersion", committedVersion)
return false, nil
}
return true, nil
}
!version.AreSame(committedVersion, readVersion)
会验证读写集中的版本与世界状态中的是否一致,不一致会导致交易交验失败。committedVersion
和readVersion
类型为version.Height
,其定义位于core/ledger/internal/version
:
// Height represents the height of a transaction in blockchain
type Height struct {
BlockNum uint64
TxNum uint64
}
// NewHeight constructs a new instance of Height
func NewHeight(blockNum, txNum uint64) *Height {
return &Height{blockNum, txNum}
}
// NewHeightFromBytes constructs a new instance of Height from serialized bytes
func NewHeightFromBytes(b []byte) (*Height, int, error) {
blockNum, n1, err := util.DecodeOrderPreservingVarUint64(b)
if err != nil {
return nil, -1, err
}
txNum, n2, err := util.DecodeOrderPreservingVarUint64(b[n1:])
if err != nil {
return nil, -1, err
}
return NewHeight(blockNum, txNum), n1 n2, nil
}
// ToBytes serializes the Height
func (h *Height) ToBytes() []byte {
blockNumBytes := util.EncodeOrderPreservingVarUint64(h.BlockNum)
txNumBytes := util.EncodeOrderPreservingVarUint64(h.TxNum)
return append(blockNumBytes, txNumBytes...)
}
// Compare returns -1, zero, or 1 based on whether this height is
// less than, equals to, or greater than the specified height respectively.
func (h *Height) Compare(h1 *Height) int {
switch {
case h.BlockNum < h1.BlockNum:
return -1
case h.BlockNum > h1.BlockNum:
return 1
case h.TxNum < h1.TxNum:
return -1
case h.TxNum > h1.TxNum:
return 1
default:
return 0
}
}
// String returns string for printing
func (h *Height) String() string {
if h == nil {
return "<nil>"
}
return fmt.Sprintf("{BlockNum: %d, TxNum: %d}", h.BlockNum, h.TxNum)
}
// AreSame returns true if both the heights are either nil or equal
func AreSame(h1 *Height, h2 *Height) bool {
if h1 == nil {
return h2 == nil
}
if h2 == nil {
return false
}
return h1.Compare(h2) == 0
}
我正在参与2023腾讯技术创作特训营第四期有奖征文,快来和我瓜分大奖!
声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。