Skip to content

27 场景决策对照:同一类业务,不同设计怎么选 ​

详细程度:速查 · 约 20 分钟 材料:W03-1、W04-2、W04-6、W06-2、W09-1、W10-3、W11-1、W12-2、W13-2、W14-1、W15-2、W16-2、W17-2、W19-1、W19-2、WX-sms · 旧长版:无 前置:先读过对应正文章节,本节只做横向对照 说明:本节是决策索引,不是教程。每个场景给方案对照、判据和手册落点;展开与可运行示例在各节。

先看一个让人犹豫的时刻 ​

评审会上被问"计数为什么不用 Redis 直接存?"、登录为什么选 JWT 不选 Session、"这个表为什么要拆两张"。这些问题的答案散在各节正文里,临场容易想不全。这一节把它们收成一张对照表:每个业务场景,课程里出现过的方案、各自的代价、怎么定。它不替代正文,替你省的是"我记得讲过,在哪一节来着"。〔W21-1 p3〕

一、登录态与凭证 ​

方案适合代价
Session(服务端存储)单体或少量服务,需要立刻登出状态在服务端,扩容要共享存储
JWT(无状态签名)无状态服务、跨服务鉴权登出难,签出后到过期前一直有效
长短 token上述两者折中refresh 轮换逻辑要自己做

判据:先问登出要不要立刻生效。要立刻登出且有集中存储,Session;要无状态水平扩容,JWT 加黑名单或短过期;移动端长会话,长短 token(access 短、高频、易泄露;refresh 长且只用于刷新,泄露面小)。第三方登录跳 OAuth2,微信扫码就是授权码流程的特例。〔W03-1 p5〕〔W05-2 p5-7〕〔W05-1 p4-12〕验证码登录的竞态处理(lua 原子化)见 11 节。 实战落点:minifeed/auth 的 SignToken/ParseToken 就是无状态签名的最小 Go 版(HMAC 加过期校验,80 行);给登录态补"立刻登出",思路是给 token 加版本号并把版本存服务端,校验时多比一次。

二、计数与关系数据 ​

方案适合代价
维护计数列(事务更新)计数要强一致、参与业务判断热点行写放大,要 Lua 防并发丢更新
Redis 计数 + Count 兜底弱一致可接受、读多写多缓存 miss 时 Count 慢,快慢路径要降级设计
不缓存低频读大 V 的高频读打穿数据库

判据:先问这个计数差一个能不能接受。粉丝数差 1 无感就走 Redis 加兜底;库存、余额差 1 是事故就维护计数列加事务。"是否关注某人"只在大 V 场景值得缓存,小透明之间命中率太低。〔W16-2 p14〕〔W16-2 p15〕〔W16-2 p13〕 实战落点:minifeed/counter 的 MemoryCounter 是"内存计数加批量落库"的 Go 版:Like/Unlike 改内存 map,flush 回调定期落库,落库失败只记日志不堵主路径。把 flush 换成写 Redis、再加 Count 兜底,就是课程方案的完整形态。

三、内容状态与存储选型 ​

方案适合代价
单库单表加状态列状态少、读路径单一线上查询要带 status 过滤
制作库/线上库分库创作侧草稿与读者侧可见性分离跨库操作没有事务,要自己设计补偿
内容正文挪 OSS + CDN正文大、图片多媒体多CDN 缓存失效是漏洞(仅自己可见时对象删了缓存还在)

判据:先问读者侧和创作侧的读模式差多少。差得多就分库;正文只是文本先不分。零值禁忌恒成立:Unknown=0 不许做成有意义的业务状态。换存储(MySQL 到 MongoDB)是 DAO 层的事,上层不动;没有自增主键就雪花算法。〔W06-2 p13〕〔W06-2 p48-52〕〔W06-2 p32-33〕 实战落点:minifeed/post 与 minifeed/user 各有 MemoryStore/SQLiteStore 双实现,同一接口两种存储,Service 层零改动,这就是"换存储是 DAO 层的事"的可运行证明。

四、消息幂等与去重 ​

方案适合代价
唯一索引(biz_type+biz_id)所有能补业务键的场景键设计错了要改表
状态机(只允许合法迁移)流程型业务(订单、支付)状态枚举要维护
乐观锁(版本号条件更新)更新冲突低频高冲突下重试率高

判据:先问重复执行一次会怎样。天然幂等(设置为某值)什么都不用加;不幂等就唯一索引兜底,重复消息来两遍数据库自己挡。短信服务的幂等回放更进一步:重复请求不报错,查上次结果返回。〔W09-2 p20〕〔WX-sms p3〕 实战落点:minifeed/user 的 SQLite 建表里 email TEXT UNIQUE,Create 把唯一约束冲突当正常业务分支处理(10 节讲过的判断),正是"唯一索引兜底幂等"的最小样本。

