十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Hyperledger Fabric一体化实战:资产交易防伪溯源同链实现

Hyperledger Fabric一体化实战:资产交易防伪溯源同链实现 简介基于Fabric超级账本的企业资产管理、交易、防伪、溯源一体化区块链解决方案面向区块链与Java方向的开发者以及计算机相关专业的在校师生。该方案覆盖从链码开发、业务接口到前端展示的完整技术链路既可用于毕业设计、课程设计也适合作为企业级区块链项目的初期原型。压缩包共收录2000个文件大小16.33MB以Go语言核心链码、Java业务代码、Python辅助脚本为主同时包含Markdown文档、HTML页面、Shell部署脚本及YAML配置等文件类型丰富便于按模块查阅与二次开发。项目代码均经测试运行成功并获得导师认可、答辩评审分95分质量可靠。内置详细文档可帮助读者理解Fabric网络搭建、智能合约编写、数据上链与溯源查询等关键环节在此基础上修改即可扩展其他业务功能。目前已有52人学习下载适合需要快速上手区块链开发或完成相关课题的读者参考。1. 为什么企业资产、交易、防伪、溯源都压在Fabric同一张链上一条链同时做资产管理、交易、防伪和溯源听上去像把四个业务系统硬塞进一个分布式数据库但Hyperledger Fabric恰好是这个场景少有的合理选择。它和公链的核心差异在于“许可”不是任何人都能读写账本而是由联盟成员共同维护。企业需要防伪溯源时真正的问题不是“数据能不能公开”而是“各参与方如何按同一套规则记账、又互不篡改”。Fabric用通道隔离、背书策略和Raft排序服务把这个边界划得很清楚。这套方案的实际形态通常是一个Fabric网络下挂多通道、多链码而不是一条链一个超级智能合约。适合正在做资产数字化或供应链改造、想把商品溯源和交易确权合到一套账本上的团队。2. Fabric网络如何支撑“资产交易防伪溯源”通道、链码与状态设计2.1 先分清Fabric哪几层在替你扛数据排序、背书、世界状态Fabric的交易流程可以压缩成三步客户端向背书节点提交提案背书节点执行链码并返回读写集Orderer节点把交易打包排序进区块Peer节点验证区块后提交到本地账本和世界状态。你写链码时只跟两个存储打交道账本Ledger和世界状态World State。账本是区块链本身只追加、不可篡改世界状态是当前所有资产的“快照”用一个Key-Value数据库承载。理解了这个分层再看资产管理就顺了。资产的确权信息应该放世界状态因为它需要被频繁查询和更新资产的历史流转、防伪哈希的存证时间、溯源事件则天然落在账本上。链码里所有读操作都走世界状态但如果你需要证明“某个资产在某个时间点存在过”靠世界状态不够得回溯账本里该Key的历史版本。这就是Fabric官方链码开发里常说的GetHistoryForKey的用途。排序服务选Raft而不是Kafka是当前Fabric 2.x的默认答案。Raft不需要额外维护ZooKeeper集群三个Orderer节点就能组成共识组对中小规模部署友好得多。生产环境至少要三个Orderer副本否则单点故障直接干掉交易排序。2.2 最小资产链码定义资产结构与改动规范先看一个能覆盖四类业务的最小Go链码它定义了资产结构、创建资产、转移资产、追加溯源事件和查询的方法。这个骨架可以直接放进fabric-samples/asset-transfer-basic里改造。package main import ( encoding/json fmt time github.com/hyperledger/fabric-contract-api-go/contractapi ) // TraceEvent 溯源事件只追加不修改 type TraceEvent struct { EventType string json:EventType Operator string json:Operator Location string json:Location Timestamp string json:Timestamp Note string json:Note } // Asset 资产结构同时服务资产、交易、防伪、溯源四类需求 type Asset struct { AssetID string json:AssetID Owner string json:Owner Value int json:Value AntiFakeHash string json:AntiFakeHash TraceEvents []TraceEvent json:TraceEvents } type SmartContract struct { contractapi.Contract } // CreateAsset 创建资产写入资产ID、持有人、初始价值和防伪哈希 func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, assetID string, owner string, value int, antiFakeHash string) error { exists, err : s.AssetExists(ctx, assetID) if err ! nil { return err } if exists { return fmt.Errorf(资产 %s 已存在, assetID) } // 只有Org1MSP可以创建新资产防伪码必须由发证方写入 mspID, err : ctx.GetClientIdentity().GetMSPID() if err ! nil { return fmt.Errorf(读取MSP失败: %v, err) } if mspID ! Org1MSP { return fmt.Errorf(仅Org1MSP可创建资产当前调用方是 %s, mspID) } asset : Asset{ AssetID: assetID, Owner: owner, Value: value, AntiFakeHash: antiFakeHash, TraceEvents: []TraceEvent{}, } assetJSON, err : json.Marshal(asset) if err ! nil { return err } return ctx.GetStub().PutState(assetID, assetJSON) } // TransferAsset 资产交易先校验持有人再写入新持有人 func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, assetID string, from string, to string) error { asset, err : s.GetAsset(ctx, assetID) if err ! nil { return err } if asset.Owner ! from { return fmt.Errorf(资产 %s 当前持有人是 %s不是 %s, assetID, asset.Owner, from) } asset.Owner to // 交易本身也是一条溯源事件 asset.TraceEvents append(asset.TraceEvents, TraceEvent{ EventType: TRANSFER, Operator: from, Location: 链上交易, Timestamp: time.Now().Format(time.RFC3339), Note: fmt.Sprintf(从 %s 转移到 %s, from, to), }) assetJSON, err : json.Marshal(asset) if err ! nil { return err } return ctx.GetStub().PutState(assetID, assetJSON) } // AddTraceEvent 追加溯源事件不覆盖已有记录 func (s *SmartContract) AddTraceEvent(ctx contractapi.TransactionContextInterface, assetID string, eventType string, location string, operator string, note string) error { asset, err : s.GetAsset(ctx, assetID) if err ! nil { return err } asset.TraceEvents append(asset.TraceEvents, TraceEvent{ EventType: eventType, Operator: operator, Location: location, Timestamp: time.Now().Format(time.RFC3339), Note: note, }) assetJSON, err : json.Marshal(asset) if err ! nil { return err } return ctx.GetStub().PutState(assetID, assetJSON) } // GetAsset 查询资产当前状态 func (s *SmartContract) GetAsset(ctx contractapi.TransactionContextInterface, assetID string) (*Asset, error) { assetJSON, err : ctx.GetStub().GetState(assetID) if err ! nil { return nil, fmt.Errorf(读取资产 %s 失败: %v, assetID, err) } if assetJSON nil { return nil, fmt.Errorf(资产 %s 不存在, assetID) } var asset Asset err json.Unmarshal(assetJSON, asset) if err ! nil { return nil, err } return asset, nil } func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, assetID string) (bool, error) { assetJSON, err : ctx.GetStub().GetState(assetID) if err ! nil { return false, fmt.Errorf(读取资产 %s 失败: %v, assetID, err) } return assetJSON ! nil, nil } func main() { chaincode, err : contractapi.NewChaincode(SmartContract{}) if err ! nil { panic(err) } if err : chaincode.Start(); err ! nil { panic(err) } }代码里最值得注意的约束是CreateAsset的MSP校验。资产管理最怕“谁都能发资产”所以我把创建权限限定到Org1MSP这个策略可以在链码里用GetClientIdentity().GetMSPID()判断也可以在通道的背书策略里统一声明。两者各有侧重链码内判断适合“不同函数不同权限”的场景背书策略适合“整包链码限定背书组织”的场景生产环境建议同时做。TransferAsset检查持有人之后再更新是资产交易里最简单也最关键的防错逻辑。Asset结构里把溯源事件以数组形式放在世界状态是一种取舍。它牺牲了“部分更新”的粒度换来查询时的整体性读取一次GetAsset就能拿到当前持有人和全部溯源记录不需要跨Key拼接。缺点是面对大量事件时数组会膨胀所以单个资产的溯源事件上万条时应改成按时间分片存储后面会提到。2.3 状态数据库选CouchDB还是LevelDB直接影响防伪查询Fabric的世界状态库有两个选择LevelDB只支持Key查询和范围查询CouchDB支持富查询和JSON条件检索代价是额外占用Peer节点资源和内存。这个一体化方案里我一般直接选CouchDB原因很实际防伪码查询很少是“给我asset001”而是“所有防伪码前缀为A001的商品有哪些”这用范围查询可以凑合但“按持有人查全部资产”“按事件类型统计溯源记录”这类运营需求只有CouchDB的JSON查询能体面地完成。// GetAssetsByOwner 按持有人查询资产需要CouchDB富查询支持 func (s *SmartContract) GetAssetsByOwner(ctx contractapi.TransactionContextInterface, owner string) ([]*Asset, error) { queryString : fmt.Sprintf({selector:{Owner:%s}}, owner) resultsIterator, err : ctx.GetStub().GetQueryResult(queryString) if err ! nil { return nil, err } defer resultsIterator.Close() var assets []*Asset for resultsIterator.HasNext() { queryResponse, err : resultsIterator.Next() if err ! nil { return nil, err } var asset Asset err json.Unmarshal(queryResponse.Value, asset) if err ! nil { return nil, err } assets append(assets, asset) } return assets, nil }富查询的selector语法沿用MongoDB风格注意一点GetQueryResult在背书阶段执行如果查询结果集很大会拖慢背书耗时甚至超时。防伪追溯场景下我会顺手给CouchDB建一个索引比如按Owner字段建JSON索引避免全表扫描。索引写入META-INF/statedb/couchdb/indexes/owner.json随链码一起打包部署{ index: { fields: [Owner] }, ddoc: indexOwnerDoc, name: indexOwner, type: json }CouchDB部署在Peer节点旁生产环境同样要持久化卷和定期快照。很多Fabric部署出问题都是Peer和CouchDB同时重启后状态不一致所以docker-compose里务必给CouchDB配--volume挂在宿主机。3. 资产→交易→防伪→溯源在链码里把四件事写成可调用的方法3.1 资产数字化与确权防伪码和持有人从哪来资产管理这块的落地路径本质上做的是“真实世界资产RWA数字化”把一件实物商品映射成一个链上唯一的资产ID。映射关系必须在线下完成通常做法是生产商生成全球唯一资产ID和防伪码同时给实物打码再把assetID 防伪码 商品描述交给CreateAsset接口上链。如果线下映射和线上写账不是同一个操作员那么链上数据的可信度就打了折扣这是方案设计时必须在文档里讲清楚的。确权还分两个层面所有权归谁以及谁能修改资产信息。所有权通过Owner字段体现修改权限靠MSP校验和背书策略控制。实际业务里生产商和物流商的修改权应该不同所以链码内校验MSP比背书策略更灵活。比如生产商能写防伪哈希物流商只能追加Location类型的溯源事件不能改持有人。3.2 交易与转让双花问题靠MVCC兜底资产交易最怕的是“一笔货卖给两个人”。Fabric处理双花和其他公链不一样它没有PoW共识而是依赖世界状态的版本号做MVCC冲突检测。同一个Key被同一个区块里的两个交易同时修改时只有一个能提交成功另一个会因为读集版本过期被拒绝。链码层要做的就是配合这个机制——先读再写而不是粗暴地PutState覆盖。拿TransferAsset来说它先GetAsset拿到世界状态里的版本再修改后PutState。两个客户端同时对资产A001发起转让时Raft排序后进同一个区块的两个交易都会带上读取时的版本号第二个交易在Peer提交阶段被判定为MVCC冲突不会覆盖第一个交易的结果。你的应用层代码要捕获到这个错误提示“资产正被其他交易处理请重试”而不是让用户以为链上真的出现了双花。Fabric还有一个容易踩的路把重量、温度这类数值字段设计成“增量更新”比如UpdateValue(assetID, delta)。每次读改写都基于旧值做加减法并发修改会把某一次的更新吞掉。正确做法是记录完整的Value快照或者用独立的增量事件去重放别在世界状态里做累加。3.3 防伪存证防伪码、哈希与实物锚点的关系防伪的业务逻辑比技术逻辑更绕。链上能做的是“数据存证”比如某个防伪码在某个时间点被登记哈希值是多少验证方可以拿实物扫码结果和链上哈希比对一致即认为该防伪码在链上“存在过”。但请注意链上有个防伪码不代表对应的实物一定是真的。中间那根线是线下锚定——防伪码必须以不可剥离的方式物理附着在商品上并且不能被批量复制。这是方案里最容易造假的一环。上链时我一般会做两层哈希。第一层是防伪码本身的SHA256第二层是“防伪码商品序列号生产批次”组合后的哈希。只存第一层的话验证方拿到防伪码就能碰撞存第二层可以兼顾商品溯源和防伪验真。链码里对应方法是CreateAsset时把组合哈希写入AntiFakeHash字段验证时调用GetAsset取出哈希再做同样的组合计算# 防伪码 ABC123序列号 SN8848批次 B202403 echo -n ABC123SN8848B202403 | sha256sum验证方把算出的哈希和链上AntiFakeHash比对。这种方案的完整路径是生产系统生成组合字符串链码只保存哈希验证系统把实物扫码结果重新组合并比对。这个过程必须写进给客户的详细文档否则技术上线了业务方还是不知道怎么验真。3.4 溯源查询只追加不删除区块高度就是时间戳溯源的本质是“每个动作都留下不可抵赖的记录”。链码里要把所有溯源事件作为TraceEvent追加到资产下删除和修改只对事件数组整体操作不允许单独改某一条。TransferAsset已经把交易写进TraceEvents了物流过程用AddTraceEvent追加。查询端拿到的是带有完整时间线的事件数组按时间排序后即是该资产的流转史。Fabric在账本层面还提供另一层溯源能力GetHistoryForKey(assetID)返回该Key每次世界状态变更的历史版本和对应交易ID。业务溯源用TraceEvents数据审计用GetHistoryForKey两者并不冲突。前者是你要给客户看的“商品流转记录”后者是给监管方和审计看的“链上操作记录”详细文档里最好把这两层分开写避免业务方混淆。// GetHistory 查看资产Key的所有历史状态变更 func (s *SmartContract) GetHistory(ctx contractapi.TransactionContextInterface, assetID string) ([]map[string]interface{}, error) { historyIterator, err : ctx.GetStub().GetHistoryForKey(assetID) if err ! nil { return nil, err } defer historyIterator.Close() var history []map[string]interface{} for historyIterator.HasNext() { response, err : historyIterator.Next() if err ! nil { return nil, err } record : map[string]interface{}{ txId: response.TxId, timestamp: response.Timestamp, isDelete: response.IsDelete, value: string(response.Value), } history append(history, record) } return history, nil }GetHistoryForKey返回的timestamp取自区块时间来自Orderer排序时写入的区块头不会因为某个Peer本地时钟不准而漂移。这个特性让溯源记录天然拥有可信时间戳。注意该接口返回的是世界状态的历史不是业务事件的历史所以我在生产方案里会让GetHistory专注审计场景业务溯源界面则优先渲染TraceEvents。4. 把方案跑起来Fabric网络部署与链码上链的最短路径4.1 网络组件与最小部署形态一个可运行的Fabric分布式应用至少包含Peer、Orderer、CA、CLI和状态数据库五类组件。Peer负责执行业务链码和保存账本Orderer负责把交易排序成区块CA签发身份证书CLI用来向网络发送管理命令CouchDB是Peer的世界状态库。最小开发网络可以是两个组织各一个Peer节点、一个单节点Raft排序服务但生产环境至少三个Orderer、每个组织两个Peer跨可用区部署。部署形态上我强烈建议用Docker Compose起组件不要裸装二进制。Fabric官方镜像hyperledger/fabric-peer、hyperledger/fabric-orderer、hyperledger/fabric-ca都是现成的版本号保持和fabric-samples一致避免peer和orderer的版本协议对不上。核心组件的关系和端口规划可以按下面这张表去对齐组件镜像默认端口用途Peerfabric-peer7051gRPC、7052Chaincode执行链码、保存账本与世界状态Ordererfabric-orderer7050gRPC交易排序、出块CAfabric-ca7054HTTPS身份证书颁发CouchDBcouchdb5984HTTPPeer的世界状态存储CLIfabric-tools无管理命令入口端口冲突是最常见的部署事故多个网络共用一个宿主机时我会把Peer的7051改成映射宿主机的9051、10051这种偏移端口。版本升级时注意CA镜像的命名空间证书结构和2.2之后保持一致但老的客户端库未必兼容新CA升级前先看变更日志。4.2 configtx.yaml与通道配置的核心参数通道是Fabric隔离数据和权限的边界。这个“资产交易防伪溯源”的方案通常在同一个网络里建多个通道生产方和物流方共用一个溯源通道销售方和购买方共用一个交易通道防伪验证服务走只读通道。配置全部集中在configtx.yaml里生成创世块的命令是configtxgen。Profiles: AssetChannel: Consortium: SampleConsortium Application: Organizations: - Org1 - Org2 Capabilities: Channel: V2_0 Application: V2_0 Orderer: OrdererType: etcdraft Addresses: - orderer.example.com:7050 EtcdRaft: Consenters: - Host: orderer.example.com Port: 7050 ClientTLSCert: path/to/tls/client-cert.pem ServerTLSCert: path/to/tls/server-cert.pem Options: TickInterval: 500ms ElectionTick: 10 HeartbeatTick: 1 MaxInflightBlocks: 5 SnapshotIntervalSize: 16MB这里有几个直接影响共识表现的参数。ElectionTick和HeartbeatTick控制了Raft leader的选择频率和心跳间隔HeartbeatTick必须小于ElectionTick比例一般保持ElectionTick 10 * HeartbeatTick否则网络抖动会频繁触发leader选举。SnapshotIntervalSize是Raft日志压缩的触发阈值16MB适合多数场景太小会让节点频繁做快照产生额外IO太大在链上交易增长迅速时会拖慢重启后的同步。通道创建后每个组织都要给本组织的Peer节点设置锚点让不同组织的Peer能通过Gossip协议发现对方并同步账本。锚点配置在configtx.yaml的Organizations.Org1.AnchorPeers里指定该组织对外可达的Peer地址。漏配锚点是跨组织账本不同步的头号原因排查时登录两个Peer各自执行peer channel getinfo -c assetchannel比对最大区块高度是否一致。4.3 安装、批准、提交链码的命令序列Fabric 2.x之后的链码生命周期不再是简单的peer chaincode install加instantiate而是引入打包、安装、批准、提交四步。关键是“批准”这个动作通道内每个组织都要对链码定义表示同意达到策略要求的背书数量后才能提交上链。# 1. 打包链码 peer lifecycle chaincode package assetcc.tar.gz \ --path ../chaincode/asset/go \ --lang golang \ --label assetv1 # 2. 安装链码包到Peer节点 peer lifecycle chaincode install assetcc.tar.gz # 3. 查询安装后的包IDapproveformyorg需要用到 peer lifecycle chaincode queryinstalled # 4. Org1批准链码定义 peer lifecycle chaincode approveformyorg \ -o orderer.example.com:7050 \ --channelID assetchannel \ --name assetcc \ --version 1.0 \ --package-id assetcc:包ID \ --sequence 1 \ --tls \ --cafile ${ORDERER_CA} # 5. 切换到Org2的管理员身份再次批准 export CORE_PEER_LOCALMSPIDOrg2MSP export CORE_PEER_MSPCONFIGPATH${PWD}/organizations/peerOrganizations/org2.example.com/users/Adminorg2.example.com/msp peer lifecycle chaincode approveformyorg \ -o orderer.example.com:7050 \ --channelID assetchannel \ --name assetcc \ --version 1.0 \ --package-id assetcc:包ID \ --sequence 1 \ --tls \ --cafile ${ORDERER_CA} # 6. 提交链码定义 peer lifecycle chaincode commit \ -o orderer.example.com:7050 \ --channelID assetchannel \ --name assetcc \ --version 1.0 \ --sequence 1 \ --peerAddresses peer0.org1.example.com:7051 \ --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem \ --peerAddresses peer0.org2.example.com:7051 \ --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem \ --tls \ --cafile ${ORDERER_CA}package-id参数不是随便写的严格执行queryinstalled返回的结果格式是assetcc:hash。--sequence表示链码定义版本升级的序号首次部署填1以后升级代码时递增成2、3可以理解为链码的“版本提交次数”。approveformyorg和commit都要带TLS参数因为Fabric 2.x在通道里强制TLS双向认证漏了--cafile或者根证书路径不对命令会直接报连接错误。提交完成后用peer chaincode invoke验证资产创建和查询是否正常。调用时注意环境变量已经切回Org1peer chaincode invoke \ -o orderer.example.com:7050 \ --channelID assetchannel \ --name assetcc \ -c {Args:[CreateAsset, A001, Org1MSP, 100, a1b2c3d4e5f6]} \ --tls \ --cafile ${ORDERER_CA} peer chaincode query \ --channelID assetchannel \ --name assetcc \ -c {Args:[GetAsset, A001]} \ --tls \ --cafile ${ORDERER_CA}invoke和query的区别在于前者会产生交易需要经过背书、排序、提交全套流程耗时更长后者只从本地世界状态读取不用出块。做高频查询的业务界面必须走query如果误用invoke去做只读查询Raft排序服务会被无意义的空交易刷爆。4.4 资料包里的“全部资料详细文档”应该怎么被使用标题里的“全部资料详细文档.zip”这类压缩包在实际交付里通常是五个目录network/放docker-compose和configtx配置chaincode/放链码源码application/放Java或Node.js的SDK调用示例docs/放下文说的部署手册和接口文档test/放压力测试脚本和测试用例。拿到压缩包第一步不是看代码是先跑networks脚本确认能不能把整个fabric网络拉起来。文档部分最容易被忽视的是“参数与环境的对应表”。Fabric部署除了版本号之外还有操作系统内核参数、Docker版本、磁盘IOPS要求这些硬性指标这些不写清楚换个环境就会踩莫名其妙的坑。按我的经验一份合格的Fabric部署文档至少应该包含一页纸的网络拓扑图、每个组件的环境变量清单、启动顺序CA先于Peer、Orderer先于通道创建、故障排查手册节点日志在哪、账本不同步怎么处理、证书过期怎么轮换。5. 一体化方案验收从数据验证到压测的几个关键手段5.1 链上数据与企业数据库的Hash校验上线之后首先要验证的不是防伪查询而是Fabric账本、CouchDB世界状态、企业业务数据库三者是否一致。常见做法是写一个定时任务对账从业务数据库读资产的主数据做SHA256后从Fabric读出对应资产的AntiFakeHash比对一致就通过。不一致时先看CouchDB里的文档值再看Fabric账本的历史记录定位是哪一端的更新丢了。# 资产哈希对账脚本片段 import hashlib from fabric_sdk_py import Gateway # 实际SDK以项目为准 def verify_asset(gateway, asset_id, business_data: dict): contract gateway.get_network(assetchannel).get_contract(assetcc) chain_asset contract.evaluate_transaction(GetAsset, asset_id) business_hash hashlib.sha256( f{business_data[code]}{business_data[serial]}{business_data[batch]}.encode() ).hexdigest() return chain_asset[AntiFakeHash] business_hash, chain_asset对账任务本身要落库记录每次比对结果否则无法追踪问题在哪个环节发生。5.2 用区块链浏览器验证Raft排序一致性部署一套Hyperledger Explorer能直观看到通道里的区块高度、交易数量和每个区块的哈希。验证时重点关注的不是“有多少交易”而是两个Peer的区块高度差和时间差。正常情况下同一通道内Org1和Org2的Peer最大区块高度应该始终一致误差超过一个区块就要查Gossip协议是否正常。如果只有Org1有交易但Org2账本一直不涨大概率是锚点没配好。5.3 用Caliper压测背书节点和排序服务生产上线前用Hyperledger Caliper做一次基础压测重点看三个指标TPS每秒交易数、平均延迟、MVCC冲突率。简单的固定速率压测配置如下test: name: asset-transfer-benchmark workers: type: local number: 4 rounds: - label: create-asset txDuration: 60 rateControl: type: fixed-rate opts: tps: 50 workload: module: benchmarks/create-asset.js压测结果里最容易被忽略的是MVCC冲突率。如果冲突率超过5%说明业务上大量并发写同一资产这时要么改造应用层做分区不同资产走不同链码实例要么接受冲突后重试的延时。排序服务的SnapshotIntervalSize这次可能调成32MB但如果压测中看到Orderer日志频繁输出snapshot相关警告就说明快照太频繁是IO瓶颈的信号。先把那条按Owner建的CouchDB索引建上再回头调这些参数才是让这套一体化方案在生产里稳定运行的正确顺序。本文还有配套的精品资源点击获取
返回列表