专题
专题
这里是按主题整理的专题入口。每个专题围绕一条工作主线展开,把相关文章串成可长期查阅的知识脉络。
目录
架构专栏
银行系统的复杂度,往往不在单点技术,而在「如何把监管、账务、渠道与性能约束织成一张自洽的网」。本专栏拆解其中的关键决策与权衡。
下面是本专栏的文章:
聚合根与领域事件:DDD 战术建模
银行核心系统里最容易被低估的概念,是「聚合」。很多人把 DDD 战术设计理解成「给实体加几个注解」,结果聚合越写越大,事务越来越长,最后把数据库和团队都拖垮。本文把聚合根和领域事件放回它们本来的位置:一致性边界的设计工具,以及领域向外界发声的窗口。
为什么需要聚合
领域模型不是一张 ER 图。ER 图关心「数据怎么存」,聚合关心「哪些数据必须在同一个事务里保持一致」。这两件事经常错位,而错位的代价在金融场景里格外昂贵——账务不平、状态错乱,往往不是因为算法错,而是因为一致性边界划错了。
一致性边界
聚合的第一性原理是:在边界之内,用强一致性保证业务规则不被破坏;在边界之外,用最终一致性传递变化。一个账户余额不能为负,这条规则必须在一个事务内被满足,所以它属于聚合内部。而「账户余额变动后通知风控」,则属于边界之外,交给领域事件。
经验法则:如果两条业务规则必须同时为真,它们大概率属于同一个聚合;如果可以接受短暂的不一致,它们就分属不同聚合。
聚合不是「大对象」
最常见的反模式,是把「客户」做成包含地址、联系人、合同、账户、画像的超级对象。它在概念上很完整,但在工程上是个灾难:每次改一个手机号,都要加载半个数据库,还要锁住一堆根本无关的数据。
聚合的边界应以事务一致性而非业务概念大小来划。概念上属于「同一个东西」的数据,未必需要在同一个事务里。
聚合根的设计原则
聚合根是聚合对外的唯一入口。外部只能持有聚合根的引用,不能直接操作聚合内部的实体或值对象。
引用靠 ID,不靠对象
跨聚合的关联,永远用标识符,而不是对象引用。聚合 A 不应直接持有聚合 B 的对象,否则两个聚合会被悄悄绑进同一事务。
工厂与仓储
复杂聚合的创建交给工厂,避免调用方掌握内部构造细节;聚合的持久化交给仓储,且仓储只按聚合根 ID 加载整个聚合,不存在「只查一半」的仓储方法。
- 工厂负责「从无到有」并保证初始不变式成立。
- 仓储负责「整存整取」,不暴露部分更新。
- 应用服务协调工厂、仓储与领域事件发布,但不写业务规则。
不变式要内聚
不变式(invariant)是聚合存在的理由。余额不为负、转账双方必须同币种、合同生效前不能放款——这些规则写在聚合内部的方法里,而不是散落到 service 层去做 if 校验。把规则内聚到聚合里,测试时只需构造一个对象就能验证业务,不必搭一整套上下文。
领域事件是聚合的「对外窗口」
聚合根在自己身上记录了事件,但绝不能自己发消息。聚合只负责产生事件对象,发布动作由应用服务或仓储在完成事务后统一处理。这样聚合保持纯净,测试也简单。
事件如何产生
事件是对「已经发生的事实」的描述,命名用过去式:MoneyWithdrawn、AccountOpened、LimitExceeded。它携带聚合根 ID、关键数值和时间戳。
发布与订阅的边界
发布要在本地事务提交之后,否则会出现「消息发了、事务回滚」的幽灵事件。常见做法是在同一个数据库事务里写一张发件箱表(outbox),再由独立投递器搬运到消息队列。
Outbox 模式的价值不在于花哨,而在于它把「数据库一致」和「消息可靠」这两件事重新对齐:要么都成功,要么都失败。
| 方案 | 一致性保证 | 复杂度 |
|---|---|---|
| 事务内直发 MQ | 弱,可能幽灵事件 | 低 |
| Outbox + 投递器 | 强,与本地事务同生命周期 | 中 |
| CDC 捕获 binlog | 强,对业务无侵入 | 高 |
一个银行开户的例子
假设开户需要:建立客户档案、创建主账户、初始化额度、通知风控建档。
| 步骤 | 归属聚合 | 一致性要求 |
|---|---|---|
| 创建客户档案 | Customer 聚合 | 强一致,档案落库即生效 |
| 创建主账户 | Account 聚合 | 强一致,账户初始余额为零 |
| 初始化额度 | Limit 聚合 | 强一致,额度与账户绑定 |
| 通知风控 | 跨聚合 | 最终一致,事件驱动 |
前三项各自在自己的聚合内完成,开户服务用一次 saga 或本地事务把它们串起来;最后一项通过领域事件异步触发,风控系统晚几秒建档完全可接受。
事件流
订阅方消费 AccountOpened 即可并行去建额度、建风控档案,不必等开户服务逐个调用。这里体现的是聚合协作的核心思想:同步走强一致,异步走事件,两者各司其职。
常见误区
- 把聚合当 CRUD 载体:聚合方法名是
save、update,里面没有任何业务规则。这时 DDD 只是换了个地方写 SQL。 - 跨聚合事务:为了「保证一起成功」而在应用层开一个大事务锁住多个聚合。正确做法是 sagas/事件,接受最终一致。
- 聚合里注入仓储或发消息:聚合一旦依赖外部基础设施,就再也单元测试不了,领域层也被污染。
- 过度拆分聚合:为了「小」而把本应一致的两条规则拆到两个聚合,结果反而要处处补偿。
- 聚合越小,并发越高,但跨聚合协作成本也越高。
- 聚合越大,一致性越好写,但锁竞争和加载成本会反噬。
- 平衡点来自对业务规则的诚实审视,而非拍脑袋。
结论
聚合根和领域事件不是花活,它们是把「业务的硬约束」翻译成「代码的硬边界」的手段。先把一致性边界划对,再让聚合通过事件对外低耦合地协作,银行核心系统才能真正既稳又活。下一讲我们会把这套思路接到 CQRS 与 Saga 上,看复杂长事务怎么在不牺牲一致性的前提下拆开。
CQRS 与 Saga:复杂业务的读写分离与最终一致
很多团队把 CQRS 当成「加个 MQ 同步数据」就完事,结果读模型对了、写模型乱了,长事务更无从下手。CQRS 解决「读写诉求不同」,Saga 解决「业务跨多个聚合却不能开大事务」,两者常一起出现但职责不同。
读写为什么要分家
写模型关心一致性与不变量,是一组小而强的聚合;读模型关心展示与组装,最好能直接查出前端要的 DTO。诉求冲突时强行共用一个模型,两边都不讨好。
不要为了 CQRS 而 CQRS。单表就能满足读写、流量不大的模块,分家只会制造延迟和负担。
Saga 管理长事务
转账、跨境汇款天然跨账户跨系统,不可能用本地事务锁住。Saga 把大事务拆成一串本地事务,每步都有补偿。
| 模式 | 思路 | 适用 |
|---|---|---|
| 编排 | 中心协调器逐步调用并补偿 | 需强管控 |
| 协同 | 各服务靠事件触发 | 去中心低耦合 |
补偿与幂等
Saga 最怕「补偿自己也失败」,所以每步都要幂等:用业务流水号去重,重试不重复扣款。每步记录状态机 PENDING -> DONE / COMPENSATED,补偿顺序与正向相反,对账做兜底而非第一道防线。
最终一致不是「不管了」,而是把一致性从「即时」换成「可验证」:允许短暂中间态,但必须能收敛到正确态。
落地上,CQRS 让读写各取所需,Saga 让长流程可回退,再加独立对账,复杂业务才算立得住。
六边形架构在银行核心系统的落地
银行核心最痛的不是业务复杂,而是业务逻辑和周边技术死死绑在一起:换连接池要改核心,接新渠道要动账务,单测要起一整套中间件。六边形架构(Ports & Adapters)给出干净解法。
核心、端口与适配器
- 领域核心:纯业务规则,不依赖任何框架、数据库或 HTTP 客户端。
- 端口:核心暴露(入站)和需要(出站)的接口,用 Java 接口表达意图。
- 适配器:把端口接到具体技术——REST 控制器是入站适配器,JDBC 仓储是出站适配器。
判断架构是否干净的一条硬标准:删掉数据库、MQ、Web 框架,核心业务代码还能原封不动地跑并被测。
在银行核心里怎么切
核心账务、计息、限额放在中心;大小额网关、CBS 接口、短信、风控回调全做成外围适配器。监管规则变了只动核心,接新清算通道只加一个适配器。
| 层次 | 内容 | 依赖框架 |
|---|---|---|
| 领域核心 | 账务、计息、限额 | 否 |
| 应用层 | 用例编排 | 否 |
| 适配层 | REST/JDBC/MQ | 是 |
收益与代价
收益是可单测、可独立部署、渠道可替换;代价是初期抽象成本与团队纪律。在银行这种长生命周期系统里,核心被技术债锁死的代价远高于前期投入。
架构不是墙上的图,而是写在依赖箭头里的纪律。六边形的价值,是让「技术变了业务不动」成为默认结果。
在银行客户信息系统(ECIF)里落地 DDD 聚合
ECIF 里「客户」的概念极其庞大:个人、对公、同业,各自的属性、关系、生命周期都不同。如果用一个巨大的 Customer 实体硬扛,代码会迅速腐化。
用界限上下文切分
把客户拆成几个界限上下文:
- 客户主数据(Party):统一的自然人/机构标识与基础属性。
- 客户画像(Profile):风险偏好、营销标签,读写频率高、变化快。
- 客户关系(Relationship):持股、担保、集团关系。
聚合根怎么定
每个上下文内部再定聚合根。例如 Party 上下文里,Party 是聚合根,Address、Contact 是其值对象,保证一致性边界内不跨聚合调用。
经验:聚合的边界应以「事务一致性」而非「业务概念大小」来划。ECIF 里最容易犯的错,就是把所有客户信息塞进一个聚合。
这样设计后,主数据服务稳定,画像服务可以独立迭代,互不影响。
银行业务专栏
银行的「业务规则」才是系统真正的复杂度来源。本专栏把核心业务从底层机制讲起,让技术决策有业务依据。
下面是本专栏的文章:
支付清算全流程拆解
普通人眼里的「转账」是一个瞬间动作:输金额、点确认、余额变了。但背后的资金流动要经过支付、清分、结算三个阶段,跨过多个系统,才可能真正「钱到了」。理解这套链路,是做银行支付系统的前提,也是排查「钱怎么少了」「为什么还没到账」的地图。
一笔转账背后发生了什么
当你在 App 上给朋友转 1000 元,这笔指令从你的银行出发,要穿越自己的核心系统、跨行清算通道、对方银行的核心系统,最后落到对方账户。中间任何一环的状态错位,都会让这笔钱「卡在半路」。所以支付系统设计的第一个原则,是把每个阶段的状态显式建模,而不是用一个「已转账」含糊带过。
支付系统最危险的 bug,不是算错金额,而是状态机说不清一笔钱现在到底在哪:是已扣、已发、已清算、已结算,还是已退回。
三个阶段:支付、清分、结算
这是整条链路的主干,三者的边界必须划清。
支付:机构内部的记账
付款方银行收到指令后,先校验账户状态、余额、限额、风控,然后在自己的核心账本上记一笔「待清算」的借贷。这一步完全发生在单个机构内部,还没有任何人把钱真正挪动。
清分:轧差与净额
清分是交易双方机构把彼此的借贷指令汇总、计算净额的过程。它有两种模式:
- 全额清分:每一笔借贷都单独计算,资金流与业务流一一对应,安全但占用流动性。
- 净额清分(轧差):把一段时间内的多笔借贷相互抵扣,只交差额。比如 A 欠 B 100 万、B 欠 A 80 万,净额就是 A 再付 B 20 万。
结算:资金的真实位移
结算是资金在央行或清算机构的账户上真正转移,借贷最终平衡。只有结算完成,这笔钱才算「落地」。在此之前,它只是账面上的债权。
| 阶段 | 发生位置 | 是否动真实资金 | 关键产物 |
|---|---|---|---|
| 支付 | 付款方银行内部 | 否(仅记账) | 支付流水、待清算挂账 |
| 清分 | 清算机构/双方对账 | 否(算差额) | 净额头寸、清分文件 |
| 结算 | 央行/清算账户 | 是 | 准备金账户余额变动 |
清分算的是「谁欠谁多少」,结算做的是「把钱真正挪过去」。只清不分或只分不结,都会让账面悬空。
大小额与网银清算系统
中国人民银行的大小额支付系统,是跨行资金流动的主动脉。不同系统对应不同的金额、时效与成本取舍。
| 系统 | 处理方式 | 金额与时效 | 适用场景 |
|---|---|---|---|
| 大额支付系统(HVPS) | 逐笔实时全额 | 大额、实时、营业时间 | 对公大额、同业头寸调拨 |
| 小额支付系统(BEPS) | 批量净额轧差 | 小额、定时撮合 | 日常零售、代收付 |
| 网上支付跨行清算 | 逐笔/批量 | 7x24、准实时 | 网银、手机银行跨行 |
大额走全额是为了安全和实时,小额走净额是为了降低流动性占用和通道成本。设计路由时,按金额和时效要求分流,而不是一刀切全走大额。
卡组织与三方支付清算
银行卡跨行交易走银联(银行卡)或网联/银联(网络支付)。资金路径是:
商户侧的收单机构先记账,再通过对账文件与卡组织清分,最后在准备金账户结算。三方支付(钱包)则多了「支付机构备付金账户」这一层,资金在用户钱包余额、支付机构存管账户、银行之间多跳流转。
清分净额怎么算
净额不是拍脑袋,而是可复现的计算。一个简化的轧差脚本:
结算时,正头寸等待收款,负头寸从准备金账户划出,借贷两方最终在央行账上平衡。
对账:日终的生命线
日终,各方用对账文件(通常固定格式、带汇总校验和笔数)核对借贷。不平的这笔叫「差错」,进入差错处理流程,而不是默默掩盖。
- 对账文件必须可追溯、可重跑,最好带哈希校验。
- 长款(我方多收)和短款(我方少收)处理规则不同,不能混为一谈。
- 自动勾对 + 人工差错处理是标配,勾对规则要可解释。
- 对账滞后会让资金敞口放大:T+1 才发现 T 日差错,补救成本指数上升。
对账不是事后补丁,而是支付系统的「最后一道真理」。它用独立的数据源交叉验证主流程,发现那些主流程自己发现不了的错。
常见坑
- 把清分当结算:以为指令发出就到账,实际资金还在途,引发透支或重复支付。
- 忽略时区与营业日:大额系统非营业时间不运行,跨日交易要排队,别用本地时间当清算日。
- 对账滞后:T+1 才跑 T 日对账,资金敞口已经放大一整天。
- 状态机缺失:把「已转账」当一个终点,出问题时无法定位卡在支付、清分还是结算。
结论
支付清算的本质,是把「信任」从机构内部延伸到机构之间:支付建立债权,清分计算净额,结算完成资金转移。把这三者的边界、状态机和对账机制刻进系统设计,跨行交易才不会在某个环节 quietly 出错。下一讲我们看计息与限额,那是另一道「算得清才能管得住」的闸门。
计息与限额:算得清才能管得住
银行系统里最容易「看起来很简单、做起来全是坑」的两件事:计息和限额。它们一个决定「钱怎么生钱」,一个决定「钱不能出什么事」。两者共同的特点是:规则必须可被验证,结果必须可被复核。
计息:口径比公式重要
公式 本金 × 利率 × 天数 / 基数 人人会写,难的是口径统一。
- 计息基数:是每日余额、还是日均余额?贷款多用实际天数,存款各有约定。
- 闰年与节假日:一年按 365 还是 360?结息日遇节假日顺延还是提前?
- 利率变动:固定利率、浮动利率重定价日如何衔接,分段计息怎么切。
计息 bug 最可怕的结局,不是算错一笔,而是「每天错一点点,半年后对不上,却找不到从哪天开始错」。所以计息必须有可复现的逐日明细。
限额:控制点要前置
限额分好几类:单笔、日累计、月累计、余额上限、渠道限额。关键不在「存一个上限数字」,而在控制点必须前置到交易校验环节,而不是事后统计。
| 限额类型 | 控制点 | 典型场景 |
|---|---|---|
| 单笔限额 | 交易发起前 | 防单笔大额盗刷 |
| 日累计 | 交易前累计判断 | 渠道风控 |
| 余额上限 | 入账后校验 | 产品合规约束 |
- 限额规则要可配置,且变更留痕、可追溯生效时间。
- 累计类限额必须考虑并发:用原子计数或数据库约束,别靠「先查后扣」。
- 超限处理要明确:拒绝、转人工、还是降级,不能悄悄放行。
限额系统宁可「误杀一笔正常交易」,也不能「放过一笔该拦的」。前者是体验问题,后者是风险事件。
把计息的逐日明细和限额的前置校验都做扎实,核心账务才谈得上「算得清、管得住」。
AML/KYC:合规不是绊脚石
一提 AML/KYC,业务侧往往皱眉:开户变慢、交易被拦。但合规不是外挂的「麻烦」,而是银行必须内建的能力。把它当系统一等公民来设计,风控和体验才能双赢。
KYC:先搞清楚你是谁
KYC 是起点,不是收集证件复印件,而是建立持续可验证的客户画像。
- 身份核验:证件真实性、活体、工商比对。
- 风险分级:按行业、地域、业务性质定级。
- 受益所有人:穿透到最终自然人,防借壳。
AML:盯着钱往哪走
反洗钱看交易行为与资金路径,而非单次金额大小:名单监控、交易监测(规则+模型打分)、可疑报告(STR)走调查上报流程。
| 维度 | 信号 | 处置 |
|---|---|---|
| 频率 | 短期多笔等额进出 | 触发复核 |
| 对手方 | 命中制裁名单 | 阻断上报 |
| 金额 | 拆分规避限额 | 合并评估 |
真正的反洗钱是「识别不合理模式」:规整发工资的小额账户突然集中收款,比一笔大额更可疑。
工程落地要点
名单更新要准实时且对存量重跑;规则先求可解释、可回测;误报率持续度量;告警工单化、可审计。合规做扎实,业务国际化反而少踩坑。
一文理清 ECIF:客户信息为什么要「集中」
在没有 ECIF 的年代,网点、网银、信用卡中心各自维护一份客户信息。同一个客户,在不同系统里姓名、证件、联系方式都不一样,营销和风控都无从谈起。
ECIF 解决什么
ECIF(企业客户信息整合)把分散在各业务系统的客户主数据收敛到一处,对外提供唯一客户视图。
- 唯一标识:用客户号(Party ID)统一自然人/机构,而非证件号。
- 主次关系:支持一人多户、一户多卡,但主数据唯一。
- 服务化:其他系统通过接口查询,不再各自落库。
技术上的关键取舍
- 读写分离:主数据写入强一致,查询可走缓存/只读副本。
- 变更可溯源:客户信息变更需要留痕,满足监管审计。
ECIF 不是「又一个数据库」,而是银行数字化的最底层地基。
数据专栏
数据是现代银行的资产,也是风险。本专栏关注「让数据可用、可信、可控」的工程实践。
下面是本专栏的文章:
数据治理:从资产目录到口径统一
很多银行的数据治理项目,最后都沦为「补元数据、填责任表、交汇报 PPT」。运动一过,元数据过期、口径继续打架、下游照样不敢用这份数据。治理之所以失效,是因为它被当成了一份额外的「填表工作」,而不是数据生产流水线本身的属性。真正有效的治理,是让目录、血缘、口径和质量规则内建到系统里,而不是靠人肉维护。
治理为什么总做成运动
根本原因是把「治理」和「生产」割裂了。业务系统吐数据,治理团队在下游追着补标签、对口径。一旦人员变动或项目结项,治理成果立刻失真。
治理的目标不是「漂亮的报告」,而是让下游系统敢用这份数据。如果一份数据没人敢用来做决策,它就算被打了满分标签,也还是负债。
要扭转这个局面,得从三件最实在的事入手:盘清家底(资产目录)、追清来路(元数据血缘)、对齐说法(口径统一)。
资产目录:先盘清家底
资产目录回答的是「我们到底有哪些数据、归谁管、能不能用」。它不是 Excel 清单,而是带权属和分级的结构化注册表。
| 资产类型 | 例子 | 治理关注点 |
|---|---|---|
| 贴源表 | 核心系统流水 | 来源系统、更新频率 |
| 派生表 | 客户宽表 | 加工逻辑、依赖 |
| 指标 | 月活、不良率 | 口径定义、负责人 |
| 文件/接口 | 监管报送文件 | 敏感度、共享范围 |
目录必须和真实的元数据打通,否则就会出现「目录里说有、库里早就删了」的尴尬。一个可行的做法是:目录条目由采集任务自动生成,人工只补充业务语义,而不是反过来手工录入。
元数据与血缘:字段从哪来、到哪去
元数据解决「这个字段是什么」,血缘解决「它怎么变来的、又被谁用了」。两者合起来,才能做影响分析和溯源。
血缘怎么采
血缘最好自动采集,而不是靠文档口述。主流方式有两种:
- 静态解析:扫描 SQL、ETL 脚本、Spark/DAG 定义,提取表与字段级的输入输出关系。
- 运行时采集:在任务执行时记录实际读写的上下游,准确率最高但侵入性较强。
影响分析
有了血缘,一次核心系统字段口径变更,能立刻列出受影响的下游报表和指标。没有血缘,这类变更只能靠「老员工记忆」,风险极高。
血缘的价值不在炫技,而在于把「改一个字段要通知谁」从玄学变成可查询的图。它是治理能规模化的前提。
指标口径统一:最难的最后一公里
银行里「不良率」「活跃客户」「存款日均」这类指标,不同部门算出来常常不一样。根因不是谁算错,而是口径没有单一可信来源。
口径冲突的例子
「月活客户」可能被定义为:
- 渠道侧:当月登录 App 即算活跃;
- 零售侧:当月有动账交易才算活跃;
- 监管侧:按监管文件口径,且需去重。
三个数字放在一起对比,结论自然矛盾。更糟的是,没人说得清哪个是「官方版本」。
指标字典与单一来源
解决思路是建立指标字典:每个指标一条定义,包含口径 SQL、维度、过滤条件、负责人和生效时间。下游统一从字典取数,禁止各自重写逻辑。
口径变更要走版本管理,旧口径保留可追溯,新口径标注生效日,避免历史报表被悄悄改义。
数据质量内建到流水线
质量不是事后抽查,而是 ETL/湖仓任务里的「门禁」。规则不过,数据不入库。
- 规则要可配置、可观测,失败要告警而非静默跳过。
- 关键字段(证件号、金额)非空、唯一、非负,是底线规则。
- 质量趋势要可视化,恶化要能回溯到哪天哪个任务开始。
质量内建的核心,是把「信任」从事后审计提前到数据落库之前。一条坏数据一旦进了湖,下游十张报表一起错。
安全与合规:治理的硬约束
治理绕不开安全。银行对敏感字段的处理有硬要求:
- 静态加密:证件号、卡号、手机号落盘即加密,密钥与数据分离管理。
- 动态脱敏:查询侧按角色脱敏,开发人员查生产只能看到掩码。
- 分级分类:数据按敏感度分级,决定谁能看、能传到哪、能留多久。
这些不是治理的附加项,而是治理成立的边界条件。
落地节奏建议
治理切忌「全面铺开、一步到位」,那样必成运动。更稳的节奏是:
- 先选一个高价值域(如客户、账户),把目录、血缘、核心指标跑通。
- 把质量门禁接进现有流水线,不新建独立系统,降低阻力。
- 指标字典从冲突最严重的一批指标开始统一,尽快产出可见收益。
- 用自动化替代手工填表,让目录和血缘随任务自动更新。
小步快跑、用真实收益说话,比一份宏伟蓝图更能让治理活下来。
结论
数据治理不是运动,也不是额外的填表负担,而是把「可发现、可追溯、可信任」变成数据流水线的默认属性。从资产目录盘清家底,用元数据血缘追清来路,靠指标字典统一说法,再把质量门禁内建进去——四件事做实,数据才真正从负债变成资产。下一讲我们聊消息队列选型,那是让这些数据可靠流动的另一块基石。
消息队列选型:Kafka vs Pulsar vs RabbitMQ
消息队列是分布式系统的神经系统,但 Kafka、Pulsar、RabbitMQ 三者的设计哲学差异极大。选错不是「换个客户端」的事,而是架构重做。下面从几个工程最关心的维度拆开看。
模型差异是根本
- RabbitMQ:经典消息代理,面向「消息」和「队列」,支持丰富路由(直连、主题、头部、扇出)。适合任务分发、低延迟小消息。
- Kafka:日志型、分区有序、拉取消费,面向「流」和「事件溯源」。适合高吞吐、可重放。
- Pulsar:计算存储分离,分层架构,原生多租户和跨地域复制。适合既要 Kafka 的流、又要 RabbitMQ 的队列语义的统一平台。
关键维度对比
| 维度 | Kafka | Pulsar | RabbitMQ |
|---|---|---|---|
| 吞吐 | 极高 | 极高 | 中等 |
| 消息模型 | 流/日志 | 流+队列 | 队列 |
| 顺序性 | 分区内有序 | 分区/Key 有序 | 队列有序 |
| 运维复杂度 | 中 | 高(含 BookKeeper) | 低 |
| 消费模型 | 拉取、可重放 | 拉取、可重放 | 推送 |
选型思路
- 事件流、日志、可重放 → Kafka 稳。审计、CDC、行为埋点这类场景它的重放能力几乎是刚需。
- 统一消息平台、多租户、跨地域 → Pulsar 更合适,但团队要扛得住运维。
- 任务队列、复杂路由、低延迟 → RabbitMQ 更直接。
别用 RabbitMQ 扛日均百亿事件流,也别用 Kafka 做需要复杂路由的任务分发——用对的工具,比用最强的工具重要。
银行场景里,核心事件总线多用 Kafka 保证可重放与审计;内部任务编排可用 RabbitMQ。Pulsar 适合已经长成「平台」的团队。选型时还要把「团队能不能运维」算进成本,而不是只看基准测试数字。
分库分表策略与热点治理
当单表涨到几亿行、单库连接打满,分库分表就从「可选项」变成「生存项」。但分片一旦定错键,后期重构的代价接近重写。所以策略要在一开始就想清楚。
分片键是第一步,也是最重要的一步
分片键决定数据怎么散、请求怎么走。选错键,要么数据倾斜,要么跨分片查询泛滥。
- 选高频等值查询字段:比如账户号、客户号,保证大多数请求落单分片。
- 避免低基数或单调递增:性别这种分片键会制造大分片;自增 ID 做键会集中在最新分片,形成写入热点。
- 兼顾关联查询:同一客户的账户、交易最好同片,减少跨片 JOIN。
扩容:预分片优于临时拆
一开始就按「未来三年规模」定足够多的逻辑分片(如 1024、4096),物理上先少后多。扩容只是把逻辑分片映射到新物理库,数据迁移量远小于重新分片。
| 策略 | 优点 | 缺点 |
|---|---|---|
| 预分片 | 扩容平滑 | 初期略冗余 |
| 范围分片 | 易理解 | 易热点 |
| 哈希分片 | 均衡 | 范围查询跨片 |
热点治理
即使哈希均匀,业务上仍会有「明星账户」「大商户」成为单点热点。
- 二级分片:对超大 key 再按时间或子维度拆。
- 读写分离 + 缓存:把热点读打到只读副本或缓存。
- 限流与隔离:热点账户单独队列,避免拖垮整片。
分库分表解决的是「装得下」,热点治理解决的是「扛得住」。两者都做,系统才既大又稳。
最后提醒:分片后事务、全局唯一 ID、跨片聚合统计都要重新设计,别等上线才发现漏了。分布式 IDs 建议用号段或雪花算法,跨片统计要么预聚合、要么上 OLAP 旁路,不要把在线事务库当报表库用。