五、缓存一致性 ​

方案适合代价
先更新库再删缓存大多数读写场景删除失败要重试兜底
订阅 binlog 异步删(Canal)要解耦、删失败率要低多一套 Canal 组件与延迟
双写读极敏感并发下不一致窗口难消灭

判据:先问能不能容忍短暂不一致。能容忍秒级就 binlog 订阅;不能就同步删加重试。删缓存是缓解不解决,别指望任何方案消灭不一致。〔W13-2 p3-5〕

六、跨服务事务 ​

方案适合代价
二阶段消息不需要回滚,下游必然成功只覆盖"发出去必到达",不含逆操作
SAGA长事务拆短事务,失败逆序补偿每步要写补偿,中间状态对外可见
TCC一致性要求高,不接受"先改后补偿"Try/Confirm/Cancel 三套接口,侵入业务
XA并发不高、无热点争抢prepare 期间持锁,性能差

判据(DTM 口诀):先问失败时要不要回滚。不需要回滚(发消息、发短信、删缓存)用二阶段消息;要回滚(下单涉及建单、扣库存、扣券多步)用 SAGA;"先改后补偿"不可接受才上 TCC;并发不高且有数据库原生支持才考虑 XA。秒杀是二阶段消息的典型:Prepare 在 Redis 扣库存(大概率失败),Commit 才写 MySQL 订单。〔C:dachang/distributed_transaction/分布式事务模式.md:54-59〕自建形态是 outbox 本地消息表(业务数据与待发消息同事务写入,中继异步投递),与消费端幂等是同一枚硬币的两面,展开见 18 节。〔W15-2 p12-14〕

七、数据迁移 ​

方案适合代价
停机迁移中小体量、可停窗口停机时长随数据量线性涨
双写迁移(八步)不许停机步骤多,校验任务要跑很久

判据:先问数据在迁移期间会不会变。不会变(一次导入的归档表)就停机直搬;会变就得面对"蜗牛悖论":校平了旧库,应用一写新库又落后,只能双写加增量校验追平,最后切读、切单写。〔W11-1 p6〕〔W11-1 p9〕〔W11-1 p11〕

八、Feed 流生成 ​

方案适合代价
推(写扩散进收件箱)粉丝数低于阈值的普通作者大 V 发文写放大:百万粉一次发文百万条记录
拉(读扩散查发件箱)大 V、粉丝量不可控的账号读放大:每次读合并 N 路查询,分页要各路取前 20 再内存排序
推拉结合生产环境的标准答案两套路径都要实现,阈值要压测定

判据:先问作者粉丝数的分布。全体都是小透明,纯推就够;有几个头部大 V,纯推必然写放大、纯拉必然读放大,按粉丝阈值分流:低于阈值走推,超过走拉,再叠加"只对活跃用户推"。口诀:读扩散查询慢,写扩散数据多;推拉结合不消灭缺陷,只是让两个都变轻。〔W19-1 p9-19〕

实战落点:minifeed 的 FeedStore 同时有 Inbox(推)与 Outbox(拉)两套,25 节判断题第 3 问就是它;阈值怎么定见 21 节压测段。

九、榜单与热门计算 ​

方案适合代价
异步定时全量计算榜单更新间隔分钟级即可实时性差,计算窗口内新数据不体现
流式增量更新实时性要求高实现复杂,要处理乱序与回撤

判据:先问榜单的实时性是产品需求还是研发执念。课程选 HackerNews 模型(投票数加时间衰减)加定时计算,千万级数据实时算谁都扛不住,间隔可控的异步计算加缓存才是正解。〔W09-1 p4〕〔W09-1 p12〕〔W09-1 p13〕 实战落点:minifeed/rank 的 Refresh(13 行):TTL 内直接返回旧榜(幂等),并发刷新用 mutex 单飞,真算失败沿用旧榜,"定时全量加宁可旧不可挂"的最小样本。

十、服务间调用与负载均衡 ​

方案适合代价
客户端轮询(roundrobin)节点性能均匀不感知负载
平滑 WRR(自算权重)节点性能不均或要按错误率避让客户端要维护权重状态
集中式 LB(grpclb/独立代理)统一管控多一跳延迟与组件运维

