主题
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 endpoint | P2P session 数 |
| 业务 | auditor(签名侧) | ⚠️ 单实例 | 签名私钥唯一 + ttxdb/auditdb 单写;只做查询侧横向扩 | 单机 CPU + auditdb |
| 业务 | auditor(查询侧) | ✅ N 实例 | 只读副本从 auditdb 副本拉,不参与签名 | auditdb 读 QPS |
| 业务 | institution-00N | ✅ 每家 N 实例 | Redis 分布式 token_selector + sticky routing by walletID | Redis 并发 |
| 账本 | orderer Router | ✅ N 实例/org | 加 endpoint,客户端 LB | 网络入口带宽 |
| 账本 | orderer Batcher | ✅ 按 shard | 加 shard_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=20(fabric-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 |
| 身份 | ca(cbdc-ca-server-kms 镜像) | ✅ N 实例 | 共享 PG(fabric-ca 原生支持 cluster;本地默认 db.type: sqlite3),前置 LB | CA PG |
| 身份 | Sign-KMS | ✅(取决于厂商) | KMS 后端自己的 HA 方案 | — |
| 观测 | OTLP collector | — | 各 app 通过 OTLP_ENDPOINT 导出 trace/metric(cbdc-biz/.env.example:43、conf/*/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-writer | CPU(ECDSA + ZK)+ 网络 | 主备共享同一身份证书,私钥在 Sign-KMS 同一 slot |
| endorser | 每副本独立 cbdc_endorser_N 库(TMS 部署状态 + ttxdb 是 per-FSC-node 的) | CPU(签名验证 + 代理)+ 网络(大量 P2P session) | 每副本独立身份证书,同父 CA;FSC topology 里列全 |
| auditor 签名侧 | 共享 cbdc_auditor 库 — single-writer | CPU(ZK 验证 + ECDSA)+ 内存(3 SDK 缓存) | 签名实例唯一身份 |
| auditor 查询侧 | 连 cbdc_auditor 只读副本 | IO + 内存(auditdb 读 + 规则缓存) | 与签名侧共享身份 |
| institution-00N | 同一 logical institution 的多副本共享本行 DB;不同 institution 不共享 | CPU(选币 + 签名)+ 内存(pendingTxStore 进程内);Redis 网络 | 同一 institution 的多副本共享身份证书;不同 institution 证书独立 |
| orderer Router | 无 DB | CPU + 网络(gRPC + CRC64 + 转发) | 每副本独立 TLS + 独立 MSP 身份,同 OrdererOrg |
| orderer Batcher | 无共享 DB;每节点本地 WAL | 磁盘 IO(WAL fsync)+ 内存(mempool 默认 MemPoolMaxSize=1000000,fabric-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 可选 |
ca(cbdc-ca-server-kms 镜像) | 集群模式需共享后端 PG(本地默认 db.type: sqlite3,fabric-ca 原生支持 cluster) | CPU(RSA / ECDSA 签发) | 所有 CA 实例共享同一 root + signing cert |
三条一般规则
身份 ≠ 副本数
- "身份"(MSP cert + Token SDK wallet key)由业务角色决定,不由副本数决定。
issuer/auditor 签名侧/ 每个institution-00N各是一个身份,无论副本数。endorser与 orderer 所有角色相反:每副本就是独立身份(签名进入不同集合:BFT 投票 / FSC responder 路由)。
DB 共享 ≠ 身份共享
- bizhub 多副本共享
cbdc_bizhub,但 TLS 证书独立。 - endorser 多副本每个独立 DB,证书也独立。
- "DB 共享 + 证书共享" 的只有 issuer / auditor 签名侧(active-passive 语义)。
- 判断标准:看 MSP 是否唯一(信道权限决定)。
- bizhub 多副本共享
机型选择的核心矛盾
- 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 Coordinator | streamActive 单活跃锁 | 内存状态 | 共享 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 增加副本要改哪些地方?
- compose / k8s manifest 加副本
- 每副本独立 cert + FSC identity(用同一 bank 的父证书签)
- LB 前置,sticky by walletID
- 不需要改 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-113、130-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 同步穿过五段:发起行 NewAuditAndEndorseView(cbdc-biz/pkg/views/transfer.go:217)→ Endorser 注册 AuditResponderView 转发至 Auditor RequestAuditView(endorser/cmd/endorser/main.go:107-110、auditor/cmd/auditor/main.go:205-209)→ Orderer → Committer。
⚠️ 关键结论:Auditor 签名侧是当前代码的吞吐天花板
每笔 transfer 都强制经 Auditor 同步验证 + 签名才能上链,且该签名在协议层不可省:链上 validator 对配置了 auditor 的 PP 强制 1-of-N auditor 签名,缺失即 ErrAuditorSignaturesMissing(fabric-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-48、conf/auditor/core.yaml:44-53 |
含义:5 万 TPS 下 Auditor 只能纵向压榨单进程:
- 加核 + 调大
audit_worker_pool.size(默认120,硬上限32 × GOMAXPROCS,conf/auditor/core.yaml:32-34、pkg/workerpool/pool.go:49,60-66)以并行 ZK 验证; - 签名走 HSM(PKCS#11 → sign-kms,
conf/auditor/core.yaml:194-205),提高 sign-kms 每分区 session pool(默认5,硬上限30,sign-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(默认 memory,conf/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 并发硬上限 16(wallet_biz.go:25-28);submit 亲和见下方注 |
| Endorser | 加副本,每副本独立 FSC 身份 + 独立 DB cbdc_endorser_N(FSC 池 maxOpenConns=50,conf/endorser1/core.yaml:26-33) | ⌈50000 / R_end⌉ | 启动需手动 grpcurl :9300 EndorserService/Init(cbdc-biz/README.md:122) |
| Auditor 签名侧 | ❌ 仅纵向(见上) | 1 | R_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+1,fabric-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)%N,batcher_role.go:170-178);批上限 MaxMessageCount=10000 / AbsoluteMaxBytes=10MB / BatchCreationTimeout=500ms、MemPoolMaxSize=1000000、BatchSequenceGap=10(config/defaults.go:21-25,56-69、request/pool.go:71-87) |
| Orderer Consenter(BFT) | 加 party = 加容错非加吞吐,N=len(Consenters)、N≥3f+1(common/utils/state.go:13-19、consensus_builder.go:267-272) | 例 N=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,模板设 40,service/verifier/config.go:31、cmd/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(默认 20,service/vc/config.go:71-73);所有 VC 共写同一 state DB | ⌈50000 / R_vc⌉ | DB 连接池 max-connections 默认 20(vc/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:23) | 1 / 链 | 单链单 coordinator;多 coordinator 需多 namespace/channel 编排,committer 代码无 namespace 分片 |
| committer-db | postgres 单主(逻辑单写,test-full-kms-postgres.yaml:191-205)→ 切 committer.database.type: yugabyte(3 tserver、load_balance、table-pre-split-tablets,test-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(5→30)+ 多分区 + 更快设备 | 受 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-170) | 1(按内存/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_institution(core.yaml:38-44,52)。分片是请求路由 + 进程内状态局部性,不是像 endorser 那样每副本独立身份/库。(跨不同银行才是独立institution_code+ 独立部署,属多银行而非分片。)
submit 步骤需要显式亲和(代码现状)
pendingTx 在 prepare 时按 TxID 存入进程内 store(service/transfer_service.go:81、biz.go:41,46-58,TTL 2min),submit 时从同进程 Pop(transfer_service.go:109)。但两个请求携带的字段不同:
PrepareTransferRequest带sender(钱包),可按尾号路由(api/cbdc-biz/v1/institution.proto:739-745);SubmitTransferRequest只有tx_id+signature,不带 sender(institution.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,不占中心 HSM | pkg/views/transfer.go:662,694(NewPreSubmittedSignatureSigner) |
| Auditor sigma(每笔强制 1 枚) | ✅ auditor 钱包 BCCSP Default: PKCS11 → libkms_pkcs11.so → HSM | conf/auditor/core.yaml:124,194-205 |
| Endorser 背书签名(每笔 1 枚) | ✅ BCCSP Default: PKCS11 | conf/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()(CreateEndorserSignedTX) | fsc/initiator.go:106、fabric-smart-client core/generic/transaction/envelope.go |
| Issuer 签名 | 仅 issuance/redeem,不在 transfer 热路径 | conf/issuer/core.yaml:80 |
关键约束(代码事实):
- 一把 key 绑一台 HSM:
HSM.providers是列表(sign-kms/internal/conf/conf.proto:51-52、conf/core.yamlhsm.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、最大30,sign-kms/pkg/pkcs11/session_pool.go:18-19),Sign不串行(仅GenerateKeyPair按分区串行,kms_biz.go:26-28)。
结论(22k tps 为【规划假设】·README 标称):
- 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 够也未必够。 - Endorser + 节点身份签名是额外负载,但 endorser 可多副本、各持独立 key(
ns.ProviderID不同),天然可摊到多台 HSM/多分区。 - 每笔 transfer 的中心-HSM 签名数 = 4(代码可证):auditor σ 1 + endorser 背书 1 + institution 节点 2(proposal
fsc/initiator.go:77+ envelopefsc/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),与核数无关。三个推论(代码事实):
- 同一 SoftHSM2 实例内多切 token 不增加吞吐——token 只是同一
.so(同进程、同内部锁)里的逻辑分区; sign-kms/conf/core.yaml:27-38现有 2 个 provider 指向同一个libsofthsm2.so,dlopen 同库共享实体,并行增益为零;- 要真并行须多个独立 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_pool≤32×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 ring,cbdc-stress/main.go:26-35,输出 P50/P90/P99 + 失败原因)。
| 组件 | 【假设】单节点吞吐(示例) | 推导副本/分片数 | 校准方法 |
|---|---|---|---|
| Institution | 500 tx/s | ⌈50000/500⌉ = 100 | 单 institution 实例 ring 压测,观察 ZK 证明 CPU 饱和点 |
| Endorser | 2,000 tx/s | ⌈50000/2000⌉ = 25 | 压测时看 FSC P2P session 与 CPU |
| Auditor 签名侧 | R_aud(需实测) | 1 | 单 auditor 持续压测,R_aud<50000 即架构阻断 |
| Orderer shard | 5,000 tx/s | ⌈50000/5000⌉ = 10 shard | 单 shard batcher 切批速率 + 下游消化能力 |
| Committer Verifier | 4,000 tx/s | ⌈50000/4000⌉ = 13 | 单 verifier parallelism=40 下验签速率 |
| Committer VC | 3,000 tx/s | ⌈50000/3000⌉ = 17 | 单 VC committer=20 下写 state 速率 |
| committer-db | 6,000 wtx/s/tserver | ⌈50000/6000⌉ = 9 tserver | yugabyte 单 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:机制 + 【规划假设】数量 |
|---|---|---|---|
| Orderer | Router | 每 org 1 → 4 | 入口 gRPC 扇入;Router 按 CRC64 取模分 shard(node/router/mapper.go:40-48),按 org 加副本 + 客户端 LB |
| Orderer | Batcher | 每 org 1、全 shard_id:1 → 单分片 4 副本 | 加 shard 提吞吐:每 party 每 shard 各 1 个,batcher 总数 = party 数 N × shard 数 S。取示例 S=10、N=4 → 40 batcher |
| Orderer | Consenter | 每 org 1 → 4 | BFT 排的是 BAF 批元数据(非交易体),N=len(Consenters)、N≥3f+1;N=4(f=1) 或 N=7(f=2)。批级吞吐,非 per-tx 瓶颈 |
| Orderer | Assembler | 每 org 1 → 4 | 把各 shard 批次拼回单条有序账本;加副本增可用性非吞吐 |
| Committer | Sidecar | 1 | 拉那条有序块流,1:1 绑 Coordinator(sidecar/config.go:23) |
| Committer | Coordinator | 1 | ❌ 单活跃流、1 个/链(coordinator.go:52-58),逐笔做依赖图——账本面漏斗,见下 |
| Committer | Verifier | 1 | 扇出:coordinator.yaml verifier.endpoints 加地址 + 进程内 parallelism(模板 40)。取示例 13 个 |
| Committer | VC(validator-committer) | 1 | 扇出:validator-committer.endpoints 加地址 + max-workers-for-committer=20;共写同一 state DB。取示例 17 个 |
| Committer | Query Service | 1 | 按只读 QPS 加副本,走 committer-db 读副本 |
| Committer | committer-db | 1(postgres 单写) | 切 committer.database.type: yugabyte。取示例 9 tserver(RF=3) |
(示例 S/V/M/tserver 数量来自上方示例算例的 R_x 假设,须用 cbdc-stress 实测替换。)
账本面有两个 per-chain 串行点(先于"数 verifier/VC"的瓶颈):
- Committer Coordinator——
streamActive同时刻只允许一条 sidecar→coordinator 流(coordinator.go:52-58)、sidecar:coordinator 为 1:1。整条链每笔交易都经这一个 coordinator 做依赖分析,是账本面的瓶颈(类比业务面的 auditor)。committer 代码无 namespace/channel 分片,无法让多 coordinator 并列同一条链。 - 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/VC | coordinator.yaml 的 verifier.endpoints / validator-committer.endpoints 列多地址(samples/coordinator.yaml:49-66);verifier parallelism、vc max-workers-for-committer / database.max-connections |
| committer-db 换分布式 | committer.database.type: yugabyte(test-full-kms-yugabyte.yaml),多 tserver + 预分片 |
| 每角色独立 PG + 调连接数 | 当前本地 max_connections=300(compose-local-run.yml:48-57)< 各 FSC 池之和(issuer 20 + endorser 50 + auditor 160 + institution 200 = 430),生产须每角色独立 PG 实例并按 池 × 实例数 设 max_connections |
必须先补的代码缺口(否则模型不成立)
- Auditor 单写天花板——见上,5 万 TPS 前最大风险,需协议/架构改造(代码中无 sharded auditor)。
- Institution 尾号分片路由 + submit 亲和 + inflight 持久化——按发起方钱包尾号分片需自建网关路由;且因
SubmitTransferRequest不带 sender、pendingTxStore进程内按 TxID 存取,submit 亲和需额外机制(客户端 pin /tx_id编码分片提示 / 共享 pendingTxStore),FinalityWorker的 in-flight 也需持久化。三者仓库内均无实现,详见钱包尾号分片。 - Committer Coordinator 单活跃流——单链单 coordinator;要多 coordinator 必须按 namespace/channel 拆多链,committer 代码无 namespace 分片旋钮。
- DB 连接预算——按上表每角色独立 PG 并显式设
max_connections。 - Endorser
Init手动——每个 endorser 副本启动后需grpcurl EndorserService/Init(cbdc-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 未传 --aries,cbdc-biz/scripts/gen_crypto.sh:85-89。证明验证比生成快,但该 benchmark 未给出本配置的验证 tps。)
TPS 被「每笔都过、不可横向扩」的单实例点封顶,链条 Orderer → Coordinator → Auditor:
- 硬上界 = 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 不超过这个数。 - 真实持续值更低,被两个单实例逐笔串行点压制,二者都无端到端基准:
- 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 个/链。
- Auditor 单进程逐笔 ZK 验证(BN254 bulletproof 范围证明,
- Orderer 不是该链条瓶颈:BFT 排的是批级 BAF(每批 ≤
10000tx),50k tx → 极少数共识决议(fabric-x-orderer node/batcher/batcher_role.go:319-379、config/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 shard | cbdc-network/configs/test-full.yaml + 重跑 config-builder gen-compose |
| 加 OrdererOrg | 同上 + 通道配置更新 + MSP 注册 |
| 加 committer Verifier / Query | cbdc-network/configs/test-full.yaml committer.components 段加条目 |
| 调 committer worker | committer 启动参数或配置 yaml(默认值 fabric-x-committer/service/vc/config.go:71-73,字段 max-workers-for-*) |
| 开 PG 读副本 | biz-db / committer-db compose 加 replica service + primary_conninfo |
| 切 Redis Cluster | cbdc-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 节。