Fabric MVCC 简介

2023-12-02 14:33:07 浏览数 (3)

什么是 MVCC

在Hyperledger Fabric中,MVCC(Multi-Version Concurrency Control,多版本并发控制)是一个重要的机制,用于管理区块链上的数据版本。MVCC的目标是允许多个事务并发执行,而不会导致数据一致性问题。以下是有关Fabric中MVCC问题的详细介绍:

  1. 多版本数据存储:Fabric使用MVCC来处理世界状态(World State)数据的版本控制。世界状态是指当前区块链状态的一个快照,其中包含了所有已提交的事务的最新状态。MVCC通过在世界状态中维护多个版本的数据来实现。
  2. 数据版本标识:每个数据项都有一个版本标识,通常是一个递增的数字或时间戳。当执行事务时,它会检查数据项的版本,以确保事务是基于最新的数据状态进行操作的。
  3. 事务并发处理:MVCC允许多个事务同时对相同的数据进行读取,而不会产生冲突。每个事务都会根据其启动时间戳或其他标识来选择适当版本的数据。这使得多个事务可以并发执行,提高了整个系统的吞吐量。
  4. 事务提交和版本更新:当事务成功执行并提交时,它将更新相关数据项的版本信息。这样,下一个事务在读取这些数据时就会获得新的版本标识。这确保了对数据的修改是有序且不会产生冲突的。
  5. 避免冲突和回滚:MVCC有助于避免事务之间的冲突,因为每个事务都基于其读取数据时的版本进行操作。如果两个事务试图同时修改相同的数据项,Fabric将会检测到冲突,并且其中一个事务可能需要回滚。
  6. 查询历史数据:MVCC还允许查询历史数据,即过去某个时间点的数据状态。这对于审计和追溯数据变更非常有用。

MVCC 冲突

MVCC冲突(Multi-Version Concurrency Control)在Hyperledger Fabric或其他使用MVCC机制的区块链系统中,通常在以下情况下会发生:

  1. 并发事务修改相同数据项:当两个或多个事务同时试图修改相同的数据项时,可能发生MVCC冲突。这包括多个事务同时尝试写入相同的键值对。
  2. 读取已被修改的数据:如果一个事务在读取数据时,另一个事务已经修改了该数据,那么在提交时可能会引发MVCC冲突。这是因为读取事务在执行期间依赖的数据版本不再是最新的。
  3. 并发提交时的版本检查:当两个事务同时提交,并且它们的写集中包含相同的键值对时,MVCC机制会执行版本检查。如果检测到冲突,其中一个事务可能需要回滚,以确保数据的一致性。
  4. 网络分区和延迟:在分布式系统中,网络分区和延迟也可能导致MVCC冲突。当节点之间的通信出现问题或者由于网络延迟,导致事务在某些节点上的执行时间差异较大时,可能会出现冲突。
  5. 合约逻辑引发的冲突:智能合约中的业务逻辑可能引发MVCC冲突。例如,一个合约可能先查询某个数据项的值,然后基于该值执行一些逻辑,最后尝试写入新的值。如果在此期间其他事务修改了该数据项,就可能引发冲突。

需要注意的是,MVCC冲突并不是一种错误,而是一个分布式系统中需要处理的正常情况。系统通过检测和解决这些冲突,确保事务的一致性和正确性。在出现冲突时,通常需要有一定的机制来处理,例如回滚事务并重新执行,以确保所有的事务都基于最新的数据状态。

源码分析

在Hyperledger Fabric中,出块前背书节点会对接收到的交易进行验证,最新(2023年12月2日)代码的实现位于core/ledger/kvledger/txmgmt/validation目录下的validator.go中,具体实现如下:

代码语言: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)会验证读写集中的版本与世界状态中的是否一致,不一致会导致交易交验失败。committedVersionreadVersion类型为version.Height,其定义位于core/ledger/internal/version

代码语言:go复制
// 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)进行许可,使用时请注明出处。


0 人点赞