单元化架构:银行核心的容灾与扩展底座
单元化的本质是把故障和容量都限制在单元内,本文记录单元切分、路由与跨单元处理的实践取舍。
单元化被讲成"异地多活的银弹",但它真正解决的问题只有两个:把故障爆炸半径收进一个单元,以及让容量可以按单元横向叠加。理解不了这两点,做出来的往往只是一套更复杂的分库分表。
为什么核心要做单元化
传统主备架构的困境是:备机平时不承载流量,切换时没人敢按那个按钮。因为没人验证过备机在真实压力下能不能扛住。
单元化把这件事变成常态:每个单元都在真实处理流量,每个单元都是完整的业务闭环——接入层、应用层、数据库全套齐备。某个单元挂了,把它的流量按客户维度切到其他单元,其他单元本来就在跑同样的代码和同样的负载模型。
容灾能力不是靠架构图证明的,是靠每月切一次流量证明的。单元化的最大价值是让切换变成一件平常事。
单元怎么切
切分维度只有一个原则:保证绝大多数交易在单元内闭环。
对零售银行来说,客户号是最合适的切分键。同一个客户的账户、协议、流水都在一个单元,查余额、转账(同行同单元)这些高频操作不跨单元。
- 按客户号切:适合零售,单元内闭环率通常能到 90% 以上
- 按机构切:适合对公,但容易出现大行倾斜
- 按账号切:看着均匀,实际会把同一客户的账户打散,跨单元查询暴增
要留一类特殊单元:全局单元。存放客户号生成、产品参数、机构信息这些必须全局唯一或全局一致的数据。全局单元只读为主,写入频率极低,靠单向复制分发到各业务单元。
路由与数据一致性
路由要在尽可能靠前的位置完成。我们的做法是接入层解析出客户号后直接算单元,错误路由的请求由应用层做二次校验并拒绝,绝不允许"就近处理"。
逻辑单元数要一次性定足(我们定了 64),物理单元可以少。扩容时把逻辑单元区间搬到新物理单元,不用改路由算法,也不用重新哈希全量数据。
跨单元的交易(比如两个客户不在同一单元的转账)不要试图做跨单元强一致事务。正确做法是拆成两段本地事务加异步通知,中间状态用"在途"余额表达,配日终对账兜底。
踩过的坑
- 路由键泄漏到业务逻辑里。有个模块直接按账号查库,没带客户号,单元化后查不到数据。所有 DAO 必须强制要求分片键,缺失就报错,不要默认广播查询。
- 广播查询滥用。运营后台要查"全行某状态的账户",图省事写成遍历所有单元。单元多了之后这类查询直接拖垮全部单元。这类需求应该走离线数仓。
- 忘了单元内的容灾。单元化解决的是单元级故障,单元内部的数据库高可用还得单独做,两者不能互相替代。
单元化的复杂度是实打实的,它换来的是可验证的容灾能力和线性的扩容路径。如果业务量还撑不起这份复杂度,主备加读写分离活得更舒服。