典型系统架构拆解:IM、电商、支付与 Feed
四种系统看起来都由「服务 + 数据库 + 缓存 + 消息队列」拼成,约束却压在不同位置:IM 被长连接与消息顺序卡住,电商被热点库存与大促尖峰卡住,支付被「一分钱都不能错」卡住,Feed 被粉丝数的幂律分布卡住。下面按「核心约束 → 关键架构决策 → 数据模型要点 → 最容易搞错的地方」拆四种系统,最后给一张横向对比表。文中数值只作量级示意,设计思路以公开资料为准,不对应任何公司的内部实现或数据。
一、IM:有状态连接与消息有序
核心约束
- 连接有状态:投递前必须知道某个用户在哪个进程上,重启进程等于让这批用户重连一次。
- 消息按会话有序、不重不漏;在线态这类数据变化极快,生命周期只有秒级。
- 群规模呈长尾:多数群几十人,少数群数万至数十万人,一套扩散策略无法同时满足两端。
关键架构决策
- 接入与逻辑分离:接入层只持有连接、心跳与编解码,逻辑层无状态,路由信息外置到高速存储(Redis 等)。
- 扩散策略按群规模分档:小群写扩散(fan-out on write)换取最简读路径,大群读扩散(fan-out on read)叠加在线广播,超大群再加限速与降级。
- ACK 语义必须先定义:客户端 ACK 代表「服务端已持久化」还是「已投递」,这两种语义对应完全不同的链路与重试策略。
- 只保留取舍判断,分层、路由表键设计、投递与重试的工程细节见
im-system教程(同样以公开资料为准),本章不重复。
数据模型要点
- 消息表按会话维度分片(会话 ID 或群 ID 作分片键),历史消息与热数据冷热分离。
- 每个会话内用单调递增的序列号(seq)定位,客户端重连后按 seq 增量补齐,而不是按时间戳。
- 在线态、未读数、多端同步游标都是可重建的派生数据,不是真相来源。
最容易搞错的地方
- 把长连接数当活跃用户数做运营决策,两者可能相差数倍。
- 会话序号依赖数据库自增生成,跨分片后无法保证会话内连续,重连补齐时出现空洞。
- 群成员变更后不重建扩散范围:新成员看不到历史,退群成员还在收消息。
二、电商:热点库存与尖峰流量
核心约束
- 库存不能超卖,而扣减集中在极少数热点商品行上,是典型的高并发单行写。
- 读远多于写:浏览、搜索、比价的请求量比下单高出几个数量级,两者可用性要求也不同。
- 订单是跨服务的长流程状态机,从下单到履约要经过库存、支付、发货、退款等多个环节。
关键架构决策
- 交易链路与浏览链路分离:商品展示走缓存与搜索读模型,不查交易库;交易库只服务下单与状态流转。
- 库存扣减用单行条件更新或缓存预扣,例如
update stock set n = n - 1 where sku = ? and n >= 1,以受影响行数判断成败,避免「先查再扣」。 - 下单幂等:以业务唯一键(用户 + 客户端请求号)建唯一索引,重试与超时重发都落回同一笔订单。
- 状态向下游传播用本地消息表(Outbox)或事务消息,不在业务事务里直接调下游接口。
- 关单与库存回补交给延时消息驱动状态机,终态用状态字段做条件更新判断,而不是指望「消息只消费一次」。
数据模型要点
- 订单主表按买家维度分片;卖家与运营维度的查询走异构索引或宽表读模型,不在同一张表上硬撑两种访问路径。
- 订单行冗余商品快照(名称、价格、规格),下单后商品改价不影响历史订单。
- 金额用最小货币单位的整数或定点小数,不使用浮点。
- 状态字段配版本号或乐观锁,所有流转都是带条件的更新。
最容易搞错的地方
- 把购物车当订单:购物车是可变的意愿数据,订单是不可变的契约数据。
- 用分布式事务把库存、订单、优惠券强绑在一起,可用性被最短的那一环决定。
- 超时关单与用户支付并发时,只做「先查状态再改状态」,出现「已支付却被关单」。
- 大促前只压测不做热点隔离:单个 SKU 打满一个库,其他商品跟着陪葬。
三、支付:账不能错,其余都可以慢
核心约束
- 资金数据必须可审计、可复算,任何一次金额变动都要能追溯到凭证。
- 外部渠道不可信:回调可能重复、乱序、延迟甚至丢失,接口也可能超时但实际成功。
- 单笔金额小、笔数有限,但错了就是事故;可用性让位于正确性。
关键架构决策
- 内部账务是唯一真相,渠道流水只是输入凭证:先落内部单据(状态为处理中),再调渠道,最后靠回调加主动查询把状态收敛。
- 幂等键落在唯一索引上:业务单号、渠道单号、请求号三者共同保证重复回调只入账一次。
- 主动查询兜底:回调只是加速手段,定时补偿任务负责把长期停留在中间态的单据推到终态。
- 对账(reconciliation)是架构的一部分而非运维脚本:日切后拉渠道对账单与内部流水逐单比对,差异进差错池,缺单、多单、金额不符分别处置。
- 退款、撤销、冲正只能是新的分录(红字冲正),不允许删除或改写既有记录。
数据模型要点
- 复式记账(double-entry bookkeeping):每笔资金变动生成借贷两条分录,分录金额之和恒为零,账户余额可由分录聚合复算。
- 支付单、渠道流水、账务分录三张表各自维护状态,通过业务单号关联,避免一张表承担所有状态。
- 关键字段(业务单号、渠道单号、金额、币种)建唯一约束或联合唯一索引,让数据库而不是应用代码来保证不重复。
- 状态机的取值域集中定义,避免出现「渠道返回未知状态码就随手写个 9」。
最容易搞错的地方
- 把「渠道返回成功」当「资金已到账」,忽略清算与结算之间的时延。
- 只维护余额表、不写分录:余额对不平时无法定位是哪一笔出的错。
- 回调验签缺失或校验不严,被伪造回调直接改状态。
- 退款只校验「退款金额 ≤ 订单金额」,忽略已发生的多笔退款,应改为带条件的余额扣减。
- 对账只对总额不对单据:总额平了但两笔错单互相抵消,问题被永久掩盖。
四、Feed:幂律分布下的读放大
核心约束
- 读多写少到极端:打开首页就是一次读,发布内容才是写。
- 关注关系呈幂律分布:少量账号拥有千万级粉丝,一次发布若同步扩散,会瞬间打满消息队列与存储。
- 内容有时效性与排序诉求,纯按时间倒序的体验会持续下降。
关键架构决策
- 推拉结合(push-pull hybrid):普通账号发布时写扩散到粉丝的收件箱(inbox),大 V 只写自己的发件箱(outbox),读时合并。
- 收件箱设容量上限:只保留最近 N 条或按时间窗口截断,历史内容回发件箱回捞,避免存储随粉丝数无限增长。
- 按活跃度分档:在线或近期活跃的粉丝走写扩散加实时推送,沉默粉丝留给读时拉取。
- 计数与内容分离:点赞、评论、转发数单独维护,用幂等去重(同一用户对同一对象只计一次)加最终一致同步,读路径上不做全表聚合。
- 排序模型与召回解耦:在线链路只做轻量重排与规则过滤,重模型离线算分。
数据模型要点
- 关注关系双向存储(关注列表与粉丝列表),按用户 ID 分片;大 V 的粉丝列表需要额外分页与限流保护。
- 收件箱用有序集合或固定容量列表存内容 ID,内容本体按内容 ID 分片存放在独立存储,二者不放在同一行。
- 内容不可变:需要修改就发新版本或标记替换,避免缓存与分发链路出现多版本不一致。
- 删除、拉黑、取关采用「读时过滤 + 异步清理」双保险,收件箱是派生数据,删除时不可能同步清干净。
最容易搞错的地方
- 对所有粉丝一视同仁做写扩散:一个账号发布就能把整条链路打爆。
- 收件箱不设上限,存储成本随「粉丝数 × 发布量」线性膨胀。
- 用
count(*)实时算互动数,把读路径变成聚合查询。 - 冷启动用户只给空推荐:没有关注关系的用户需要热度或内容质量兜底。
横向对比:四个维度上的四种选择
| 维度 | IM | 电商交易 | 支付 | Feed |
|---|---|---|---|---|
| 一致性要求 | 会话内顺序 + 至少一次投递 + 幂等去重,跨设备最终一致 | 库存与订单关键路径强一致,展示链路最终一致 | 单据级强一致加对账兜底,允许中间态但不允许错账 | 秒级最终一致即可,重复在读时去重 |
| 读写特征 | 写持续均匀,读集中在在线端,长连接常驻 | 读远多于写,写集中在热点 SKU 与大促时间窗 | 写量小、每笔都落地,读少而严 | 极端读多写少,写被粉丝数放大 |
| 扩展维度 | 按连接数与网关扩容,按用户与会话分片 | 按买家与商品分片,热点行隔离并全链路限流排队 | 按账户分片,追求可复算而非水平扩展 | 按用户分片收件箱,按内容分片正文 |
| 典型技术选型 | 长连接网关 + 路由存储 + 分片关系库 + 消息队列 + 厂商推送 | 分片关系库 + 缓存与原子扣减 + 消息队列 + 搜索引擎 | 强一致关系库 + 本地事务与 Outbox + 定时对账 + 唯一索引 | 有序集合等专用存储 + 对象存储 + 消息队列扩散 |
| 故障时的优先动作 | 保投递,降级为拉取与离线补 | 停写保库存正确,读路径继续服务 | 关闭收款入口,而不是继续记账 | 降级排序与个性化,退回时间序 |
四条通用判据
- 谁是不可信输入:支付的渠道回调、IM 的客户端 ACK、电商的客户端下单结果都不可信,架构要给不可信输入配一个权威的本地状态机。
- 读写放大在哪一环:IM 放大在连接数与消息扩散,电商放大在热点行争抢,Feed 放大在粉丝数;先定位放大点,再决定用缓存、分片还是异步。
- 终态由谁判定:凡是有中间态的系统(订单、支付单、消息投递)都要明确谁有权把单据推到终态,并且用条件更新而不是覆盖写。
- 一致性的边界画在哪:把强一致压缩到最小的必要范围(单行库存、单笔账务),其余全部交给最终一致加对账或补偿。
四种系统的差异不在用了哪些中间件,而在约束把复杂度压到了不同位置。参考任何一份公开架构图之前,先用自己的约束验证一遍:这套结构解决的是不是一个真问题。
小结:IM 压在有状态连接与消息顺序上,电商压在热点库存与长流程状态机上,支付压在可审计与幂等上,Feed 压在幂律分布的读放大上;识别约束之后,用「不可信输入、放大点、终态判定、一致性边界」四问校验任何参考架构,比照抄结论可靠得多。