Skip to content

模块导航: 总入口 · 总纲与方法 · 后端场景 · AI与算法工程 · 国企与金融 · 网络问答


109. 服务 A 改了数据,B/C/D 都要跟着变(微服务数据一致性) ​

【考察内容】微服务数据一致性是架构岗必考

【题目】微服务 A 修改了核心数据(如用户信息),下游 B(订单)、C(消息)、D(统计)都要基于新数据。A 不能直接改 B/C/D 的库,怎么保证数据最终一致?请给出完整方案(事件驱动、本地消息表、事务消息、订阅消费)与失败兜底。

【参考答案】

  1. 核心思想:事件驱动 + 最终一致,避免跨服务强事务;
  2. 方案组合:
    • 可靠事件(本地消息表/事务消息):A 的业务与事件同事务落库,事件异步投递到 MQ,B/C 订阅消费——保证“事件不丢”;
    • 消费幂等:B/C 用业务唯一键去重,保证重复事件只生效一次;
    • Outbox 模式:业务表旁建 outbox 表,同事务写入,relay(如 Debezium 监听 binlog)把 outbox 记录转发 MQ——比本地消息表更解耦;
    • 对账兜底:定时任务比对 A 与 B/C 的数据,发现不一致自动补偿(重发事件/直接修复);
    • 状态机推进:B/C 各自维护状态(如“待同步→已同步”),对账按状态处理;
  3. 什么时候不能最终一致:资金强一致(扣款+入账)用 TCC/Seata;大部分业务最终一致足够;
  4. 设计原则:明确每个数据的所有者(单一写入方),其他服务只读或事件同步。
  5. 容量/延迟估算:A 服务写峰值 2000 TPS,若每条变更需通知 3 个下游,则事件峰值 6000 msg/s;B/C/D 消费能力必须覆盖。不一致窗口:MQ 正常秒级;本地消息表秒~十秒;下游批量同步可到分钟级。对账任务按数据量:日变更要先从峰值折回均值(取峰值的 1/10 ≈ 200 TPS)再乘全天,200×86400 ≈ 1700 万事件/日;若按峰值 2000 TPS 直乘会得出 1.7 亿,那是几乎不可能的上限口径,别拿它做容量。对账不能每晚全量扫,应增量+抽样+关键账户全量。缓存失效风暴:热点 key 更新时多个下游同时失效,可用延迟双删/版本号。
  6. 失败与降级:消息丢失→本地消息表/事务消息;下游消费失败→重试+死信+补偿;缓存与 DB 不一致→延迟双删/订阅 binlog;对账发现差异→差错工单自动补偿。非核心下游可降级关闭;核心扣减不可最终一致了事,要强一致或预留。

【原理溯源】

  • 为什么不让 A 直接改 B/C/D 的库? 破坏服务边界与自治:耦合表结构、部署与故障相互拖累,且形成事实上的分布式事务。单一数据所有者才能让演进与故障隔离。
  • 为什么“先写库再发 MQ”不够? 写库成功、发消息前宕机,消息丢失。本地消息表/Outbox 把“业务变更”和“待发事件”放进同一本地事务,消除丢消息窗口。
  • Outbox 与本地消息表差别? 思路相同;Outbox 常配合 CDC(Debezium 读 binlog)转发,应用不必写投递任务,业务与发布管道解耦。
  • 为什么消费必须幂等? MQ 至少一次投递 + 网络重试 + 生产者重发,重复是常态。没有幂等,重复事件会把下游状态打乱。
  • 为什么还要对账? 消息可能进死信、消费者可能 bug、人工可能改数。对账是最终一致的最后收敛器,按所有者数据修复下游。

【选型判断树】

text
数据被多个服务使用?
├─ 是否资金/强约束?
│   ├─ 是 → TCC / Seata 强一致链路
│   └─ 否 → 事件最终一致
└─ 最终一致选型:
    ├─ 应用要简单、已有 MQ → 本地消息表 / 事务消息
    ├─ 要与业务代码解耦、有 CDC → Outbox + Debezium
    └─ 必配:消费幂等 + 对账 + 状态机

