Skip to content

08 · 扩容指南

说明

本章的扩容/单点结论以代码与配置为准(cbdc-biz、cbdc-network/configs、fabric-x-orderer、fabric-x-committer)。表中的容量数字(TPS、RTT、吞吐、内存换算等)是经验估算,代码中无对应配置,仅作部署参考。"bank-node" 对应本 wiki 的 institution

扩容矩阵

平面组件水平扩容?怎么扩上限信号
业务bizhub✅ N 实例共享 cbdc_bizhub 库,前置 gRPC LB;worker 走 FOR UPDATE SKIP LOCKED 天然互斥settlement 表 DB 负载
业务issuer⚠️ 逻辑单实例同一 issuer 身份只能一个写端;HA 用主备,不要多活单机 CPU / 网络
业务endorser✅ N 实例每实例一个 FSC P2P 端口;客户端 topology 加多个 endorser endpointP2P session 数
业务auditor(签名侧)⚠️ 单实例签名私钥唯一 + ttxdb/auditdb 单写;只做查询侧横向扩单机 CPU + auditdb
业务auditor(查询侧)✅ N 实例只读副本从 auditdb 副本拉,不参与签名auditdb 读 QPS
业务institution-00N✅ 每家 N 实例Redis 分布式 token_selector + sticky routing by walletIDRedis 并发
账本orderer Router✅ N 实例/org加 endpoint,客户端 LB网络入口带宽
账本orderer Batcher✅ 按 shardshard_id,不是加副本单 shard 吞吐
账本orderer Consenter❌ 仅随 Org 数增加 OrdererOrg = 加 MSP + channel 配置更新BFT N ≥ 3f+1
账本orderer Assembler✅ 多副本/org(备份 Deliver)不增吞吐,增可用性
账本committer Sidecar⚠️ 1:1 绑 Coordinator随 Coordinator 扩Orderer Deliver 流
账本committer Coordinator⚠️ 单活跃 stream上游 SDK 现有代码不支持多活;按 namespace 分片可多组streamActive
账本committer Verifier✅ N 实例Coordinator 连多个 verifier 做 client-side LB单机 CPU(吞吐由 ECDSA 单核能力决定,无代码配置)
账本committer VC Service⚠️ 实例级可多,worker 级先扩默认 Preparer=1/Validator=1/Committer=20fabric-x-committer/service/vc/config.go:71-73),先调大单机 CPU / DB 写
账本committer Query Service✅ N 实例直接 LB,走 committer-db 读副本读副本 IOPS
存储committer-db⚠️ 主库单点读副本 + 迁 YugabyteDB(cbdc-network/configs/test-full-kms-yugabyte.yaml 已有 type: yugabyte 外部 DB 模式)主库 WAL
存储biz-db(PG18,cbdc_local_db⚠️ 主库单点读副本;PgBouncer;按银行分库PG backend 数
存储Redis⚠️ 单实例Redis Cluster(分片)或 Sentinel(HA)单实例 CPU
身份cacbdc-ca-server-kms 镜像)✅ N 实例共享 PG(fabric-ca 原生支持 cluster;本地默认 db.type: sqlite3),前置 LBCA PG
身份Sign-KMS✅(取决于厂商)KMS 后端自己的 HA 方案
观测OTLP collector各 app 通过 OTLP_ENDPOINT 导出 trace/metric(cbdc-biz/.env.example:43conf/*/core.yaml: observability.tracing.endpoint),后端自行选型/扩容

图例:✅ 可水平扩 · ⚠️ 有限制或需架构改动 · ❌ 不可水平扩

副本前必须想清楚的三件事

每次上副本之前对照本表。大多数扩容事故不是"加机器不够",而是 DB / 证书 / 机型里有一件搞错。

组件副本间 DB 关系机器侧重副本间证书关系
bizhub共享 cbdc_bizhub(settlement 靠 FOR UPDATE SKIP LOCKED 分工)CPU + 内存;RTGS 有 SMB 阻塞,不吃磁盘每副本独立 TLS 证书,同父 CA;服务身份一致(SAN 覆盖 LB 域名)
issuer(主备)共享 cbdc_issuer 库 — ttxdb / tokendb 是 single-writerCPU(ECDSA + ZK)+ 网络主备共享同一身份证书,私钥在 Sign-KMS 同一 slot
endorser每副本独立 cbdc_endorser_N 库(TMS 部署状态 + ttxdb 是 per-FSC-node 的)CPU(签名验证 + 代理)+ 网络(大量 P2P session)每副本独立身份证书,同父 CA;FSC topology 里列全
auditor 签名侧共享 cbdc_auditor 库 — single-writerCPU(ZK 验证 + ECDSA)+ 内存(3 SDK 缓存)签名实例唯一身份
auditor 查询侧cbdc_auditor 只读副本IO + 内存(auditdb 读 + 规则缓存)与签名侧共享身份
institution-00N同一 logical institution 的多副本共享本行 DB;不同 institution 不共享CPU(选币 + 签名)+ 内存(pendingTxStore 进程内);Redis 网络同一 institution 的多副本共享身份证书不同 institution 证书独立
orderer Router无 DBCPU + 网络(gRPC + CRC64 + 转发)每副本独立 TLS + 独立 MSP 身份,同 OrdererOrg
orderer Batcher无共享 DB;每节点本地 WAL磁盘 IO(WAL fsync)+ 内存(mempool 默认 MemPoolMaxSize=1000000fabric-x-orderer/config/defaults.go:23,由 config/generate/local_config_gen.go:251 写入每个 batcher 配置)每 Batcher 独立身份,同 OrdererOrg MSP
orderer Consenter本地 Consensus ledger + WAL,不共享CPU + 跨节点网络(BFT 投票,延迟敏感)每 Consenter 独立身份,BFT 投票 per-identity
orderer Assembler本地 Assembler ledger,不共享磁盘 IO(区块落盘 + Deliver fanout)每副本独立身份
committer Sidecar无 DB网络 + 内存(Orderer Deliver stream relay)TLS 证书,同 Peer Org MSP
committer Coordinator无持久化 DB(DependencyGraph 内存)CPU + 内存(依赖图分析)TLS 证书
committer Verifier无 DB(纯函数式签名验证)纯 CPU(ECDSA 验签);具体 sigs/s 为经验估算、代码无对应配置每副本独立 TLS
committer VC Service共享 committer-db(所有 VC 副本写同一个 state DB)CPU + DB 连接(MVCC 验证 + PG 写入)每副本独立 TLS
committer Query Service共享 committer-db 主库或读副本(推荐读副本)IO + CPU(DB 读 + gRPC 序列化)每副本独立 TLS
committer-db本身是 DB磁盘 IO(NVMe 强制)+ 内存(shared_buffers)仅 TLS 连接证书
biz-db本身是 DB磁盘 IO + 连接数仅 TLS
Redis本身是 DB内存(索引 + lock)+ CPU(单线程)自身无 MSP;TLS 可选
cacbdc-ca-server-kms 镜像)集群模式需共享后端 PG(本地默认 db.type: sqlite3,fabric-ca 原生支持 cluster)CPU(RSA / ECDSA 签发)所有 CA 实例共享同一 root + signing cert

三条一般规则

  1. 身份 ≠ 副本数

    • "身份"(MSP cert + Token SDK wallet key)由业务角色决定,不由副本数决定。
    • issuer / auditor 签名侧 / 每个 institution-00N 各是一个身份,无论副本数。
    • endorser 与 orderer 所有角色相反:每副本就是独立身份(签名进入不同集合:BFT 投票 / FSC responder 路由)。
  2. DB 共享 ≠ 身份共享

    • bizhub 多副本共享 cbdc_bizhub,但 TLS 证书独立。
    • endorser 多副本每个独立 DB,证书也独立。
    • "DB 共享 + 证书共享" 的只有 issuer / auditor 签名侧(active-passive 语义)。
    • 判断标准:看 MSP 是否唯一(信道权限决定)。
  3. 机型选择的核心矛盾

    • CPU 密集:orderer Consenter、committer Verifier、auditor 签名侧、issuer、institution 选币
    • IO 密集:committer-db、biz-db、orderer Batcher WAL、committer Assembler
    • 内存密集:Redis、orderer Batcher mempool、institution pendingTxStore
    • 网络密集:orderer Router 入口、Consenter BFT 投票、committer Sidecar relay
    • 同机混部时优先把"同类型压力"放一起;committer-db 与 orderer Batcher 不要混部(都抢 IO)。

不能水平扩的单点

节点原因DB 归属证书归属HA 方案
issuer 签名侧私钥 + Token SDK 写一致性主备共享 ttxdb共享身份Active-passive
auditor 签名侧同上主备共享 auditdb共享身份Active-passive
orderer Consenter(单 org 内)BFT 同身份多活跃冲突本地 ledger共享 MSP 身份Active-passive;故障等 view change
committer CoordinatorstreamActive 单活跃锁内存状态共享 Peer Org 身份Active-passive 或 fork 改 per-namespace
committer-db 主库PG 单主本身是 DB仅 TLS流复制主备 + Patroni
biz-db 主库同上同上仅 TLS同上
Redis(Memory 模式 token_selector)进程内状态同上仅 TLS切 Distributed 模式后解除
每家 institution 同一 walletID 的 pendingTxStore进程内 map副本共享本行 DB + 共享身份持久化 inflight 后解除 sticky routing

扩容前的检查清单

代码侧

  • [ ] token_selector 已切换到 Distributed (Redis) 模式,否则 institution 多实例不能工作(cbdc-biz/conf/institution-001/core.yaml: token_selector.mode
  • [ ] FinalityWorker 已持久化(防故障后 inflight watcher 丢失)
  • [ ] bizhub / institution 的 ApprovalWorker 走乐观锁(ClaimApproval,已是)
  • [ ] HTTP / gRPC 每请求有 deadline

部署侧 DB

  • [ ] bizhub 多副本连同一cbdc_bizhub
  • [ ] issuer / auditor 主备连同一ttxdb / auditdb
  • [ ] endorser 每副本各自独立cbdc_endorser_N
  • [ ] 同一 institution 多副本连本行同一 DB;不同 institution 各自独立 DB
  • [ ] committer 所有 VC 副本连同一 committer-db
  • [ ] Query Service 连 committer-db 只读副本
  • [ ] auditor 查询侧连 auditdb 只读副本
  • [ ] 所有 PG 实例 max_connections ≥ (app pool × app 实例数) + 50

部署侧证书

  • [ ] issuer 主备:同一份证书 + 私钥(或 KMS 同 slot)
  • [ ] auditor 签名侧与查询侧:共享证书(查询侧不需要私钥)
  • [ ] 同一 institution 多副本:共享身份证书
  • [ ] 不同 institution 之间:证书独立
  • [ ] endorser 每副本:独立身份证书,同父 CA
  • [ ] orderer 每副本(R/B/C/A):独立身份,同 OrdererOrg MSP
  • [ ] committer Verifier 每副本:独立 TLS 即可(Verifier 验签不走 Fabric MSP)
  • [ ] fabric-ca 集群:共享 root + signing cert

部署侧机型 / 资源

  • [ ] Orderer Consenter 同机房低 RTT(BFT 投票延迟敏感;具体毫秒阈值为经验估算,代码无此配置)
  • [ ] committer-db 落 NVMe + 带电池 RAID 控制器;committer Batcher 同
  • [ ] Verifier / Consenter 选高频 CPU(ECDSA 单核吞吐决定)
  • [ ] Redis 内存按期望峰值 UTXO 数配置(换算公式如 2 × UTXO 数 × 512B 为经验估算,代码无此字段)
  • [ ] LB 支持 gRPC 长连接 + keepalive
  • [ ] Sticky routing 写在 LB 配置里(institution 按 walletID hash)
  • [ ] 告警加 PostgreSQL connection saturation > 80% / Redis memory > 80% / PG replica lag > 5s

测试侧

  • [ ] 压测基线已跑,知道每组件 CPU / memory / 连接数基线
  • [ ] 混沌测试:kill 一个 institution / bizhub 副本,验证不丢单
  • [ ] 双活测试:两个 institution 副本同时处理同一 walletID 的 prepare(应该只有一个成功)

FAQ

Q1:issuer 能否 active-active?

不能。除非重写 Token SDK 的 ttxdb 为分布式事务系统。中小吞吐下 active-passive 够用(具体 TPS 阈值为经验估算,代码无对应配置)。

Q2:多 bank 是否每家 institution 单独 Redis?

不需要。一个 Redis 实例承载所有 bank 的 token_selector 够用,key 自带 {walletID} 前缀隔离。Redis Cluster 时按 hash tag 自动分片。

Q3:Orderer / Committer 能否跨城?

  • 同城 BFT + 异地灾备:推荐。BFT 节点同城低 RTT,异地只做 Deliver 订阅复制。
  • 跨城 BFT:每次 Decision 多一跳跨城 RTT,延迟显著放大。不推荐。(以上 RTT / 延迟数字均为经验估算,代码无对应配置)

Q4:institution 增加副本要改哪些地方?

  1. compose / k8s manifest 加副本
  2. 每副本独立 cert + FSC identity(用同一 bank 的父证书签)
  3. LB 前置,sticky by walletID
  4. 不需要改 bizhub / issuer / auditor 配置(它们对 institution 副本透明)

Q5:什么时候才需要扩容?

cbdc-network/configs/test-full.yaml 默认拓扑为 4 个 OrdererOrg × 4 节点(router/batcher/consenter/assembler)= 16 orderer、6 committer 组件(committer-db / validator / verifier / coordinator / sidecar / query-service)、5 个 biz app(test-full.yaml:26-113130-157)。扩容的触发信号不是某个固定 TPS 值,而是资源:CPU / DB wait / Redis mem / goroutine 泄漏,任意一个先到就扩对应那一层(具体阈值百分比为运维经验值,代码无对应配置)。

5 万 TPS 参考部署模型

口径声明(务必先读)

本节给出一套以 5 万 TPS 持续 token transfer 为目标的部署模型,沿用本章既有口径:

  • 拓扑、扩容维度、配置旋钮、瓶颈定位全部出自代码,逐条附 路径:行号
  • 唯一的非事实是「每节点吞吐 → 副本数」里的单节点吞吐:仓库内无任何基准数字,下文一律标注 【规划假设】,给出推导公式与 cbdc-stress 校准方法,请用实测值替换示例值。
  • 底座按逻辑拓扑给出,映射 cbdc-network/configs/test-full*.yaml(账本面)与 cbdc-biz/docker/compose-local-run.yml(业务面),k8s / 多机均可落地。
  • 「5 万 TPS」只指 token transfer。发行 / 赎回经 BizHub + RTGS(SMB MT 报文)走带外批发结算面,不在此热路径——fabric-x-committer/ 全仓对 bizhub / rtgs 零引用,settlement 仅 ISSUE / REDEEM 两类(cbdc-biz/app/bizhub/internal/types/settlement.go:61),其速率单独规划。

逐笔 transfer 关键路径

每笔 transfer 同步穿过五段:发起行 NewAuditAndEndorseViewcbdc-biz/pkg/views/transfer.go:217)→ Endorser 注册 AuditResponderView 转发至 Auditor RequestAuditViewendorser/cmd/endorser/main.go:107-110auditor/cmd/auditor/main.go:205-209)→ Orderer → Committer。

⚠️ 关键结论:Auditor 签名侧是当前代码的吞吐天花板

每笔 transfer 都强制经 Auditor 同步验证 + 签名才能上链,且该签名在协议层不可省:链上 validator 对配置了 auditor 的 PP 强制 1-of-N auditor 签名,缺失即 ErrAuditorSignaturesMissingfabric-token-sdk@v0.11.0 token/core/common/validator_auditing.go:22-77)。而 Auditor 签名侧无法横向扩,证据:

约束出处
单逻辑身份、单私钥(wallet 仅一个 auditors 条目)cbdc-biz/conf/auditor/core.yaml:189-205
协议层把所有 auditor 公钥视作同一逻辑实体,多公钥只用于轮换、不做 per-namespace/per-institution 拆分token/core/common/validator_auditing.go:22-77
auditdb 写入用进程内 sync.RWMutex 按 enrollmentID 加锁(非 DB 行锁,不跨进程)fabric-token-sdk@v0.11.0 token/services/storage/auditdb/store.go:278-298
每笔审计要做全局 holdings / ruleset / 撤销检查(拆分多实例会各自只持局部审计视图)cbdc-biz/pkg/views/audit.go:171-260
shard 概念只存在于只读 indexer 侧,且 Phase 1 固定单 shard auditor-0;签名侧无分片机制auditor/internal/conf/conf.proto:38-48conf/auditor/core.yaml:44-53

含义:5 万 TPS 下 Auditor 只能纵向压榨单进程:

  • 加核 + 调大 audit_worker_pool.size(默认 120,硬上限 32 × GOMAXPROCSconf/auditor/core.yaml:32-34pkg/workerpool/pool.go:49,60-66)以并行 ZK 验证;
  • 签名走 HSM(PKCS#11 → sign-kms,conf/auditor/core.yaml:194-205),提高 sign-kms 每分区 session pool(默认 5,硬上限 30sign-kms/pkg/pkcs11/session_pool.go:17-21)、换更高标称 QPS 的 HSM(sign-kms/README.md:129:SoftHSM ~11000、Luna S790 22000 tps,README 标称值、非代码)。

单进程上限 R_aud = min(核数 × 单核「验 ZK + 审计 + 签名」速率, HSM 签名 QPS)若实测 R_aud < 50000,当前代码无法达标 5 万 TPS——这是协议/架构问题,调副本/调参都解决不了,必须先做架构改造(sharded auditor 仅见于已与代码脱节的 docs/,代码中不存在)。上线前第一步:用 cbdc-stress 实测单 Auditor 上限。

逐层扩容规划

下表「扩容机制 / 旋钮 / 约束」均代码事实;「副本/分片数」列是按 ⌈50000 / R_x⌉ 的推导式,R_x 为【规划假设】单节点吞吐。

扩容机制(代码事实,附旋钮默认值与出处)副本/分片数约束
Institution 发起行按发起方钱包尾号(walletID)分片:每分片若干副本,前提:①token_selector.mode: distributed + 共享 Redis(默认 memoryconf/institution-001/core.yaml:35-36);②网关按 sender 钱包尾号路由(详见下方钱包尾号分片 —— Redis 选币键已按 walletID 隔离,分片间无 UTXO 争用,distributed_store_redis.go:155-160⌈50000 / R_inst⌉ZK 证明生成为 CPU 热点(gnark-crypto,go.mod:51,70),institution 进程专设 ZK worker pool;BatchQueryBalances 并发硬上限 16wallet_biz.go:25-28);submit 亲和见下方注
Endorser加副本,每副本独立 FSC 身份 + 独立 DB cbdc_endorser_N(FSC 池 maxOpenConns=50conf/endorser1/core.yaml:26-33⌈50000 / R_end⌉启动需手动 grpcurl :9300 EndorserService/Initcbdc-biz/README.md:122
Auditor 签名侧❌ 仅纵向(见上)1R_aud<50000 → 架构阻断
Auditor 查询/索引侧indexer 加 shard(shard: auditor-0/-1),与签名路径隔离(独立 goroutine/通道,背压只暂停 Deliver 流)(finality_capture_worker.go:39-54按只读 QPS不缓解签名瓶颈
Orderer Batcher加 shard(= 给每 party 增加 batcher 端点,ShardID=i+1fabric-x-orderer/config/generate/local_config_gen.go:258-266)。注意 test-full.yaml 4 个 batcher 全是 shard_id:1(单分片 4 副本),加副本不加吞吐⌈50000 / R_shard⌉ shard单 shard 同时刻仅一个主 batcher 切批((Shard+term)%Nbatcher_role.go:170-178);批上限 MaxMessageCount=10000 / AbsoluteMaxBytes=10MB / BatchCreationTimeout=500msMemPoolMaxSize=1000000BatchSequenceGap=10config/defaults.go:21-25,56-69request/pool.go:71-87
Orderer Consenter(BFT)加 party = 加容错非加吞吐N=len(Consenters)N≥3f+1common/utils/state.go:13-19consensus_builder.go:267-272N=4(f=1) 或 N=7(f=2)跨城 BFT 每决议多一跳 RTT,不推荐(见 Q3
Committer Verifier加 endpoint(coordinator client-side LB,verifier.endpoints 列表,service/coordinator/config.go:16)+ 进程内 parallelism(默认 4,模板设 40service/verifier/config.go:31cmd/config/templates/verifier.yaml.tmpl:7⌈50000 / R_ver⌉验签支持 ECDSA/BLS/EDDSA + MSP 策略(utils/signature/verify.go:91-110
Committer VC(validator-committer)加 endpoint(validator-committer.endpoints 列表,coordinator/config.go:17)+ 调 worker max-workers-for-committer(默认 20service/vc/config.go:71-73);所有 VC 共写同一 state DB⌈50000 / R_vc⌉DB 连接池 max-connections 默认 20vc/config.go:68);逐笔单事务写 state(vc/database.go:217-296
Committer Coordinator❌ 单活跃流:streamActive.TryLock 同一时刻只允许一条 sidecar→coordinator 流(coordinator.go:52-58,291-294);Sidecar:Coordinator 为 1:1(sidecar/config.go:231 / 链单链单 coordinator;多 coordinator 需多 namespace/channel 编排,committer 代码无 namespace 分片
committer-dbpostgres 单主(逻辑单写,test-full-kms-postgres.yaml:191-205)→ 切 committer.database.type: yugabyte(3 tserver、load_balancetable-pre-split-tabletstest-full-kms-yugabyte.yaml:196-213⌈50000 / R_db⌉ tserver(RF=3)postgres 忽略预分片(vc/dbinit.go:32-34);读副本只供 Query Service
HSM(institution/auditor 签名)调大每分区 session pool(530)+ 多分区 + 更快设备受 Auditor 单点约束,详见下方 HSM(Luna S790)需要几台GenerateKeyPair 按分区串行、Sign 可并发至 pool 大小(sign-kms/internal/biz/kms_biz.go:26-28,268-317
Redis(distributed selector)多 institution 副本共享单 Redis,键按 walletID 隔离(distributed_store_redis.go:154-1701(按内存/CPU 扩)当前用单节点 NewClient 非 Cluster;所有副本 bucket_boundaries 必须一致否则拒启动(distributed_store_redis.go:126-152

Institution 按钱包尾号分片

发起行是除 Auditor 外唯一需要进程内状态亲和的热路径组件,按发起方钱包尾号(或 walletID 哈希)做确定性分片是它最干净的水平扩容方式。代码事实支撑:

  • 选币天然按钱包分区:distributed 选币的 Redis 键格式为 selector:{walletID}:bucket:...pkg/token_selector/distributed_store_redis.go:155-160),单钱包内原子选币+标记。按钱包尾号分片后各分片处理互不相交的发起方钱包集,分片间零 UTXO 争用。
  • 不新增身份/库:同一银行(institution_code: "001"conf/institution-001/core.yaml:16)所有分片共享单 FSC 身份 + 单库 cbdc_institutioncore.yaml:38-44,52)。分片是请求路由 + 进程内状态局部性,不是像 endorser 那样每副本独立身份/库。(跨不同银行才是独立 institution_code + 独立部署,属多银行而非分片。)

submit 步骤需要显式亲和(代码现状)

pendingTx 在 prepare 时按 TxID 存入进程内 store(service/transfer_service.go:81biz.go:41,46-58,TTL 2min),submit 时从同进程 Poptransfer_service.go:109)。但两个请求携带的字段不同:

  • PrepareTransferRequestsender(钱包),可按尾号路由(api/cbdc-biz/v1/institution.proto:739-745);
  • SubmitTransferRequest 只有 tx_id + signature不带 senderinstitution.proto:764-767)——网关在 submit 时看不到钱包,无法仅凭尾号路由到同一分片。

因此尾号分片对选币/Prepare 侧成立,但 submit 亲和需额外机制三选一:①客户端记住 prepare 落在哪个分片,submit 直连该分片;②把分片提示编码进返回的 tx_id,让网关按 tx_id 反解路由;③把 pendingTxStore 改为共享存储(如 Redis),任意分片都能 Pop,从根本上取消亲和要求。仓库内既无尾号路由器,也无共享 pendingTxStore,三者均需自建。 另:FinalityWorker 状态也在进程内(app/institution/internal/worker/finality_worker.go:57-76),分片/副本崩溃会丢失 in-flight 终局监听,需一并持久化。

HSM(Luna S790)需要几台

先厘清哪些签名真正落到中心 HSM——这决定 HSM 负载,不能直接拿 50000 去除:

每笔 transfer 的签名是否经 sign-kms→HSM出处
发起方 owner 签名(量最大,每笔 1 枚)外部注入,银行自托管 X.509,不占中心 HSMpkg/views/transfer.go:662,694NewPreSubmittedSignatureSigner
Auditor sigma(每笔强制 1 枚)✅ auditor 钱包 BCCSP Default: PKCS11libkms_pkcs11.so → HSMconf/auditor/core.yaml:124,194-205
Endorser 背书签名(每笔 1 枚)BCCSP Default: PKCS11conf/endorser1/core.yaml:80
Institution 节点 proposal 签名(每笔 1 枚)✅ PKCS11:RequestApprovalView 起始 tx.EndorseProposal() 用节点 fabric 身份签conf/institution-001/core.yaml:104-108、fabric-token-sdk token/services/network/fabric/endorsement/fsc/initiator.go:77
Institution 节点 envelope 签名(每笔 1 枚,与 proposal 同 key、独立一次 Sign✅ PKCS11:背书返回后 tx.Envelope()CreateEndorserSignedTXfsc/initiator.go:106、fabric-smart-client core/generic/transaction/envelope.go
Issuer 签名仅 issuance/redeem,不在 transfer 热路径conf/issuer/core.yaml:80

关键约束(代码事实):

  • 一把 key 绑一台 HSMHSM.providers 是列表(sign-kms/internal/conf/conf.proto:51-52conf/core.yaml hsm.providers),但每个 namespace 经 ns.ProviderID 解析到唯一 provider(sign-kms/internal/biz/kms_biz.go:58-87)。代码「同一把 key 的签名负载均衡到多台」的逻辑——多 provider 只能让不同 key 落到不同 HSM。
  • Auditor 是单签名身份且不可分片(见上方关键结论)→ auditor 那把 key 钉在一台 Luna 上。
  • 单台 Luna S790 标称 22,000 tps(ECC P-256)——此数字来自 sign-kms/README.md:129 的标称值,非代码常量;单分区并发签名上限 = session pool(默认 5、最大 30sign-kms/pkg/pkcs11/session_pool.go:18-19),Sign 不串行(仅 GenerateKeyPair 按分区串行,kms_biz.go:26-28)。

结论(22k tps 为【规划假设】·README 标称)

  1. Auditor 是 HSM 侧的硬约束:每笔 transfer 至少 1 枚 auditor 签名,50000 tps 即需 50000 auditor-sign/s。按 README 22k tps,⌈50000/22000⌉ = 3 台——但代码 one-key-one-provider,这 3 台只能以 Luna HA-group(Cryptoki 库层把单逻辑 slot 摊到多台物理 HSM,属厂商/部署层,本仓库无法核实)形式服务同一 auditor key;否则代码默认把 auditor 钉在 1 台 ≈ 22k tps,达不到 50k。且 auditor 同时受单进程 ZK 验证 CPU 约束(R_aud=min(CPU, HSM)),HSM 够也未必够。
  2. Endorser + 节点身份签名是额外负载,但 endorser 可多副本、各持独立 key(ns.ProviderID 不同),天然可摊到多台 HSM/多分区。
  3. 每笔 transfer 的中心-HSM 签名数 = 4(代码可证):auditor σ 1 + endorser 背书 1 + institution 节点 2(proposal fsc/initiator.go:77 + envelope fsc/initiator.go:106,同一把 key 两次独立 Sign);owner 外部注入不计。故 HSM 台数 ≈ ⌈(4 × 50000) / 单台实测 tps⌉,且负载不均——institution 那把 key 独占一半签名量,分片时应优先把它隔离到独立 provider/设备。单台真实 tps 仍须实测替代标称值(sign-kms/pkg/pkcs11/ 自带 TestSignThroughput)。

SoftHSM2(开发/测试后端)的串行天花板

sign-kms/README.md:119-128 实测(Apple M4 Pro):pool=1/并发 1 ≈ 10,200 tps;pool=10/并发 50 ≈ 11,400 tps——并发 ×50 吞吐只涨 12%,即签名路径串行,单实例上限 ≈ 1/单次签名延迟(~100µs ⇒ ~11K),与核数无关。三个推论(代码事实):

  1. 同一 SoftHSM2 实例内多切 token 不增加吞吐——token 只是同一 .so(同进程、同内部锁)里的逻辑分区;
  2. sign-kms/conf/core.yaml:27-38 现有 2 个 provider 指向同一个 libsofthsm2.so,dlopen 同库共享实体,并行增益为零
  3. 要真并行须多个独立 sign-kms/SoftHSM2 进程实例 + 按 key 分片(CreateNs(providerID, …) 已支持 per-namespace 指定 provider,sign-kms/internal/biz/kms_manage_biz.go:54-57)。

按每笔 4 次签名折算:单 SoftHSM2 实例 ≈ 11K/4 ≈ 2,750 笔/s 的签名面地板。生产换真 HSM(Luna 标称 22K)也只是 ~2 倍,分片架构同样适用。

变体:Auditor 改用本地密钥(BCCSP SW)

把 auditor 的两处 BCCSP 从 PKCS11 切到 SW(节点身份 conf/auditor/core.yaml:122-124、auditors 钱包 :196,注释明确「Can be SW or PKCS11」),即用进程内软件密钥库做本地 ECDSA P-256 签名(Hash: SHA2 / Security: 256),绕开 sign-kms→HSM。影响:

  • 去掉 auditor 的 HSM 制约:消除每签名一次 KMS gRPC RTT 与单台 Luna ≈ 22k tps 的吞吐上限。auditor 需要的 Luna 台数 = 0;HSM 仅剩 endorser / issuer / 其它仍配 PKCS11 的节点身份在用。
  • ➡️ 瓶颈从 HSM 移到单进程 CPU:本地软件签名远快于 HSM 往返,签名不再是瓶颈;auditor 吞吐 R_aud 回落为「单进程(ZK 验证 + 审计入账 + 本地签名)吞吐」,由核数与单笔 ZK 验证成本决定(受 audit_worker_pool32×GOMAXPROCS 约束,pkg/workerpool/pool.go:49)。能否达 50k 取决于实测 R_aud,需 cbdc-stress 验证。
  • 不解除架构天花板:auditor 仍是单签名身份、单进程、auditdb 进程内锁、协议 1-of-N 视所有 auditor 公钥为同一逻辑实体(token/core/common/validator_auditing.go:22-77)。本地签名只去掉 R_aud=min(CPU, HSM) 里的 HSM 项,没让 auditor 可横向分片;若单进程 ZK-验证 CPU 仍 < 50k,依旧需架构改造。
  • ⚠️ 安全权衡(非性能):SW 模式下 auditor 私钥落在进程内存 / 磁盘软件 keystore,失去 HSM 隔离防护——对央行审计签名密钥是显著降级。技术上可切(BCCSP 支持),是否接受属安全策略决定。

示例算例(数字均为【规划假设】·示例值·非实测)

下表把 R_x 代入示例假设值得出一套具体副本数,仅为说明推导方式。这些吞吐数字代码中无依据,必须用 cbdc-stress 实测替换cbdc-stress -url http://<inst>:9502 -c <并发> -d 60s -mode ringcbdc-stress/main.go:26-35,输出 P50/P90/P99 + 失败原因)。

组件【假设】单节点吞吐(示例)推导副本/分片数校准方法
Institution500 tx/s⌈50000/500⌉ = 100单 institution 实例 ring 压测,观察 ZK 证明 CPU 饱和点
Endorser2,000 tx/s⌈50000/2000⌉ = 25压测时看 FSC P2P session 与 CPU
Auditor 签名侧R_aud(需实测)1单 auditor 持续压测,R_aud<50000 即架构阻断
Orderer shard5,000 tx/s⌈50000/5000⌉ = 10 shard单 shard batcher 切批速率 + 下游消化能力
Committer Verifier4,000 tx/s⌈50000/4000⌉ = 13单 verifier parallelism=40 下验签速率
Committer VC3,000 tx/s⌈50000/3000⌉ = 17单 VC committer=20 下写 state 速率
committer-db6,000 wtx/s/tserver⌈50000/6000⌉ = 9 tserveryugabyte 单 tserver 写 QPS(热点 tablet 看是否需预分片)

Consenter 取 N=4(f=1,与 test-full.yaml 4 OrdererOrg 一致)或 N=7(f=2)按容错需求定,与吞吐无关。

network(账本面)部署形态与单链上限

cbdc-network 编排的账本面 = Orderer(Arma,4 角色)+ Committer(6 服务)。默认拓扑(configs/test-full.yaml:4 个 OrdererOrg、channel_id: arma 单链、committer 各组件各 1 个)与 5 万 TPS 部署对照:

平面组件默认(test-full.yaml)5 万 TPS:机制 + 【规划假设】数量
OrdererRouter每 org 1 → 4入口 gRPC 扇入;Router 按 CRC64 取模分 shard(node/router/mapper.go:40-48),按 org 加副本 + 客户端 LB
OrdererBatcher每 org 1、全 shard_id:1单分片 4 副本加 shard 提吞吐:每 party 每 shard 各 1 个,batcher 总数 = party 数 N × shard 数 S。取示例 S=10N=440 batcher
OrdererConsenter每 org 1 → 4BFT 排的是 BAF 批元数据(非交易体),N=len(Consenters)N≥3f+1N=4(f=1) 或 N=7(f=2)。批级吞吐,非 per-tx 瓶颈
OrdererAssembler每 org 1 → 4把各 shard 批次拼回单条有序账本;加副本增可用性非吞吐
CommitterSidecar1拉那条有序块流,1:1 绑 Coordinatorsidecar/config.go:23
CommitterCoordinator1单活跃流、1 个/链coordinator.go:52-58),逐笔做依赖图——账本面漏斗,见下
CommitterVerifier1扇出:coordinator.yaml verifier.endpoints 加地址 + 进程内 parallelism(模板 40)。取示例 13 个
CommitterVC(validator-committer)1扇出:validator-committer.endpoints 加地址 + max-workers-for-committer=20;共写同一 state DB。取示例 17 个
CommitterQuery Service1按只读 QPS 加副本,走 committer-db 读副本
Committercommitter-db1(postgres 单写)committer.database.type: yugabyte。取示例 9 tserver(RF=3)

(示例 S/V/M/tserver 数量来自上方示例算例R_x 假设,须用 cbdc-stress 实测替换。)

账本面有两个 per-chain 串行点(先于"数 verifier/VC"的瓶颈):

  1. Committer Coordinator——streamActive 同时刻只允许一条 sidecar→coordinator 流(coordinator.go:52-58)、sidecar:coordinator 为 1:1。整条链每笔交易都经这一个 coordinator 做依赖分析,是账本面的瓶颈(类比业务面的 auditor)。committer 代码 namespace/channel 分片,无法让多 coordinator 并列同一条链。
  2. Orderer 共识 + Assembler——所有 shard 的批次经一个 BFT 组全序、由 assembler 拼成一条账本(channel_id: arma 单链)。共识排的是批元数据(cheap,50k tx 按批 ≤1 万条即 ≤5 批/s),账本仍是单条。

含义:orderer 可用 shard 把"切批"并行化,但 committer 又把所有交易重新串行汇入一个 coordinator。单链能否扛 50k,取决于单 coordinator 的持续吞吐——若 < 50k,唯一出路是多链 / 多 channel(每链一套独立 orderer+committer 栈),而 cbdc-network 默认单 channel_id、committer 代码无多链分片旋钮,需在编排层自建。部署前第一步:cbdc-stress 实测单 coordinator 与单 shard batcher 的持续吞吐,据此定 shard 数与是否需要多链。

附:fabric-x 账本面 20 万 TPS 配置(见参考资料)

账本面(Arma orderer + committer)的 20 万 TPS 详细配置单、Arma 论文 LAN/WAN 实测数据、以及全部数据溯源,已整理至 参考资料 · Fabric-X 性能基准与数据溯源

要点(均 IBM 实测,eprint 2023/1717)——真实部署 ~42 节点账本面达 >200K TPS(端到端)/ >400K(组件级):排序面 28 节点(4 party ×(4 batcher + router + consenter + assembler),4p×4s 排序峰值 430K tps)+ committer 面 14 节点(3 verifier + 9 VC/DB 同机 + 1 coordinator + 1 sidecar,峰值 474K/280K TPS);单节点 96 核 / 64 GB / 1 TB SSD / 10 Gbps 裸金属,论文 §6.4 记为「每组织 21 台」。注意:这是 IBM 的 UTXO CBDC 样例负载(300 B / 读写集 1–4)——本系统 cbdc-biz 端到端仍被单 auditor 卡在万级(见这套系统实际能到多少 TPS),"整套 cbdc 系统 20w" 在当前代码下不成立。完整实测记录、配置单与溯源见 参考资料

落地这套模型要改的配置

改动文件 / 键(出处)
institution 切分布式选币conf/institution-001/core.yaml:22-36 token_selector.mode: distributed + redis 块;所有副本 bucket_boundaries 一致
orderer 加分片cbdc-network/configs/test-full*.yaml 给每 party 增加 batcher 端点(当前全 shard_id:1),重跑 config-builder gen-compose
committer 加 verifier/VCcoordinator.yamlverifier.endpoints / validator-committer.endpoints 列多地址(samples/coordinator.yaml:49-66);verifier parallelism、vc max-workers-for-committer / database.max-connections
committer-db 换分布式committer.database.type: yugabytetest-full-kms-yugabyte.yaml),多 tserver + 预分片
每角色独立 PG + 调连接数当前本地 max_connections=300compose-local-run.yml:48-57)< 各 FSC 池之和(issuer 20 + endorser 50 + auditor 160 + institution 200 = 430),生产须每角色独立 PG 实例并按 池 × 实例数max_connections

必须先补的代码缺口(否则模型不成立)

  1. Auditor 单写天花板——见上,5 万 TPS 前最大风险,需协议/架构改造(代码中无 sharded auditor)。
  2. Institution 尾号分片路由 + submit 亲和 + inflight 持久化——按发起方钱包尾号分片需自建网关路由;且因 SubmitTransferRequest 不带 sender、pendingTxStore 进程内按 TxID 存取,submit 亲和需额外机制(客户端 pin / tx_id 编码分片提示 / 共享 pendingTxStore),FinalityWorker 的 in-flight 也需持久化。三者仓库内均无实现,详见钱包尾号分片
  3. Committer Coordinator 单活跃流——单链单 coordinator;要多 coordinator 必须按 namespace/channel 拆多链,committer 代码无 namespace 分片旋钮。
  4. DB 连接预算——按上表每角色独立 PG 并显式设 max_connections
  5. Endorser Init 手动——每个 endorser 副本启动后需 grpcurl EndorserService/Initcbdc-biz/README.md:122),多副本要脚本化。

这套系统实际能到多少 TPS

没有端到端基准

仓库内任何端到端 TPS 实测数据,给不出"实测能跑多少"。以下是代码可推的上界 + 瓶颈链,非实测值。唯一的吞吐实测锚点是下面两个组件级数字(均为上游文档/README,非本部署端到端)。

组件级实测锚点数值出处
单台 Luna S790 ECDSA P-256 签名标称 22,000 tps(SoftHSM ~11,000)sign-kms/README.md:128-129
ZK transfer 证明生成 BN254 2in2out / 10 workers~241 ops/s(P50 ~41ms/笔,≈24 笔/s/核)fabric-token-sdk/docs/drivers/benchmark/core/dlognogh/dlognogh.md:192-257

(本部署 token 曲线确认为 BN254:tokengen 默认 BN254 且 gen_crypto.sh 未传 --ariescbdc-biz/scripts/gen_crypto.sh:85-89。证明验证比生成快,但该 benchmark 未给出本配置的验证 tps。)

TPS 被「每笔都过、不可横向扩」的单实例点封顶,链条 Orderer → Coordinator → Auditor

  1. 硬上界 = auditor 的 HSM 签名 ≈ 22,000 tps(Luna)/ ~11,000(SoftHSM)。每笔 transfer 必过 auditor,且确认每笔仅 1 次 token-level HSM 签名(fabric-token-sdk token/services/ttx/auditor.go:386-392,整笔一条 MarshallToAudit 消息,与 input/output 数无关),auditor 签名密钥钉在单台 HSM(ns.ProviderID 一对一)。默认 HSM 签名下,无论其它层扩多少,系统 TPS 不超过这个数。
  2. 真实持续值更低,被两个单实例逐笔串行点压制,二者都无端到端基准:
    • Auditor 单进程逐笔 ZK 验证(BN254 bulletproof 范围证明,zkatdlog/nogh/v1/auditor.go:47-86,逐 transfer/input/output 循环,不可跨笔批量验证)——同源的证明生成实测每笔 ~41ms,验证更快但仍受单进程 + audit_worker_pool ≤ 32×GOMAXPROCS 约束。
    • Committer Coordinator 单 goroutine + 单 mutex 逐笔依赖图fabric-x-committer service/coordinator/dependencygraph/global_dependency_manager.go:44,124-188,默认 NumOfLocalDepConstructors=1),且单活跃流 1 个/链。
  3. Orderer 不是该链条瓶颈:BFT 排的是批级 BAF(每批 ≤ 10000 tx),50k tx → 极少数共识决议(fabric-x-orderer node/batcher/batcher_role.go:319-379config/defaults.go:56-68)。

结论(上界 + 瓶颈分析,非实测):当前架构(单 auditor + 单 coordinator + 默认 HSM 签名)下,可持续 TPS 数量级在「万级」、上界约 1–2 万,而非 5 万。要逼近 5 万须先解除单点:① auditor 改 SW 本地签名去掉 22k 的 HSM 上界(变体)→ 瓶颈移到单进程 ZK 验证;② auditor 分片(代码中不存在,受协议 1-of-N + 进程内 auditdb 锁约束);③ committer 多链 / 多 coordinator(代码无多链分片)。在这些之前,本章「5 万 TPS 部署模型」里 auditor 与 coordinator 两层的副本数改变不了上界——先用 cbdc-stress 实测单 auditor 与单 coordinator 的持续吞吐,再谈整体目标。

涉及的配置文件

改动文件
加 bizhub 实例cbdc-biz/docker/compose-local-run.yml + conf/bizhub-00N/
加 institution 实例cbdc-biz/docker/compose-local-run.yml + conf/institution-00N/ + LB 配置
加 orderer shardcbdc-network/configs/test-full.yaml + 重跑 config-builder gen-compose
加 OrdererOrg同上 + 通道配置更新 + MSP 注册
加 committer Verifier / Querycbdc-network/configs/test-full.yaml committer.components 段加条目
调 committer workercommitter 启动参数或配置 yaml(默认值 fabric-x-committer/service/vc/config.go:71-73,字段 max-workers-for-*
开 PG 读副本biz-db / committer-db compose 加 replica service + primary_conninfo
切 Redis Clustercbdc-biz/pkg/token_selector/distributed_store_redis.go 连接串 + cluster client

参考文档与数据溯源

本章性能 / 吞吐 / 硬件数字的全部来源(外部论文、上游官方文档、代码与基准依据、访问状态)已独立整理,见 参考资料 · Fabric-X 性能基准与数据溯源

要点:200k+ 出自 Arma 论文arXiv:2405.16575,排序 160 B 摘要的实测,已抓取);端到端 Fabric-X 白皮书eprint.iacr.org/2023/1717)PDF 受限(403)未取到 committer 硬件;HSM 22k / ZK 241 ops/s 等本部署上界依据见该文 C 节。