判据:先问要不要为省一次转发付复杂度。gRPC 默认 pick_first(永远选第一个节点),多副本部署必须显式配负载均衡;官方 WRR 是客户端按 QPS 与错误率自算权重加 EDF 挑选,不依赖服务端配置权重。注册中心选型要问四件事:自动续约有没有、续约失败怎么处理、变更多久同步到客户端、SDK 是否可换。〔W13-2 p4〕〔W13-2 p9〕〔W12-2 p19〕

十一、限流与容错阀门 ​

方案适合代价
并发计数器限"同时在处理多少个",保护自身资源不限速率,慢请求占坑时间长
令牌桶限速率且要放行合理突发参数(速率、容量)要靠压测定
漏桶要绝对匀速 outbound(如调第三方)攒的突发全被削平

容错侧的对立选择:熔断(下游持续失败就整段拒绝,给它喘息)对 failover(换节点再试,节点级故障)对舱壁(每个下游独立并发池,一个下游挂了不拖垮别的)。判据:先问故障是速率问题还是节点问题。下游整体过载,failover 只是换一个一样过载的节点,该熔断;单节点故障,熔断把好节点也拒了,该 failover;多个互不相关的下游,舱壁做隔离。三者常叠加:入口限流、下游熔断加 failover、关键路径舱壁。〔W14-1 p20〕〔W14-1 p25〕〔W14-1 p13〕〔W04-6 p25-26〕

实战落点:dnsproxi-go/guard 是两个阀门的最小 Go 版:TokenBucket.Allow/WaitN(时钟注入,测试不用真等待,111 行)、Breaker 三态加环形失败窗口(189 行);16 节正文有逐行讲解与 golang.org/x/time/rate、sony/gobreaker 的生产选型。

十二、支付类异步结果 ​

方案适合代价
回调(服务商通知你)一切能收回调的场景回调会丢会迟到,要有补偿
前端轮询用户在支付页等结果的场景轮询频率与超时次数要设计

判据:回调加轮询不是二选一,是双保险。用户支付完只跟微信交互,你的服务靠回调才知道结果;回调没来(网络抖、处理失败),前端轮询立刻补上,超时或超次再报错。支付结果三态(没收到回调/处理失败/成功)在前端就按这个设计。〔W15-2 p10〕

十三、搜索接入 ​

方案适合代价
数据库 LIKE数据量小、场景内部全表扫,不可控
同步双写到 ES一致性要求高写路径变长,失败要补偿
binlog 订阅到 ES解耦、允许秒级延迟多一套组件

判据:先问搜索是不是核心写路径。站内搜索几乎都不是,binlog 订阅最常见。字段设计先过隐私:email/phone 公开平台不可搜,toB 私有 IM 才要可搜。〔W17-2 p6〕〔W17-2 p13〕

十四、压测工具 ​

方案适合代价
wrk冒烟、单接口吞吐脚本能力弱
k6 + Prometheus + Grafana正式压测、要图表与指标留存全栈搭建

判据:wrk 做冒烟,k6 做正餐。读要分 push/pull/混合三维度、数据量分档压,推拉阈值才有依据;延迟分解排查表(blocked/sending/receiving 谁非零)比总 P99 有用。〔W19-2 p4〕〔W19-2 p12〕

十五、微服务框架 ​

方案适合代价
标准库 + gRPC团队小、需求可控生态件自己拼
go-zero要一站式(Web+RPC+治理)框架绑定,底层仍是 gRPC
Dubbo-go要与 Java 侧 Dubbo 互通社区相对小

判据:功能性、成熟度、非功能性三维度打分(业务无特别则主流都够;看社区与招人;性能中小公司基本不用管),别为 benchmark 1% 的差距选小众框架。API 管理三方案(公司级仓库/服务内目录/API 与实现分离)按团队规模选,30 人内公司级仓库最省心。〔W10-3 p4〕〔W10-3 p7〕

怎么用这一节 ​

  1. 评审被问"为什么不用 X"时,先定位场景,再按判据句作答;判据都在"先问"后面。
  2. 每张表都是课程里真实出现过的方案,不是穷举;你的场景若三个方案都不匹配,回对应章节看约束再决定。
  3. 判据冲突时(既要强一致又要高并发写),回到 18 节一致性那一章:先定业务能接受哪头弱。

想深入去哪查 ​

  • 登录与凭证:11 节认证小节;原材料 W03-1、W05-1、W05-2。
  • 计数与关系:21 节用户关系段;原材料 W16-2。
  • 状态与存储:12 节内容状态机段;原材料 W06-2。
  • 迁移:17 节;原材料 W11-1、W11-2。
  • 负载均衡:19 节;原材料 W13-1、W13-2。
  • 压测:21 节压测段;原材料 W19-2。
  • Feed 与事务:21 节 Feed 段、18 节;原材料 W19-1、W15-2。