【口述骨架】(5 分钟;框架 A,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“单一数据所有者 + 事件驱动最终一致”
0:30–1:30为什么不能直改库耦合与分布式事务代价
1:30–2:30可靠事件本地消息表 / Outbox
2:30–3:30消费与收敛幂等、状态机、对账
3:30–4:30强一致边界资金场景例外
4:30–5:00收尾“事件不丢 + 消费幂等 + 对账兜底 三件套”

【关键数字】

参数经验值说明
事件延迟秒级看 MQ 与消费能力
对账周期分钟~小时;资金 T+0/T+1按业务
重试与死信有限重试后进 DLQ必须可人工重投
Outbox relayDebezium/CanalCDC 转发
幂等键业务单号/版本号不要用随机 UUID 作业务幂等
事件峰值写TPS×下游数2000×3=6000 msg/s
不一致窗口秒~分钟级按业务 SLA
对账策略增量+抽样+关键全量避免全表扫

【追问链】(三层)

L1|“同步 RPC 调下游改数据不行吗?” → 行但差:形成调用耦合与分布式事务,下游慢/挂会拖垮 A,扩下游要改 A。事件异步化更稳。

L2|“消费失败进死信后业务就一直不一致?” → 不会「一直」,但前提是死信不是终点。第 6 条的失败处置里(第 2 条的方案组合只写到对账兜底),死信之后还有两件事要做:① 有限重试后进 DLQ,而 DLQ 必须可人工或自动重投(关键数字写死了这条),重投时带上原业务单号或版本号,配合消费幂等保证只生效一次;② 对账作为最后收敛器——原理溯源说得很清楚,消息可能进死信、消费者可能有 bug、人工可能改数,对账按数据所有者 A 的数据修复下游,并落差错工单(第 6 条)。下游还要用状态机(待同步→已同步)表达中间态,补投才敢做。窗口按第 5 条是秒到分钟级:死信一旦持续积压并越过这个窗口就该告警,否则「最终一致」会悄悄变成「永远不一致」。

L3|“以谁的数据为准修不一致?” → 以数据所有者(A)为准。下游用事件重放或拉取全量修复,并记录审计。禁止下游反向覆盖所有者。

【评分标准】

档位答案特征
60 分知道发 MQ 异步通知下游
80 分能讲本地消息表/Outbox+幂等
95 分强调单一数据所有者;对账与状态机闭环;指出资金场景不能最终一致

【关联题】

【自测】

  1. 为什么 A 不能直接改 B 的数据库? 参考答案:破坏服务自治,引入耦合与分布式事务,故障相互拖累。
  2. Outbox 模式如何保证事件不丢? 参考答案:业务变更与 outbox 记录同一本地事务提交,再由 relay/CDC 转发 MQ。
  3. 下游消费乱序/重复怎么办? 参考答案:幂等键去重;必要时按版本号/状态机拒绝旧事件;对账兜底修复。

110. 扣款接口被重复调用就重复扣钱(接口幂等) ​

【考察内容】接口幂等是后端最高频场景题之一

【题目】扣款接口因为网络重试、用户双击、消息重投被调用了两次,用户被扣了两笔钱。接口幂等怎么设计?幂等 key 怎么生成与存储?数据库唯一约束、Redis 标记、状态机三种方案各适用什么场景?

【参考答案】

  1. 幂等定义:同一操作执行多次与执行一次效果相同;
  2. 实现层次:
    • 前端/网关:按钮防重复提交、Token 机制——业务前先申请唯一 token,请求携带,服务端校验并删除(一次性消费必须原子:Redis≥6.2 用 GETDEL,更早版本用 Lua 的 EXISTS+DEL;SETNX 只能表达“首次写入成功”的占位语义);
    • 应用层:幂等键(Idempotency-Key)——客户端传业务唯一键(订单号/支付流水号),服务端以“唯一键+处理结果”存 Redis,重复请求直接返回上次结果;
    • 数据库层(最终防线):业务唯一键唯一索引,重复插入报错捕获;状态机幂等——处理前检查状态;乐观锁 version 字段;
    • 消息消费幂等:唯一键+去重表/Redis SETNX;
  3. 关键设计:
    • 幂等键要选业务唯一维度(订单号、支付单号),不能每次请求随机生成;
    • 查询接口天然幂等;扣款/发券/下单必须幂等;
    • 返回语义:重复请求返回与首次一致的结果(查已有结果而非报错);
  4. 验证:用相同请求重复调用测试。
  5. 容量估算:幂等层要按峰值接口 QPS 设计,而不是平均。扣款峰值 3000 QPS 时:唯一键写 DB 需要该表扛 3000 插入/s(应分表或热点优化);Redis SETNX 预检可挡住重复,但最终仍要 DB 唯一约束。幂等记录保留时间 ≥ 业务重试窗口(支付常见 24h~7 天)。存储:3000/s×86400×7 天若全存会巨大,需按天分区/定期清理成功记录。条件更新 UPDATE ... WHERE status=待支付 的行锁热点要评估。
  6. 失败与降级:Redis 幂等层挂→降级只靠 DB 唯一索引(限流保护);重复请求视为成功返回(查询真实状态)而不是报错,避免客户端无限重试;分布式锁过期导致并发重复→仍靠 DB 约束兜底。监控:幂等冲突次数(异常高可能是攻击或客户端 bug)、扣款重复拦截数。

【原理溯源】

  • 为什么会重复? 网络超时重试、用户双击、MQ 至少一次投递、网关重发——分布式里“调用一次”不保证“到达一次”。幂等是对不可避免的重复的防御。

  • 为什么前端防重不够? 可被脚本绕过、多端不同步、网关/客户端仍会重试。前端只能降低概率,正确性必须在服务端。

  • 为什么幂等键不能随机生成? 随机键每次请求都不同,服务端无法识别“这是同一次业务操作”。必须用业务唯一标识才能把重复请求映射到同一操作。

  • 为什么重复请求应返回首次结果而不是报错? 客户端超时重试时,往往不知道首次是否成功。返回首次结果符合“效果与一次相同”的幂等语义,也避免把成功误判为失败。

  • 为什么 DB 唯一索引是最终防线? Redis 可能丢标记、应用可能并发双写。唯一索引在存储引擎层保证“同一业务键只插一次”,冲突即失败,不会静默双写。

  • 元理论对应: 原理 5「不可靠信道上的正确性协议」(题群见《核心考点与元理论》§5.7)

【选型判断树】

text
接口是否会产生副作用(写/扣减/发券)?
├─ 否(查询)→ 天然幂等
└─ 是
   ├─ 请求前可获取一次性凭证 → Token 机制(防进入)
   ├─ 有明确业务唯一键 → 幂等键 + 结果缓存(Redis)
   ├─ 落库可建唯一约束 → DB 唯一索引(最终防线)
   └─ 有明确状态流转 → 状态机 + 乐观锁
生产建议:Token/幂等键 + DB 唯一索引 多层叠加

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“分布式重复不可避免,幂等是写接口标配”
0:30–1:30重复来源重试、双击、消息重投
1:30–3:00三层设计Token / 幂等键 / 唯一索引与状态机
3:00–4:00关键细节业务唯一键、返回首次结果
4:00–4:30验证相同请求压两次
4:30–5:00收尾“锁防互斥,幂等防重复,二者互补”

【关键数字】

参数经验值说明
幂等记录 TTL大于业务重试窗口(如 24h)过短会漏
Redis 标记幂等标记:SETNX + 过期;一次性消费(Token)要 GETDEL(≥6.2)或 Lua EXISTS+DELSETNX 只表达「首次写入成功」,不保证取走即失效;另需注意与 DB 对齐
消息重复至少一次语义下常态必须消费幂等
重试间隔指数退避避免重试风暴
唯一索引(user_id, biz_no) 类复合键按业务设计
幂等层容量= 峰值接口 QPS3000 QPS 峰值
记录保留≥ 重试窗口支付 24h–7d
最后防线DB 唯一键/条件更新缓存过期也能防重

【追问链】(三层)

L1|“只有 Redis 幂等标记够吗?” → 不够最终。Redis 可能丢数据或标记过期。关键扣减必须有 DB 唯一索引/状态机兜底。

L2|“幂等键存什么?” → 三选一或组合:① DB 唯一索引(最可靠,注意热点);② 状态机条件更新(仅允许合法迁移一次);③ Redis SETNX+TTL(快,但是缓存,过期后失效,必须 DB 兜底)。扣款场景推荐:请求唯一键+DB 唯一流水+账户余额条件更新,三重防护。返回策略:重复请求返回首次结果(或查询态),保证幂等语义。

L3|“并发两个相同请求同时到达怎么办?” → SETNX/唯一索引做互斥:一个成功占位,另一个失败或等待后读结果。幂等也要考虑并发竞态,不只是串行重试。

【评分标准】

档位答案特征
60 分知道要做防重复,会提前端防抖
80 分讲清 Token + 幂等键 + 唯一索引
95 分强调业务唯一键与返回首次结果;并发竞态;Redis 过期与 DB 兜底关系

【关联题】

【自测】

  1. 幂等键为什么不能用每次随机生成的 UUID? 参考答案:无法标识同一次业务操作,重复请求会被当成新请求。应使用订单号等业务唯一键。
  2. 重复扣款请求应报错还是返回首次结果? 参考答案:返回与首次一致的结果,符合幂等语义,避免客户端把成功当失败。
  3. 幂等的最终防线通常是什么? 参考答案:数据库唯一索引或状态机/乐观锁,在存储层挡住重复写入。

111. 用户登录后换台机器就掉登录(分布式 Session) ​

【考察内容】分布式会话设计

【题目】服务多实例部署后出现怪现象:用户这次请求被路由到 A 机器登录了,下次请求被路由到 B 机器,B 不认识他,又要重新登录。怎么解决登录态共享?Session 集中存储、JWT、Cookie 方案各自怎么做、怎么选?

【参考答案】

  1. 方案一:Session 集中存储——Session 放 Redis(spring-session),所有实例共享;登出/踢人可主动失效;适合传统 Web;
  2. 方案二:无状态 Token(JWT)——用户信息+签名放进 Token,服务端不存 Session,任意实例验签即可;适合移动端/开放 API/跨域;注意:JWT 无法主动失效(需黑名单/短有效期+refresh);
  3. 方案三:Cookie 加密(老式)——会话数据加密后放客户端;安全风险高,不推荐;
  4. 选型建议:PC 端管理系统(需要会话管理/强制下线)→Redis Session;开放平台/移动端→JWT;两者可结合(JWT 认证 + Redis 存可撤销状态);
  5. 安全要点:Token 存 HttpOnly Cookie 防 XSS、HTTPS、过期时间合理、防 CSRF(SameSite/CSRF Token)。
  6. 容量估算:日活 100 万、平均会话 2h——常驻 key 数取决于 TTL,不能直接拿日活当 key 数:TTL≥24h 或「记住我」才接近 100 万级 key(×2KB≈2GB);若按会话 2h 过期,常驻只有 100万×2/24≈8.3 万 key、约 166MB。下面按 100 万级取保守上限,Redis 单实例可容。QPS:鉴权校验几乎每个请求都要读一次——按日活 100 万×人均 100 次需鉴权的请求/日=1e8 次/日,摊算约 1160/s,集中时段按 8~14 倍 → 峰值 约 1~1.6 万 QPS——Session 读要走 Redis,可再加本地缓存(短 TTL)——但这一条会直接打掉第 1 条卖点「登出/踢人可主动失效」:TTL>0 就存在窗口,被踢/被封用户在窗口内仍被本地缓存判为有效。两种写法二选一:① 本地只缓存「JWT 验签结果+会话版本号」,踢人时版本号自增,本地比对不一致立即失效(可撤销态不落本地);② 对安全等级高的会话明确声明「可撤销态不走本地缓存」,只接受 Redis 的读放大与扩容成本。过期清理:惰性+定期。JWT 无服务端存储则 Redis 压力小,但吊销难,需要黑名单(登出/封禁时写 blacklist,其容量按需吊销比例估算)。
  7. 失败与降级:Redis Session 挂→本地无法共享登录态,可降级 JWT 验签模式或要求重新登录;多机房 Session 复制/双活;滚动发布要保证 Session 兼容。监控:Session 命中率、Redis 内存、登录失败率。安全:cookie httpOnly/secure,Session 固定攻击防护。

【原理溯源】

  • 为什么会掉登录? 默认 Session 在单机进程内存。负载均衡把后续请求打到其他实例,该实例没有这份 Session。根因是会话状态未共享。

  • 为什么 Redis Session 能解决? 把 Session 读写集中到共享存储,所有实例同一来源。代价是每次请求多一次 Redis 访问,Redis 成为可用性依赖。

  • 为什么 JWT 是无状态的? 用户信息与过期时间写在 Token 内并签名,服务端只验签不查库/缓存。水平扩展容易,但无法简单“删除”已签发 Token。

  • 为什么 JWT 主动失效难? 签名有效期内 Token 一直被接受。要么短有效期+refresh 轮换,要么维护黑名单/版本号,部分回到有状态。

  • 为什么两者可结合? JWT 负责可扩展认证,Redis 存“会话版本/黑名单”做可控失效。无状态验签 + 有状态撤销兼顾扩展与安全。

  • 元理论对应: 原理 1「资源稀缺、排队涌现与瓶颈漂移」(题群见《核心考点与元理论》§1.7)

【选型判断树】

text
是否需要强制下线/会话管理/设备管理?
├─ 是(管理后台、安全要求高)→ Redis Session
└─ 否,要跨端/开放 API/高扩展 → JWT
    └─ 仍要可撤销?→ JWT + 短有效期 + refresh + 黑名单/版本
不推荐:把大段会话明文/弱加密放 Cookie

【口述骨架】(5 分钟;框架 B,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“多实例掉登录本质是会话未共享”
0:30–1:30Redis Session集中存储、可踢人
1:30–2:30JWT无状态验签、难撤销
2:30–3:30选型管理端 vs 开放端
3:30–4:30安全HttpOnly、HTTPS、CSRF、过期
4:30–5:00收尾“可撤销性与无状态性要做取舍或混合”

【关键数字】

参数经验值说明
Session 默认过期30 分钟闲置~数小时按安全策略
JWT Access Token5 分钟~1 小时宜短
Refresh Token数天~数周用于续期
Redis Session 延迟1ms 级每请求一次
Cookie 属性HttpOnly + Secure + SameSite防 XSS/CSRF
Session 存储日活×平均体积100万×2KB≈2GB
鉴权读 QPS峰值可达数万Redis+本地缓存(只缓存验签结果+会话版本号;或明确声明可撤销态不缓存,否则吊销有 TTL 窗口)
JWT 吊销黑名单/版本号无状态的代价

【追问链】(三层)

L1|“JWT 能直接踢掉某个用户吗?” → 默认不能。需要黑名单、会话版本号,或缩短 access 有效期并使 refresh 失效。

L2|“Redis 挂了会怎样?” → 共享登录态当场失效:各实例本地都没有这份 Session,用户表现为集体掉线、要重新登录;混合方案里那份黑名单/会话版本号也读不到,吊销能力同时没了,所以必须提前定策略(读不到是拒绝还是放行,按业务选)。第 7 条给的降级是「切 JWT 本地验签或要求重新登录」——注意两者代价不对等:失去的是吊销能力,不是认证能力。缓解要按第 6 条的量级来做:日活 100 万、每 session 2KB 约 2GB,Redis 放得下,但鉴权峰值可达数万 QPS,等于把它放在核心链路上,因此要集群/双活;本地缓存只能放「验签结果+会话版本号」(踢人时版本号自增即失效)——直接缓存整个 Session 会打掉「登出/踢人可主动失效」这个卖点,安全等级高的会话应声明可撤销态不落本地。一句话结论:这里的 Redis 不是缓存,是认证依赖。

L3|“为什么不用粘性 Session(IP Hash)?” → 可临时缓解,但实例重启/扩缩容仍掉线,故障转移也破坏会话。粘性是优化不是方案,正解仍是共享存储或无状态 Token。

【评分标准】

档位答案特征
60 分知道 Session 要放 Redis
80 分能对比 Redis Session 与 JWT
95 分讲清 JWT 难撤销及混合方案;安全属性;指出粘性会话局限

【关联题】

  • 相关: 第 112 题(基础设施)、第 122 题(调用容错,Redis 依赖)
  • 安全向: 网关鉴权相关题
  • 架构: 多实例无状态化原则

【自测】

  1. 多实例掉登录的根因是什么? 参考答案:Session 存在单机内存,请求路由到其他实例后读不到。
  2. JWT 最大的工程缺点是什么? 参考答案:默认无法主动失效/踢人,需黑名单或短有效期+refresh。
  3. 管理后台强制下线你会选什么? 参考答案:Redis Session 或 JWT+Redis 会话版本/黑名单,保证可撤销。

112. 几十个微服务互相调用,怎么知道对方在哪、配置怎么统一改(注册/配置中心) ​

【考察内容】微服务基础设施

【题目】公司微服务拆了 30 个,A 调用 B 时怎么知道 B 的地址?B 的 IP 变了怎么办?另一个问题:限流阈值、功能开关散落在各服务配置文件里,改一次要重新发版。请说明注册中心和配置中心分别解决什么问题、怎么工作。

【参考答案】

  1. 注册中心(服务发现):服务启动时注册自己的地址(IP:port),消费者从注册中心获取服务实例列表并负载均衡调用;核心功能:服务注册、心跳续约、健康检查、摘除故障节点、服务订阅/变更通知。解决“服务地址动态变化”问题;
    • 选型:Nacos(功能全、AP/CP 可切换)、Eureka(AP)、ZooKeeper(CP)、Consul(多数据中心);
  2. 配置中心(动态配置):配置集中管理,修改后动态刷新到运行中的服务(@RefreshScope/长轮询),无需重启;解决“改配置要发版重启”问题;
    • 选型:Apollo、Nacos、Spring Cloud Config;
  3. 两者关系:Nacos 可同时提供;注册中心侧重“实例地址”,配置中心侧重“运行参数”。
  4. 容量估算:1000 个服务实例、心跳 5s,则注册中心写 ≈ 200 写/s,读(服务发现+配置拉取)=调用方数×每秒查找次数:1000 个实例各 5~10 次/s 才是 5000~1 万 QPS;改成订阅推送+本地缓存后,打到注册中心的实际读会掉到「实例数÷核对周期」的百级(每个调用方缓存实例列表,本地缓存后打到注册中心的实际读会低很多)。配置变更频率低,但变更推送要快:长轮询/WebSocket,秒级生效。元数据存储容量小,重点在可用性与推送而不是吞吐。多机房部署时注册中心本身要跨机房同步策略。
  5. 失败与降级:注册中心宕机→客户端本地缓存实例列表继续调用(关键);配置中心宕机→应用使用本地快照启动与运行;推送失败→客户端定时全量拉取兜底。核心原则:注册/配置中心是控制面,挂了不应导致数据面全灭。监控:心跳丢失率、推送延迟、客户端缓存命中。

【原理溯源】

  • 为什么硬编码 IP 不行? 扩缩容、容器重建、故障迁移都会改地址。硬编码导致发布耦合与频繁改配置。注册中心把“地址发现”从静态配置变成动态数据。
  • 注册中心四步闭环? 注册(上线声明)→ 心跳(证明活着)→ 健康检查/摘除(去掉死实例)→ 订阅通知(消费者更新列表)。缺一环就会调到死实例或发现不了新实例。
  • 为什么配置要动态刷新? 限流阈值、开关、超时若绑定发布,响应慢且风险大。配置中心把“变更”从“发版”解耦,支持灰度与快速回滚。
  • 为什么两者常一起谈却要分清? 都是“运行时元数据”,但生命周期与一致性要求不同:实例列表要快速摘除故障;配置变更要审计、灰度、可回滚。混用容易把配置当发现、把发现当配置。
  • 为什么 Nacos 能两者通吃? 统一元数据与控制面,降低组件数;但仍要在使用上区分领域模型(服务 vs 配置项)。

【选型判断树】

text
要解决什么问题?
├─ 服务在哪、实例变了 → 注册中心
│   ├─ 偏可用、可容忍短暂旧列表 → Nacos 临时 / Eureka
│   └─ 偏强一致 → Nacos CP / ZK / etcd
└─ 参数/开关/阈值变更 → 配置中心
    ├─ 要灰度/审计/权限强 → Apollo
    └─ 已用 Nacos、需求标准 → Nacos 配置

【口述骨架】(5 分钟;框架 A,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“注册中心管实例地址,配置中心管运行参数”
0:30–1:30服务发现闭环注册、心跳、摘除、订阅
1:30–2:30配置动态化集中管理、热更新
2:30–3:30选型Nacos/ Apollo/ Eureka/ ZK
3:30–4:30关系与边界同为控制面,职责不同
4:30–5:00收尾“动态发现 + 动态配置,才能支撑多实例快速交付”

【关键数字】

参数经验值说明
心跳/摘除秒级~30s 级看组件与模式
配置推送秒级(长轮询/推送)可灰度
Nacos 一体注册+配置减少组件
Apollo 灰度支持实例/分组发布工程强
本地缓存消费者必须有注册中心短暂不可用仍可调用
注册写 QPS实例数÷心跳间隔1000÷5=200/s
发现读 QPS调用方×查找频率本地缓存后大幅下降

【追问链】(三层)

L1|“注册中心和配置中心能不能只上一个?” → 可以用 Nacos 一体;但概念上仍要分清“服务实例”与“配置项”。只做配置或只做发现的场景也可单上对应组件。

L2|“实例列表多久更新一次?” → 两条通道叠加:变更推送 + 客户端定时全量核对。正常路径是订阅——实例上下线时注册中心把变更推给消费者,长轮询/推送是秒级到;客户端再按周期做一次全量比对,防止漏推送。这就是第 1 条「注册→心跳→健康检查摘除→订阅通知」四步闭环的最后一环。端到端时延主要由心跳决定,关键数字给的窗口是秒级到 30 秒级,坏实例通常要等续约超时才被摘掉。压力不用担心:第 4 条算过 1000 个实例、5s 心跳只有 200 写/秒,而发现侧的数千~数万 QPS 读被客户端本地缓存挡掉(关键数字:本地缓存消费者必须有)。也正因这份缓存,注册中心短暂不可用时调用不会全断。

L3|“配置改错了怎么回滚?” → 配置中心应有版本、灰度、一键回滚、审计。代码侧避免启动时一次性读死,保证热更新与回滚真正生效。

【评分标准】

档位答案特征
60 分知道有注册中心、配置中心两个东西
80 分能分别讲清解决什么、常见组件
95 分讲注册闭环与动态刷新机制;强调本地缓存与灰度回滚;能区分职责

【关联题】

【自测】

  1. 注册中心解决什么核心问题? 参考答案:服务实例地址动态变化时的自动发现与更新,避免硬编码 IP。
  2. 配置中心的核心价值是什么? 参考答案:参数集中管理与动态刷新,改配置不必发版重启,可灰度回滚。
  3. Nacos 的特点是什么? 参考答案:注册与配置一体,AP/CP 可切换,国内微服务常用。

113. 半夜注册中心宕机,服务调用会全断吗(注册中心故障) ​

【考察内容】服务发现的容错设计

【题目】半夜注册中心(Nacos)宕机,值班的人慌了:“服务之间是不是全断了?”请分析:注册中心挂了之后,服务间调用会中断吗?为什么?消费者端的缓存机制、心跳续约、摘除逻辑分别是什么?

【参考答案】

  1. 答案:短时间不会全断,取决于实现:
    • Eureka:消费者本地缓存服务实例列表,注册中心挂了不影响已缓存的调用(AP 设计,自我保护模式“宁可保留旧实例也不摘除”);
    • Nacos:临时实例模式下,消费者有本地缓存+订阅推送,短暂不可用期间仍可调用已缓存实例;服务上下线感知延迟;
    • ZooKeeper:客户端缓存连接,ZK 挂后调用依赖缓存的节点列表(CP,但新发现不可用);
  2. 真实风险:长时间不可用——新服务上线无法被发现、故障实例无法摘除(调用到已挂实例报错);
  3. 增强方案:消费者侧多级容错(本地配置兜底实例列表)、重试与故障转移、熔断降级;
  4. 心跳续约与摘除(题干直问的两问,按三步答全):续约=实例按周期上报心跳(Eureka 客户端 30s 续约、Nacos 1.x 临时实例 5s 心跳(2.x 起改为 gRPC 长连接,不再按周期发心跳 beat));标记不健康=超时未续约先标「不健康」但不立刻摘(Nacos 1.x 是 15s 标不健康、30s 才移除,2.x 起长连接一断就剔除;Eureka 90s 才出注册表);摘除=到阈值才从列表移除并推送变更,且自我保护/阈值保护会在心跳成功率过低时停止摘除(网络抖动比实例真挂更常见)。别答成「心跳一跳没回就摘」。顺带结论:注册中心高可用(集群部署)仍是必须的;但调用链不因注册中心单点而中断是分布式系统设计原则。
  5. 容量/故障窗口数字:Eureka 客户端默认缓存服务列表,注册中心宕机后在缓存 TTL/续约周期内调用不受影响(约分钟级窗口);Nacos 客户端也有本地缓存与快照。调用方 QPS 本身不打注册中心,而是打服务提供者——注册中心挂了影响的是扩缩容与故障摘除,不是已知实例的转发。容量上要评估:故障期间实例下线无法被摘除,靠调用方超时+重试+熔断消化,重试放大系数约 1+失败率×重试次数,不能超过剩余健康实例容量。
  6. 失败与降级:注册中心宕机→继续用本地缓存;实例下线→调用方超时/熔断/重试其他实例;长时间故障→运维静态路由表应急;恢复后自动重新注册。禁止:注册中心一挂就重启所有应用。演练:拔掉注册中心,验证核心交易是否仍可用。

【原理溯源】

  • 为什么短时间不断? 服务发现是“元数据”,不是每次调用都同步询问注册中心。消费者拿到列表后本地缓存,调用直接打实例。注册中心挂了,已有缓存仍可用。

  • 心跳续约做什么? 实例定期上报存活;超时未续约被标记不健康/摘除。注册中心宕机期间心跳失败,但消费者不一定立刻清空本地列表。

  • 摘除逻辑的风险? 若故障检测过于激进,网络抖动会误摘;过于保守会调到死实例。Eureka 自我保护在成功率低时停止摘除,优先保可用。

  • 为什么长时间宕机会出事? 新实例无法注册(扩容无效)、旧坏实例无法摘除、配置/元数据停止更新。系统进入“冻结的旧视图”,故障放大。

  • 为什么还要集群部署注册中心? 缓存只是短暂缓冲,不能替代高可用控制面。生产必须多副本、跨机房,并演练注册中心故障。

  • 元理论对应: 原理 3「故障是分布式系统的默认输入」(题群见《核心考点与元理论》§3.7)

【选型判断树】

text
注册中心宕机应急判断:
├─ 影响时间 < 本地缓存有效期/可接受窗口
│   → 业务通常无感;尽快恢复控制面
└─ 长时间不可用
    ├─ 消费者有静态兜底列表 → 可继续调用
    ├─ 无兜底 → 新变更冻结;调用依赖旧缓存+重试
    └─ 同时启动:集群恢复、必要时本地直连应急

设计层:消费者必须本地缓存 + 重试 + 熔断

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30结论先行“短时间不会全断,因为调用走本地缓存”
0:30–1:30机制缓存、心跳、摘除
1:30–2:30组件差异Eureka/Nacos/ZK
2:30–3:30真实风险长时间不可用的影响
3:30–4:30增强静态兜底、重试熔断、集群
4:30–5:00收尾“控制面挂,数据面要有缓冲”

【关键数字】

参数经验值说明
Eureka 自我保护15 分钟成功率 <85%停止摘除
Nacos 临时实例1.x:5s/15s/30s;2.x:断连即摘可调
本地缓存必配故障缓冲
调用超时百 ms 级快失败
注册中心集群≥3 节点高可用
客户端缓存窗口分钟级(默认配置相关)挂了不应立刻全断
重试放大1+失败率×重试次数勿超过剩余容量
应急静态路由/本地快照长故障预案

【追问链】(三层)

L1|“挂了 10 分钟会怎样?” → 若实例稳定,多数调用仍靠缓存成功;新发布/扩缩容失败;部分坏实例无法摘除导致错误率上升。要监控注册中心与错误率。

L2|“缓存里的实例已经挂了怎么办?” → 这条只能靠调用侧兜——注册中心自己正挂着,缓存列表里的坏实例没人摘。四步:① 百 ms 级超时后立刻重试到列表内其他实例,但要先按第 5 条核算放大系数(约 1 + 失败率 × 重试次数),不能超过剩余健康实例的容量,否则从「一个实例挂」变成「全挂」;② 客户端对连续失败的实例做临时剔除或降权,下个刷新周期再放回;③ 熔断保住线程池不被拖死,避免局部故障放大;④ 平时把优雅下线做扎实:先注销、等消费方列表刷新到空窗,再停进程。还要记得 Eureka 自我保护在 15 分钟内成功率 <85% 时停止摘除,这时客户端剔除与重试是唯一防线。

L3|“要不要给消费者配静态 IP 列表?” → 可作为最后兜底(本地配置/环境变量),尤其核心链路。但要防止与动态发现长期不一致,仅应急使用。

【评分标准】

档位答案特征
60 分觉得会全挂或全不挂,理由含糊
80 分知道本地缓存所以短时间不断
95 分讲心跳摘除与长时间风险;组件差异;给出静态兜底与熔断重试

【关联题】

【自测】

  1. 注册中心宕机为什么服务调用不一定中断? 参考答案:消费者本地缓存了实例列表,调用不依赖每次询问注册中心。
  2. 长期宕机的主要风险是什么? 参考答案:新实例无法发现、故障实例无法摘除、元数据停止更新,错误率上升。
  3. 消费者侧应做哪些容错? 参考答案:本地缓存、静态兜底列表、超时重试、熔断降级。

114. 订单超时扫描任务在多台机器上重复执行(分布式定时任务) ​

【考察内容】分布式任务调度

【题目】订单超时关单的定时任务每分钟扫一次,但服务部署了 10 个实例——10 个实例同时扫,订单被重复处理。怎么让定时任务只执行一次?数据量大时怎么分片并行?XXL-JOB 的分片广播机制是什么?

【参考答案】

  1. 问题:多实例同时跑同一个定时任务会重复执行——需要分布式调度;
  2. 方案:
    • 分布式锁:任务执行前抢锁(Redis SETNX),抢到才执行——简单,适合单点执行任务;
    • 分布式调度框架(XXL-JOB/Elastic-Job):
      • 任务注册到调度中心,调度中心按路由策略分发给执行器(轮询/一致性哈希);
      • 分片广播:任务拆 N 片,每个执行器处理一片(如按订单 ID 取模),提高吞吐;
      • 失败重试、任务依赖、超时控制;
    • Elastic-Job:基于 ZK 分片,分片总数固定,实例增减自动重分片;
  3. 幂等兜底:即使调度框架保证了不重复,任务处理本身也要幂等(如关单前检查订单状态);
  4. 监控:任务执行记录、失败告警、执行耗时。
  5. 容量估算:日订单 50 万、超时关单 30%,扫描任务每分钟扫一次未支付超时单,先按题干口径把量算出来:日订单 50 万×30%=15 万条超时单/日,每分钟一轮 → 每轮均值只有 15万÷1440≈104 行;「峰值数万行/轮」要成立,得让单分钟占日量 20%–33%(峰值是均值的 288–480 倍),不现实。正常写「数百~数千行/轮」;只有把「积压若干轮」或「扫描窗口放宽到小时级」当明确前提时才写数万——按错值配分片数会把并行度高配一到两个数量级。分片扫描:按订单 ID 哈希到多机,每实例每轮处理 N 千行,处理能力要 > 到达速率。分布式锁/任务调度组件(XXL-Job、ElasticJob、Quartz 集群)自身操作 QPS 很低,容量瓶颈在扫描 SQL 与关单写。避免全表扫:索引 (status, expire_time) + 时间游标。
  6. 失败与降级:锁失效导致短暂重复执行→关单接口幂等+状态条件更新;调度中心宕机→备节点接管;扫描 SQL 慢→分页/分片/限流;MQ 延迟消息已有兜底扫描时注意勿双开冲突(统一入口)。监控:任务执行成功率、重复执行次数、关单耗时、积压待关单数。

【原理溯源】

  • 为什么会重复执行? 每个 JVM 的 @Scheduled 都独立触发,彼此不知情。单机互斥原语(synchronized)锁不住其他进程。
  • 分布式锁方案的本质? 用跨进程互斥把“多实例触发”收敛为“一实例执行”。简单,但吞吐受限于单实例,且锁可靠性依赖 Redis。
  • 调度框架解决了什么? 把“谁来跑、跑哪部分、失败怎么办”集中编排:路由策略防重复、分片提升并行、失败重试与日志可运维。
  • 分片广播为什么快? 把大扫描按 ID 取模拆给多执行器并行处理,避免单点扫描超时。分片键要均匀,数据倾斜要治理。
  • 为什么仍要任务幂等? 框架只能保证“调度层尽量一次”,不能消除重试、重复消息、业务并发。关单仍要检查状态,幂等是业务正确性底线。

【选型判断树】

text
任务特征?
├─ 必须单实例执行、频率低 → 分布式锁(Redis)+ 幂等
├─ 要可视化、重试、路由 → XXL-JOB
├─ 要自动分片与弹性 → Elastic-Job / XXL-JOB 分片广播
└─ 数据量极大 → 分片并行 + 幂等 + 告警监控
任何方案都要:业务幂等

【口述骨架】(5 分钟;框架 B,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“本地定时器多实例必然重复,需要分布式调度”
0:30–1:30锁方案抢锁执行,简单但单点
1:30–2:30调度框架XXL-JOB 路由与重试
2:30–3:30分片广播取模并行
3:30–4:30幂等与监控状态检查、失败告警
4:30–5:00收尾“调度防重复,幂等保正确”

【关键数字】

参数经验值说明
扫描任务周期1 分钟常见按超时精度
分片数常等于执行器实例数可配置
锁等待0(抢不到就跳过)避免堆积
重试次数(重复任务的抢占重试)有限次+告警防止风暴
大表扫描按 ID 游标分批避免全表锁
扫描到达量日订单×超时比例÷轮次50万×30%≈15万/日 ÷1440 轮 ≈104 行/轮(均值),正常写「数百~数千行/轮」;「数万行/轮」只有把积压若干轮或小时级窗口当前提才成立,别据此配分片数
关键索引(status, expire_time)防全表扫
正确性幂等+条件更新锁只是减少重复不是唯一保证

【追问链】(三层)

L1|“只靠 Redis 锁够吗?” → 单点任务够用,但锁丢失/过期仍可能重复,所以处理必须幂等;要监控与执行日志。

L2|“分片不均匀怎么办?” → 先分清是「分片键选错」还是「数据本身有热点」。第 5 条的场景:日订单 50 万、超时约 30%(50 万×30%≈15 万单/日)要扫描关单,按订单 ID 取模各片本应等量;若按用户或商户维度分片,大商户订单整块落进同一片,就是选键问题。四个手段:① 分片键换成均匀且与业务无关的维度(订单 ID 哈希),必要时二次拆片;② 分片数与执行器实例数对齐——但对齐时每片只有一个执行器,热片不能靠加实例分走,只能按①二次拆片,或单独给热片所在实例加算力;③ 片内按 ID 游标分批并走 (status, expire_time) 索引,避免单片慢 SQL 拖死整轮;④ 每轮设上限并限流,慢片下一轮追赶。监控要盯各片耗时与积压待关单数——原理溯源那句「数据倾斜要治理」就是这条。

L3|“调度中心挂了任务会怎样?” → 调度中心需集群;任务定义与执行日志持久化;可降级为各实例抢锁模式。控制面高可用与业务幂等同时要。

【评分标准】

档位答案特征
60 分知道要加分布式锁
80 分能讲 XXL-JOB 分片广播
95 分强调业务幂等;分片倾斜;调度中心高可用与监控

【关联题】

【自测】

  1. 为什么本地 @Scheduled 多实例会重复? 参考答案:每个进程独立触发,无跨实例互斥。
  2. XXL-JOB 分片广播解决什么问题? 参考答案:把任务拆成多片并行执行,提高吞吐并避免单实例扫描瓶颈。
  3. 调度框架上线后还需要幂等吗? 参考答案:需要。调度层无法消除重试与业务并发,处理逻辑仍要可重入。

115. 一个请求跨 5 个服务变慢,怎么定位是哪一环(链路追踪) ​

【考察内容】可观测性设计

【题目】一次请求要串行经过网关、订单、库存、支付、短信 5 个服务,线上变慢了,但每个服务的监控都“看着正常”。怎么定位到底慢在哪个服务哪一环?链路追踪的核心原理是什么(traceId/span、透传、采样)?

【参考答案】

  1. 核心机制:TraceID + SpanID:
    • 请求入口生成全局唯一 TraceID,通过 HTTP 头/消息头透传到所有下游服务;
    • 每个服务内每个调用(DB、RPC、MQ)生成 Span(父子关系),记录耗时、状态、参数;
    • 所有 Span 汇总到追踪系统(按 TraceID 聚合),还原完整调用链;
  2. 组件:SkyWalking(无侵入,字节码增强)、Zipkin、Jaeger(OpenTelemetry 标准);
  3. 使用场景:
    • 定位慢请求:看各 Span 耗时,找出最慢环节;
    • 定位报错:按 TraceID 查全链路日志;
    • 依赖分析:服务间调用关系图、依赖瓶颈;
  4. 配套:日志中打印 TraceID(logback MDC),日志检索与链路关联;
  5. 采样策略:全量采样压力大,生产按需采样(如 10% 采样+错误全采+超阈值慢请求 100% 采样)。题干要「定位慢在哪一环」,而慢请求通常不是错误,按 10% 抽样会漏掉九成现场,等于把要查的那批 trace 扔了;本条判断树与【关键数字】也正是按「错误/慢请求常 100% 采样」口径写的。
  6. 容量估算:日请求 1 亿、采样率 1% → 被采请求 100 万条;一条链路按本题【关键数字】的 5~10 个服务展开,日 span 样本约 500 万~1000 万条(别把被采请求数当 span 数);若全采,存储与索引成本高,通常头部全采或重要链路提高采样。单服务 QPS 1 万时,trace 上报不能阻塞业务:本地异步队列+批量上报。存储:Jaeger/SkyWalking 等按保留天数估磁盘。查询场景是排障,不是在线业务路径——追踪系统挂了不影响交易,但要能告警。
  7. 失败与降级:追踪 agent 故障→业务不受影响(旁路);采样导致没抓到现场→关键路径提高采样/错误全采;时钟不一致导致 span 乱序→统一 NTP(见 116);与日志/指标关联(traceId 打日志)才能闭环。监控:上报丢失率、存储水位、查询延迟。

【原理溯源】

  • 为什么单服务监控“看起来正常”? 每个服务 RT 可能都 OK,但串行相加、某次下游抖动、线程池排队只在“全链路视角”暴露。缺的是跨服务关联。
  • TraceID/Span 如何还原链路? TraceID 标识一次端到端请求;Span 标识一次工作单元及其父子关系。按 TraceID 聚合即可重建调用树与耗时分解。
  • 为什么必须透传? 若下游拿不到 TraceID,链路会断成孤岛。通过 HTTP Header / gRPC metadata / MQ 消息头传递,是接入追踪的第一要求。
  • 为什么要采样? 高 QPS 全量上报会打爆存储与带宽。常用概率采样 + 错误/慢请求全采,兼顾成本与排障。
  • 为什么日志要带 TraceID? 链路告诉你“哪一环慢/错”,日志告诉你“为什么”。两者用同一 ID 关联,排障才闭环。

【选型判断树】

text
排障需求?
├─ 只看单服务指标 → 现有 metrics 足够
└─ 跨服务慢/错 → 必须链路追踪
    ├─ 要无侵入 Java → SkyWalking
    ├─ 标准化/多语言 → OpenTelemetry + Jaeger/Zipkin
    └─ 高 QPS → 概率采样 + 错误全采 + 慢请求全采

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“单服务指标正常不代表全链路正常”
0:30–1:30Trace/Span全局 ID 与父子 Span
1:30–2:30透传Header/MQ 传递
2:30–3:30定位用法按 Trace 看耗时与错误
3:30–4:30采样与日志成本与 MDC 关联
4:30–5:00收尾“追踪定位环节,日志解释原因”

【关键数字】

参数经验值说明
采样率1%~10% 常见视 QPS 与成本
错误/慢请求常 100% 采样保排障
Span 数量控制在必要调用过多成本高
日志关联必须打 TraceIDMDC
典型链路5~10 服务微服务常态
上报异步批量,不阻塞业务旁路系统

【追问链】(三层)

L1|“TraceID 怎么传到 MQ 消费者?” → 生产端写入消息 Header/属性,消费端取出放入 MDC 与新 Span,保持与上游同一个 Trace。

L2|“采样后会不会丢掉关键故障?” → 会,所以采样不能只用一种规则。第 5 条的策略是概率采样叠加错误全采:概率采样按 1%~10% 控成本,第 6 条算过日请求 1 亿、1% 采样是被采请求百万级,按一条链路 5~10 个服务展开就是 500 万~1000 万 span,全采则存储与索引扛不住;但错误和超阈值的慢请求要 100% 采样(关键数字给的就是这个口径),这是关键故障的主要入口。剩下两类漏采要单独防:① 下游把异常吞成 200、或业务错误不体现在错误码上,概率采样就会漏,需要错误码与慢阈值双重触发;② 支付、下单这类核心链路单独提高采样率甚至全采。最后的兜底是第 4 条:日志必须带 TraceID(MDC),采样没命中时还能按 ID 把各服务日志串起来看现场。

L3|“追踪系统本身挂了影响业务吗?” → 上报应异步、有界队列、失败丢弃,不阻塞业务。追踪是旁路,不能反向拖垮主链路。

【评分标准】

档位答案特征
60 分听说过 SkyWalking/Zipkin
80 分能讲 TraceID/Span 与透传
95 分讲采样策略、日志关联、旁路上报不阻塞;能说如何用 Trace 定位慢节点

【关联题】

【自测】

  1. 链路追踪的最小必要数据是什么? 参考答案:全局 TraceID + 各服务 Span(含耗时/状态/父子关系)。
  2. 为什么高 QPS 要采样? 参考答案:全量上报成本过高;用概率采样并对错误/慢请求全采。
  3. 日志如何与链路关联? 参考答案:日志中输出同一个 TraceID(MDC),可按 ID 检索全链路日志。

116. 两台服务器时间差了 5 分钟,日志排序和限流全乱了(时钟不一致) ​

【考察内容】分布式时间一致性

【题目】排查问题时发现两台服务器系统时间差了 5 分钟:日志时间线对不上、限流窗口错乱、订单超时判断不准。分布式系统时钟不一致会产生哪些问题?如何解决(NTP 同步、逻辑时钟、不依赖物理时钟的设计)?

【参考答案】

  1. 问题:
    • 日志/链路追踪乱序:多机日志时间戳不一致,排查困难;
    • 业务时间错误:订单时间、定时任务触发、过期判断出错;
    • 分布式锁/雪花 ID:时钟回拨产生重复 ID;锁过期判断依赖时钟;
    • 跨时区/容器时钟漂移;
  2. 解决:
    • NTP 时钟同步:统一 NTP 服务器,chrony/ntpd 定期校准,监控偏差;
    • 不用本地时钟做关键决策:业务时间以数据库/服务端统一时间为准(NOW()),时间戳字段由 DB 生成;
    • 单调时钟 vs 墙上时钟:性能统计、超时计时用单调时钟;业务时间用墙上时钟并同步 NTP;
    • 雪花算法:监控偏差+回拨处理;
  3. 设计原则:分布式系统里“时间”要有唯一权威来源,本地时钟只用于辅助。
  4. 容量/偏差数字:机器间时钟偏差应控制在 毫秒级(NTP 通常 <50ms,好的环境 <10ms);超过秒级会影响:分布式锁 TTL、限流窗口、日志排序、雪花 ID、会话过期、安全审计。5 分钟偏差对限流窗口(1s/1min)是毁灭性的。审计与金融日志要求更高,常要 NTP 监控+偏差告警(如 >100ms 预警、>1s 严重)。跨机房要评估时间源(GPS/北斗/NTP 层级)。
  5. 失败与降级:NTP 漂移→自动校时+告警;容器环境用宿主机时钟与 chrony;生成 ID 回拨策略(107);日志排序以服务端接收时间+逻辑时钟辅助;限流可用 Redis 中心化计数减少对本机时钟依赖——但要认清它只合并「同一个窗口桶」里的多实例计数:分钟桶的 key 仍由本机时钟生成,两台实例差 5 分钟就会写进不同桶,中心化计数并不纠正桶本身的错位,题干「限流抖动」这个症状没被解决。要真消除时钟依赖,窗口标识必须由单一权威时钟产生:网关单点计数、Redis Lua 内用 TIME(服务端时间)取桶,或直接读 DB 时间。禁止人工改生产时间。

【原理溯源】

  • 为什么分布式没有“真·同时”? 物理时钟总有偏差,网络传输也有延迟。系统设计要么接受近似并同步,要么改用逻辑序(版本号、序列)。

  • 为什么关键业务时间要 DB 生成? 多实例本地时钟不一致时,各写各的时间会乱序。DB NOW() 提供单一权威时间,避免应用机器漂移污染数据。

  • 单调时钟和墙上时钟差在哪? 墙上时钟(wall clock)可被 NTP 回拨,适合展示与业务日期;单调时钟只保证本进程内递增,适合测量耗时与超时,不能用于跨机器排序。

  • 限流窗口为什么会乱? 基于本地时间的窗口在多实例不齐,导致限流计算不一致。应把窗口计算放到共享存储/统一时钟,或用逻辑计数——但「共享存储」本身不够:若桶 key(如分钟号)仍由各实例本机时钟生成,中心化计数只是把「同一个桶」的多实例计数合并,并不纠正桶本身的错位;窗口标识必须由单一权威时钟产生(网关单点计数、Redis Lua 内 TIME、或读 DB 时间)。

  • 为什么 NTP 也不是万能? 校准有延迟与幅度,仍可能回拨;虚拟机迁移更严重。所以应用层还要“不依赖可疑时钟做关键决策”。

  • 元理论对应: 原理 4「时间差、多副本视图与一致性窗口」(题群见《核心考点与元理论》§4.7)

【选型判断树】

text
这个时间用来做什么?
├─ 展示/业务日期 → DB/统一时间服务生成
├─ 耗时/超时 → 本进程单调时钟
├─ 跨机器排序/追踪 → 统一时间源 + 逻辑序辅助
├─ 发号/锁过期 → 特殊算法与兜底(见 107/105)
└─ 全集群基础 → NTP/chrony + 偏差监控

【口述骨架】(5 分钟;框架 B,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“分布式时间必须有权威来源,不能信各自本地时钟”
0:30–1:30问题面日志、过期、锁、雪花
1:30–2:30NTP同步与监控
2:30–3:30设计规避DB 时间、单调时钟
3:30–4:30算法敏感点雪花回拨、限流窗口
4:30–5:00收尾“时间也是一种分布式共识问题”

【关键数字】

参数经验值说明
机房内 NTP 偏差目标毫秒级超标告警
公网 NTP可能几十 ms~更大生产用内网 NTP
日志时间乱序分钟级偏差就明显5 分钟必炸
墙上 vs 单调不可混用排序currentTimeMillis vs nanoTime
容器时钟易漂移宿主机与 guest 配置
偏差目标毫秒级(<50–100ms)NTP 监控
严重阈值>1s 告警处理影响锁/限流/ID
限流中心化计数只合并「同一个窗口桶」里多实例的计数桶 key 若仍由本机时钟生成,实例间差 5 分钟就写进不同桶——窗口标识须由权威时钟产生(网关单点/Lua TIME/DB 时间)

【追问链】(三层)

L1|“同步 NTP 就万事大吉?” → 不是。NTP 仍有偏差与回拨。关键决策要避免依赖可疑本地时钟,用 DB 时间或逻辑版本。

L2|“订单过期时间谁生成?” → 由统一时间源生成,实践中就是数据库:下单那条本地事务里用 DB NOW() 写下 expire_time(或由中心时间服务给一个绝对时刻),应用实例不拿本机时钟写业务时间。原因是第 1 条把它列为「业务时间错误」的重灾区:订单时间、定时任务触发、过期判断都依赖同一个时刻;两台机器差 5 分钟时各实例自己算,就会一部分订单提前被关、另一部分延后被关,用户看到的就是「刚下单就失效」。关单判断也写成 WHERE expire_time <= NOW(),让比较发生在同一个权威时钟上。第 4 条的运维口径同时成立:偏差目标毫秒级、>1s 必须处理——NTP 是地基,权威时间源是兜底。

L3|“能不能完全不用物理时间?” → 可用逻辑时钟/版本号表达顺序,但很多业务仍要人类可读时间。工程上是“物理时间近似 + 逻辑序保正确”。

【评分标准】

档位答案特征
60 分知道要 NTP 对时
80 分能列举时钟不一致的业务影响
95 分讲权威时间源、单调 vs 墙上、雪花/锁风险;不过度神话 NTP

【关联题】

【自测】

  1. 为什么业务时间戳建议由 DB 生成? 参考答案:提供唯一权威时间,避免多实例本地时钟偏差污染数据。
  2. 耗时统计应使用什么时钟? 参考答案:单调时钟(如 nanoTime),不受墙上时钟回拨影响。
  3. 时钟不一致还会破坏哪些组件? 参考答案:分布式锁过期、雪花 ID、限流窗口、日志与追踪排序。

117. 用户手抖连点两次“提交订单”,生成了两单(重复下单) ​

【考察内容】防重复下单是电商最高频实战题

【题目】用户手抖或网络重试导致“提交订单”被调用了两次,生成了两个一模一样的订单,还被扣了两份钱。前端只做按钮置灰不够,后端怎么防?幂等方案(幂等键、唯一约束、状态校验)怎么设计?

【参考答案】

  1. 前端:按钮提交后置灰/loading(防手抖,防不了脚本);
  2. 服务端 Token 机制:下单前获取唯一 token(绑定用户),下单请求携带 token,服务端原子消费 token:Lua 里判断存在并删除(或 6.2+ 的 GETDEL)——取不到即视为已使用,拒绝;
  3. 业务幂等键(前提要写出来,否则第 4 条防线形同虚设:一次提交流程只生成一次、失败重试沿用同一个键,若手抖两次各自新生成 UUID,两条请求都「合法」,唯一键根本不冲突):前端生成/服务端下发幂等键,服务端以“用户+幂等键”为唯一键处理,重复请求返回首个结果;
  4. DB 最终防线:唯一键按业务语义选,三种情况别混——① 普通下单:用 (user_id, 幂等键/请求号)(或订单号本身唯一),重复请求返回首单;不能按「用户+商品」建唯一索引,那会把正常复购直接判重报错;② 限购/一人一单活动:(user_id, sku_id, 活动期) 或 (user_id, activity_id),此时“同商品不允许第二次下单”正是业务要求,与第 131 题同口径;③ 支付回调:渠道流水号唯一,重复回调只入账一次。分布式锁/SETNX 只降低冲突概率,正确性始终以 DB 唯一索引为准(锁丢了索引还在;索引没有时,锁挡不住多实例竞态);
  5. 顺序(题干第二问括号里的「状态校验」落在这一环,不能只答幂等键+唯一约束):校验(Token)→ 状态校验(按幂等键查该请求是否已成单:SELECT order_id,status FROM ... WHERE idempotent_key=?,命中即直接返回首单结果,不再往下走)→ 防重(唯一索引)→ 生成订单(事务;后续状态流转用 WHERE status=待支付 这类条件更新守住状态机)→ 返回。要点:「查—判—插」必须原子(同一事务内靠唯一索引兜竞态,或先用 Redis SETNX 占位),只查不判会双写,只插不查则重复请求拿到的是报错而不是首单;
  6. 注意:幂等键需用户维度(同一用户同一操作同一结果),不能全局一个。
  7. 容量估算:防重复下单的幂等层按提交峰值设计,秒杀场景提交峰值可达 数千~数万 QPS。token/唯一键校验若走 Redis,需同量级;DB 唯一索引插入热点要分片时,分片键必须含(或等于)幂等唯一键的判定维度,即按 user_id/幂等键分片;不能按「订单号」分片:每次重试都带一个新订单号,同一幂等键会落到不同分片,跨分片唯一约束不成立,第 4 条的「最终防线」就被这条自己拆掉(订单号只作唯一列,不作分片键)。按钮防抖只能挡前端,挡不住脚本——服务端幂等才是关键。库存扣减与创建订单要原子或条件更新,重复提交不应导致超卖。
  8. 失败与降级:Redis token 层挂→DB 唯一约束+条件更新兜底;重复提交返回已有订单(查询态)而不是报错;脚本刷单→限流+风控+验证码。监控:重复提交拦截次数、创建订单成功率、唯一键冲突率。

【原理溯源】

  • 为什么前端置灰不够? 双开标签、脚本、弱网重试、网关重发都会造成重复。客户端提示是体验优化,不是正确性机制。

  • Token 机制防的是什么? 防“重复提交进入业务”。一次一 token,用后作废,相当于门票。适合“提交前先领票”的流程。

  • 幂等键与 Token 差别? Token 强调一次性准入;幂等键强调“同一业务操作多次请求返回同一结果”。下单两者可叠加。

  • 为什么必须 DB 唯一索引? 应用层锁/缓存都可能失效或竞态。唯一索引在存储引擎保证同一业务组合只成功一次,是不可绕过的最终防线。

  • 为什么重复时应返回已下单而不是报错? 用户/调用方往往不知道首次是否成功,返回已有订单符合幂等语义,也避免误报失败导致再次下单。

  • 元理论对应: 原理 5「不可靠信道上的正确性协议」(题群见《核心考点与元理论》§5.7)

【选型判断树】

text
防重复下单分层:
1. 前端置灰(体验)
2. Token 准入(防进入)
3. 幂等键/唯一索引(防重复业务效果;前提:一次提交流程只生成一个键、重试沿用同键,否则两条请求各自唯一、索引根本不冲突)+状态校验:按幂等键查是否已成单,命中即直接返回首单(第三问要答到防重复的落地)
4. 库存与支付也幂等(链路下游)

秒杀一人一单:
└─ 唯一索引 (user_id, activity_id) 或 用户维度锁 + 幂等

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“防重复下单是多层防御,不是单点按钮”
0:30–1:30重复来源手抖、重试、多端
1:30–3:00四层与【选型判断树】同切法:前端置灰 / Token 准入 / 幂等键+唯一索引(状态校验落在这一环:按幂等键查已成单即返首单)/ 下游库存与支付也幂等
3:00–4:00返回语义返回首次订单
4:00–4:30秒杀场景一人一单唯一键
4:30–5:00收尾“正确性在 DB 约束,体验在前端”

【关键数字】

参数经验值说明
Token 有效期分钟级领取后尽快使用
唯一索引普通下单 (user_id, 幂等键/请求号);限购活动 (user_id, sku_id, 活动期);支付回调 (渠道流水号)切勿对订单表建 (user_id,item_id)——会挡正常复购
重复响应200 + 首次订单信息非 500
Redis 锁仅辅助不能替代唯一索引
超时重试必须可安全重试幂等前提
提交峰值秒杀可达数千–数万 QPS幂等层按峰值设计
前端防抖按钮置灰/token 一次有效挡不住脚本
服务端唯一键+状态条件更新最后防线

【追问链】(三层)

L1|“只用 Redis 锁行不行?” → 不行作最终手段。锁过期/丢锁仍可能双单。锁可降并发,正确性靠唯一索引/幂等。

L2|“Token 存 Redis,Redis 挂了?” → 票发不出来,但不能把业务一起挂掉。按第 8 条:Redis token 层挂→直接由 DB 唯一约束加条件更新兜底,第 4 条①那条 (user_id, 幂等键) 唯一索引还在(一人一单活动才换成②的用户+商品+活动维度),重复提交仍然只成功一次——Redis 在这里只降低并发冲突,正确性不依赖它,关键数字也写明 Redis 锁「仅辅助,不能替代唯一索引」。副作用要提前算:token 这道前置闸门失效后,第 7 条说的数千~数万 QPS 提交峰值会直接打在唯一索引冲突上,所以 Redis 不可用期间要收紧该接口限流,必要时上验证码与风控。对用户不能返回 500:重复请求要返回已创建的订单(查询态),否则用户以为失败再下一单,反而制造更多冲突。

L3|“同一用户不同商品能不能各下一单?” → 可以。幂等键维度要与业务规则对齐:一人一单活动用活动维度;普通下单用订单幂等键维度,不能错误全局互斥。

【评分标准】

档位答案特征
60 分提到前端防抖
80 分会讲 Token/幂等键
95 分唯一索引最终防线(含分片键必须含幂等维度)、状态校验+返回首次结果、秒杀一人一单设计、幂等键「一次流程一个键」前提

【关联题】

【自测】

  1. 防重复下单为什么不能只靠前端? 参考答案:脚本、多标签、重试仍会重复请求,服务端必须有幂等与约束。
  2. 一人一单秒杀最可靠的手段是什么? 参考答案:数据库唯一索引(user_id+activity_id),冲突则返回已下单。
  3. 重复提交应如何响应? 参考答案:返回首次成功结果(已有订单),而不是报错。

118. 多实例部署后 synchronized 全失效了(多实例并发控制) ​

【考察内容】分布式并发安全是综合必考题

【题目】服务从单机改成多实例后,原来靠 synchronized 保护的并发逻辑全部失效(每个实例各锁各的)。并发安全怎么办?从数据库乐观锁、Redis 分布式锁、消息队列串行化三个层面,给出完整方案与适用场景。

【参考答案】

  1. 认知:多实例=多个 JVM 进程,本地锁只能锁住本进程,跨进程必须用分布式互斥机制;
  2. 方案矩阵:
    • 数据库:悲观锁(SELECT FOR UPDATE)、乐观锁(version/CAS 条件更新)——简单可靠,适合低频写;
    • Redis 分布式锁:SET NX EX + Lua 解锁 + 看门狗(Redisson)——性能好,适合高并发临界区;
    • ZooKeeper/etcd 锁:临时顺序节点/租约,强一致,适合严格互斥;
    • 消息队列串行化:并发写投递 MQ,单消费者串行处理——用排队换无锁;
  3. 配套原则:
    • 能避免锁就避免:幂等、唯一索引、状态机;
    • 锁粒度最小化:按用户/订单维度,避免全局锁;
    • 锁+幂等双保险:锁是事前互斥,幂等是事后兜底;
  4. 验证:多实例并发压测。
  5. 容量估算:多实例下本地 synchronized 只能保证单机互斥。若全局临界区操作峰值 1000 次/s,Redis 分布式锁要能扛同量级且持锁时间短;若临界区耗时 50ms,则全局串行上限约 20/s——说明这类热点应改设计(分段锁/队列/合并),而不是硬加锁。本地锁适合单实例内存结构;跨实例一致性要靠分布式协调或无共享分片。
  6. 失败与降级:Redis 锁不可用→可降级为“允许短暂重复+业务幂等”或 DB 行锁;锁性能不够→按 key 分段,把全局锁拆成 64/256 个分片锁,提高并行。监控:锁等待 RT、冲突率、重复执行次数。

【原理溯源】

  • 为什么 synchronized 失效? JVM 锁对象在进程内存,进程间不可见。水平扩展后临界区从“一个 JVM”变成“多个 JVM + 共享存储”,本地锁鞭长莫及。

  • 为什么数据库方案稳? 行锁/条件更新发生在共享的 DB,天然跨实例。代价是热点行锁竞争与 DB 吞吐上限。

  • 为什么 Redis 锁性能好? 内存操作+异步复制,延迟低。正确性弱于 ZK,需要幂等兜底。

  • 为什么 MQ 串行化可行? 把并发改成排队,消费者单线程处理同一队列/分区,用顺序性换简单正确。适合可异步、吞吐可削峰的写。

  • 为什么“能不锁就不锁”? 锁引入阻塞、死锁、性能瓶颈。唯一索引、状态机、CAS 用数据约束表达互斥,往往更稳。

  • 元理论对应: 原理 1「资源稀缺、排队涌现与瓶颈漂移」(题群见《核心考点与元理论》§1.7)

【选型判断树】

text
冲突频率与正确性要求?
├─ 冲突少、低频 → 乐观锁 version/CAS
├─ 冲突多、要强一致行级 → 悲观锁 SELECT FOR UPDATE
├─ 高并发临界区、可接受小概率丢锁 → Redis 锁 + 幂等
├─ 严格互斥、不能丢锁 → ZK/etcd
└─ 可异步、要削峰 → MQ 串行化
通用:锁粒度最小 + 幂等/唯一索引兜底

【口述骨架】(5 分钟;框架 B,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“本地锁跨不了进程,必须分布式互斥”
0:30–1:30数据库乐观/悲观
1:30–2:30Redis/ZK性能 vs 强一致
2:30–3:30MQ 串行排队换无锁
3:30–4:30原则最小粒度、锁+幂等
4:30–5:00收尾“先数据约束,再分布式锁”

【关键数字】

参数经验值说明
乐观锁冲突重试有限次,否则降级防活锁
Redis 锁 TTL略大于临界区耗时 + 看门狗防死锁与提前释放
行锁等待超时秒级可配避免拖垮连接
MQ 分区/队列同一业务键同一分区保证串行
压测必须多实例验证真实并发
本地锁范围仅单实例多实例失效
全局串行上限1/临界区RT50ms→约20/s,需改设计
优化分段锁/消息合并避免全局大锁

【追问链】(三层)

L1|“乐观锁和悲观锁怎么选?” → 读多写少/冲突少用乐观锁(失败重试);写冲突多、必须强制排队用悲观锁。热点极高时考虑拆分或异步化。

L2|“Redis 锁与 DB 谁是最终正确性?” → DB 侧的数据约束才是最终正确性,Redis 锁是并发优化。三条依据:① 第 6 条明写「Redis 锁不可用→降级为允许短暂重复 + 业务幂等,或 DB 行锁」——一个可以被降级的组件不承担正确性;② 机制上 Redis 是内存操作 + 异步复制,主从切换会丢锁、TTL 抖动会提前释放,而行锁/条件更新/唯一索引发生在共享的 DB 上,天然跨实例且不可绕过;③ 第 3 条把「幂等、唯一索引、状态机」排在锁前面——锁是事前互斥,幂等是事后兜底。也别用锁硬扛热点:第 5 条算过临界区 50ms 时全局串行上限约 20 次/秒,这种量级必须改设计(分段锁、队列串行化、合并写)。

L3|“会不会死锁?” → 分布式锁要统一加锁顺序、设置超时与续期;DB 锁要缩短事务、按固定顺序访问行。死锁预防靠协议与超时,不靠运气。

【评分标准】

档位答案特征
60 分知道 synchronized 多实例无效
80 分能列 DB/Redis/ZK/MQ 方案
95 分讲选型条件、锁+幂等、最小粒度;知道数据约束优于盲目加锁

【关联题】

  • 同簇: 第 104/105 题(锁)、第 110/117 题(幂等)、第 114 题(任务)
  • 相关: 第 102 题(全局锁瓶颈)

【自测】

  1. 为什么单机锁在多实例下失效? 参考答案:锁状态在进程内存,进程间互不可见。
  2. 高并发扣库存你会怎么组合? 参考答案:Redis/本地预扣 + DB 条件更新或唯一约束 + 幂等,极端热点再拆分异步。
  3. “锁+幂等”分别防什么? 参考答案:锁防并发进入;幂等防重复执行/锁失效后的错误结果。

119. 本地缓存+Redis+DB 三级,更新一处到处不一致(多级缓存一致性) ​

【考察内容】多级缓存一致性治理

【题目】系统做了“本地缓存 → Redis → DB”三级缓存,更新数据时只更新了 DB,用户读到的还是本地缓存/Redis 里的旧值,三层互相不一致。怎么设计更新策略?本地缓存怎么失效(广播/短TTL)?怎么取舍一致性 vs 性能?

【参考答案】

  1. 更新策略(写路径):
    • 先更新 DB(事务提交);
    • 删除 Redis 缓存(Cache Aside),删除失败用重试/MQ/Canal binlog 兜底;
    • 本地缓存失效:通知所有实例——Redis Pub/Sub 广播失效消息、版本号比对、短 TTL 兜底;
  2. 读路径:本地缓存 → Redis → DB → 逐级回填;
  3. 一致性等级:
    • 强一致(很难):读写串行化/绕过缓存,一般不追求;
    • 最终一致(主流):删缓存+异步补偿+短 TTL,秒级收敛;
  4. 特殊场景:
    • 更新频繁的数据不进本地缓存;
    • 广播失效实例多时压力大——用版本号替代广播;
    • 对账任务兜底修复;
  5. 原则:能容忍的短暂不一致用最终一致;不能容忍的直读 DB。
  6. 容量估算:本地缓存抗热点,进程内访问是微秒级(纳秒–微秒,无网络往返)、容量 MB~GB 级;0.1–1ms 是 Redis 那一层;Redis 扛主要读,单实例 10 万+ QPS;DB 扛残余与写。命中率目标:本地 30%+、Redis 90%+ 组合下落到 DB 的比例很低。例:读峰值 10 万 QPS,本地命中 30%→7 万打 Redis,Redis 命中 95%→约 3500 QPS 落 DB,DB 可接受。更新风暴时失效比例上升,要限流回源。缓存内存:热点 key 体积×数量×副本。
  7. 失败与降级:本地缓存不一致→短 TTL+版本号/失效广播;Redis 挂→本地缓存+限流打 DB——先把降级后的量写出来再谈限流:按本条第 6 项口径,本地只挡 30%,Redis 一挂就有 ≈7 万 QPS 直接落 DB,而同一条里 DB 的「可接受」是 3500 QPS,差 20 倍;只说「限流打 DB」等于当场拒掉约 95% 的读请求(3500 ÷ 7 万 ≈ 5%,与前面的「差 20 倍」是同一个数),且本地未命中仍要回源 DB,回源风暴更糟。降级要分级写:① 本地 TTL 临时放大+全量预热(把命中率从 30% 顶到 70%+);② 切 Redis 多副本/哨兵或只读降级;③ 只保核心接口,非核心直接返回兜底;④ 限流阈值按 DB 实测能力设,并明确「超出即拒绝」而非默默排队;DB 挂→只读缓存降级(容忍短暂旧数据);更新失败→延迟双删/binlog 订阅重试。监控:三级命中率、回源 QPS、不一致投诉、缓存内存与逐出。

【原理溯源】

  • 为什么“只更新 DB”会不一致? 缓存是副本。不主动失效/更新,TTL 到期前一直读旧值。多级副本让不一致窗口更多、更难清。

  • 为什么写路径是“先更 DB 再删缓存”? 先删缓存可能在回填旧值后 DB 更新失败,脏数据驻留更久。业界常用 Cache Aside:更新 DB 后删缓存,读时回填;仍有小概率不一致,靠重试/TTL/对账收敛。

  • 本地缓存为何更难失效? 每实例一份,无中心入口。必须广播失效、版本号轮询或短 TTL,否则实例间长期各读各的旧值。

  • 为什么更新频繁的数据不进本地缓存? 失效广播频率高、命中率低、不一致窗口难压。这类数据只放 Redis 或直读 DB。

  • 强一致缓存为何难? 多副本+异步失效无法保证“任何读都看到最新写”。要强一致只能串行化或不缓存,性能与一致性必须取舍。

  • 元理论对应: 原理 4「时间差、多副本视图与一致性窗口」+原理 6「一致性定价与按业务分级购买」(题群见《核心考点与元理论》§4.7+§6.7)

【选型判断树】

text
数据一致性要求?
├─ 不能读到旧值(资金、权限)→ 不进多级缓存 / 直读 DB / 短 TTL 强一致方案
└─ 可秒级旧值 → 多级缓存最终一致
    └─ 本地缓存失效方式:
        ├─ 实例少、变更少 → Pub/Sub 广播
        ├─ 实例多、怕广播风暴 → 版本号/短 TTL
        └─ 更新频繁 → 根本不进本地缓存

【口述骨架】(5 分钟;框架 C,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“多级缓存本质是多副本,一致性靠失效协议”
0:30–1:30写路径DB → 删 Redis → 本地失效
1:30–2:30本地失效广播/版本号/短 TTL
2:30–3:30读路径逐级回填
3:30–4:30取舍不能容忍就绕过缓存
4:30–5:00收尾“先定一致性等级,再定缓存层级”

【关键数字】

参数经验值说明
Redis TTL秒~分钟按业务兜底
本地 TTL通常更短(1s~30s)限制脏读窗口
失效广播秒级到达丢消息靠 TTL
Cache Aside主流更新 DB 后删缓存
热点更新不进本地缓存保一致性
读分流示例10万QPS:本地30%→Redis95%→DB约3.5k组合命中率决定 DB 压力
更新策略延迟双删/binlog/版本号防脏读

【追问链】(三层)

L1|“先删缓存再更 DB 行不行?” → 可能回填旧值后 DB 失败,或并发下脏数据驻留。更常用先更 DB 再删缓存,并接受小概率不一致+重试/TTL。

L2|“广播消息丢了怎么办?” → 不能把广播当可靠通道——Pub/Sub 掉线的订阅者就是永久漏收,所以整套设计要按「一定会丢一条」来做。三层兜底:① 本地缓存必须有短 TTL(关键数字给的 1s~30s 量级),丢一次推送的最坏后果被压成一个 TTL 的脏读窗口;② 用版本号比对替代纯广播:实例周期性拉一个轻量版本号,发现自己持有的版本落后就主动失效,漏掉的推送下一轮自动纠正;③ DB→Redis 那层删除同样不能只发一次,第 1 条给的是重试/MQ/Canal 订阅 binlog 兜底。观测按第 7 条看三级命中率与不一致投诉:某实例版本长期落后、或回源 QPS 从第 6 条算的约 3500 抬起,就是失效链路断了。

L3|“什么时候该放弃本地缓存?” → 强一致要求、更新极频繁、广播成本高于收益时。不是所有读都值得多级缓存。

【评分标准】

档位答案特征
60 分知道更新 DB 后要删 Redis
80 分能讲本地缓存失效与最终一致
95 分写读路径完整、更新频繁不进本地、强一致绕过缓存的取舍

【关联题】

【自测】

  1. Cache Aside 典型写路径是什么? 参考答案:先更新 DB,再删除缓存;读时未命中则读 DB 并回填。
  2. 本地缓存多实例如何失效? 参考答案:Pub/Sub 广播、版本号比对、短 TTL 兜底;更新频繁的数据不进本地缓存。
  3. 资金账户余额能放本地缓存吗? 参考答案:一般不能。需要强一致,应直读 DB/强一致缓存或极短 TTL 并严格串行化。

120. 改个限流阈值,改完整个集群立刻生效把线上搞挂了(配置灰度) ​

【考察内容】配置治理与发布工程

【题目】有人在配置中心把限流阈值改小,配置实时推送到全量实例,线上接口立刻大面积被限。配置变更怎么灰度发布(按实例/按比例/按环境)?改坏了怎么快速回滚?配置中心和发布系统怎么配合?

【参考答案】

  1. 需求:配置错误/新配置需要小范围验证,不能一键全量生效;
  2. 方案:
    • 按实例灰度:指定部分实例(按 IP/分组)应用新配置,验证后再全量;
    • 按用户/流量灰度:配置值带生效规则(user_id 百分比/白名单);
    • 按环境/集群:预发 → 生产小集群 → 全量;
  3. 流程:灰度发布 → 监控(错误率/RT/业务指标)→ 通过全量 / 失败回滚(一键回滚旧值);
  4. 工程要点:配置变更审计、变更后校验、回滚预案;Apollo/Nacos 支持命名空间/灰度;与发布系统配合:配置项随代码版本一起声明(同一条流水线里带默认值与取值范围),发版前做配置校验与灰度门禁,新代码依赖的新配置必须先于流量放量生效,回滚时代码与配置一并回退(旧代码读新配置是二次事故)。
  5. 业务代码注意:配置读取不能启动时一次性加载(动态刷新,@RefreshScope),否则灰度不生效。
  6. 容量/生效时间数字:全量配置推送,数千实例在秒级同时生效——若新阈值错误,故障也是秒级全集群。灰度目标:先 1%→10%→50%→100%,每档观察 5–15 分钟。配置变更 QPS 本身很低,关键是爆炸半径而不是吞吐。限流阈值这类配置,错误值的影响是线上的直接成功率,必须有:默认值、范围校验、一键回滚、变更审计。
  7. 失败与降级:配置中心故障→应用用本地快照;推送失败→客户端定时拉取;错误配置→秒级回滚到上一版本;高危配置需二次确认/审批流。监控:配置变更事件、关键阈值生效状态、变更后错误率/RT 关联看板。

【原理溯源】

  • 为什么配置全量即时推送是事故源? 配置是“没有编译期校验的代码”。错误阈值/开关一旦全量生效,爆炸半径=整个集群,且推送比发版更快,来不及人工反应。
  • 灰度的本质是什么? 把变更爆炸半径从 100% 降到可控子集,用监控验证后再放量。与代码发布灰度同理,只是变更载体是配置。
  • 为什么要分实例灰度和用户灰度? 实例灰度验证“运行时行为/性能”;用户/流量灰度验证“业务效果”。限流阈值更适合实例/集群灰度;功能开关更适合用户灰度。
  • 为什么必须能一键回滚? 配置变更快,出事也要同样快地恢复。没有版本与回滚的配置中心等于裸奔。
  • 为什么动态刷新是前提? 若代码启动时读一次配置,灰度改配置不生效,灰度机制形同虚设。可变更性是配置治理的地基。

【选型判断树】

text
配置变更类型?
├─ 限流/超时/池大小(性能敏感)→ 实例/集群灰度 + 强监控
├─ 功能开关 → 用户/流量灰度
├─ 中间件地址/密钥 → 通常不灰度,走双写/切换预案
└─ 高风险全局项 → 分环境:预发 → 小流量 → 全量
任何变更:审计 + 可回滚 + 监控

【口述骨架】(5 分钟;框架 B,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“配置变更风险不亚于发版,必须灰度”
0:30–1:30事故机理全量推送爆炸半径过大
1:30–2:30灰度维度实例/用户/环境
2:30–3:30流程发布-监控-回滚
3:30–4:30工程要求审计、校验、动态刷新
4:30–5:00收尾“配置即变更,变更即风险”

【关键数字】

参数经验值说明
灰度比例1 台 → 1% → 10% → 50% → 100%每档观察 5–15 分钟,按风险可加档
观察时间分钟级到小时级看指标周期
回滚目标秒级到分钟级越快越好
配置中心Apollo/Nacos支持灰度版本
刷新机制@RefreshScope / 长轮询必须真热更新
生效延迟秒级(长轮询)错误也秒级爆炸

【追问链】(三层)

L1|“配置中心挂了怎么办?” → 客户端本地缓存配置 + 启动全量拉取;推送失败用长轮询/定时拉取兜底。业务应能在旧配置下运行。

L2|“限流阈值灰度时,负载均衡会不会导致灰度实例被打爆?” → 会,这是按实例灰度的固有副作用:只有 1% 实例换了新阈值,负载均衡仍是均匀分发,这几台就得按新阈值扛下自己那一份流量。阈值往高调,它们先把本该被挡住的请求放行,保护没生效、自己先过载;往低调,同比例的请求在它们身上被误拒,观察窗口里的错误率失真,还容易被误读成「新配置没问题、全量放心」。所以第 6 条的 1%→10%→50%→100% 节奏必须配两件事:① 每档 5–15 分钟观察里同时盯灰度实例的 RT/CPU 与全集群被限流比例,任一异常就停放量;② 限流阈值属高危配置,要有默认值、范围校验、审批与双人复核,出错秒级回滚到上一版本并验证真的生效。

L3|“如何防止有人直接改生产配置?” → 权限分级、变更审批、双人复核、审计日志、高危项二次确认/只读。把“人祸”纳入治理。

【评分标准】

档位答案特征
60 分知道配置中心能热更新
80 分能讲灰度与回滚
95 分按参数类型选灰度维度;强调动态刷新与审计;考虑流量打爆等副作用

【关联题】

【自测】

  1. 为什么配置全量推送容易出事故? 参考答案:爆炸半径等于全集群,且生效极快,缺少验证与缓冲。
  2. 功能开关和限流阈值灰度方式有何不同? 参考答案:功能开关多按用户/流量;限流阈值多按实例/集群并配强监控。
  3. 业务代码如何支持配置灰度? 参考答案:运行时动态刷新(@RefreshScope/监听),不能只在启动时读一次。

121. 单机自增主键好好的,为什么要引入分布式 ID(ID 方案) ​

【考察内容】ID 方案选型判断

【题目】有人质疑:单机数据库自增主键用得好好的,为什么要搞分布式 ID?请说明自增主键在什么场景下会失效(分表、合并、跨库、泄露业务量),以及“全局唯一”和“分布式 ID”的区别,什么场景必须上。

【参考答案】

  1. 单机自增的问题:
    • 多库/分库分表:每个库独立自增,会重复;
    • 高并发写入:单库自增是热点,且依赖 DB;
    • 数据合并/迁移:自增 ID 跨库合并会冲突;
  2. 必须用分布式 ID 的场景:
    • 分库分表后的主键(全局唯一);
    • 分布式系统的主键(订单、消息、流水)需要全局唯一;
    • 高并发写入(不依赖 DB 生成);
    • 需要趋势递增或含时间信息的 ID;
  3. 不需要的场景:单库单表、ID 无业务含义、量级小——自增/业务 ID 即可;
  4. 附加考量:ID 安全性(防遍历)、前端精度(雪花 64 位转字符串)。先把概念分清(题干第二问):「全局唯一」是需求,要先定范围(库内 / 跨库 / 跨系统);「分布式 ID」只是手段之一(号段、雪花、发号服务、UUID 都能实现),单库自增在「该库全局」内也唯一。题干列的「泄露业务量」同样不是唯一性失效,而是自增号可枚举的安全问题,对策是对外单号与内部主键分离。
  5. 容量估算:单库自增在中低并发写(数百 TPS)通常足够;分库后若每库都要全局单号,自增失效。分布式 ID 容量见 106 题:号段/雪花可到数十万 ID/s 量级,远超多数业务。是否上分布式 ID 的判断阈值:是否分库分表、是否跨系统要唯一、单库自增是否成为热点、是否需要趋势递增。不是所有表都要雪花——单库配置表用自增更简单。
  6. 失败与降级:发号服务故障→双缓冲号段/本地雪花降级/备用号段;时钟回拨→107 题策略;ID 冲突→DB 唯一索引兜底。设计时不要把“自增号”当对外可枚举业务主键(防遍历)。

【原理溯源】

  • 为什么单库自增会重复? 自增计数器在库内。订单库与用户库都是 1,2,3...;分表后每表一套。唯一性范围=计数器作用域,作用域一扩就不唯一。
  • 为什么合并/迁移会冲突? 历史自增 ID 不带来源信息,多库数据落到同一存储时主键撞车。
  • 为什么高并发不喜欢单点发号? 每次 INSERT 都要获取自增值,热点与 DB 瓶颈耦合。分布式 ID 目标之一是发号去中心化/批量化。
  • “全局唯一”≠“必须用雪花”? 全局唯一是需求;雪花/号段/UUID/发号服务都是手段。单库场景用自增也能在“该库全局”唯一。要先定义唯一范围。
  • 为什么有人觉得“自增够用”? 在单体、单库、低并发成立。质疑者往往默认架构不变。正确回答是展示哪些架构变化会打破假设,而不是为分布式而分布式。

【选型判断树】

text
唯一性范围是否超过单库/单表?
├─ 否 → 自增即可,别上复杂 ID 系统
└─ 是
   ├─ 要趋势递增+简单 → 号段
   ├─ 要本地高性能生成 → 雪花
   ├─ 只要唯一可随机 → UUID
   └─ 另加:防遍历可加随机位;前端传字符串
分库分表主键:号段或雪花最常见

【口述骨架】(5 分钟;框架 C,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“自增的唯一性范围是单库,架构一变就不够”
0:30–1:30失效场景分库、合并、热点
1:30–2:30概念澄清全局唯一是需求,方案有多种
2:30–3:30何时必须上分库分表、跨系统单据、高并发
3:30–4:30何时不必单库低量,克制设计
4:30–5:00收尾“按唯一范围和并发选,不为分布式而分布式”

【关键数字】

参数经验值说明
自增热点单点发号有上限与实例写入能力相关
分库数量≥2 就可能冲突若需跨库唯一
雪花/号段见 106 题具体结构
安全避免纯自增对外暴露防遍历
自增适用单库、中低并发、无跨库唯一别过度设计
分布式 ID 吞吐号段/雪花数十万/s 量级见 106
必上场景分库分表/跨系统单号/高并发热点先定义唯一范围

【追问链】(三层)

L1|“分表但同库,自增行不行?” → 若所有表共享同一自增序列或 ID 空间隔离,可能可以;但常见分表组件/多表并行仍会冲突,稳妥用号段/雪花并规划号段位。

L2|“分布式 ID 会不会泄露业务量?” → 会。趋势自增/雪花时间戳可推测单量与时间。对外单号可:加密编码、加随机位、改用不可枚举 ID(UUID 部分场景)、对外号与内部主键分离。内部主键仍可用趋势递增保索引性能。面试点:性能友好的内部 ID ≠ 适合暴露的安全单号。

L3|“为什么说不是所有表都该雪花?” → 单库日志表、配置表用自增更简单;盲目换雪花增加时钟与运维复杂度。按约束驱动设计。

【评分标准】

档位答案特征
60 分知道分库自增会重复
80 分能列失效场景并引出分布式 ID
95 分区分需求与方案;强调克制与唯一范围;提安全与精度

【关联题】

【自测】

  1. 自增主键失效的三类典型场景? 参考答案:分库分表、跨库合并迁移、高并发单点热点。
  2. “全局唯一”和“雪花算法”是什么关系? 参考答案:前者是需求,后者是实现之一;也可用号段、发号服务、UUID 等。
  3. 什么场景不必上分布式 ID? 参考答案:单库单表、量级小、无跨库唯一要求。

122. 服务 A 调 B 慢,A 的线程全被占住拖垮自己(调用容错) ​

【考察内容】分布式调用容错设计

【题目】服务 A 每次调用 B 要 3 秒才超时,B 一抖动,A 的所有线程都被挂起等待,A 自己也跟着挂。怎么设计调用容错(超时、重试、熔断、降级、隔离)?各个机制分别解决什么问题、怎么配置?

【参考答案】

  1. 调用前:设置合理的超时时间(连接超时+读超时,按 B 的 P99 设定,如 500ms/1s),超时即失败,避免无限等待;
  2. 调用失败处理策略:
    • 重试:仅对“幂等操作”重试,且限制重试次数+退避(1~2 次指数退避),防止重试风暴;写操作默认不重试;
    • 降级:返回兜底数据(缓存/默认值/上一次结果);
    • 熔断:B 连续失败(错误率>50%)时打开熔断器,快速失败不再调用 B,半开试探恢复;
    • 异步化:非核心调用转 MQ,失败进重试队列;
  3. 隔离:线程池隔离(B 慢只占自己的线程池,不拖垮其他调用);
  4. 兜底:失败记录+定时补偿;告警人工介入;
  5. 设计原则:超时+重试(幂等限次)+降级+熔断+隔离五件套,且“调用失败不能阻塞主流程”。
  6. 容量/线程预算估算:A 线程池 200,B 超时 3s,则最坏情况下 A 的吞吐被拖到 200/3s≈67 QPS——这就是被拖垮的数学。若超时改为 500ms,同线程池理论恢复到 200/0.5=400 QPS(仍要看 CPU)。熔断打开后错误率上升但线程释放,整体可用。线程池隔离:给 B 分配 20 线程,B 挂了最多占 20,主链路 180 仍可服务其他依赖。重试放大:重试 2 次时,对 B 的最大调用流量≈3 倍输入,熔断与限流必须配合。
  7. 失败与降级:超时→快速失败;重试→仅幂等、1–2 次退避;熔断→错误率超阈值打开,半开试探;降级→缓存/默认值/只读;隔离→舱壁线程池/信号量。非核心调用转 MQ。失败补偿与人工预案。监控:依赖 RT/错误率、熔断状态、线程池水位、降级触发次数。

【原理溯源】

  • 为什么 A 会被拖垮? 同步阻塞调用把下游延迟变成自己的线程占用。B 变慢→A 线程池耗尽→A 整体不可用。这是级联故障的经典形态。

  • 超时为什么是第一道防线? 它限制单次调用最坏占用,把“无限等待”变成“有限失败”。没有超时,其他容错都无从谈起。

  • 为什么重试必须限次且偏幂等? 无限重试会放大流量,把局部故障打成全局雪崩;非幂等重试可能重复扣款。重试是药,过量是毒。

  • 熔断解决什么? 在 B 已经持续失败时,继续调用只是浪费线程并加重 B 负担。熔断快速失败,给 B 恢复时间;半开探测避免过早恢复流量。

  • 隔离为什么必要? 即便有超时,多个下游共享大线程池时,一个慢下游仍可能占满池子。按下游拆线程池/信号量,故障域隔离。

  • 元理论对应: 原理 2「过载动力学与雪崩正反馈」(题群见《核心考点与元理论》§2.7)

【选型判断树】

text
调用特征?
├─ 核心同步路径
│   → 短超时 + 有限重试(仅幂等)+ 熔断 + 降级 + 线程池隔离
├─ 非核心通知/统计
│   → 异步 MQ + 重试队列,不占主链路线程
├─ 读多可缓存
│   → 降级读缓存/默认值
└─ 写且非幂等
    → 默认不重试;失败记录补偿/人工,而不是盲目重试

超时设置:
└─ 连接超时 < 读超时 < 上游总超时;超时要略大于下游 P99

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“同步慢调用会占满线程,形成级联故障”
0:30–1:30超时第一道防线
1:30–2:30重试与幂等限次退避,写不乱重试
2:30–3:30熔断与降级快失败与兜底
3:30–4:30隔离线程池拆分
4:30–5:00收尾“五件套组合,失败不阻塞主流程”

【关键数字】

参数经验值说明
超时略大于下游 P99如 500ms~2s 视业务
重试次数(跨服务调用容错)1~2 次指数退避
熔断阈值错误率 50%+(可配)结合最小请求数
半开探测少量请求试探成功再放量
线程池隔离按下游/依赖拆分限制故障域
被拖垮公式线程池÷超时200/3s≈67 QPS
降超时收益200/0.5s=400 QPS超时是第一防线
重试放大×(1+重试次数)必须配熔断限流

【追问链】(三层)

L1|“超时时间怎么定?” → 基于下游 P99/P999 与业务可接受延迟:超时要大于正常慢尾,小于上游总超时与用户可等待时间,并预留重试预算。

L2|“熔断打开后用户体验很差?” → 熔断是保命不是终点。打开后:① 快速失败+明确错误码;② 读接口走缓存/降级数据;③ 写接口排队或提示稍后重试;④ 半开状态放少量真实流量探测;⑤ 持续失败升级人工预案。产品侧要有“部分功能不可用”的文案。关键指标:降级触发后的成功率要高于“硬调慢下游”的成功率。

L3|“重试会不会把 B 彻底打死?” → 会,若全员无限重试。必须限次、退避、熔断、隔离,并对非核心流量降级/丢弃,形成负载削减而不是放大。

【评分标准】

档位答案特征
60 分提到要设超时
80 分能讲超时+重试+熔断
95 分五件套完整;重试限幂等限次;线程池隔离;超时与 P99 关系;能解释级联故障

【关联题】

  • 同簇: 第 110 题(幂等,重试前提)、第 113 题(注册中心故障下的重试)、第 115 题(定位慢调用)
  • 相关: 雪崩、限流、隔离舱壁模式相关题
  • 配置: 第 120 题(阈值灰度)

【自测】

  1. 为什么超时是容错第一原则? 参考答案:限制单次调用最坏线程占用,避免无限等待拖垮自身。
  2. 什么操作可以安全重试? 参考答案:幂等操作(查询、带幂等键的写);非幂等写默认不重试。
  3. 熔断器半开状态做什么? 参考答案:放少量试探请求,成功则恢复,失败则继续保持打开。

附:本章知识簇索引(第五章) ​

知识簇题号
CAP / BASE 理论97, 98
分布式事务99, 100, 101, 102, 103
分布式锁104, 105
分布式 ID106, 107, 121
分片与路由108
数据/缓存一致性109, 119
幂等与防重110, 117
会话111
注册/配置中心112, 113, 120
分布式任务114
可观测性115
时钟116
多实例并发118
调用容错122

持续学习,持续积累。