跨域场景设计面试
跨域场景设计面试
高并发系统设计
【困难】如何设计秒杀库存扣减的跨层一致性方案?⭐⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:25 min | 🏷 标签:Redis / MQ / DB / 一致性 / 限流
💎 关键结论
秒杀库存扣减横跨前端→网关→Redis→MQ→DB 五层,核心原则是"Redis 防超卖、MQ 保 DB、对账兜底"。前端静态页 + CDN 挡读流量,网关限流 + 答题削峰,Redis Lua 原子扣库存,扣减成功的请求才进 MQ 异步建单,DB 写入量 = 库存量而非请求量。三层对账(Redis-MQ-DB)兜底任何一层的不一致。
⚡ 记忆卡片
- 口诀:前端挡、网关限、Lua 扣、MQ 削、DB 落、对账兜
- 关键词:瞬时海量请求 / 超卖 / 分层过滤 / Redis+Lua / 异步建单 / 三层对账
- 链路:前端静态+CDN → 网关限流+答题 → Redis Lua 扣库存 → MQ 异步建单 → DB 落库 → 三层对账
📖 核心知识
秒杀库存扣减的跨层一致性,核心是每一层各司其职、层层过滤:
1. 分层过滤(漏斗模型)
- 前端层:静态页面 + CDN 缓存,挡掉 99% 读请求;按钮点击后禁用防重复提交。
- 网关层:用户维度限流(同一用户 N 秒内只能请求一次)+ 下单令牌机制(活动开始后前端先获取令牌,无令牌直接拒绝)。
- 应用层:校验令牌有效性 + 风控拦截(黑名单 / 异常行为检测)。
2. 库存扣减(Redis + Lua)
-- 原子扣减库存脚本
local stock = tonumber(redis.call('GET', KEYS[1]))
local required = tonumber(ARGV[1])
if stock >= required then
redis.call('DECRBY', KEYS[1], required)
return 1 -- 扣减成功
end
return 0 -- 库存不足- Lua 脚本在 Redis 中原子执行,杜绝"先查后扣"的并发超卖。
- 扣减成功后才发 MQ 消息,保证 DB 写入量 = 库存量(而非请求量)。
- 热点商品可分段库存(10w 库存拆 10 个 key 各 1w,随机路由打散热点)。
3. 异步建单(MQ 削峰)
- 扣减成功的请求发 MQ,消费者按 DB 可承受速率消费建单。
- MQ 积压监控:积压超容量时提前告知用户"排队中"。
4. 三层对账(兜底机制)
- Redis vs MQ:Redis 扣减数 vs MQ 消息数,不一致说明 MQ 投递丢失。
- MQ vs DB:MQ 消费成功数 vs DB 建单数,不一致说明消费失败未重试。
- Redis vs DB:活动结束后 Redis 剩余库存 vs DB 实际售出,不一致以 DB 为准修复。
🔬 扩展知识
详情
- 【L3】库存扣减方案对比:Redis + Lua(单实例 8~10w QPS,需对账兜底);DB 乐观锁 CAS(简单可靠但热点行锁竞争,QPS 仅数千);分段库存(打散热点但引入段间不均)。选型看库存量:库存 100 件无需 Redis,库存 10w+ 峰值十万级才需要。
- 【L3】热点隔离:秒杀商品用独立 Redis 实例 + 独立 DB 库表,与主站物理隔离;活动结束资源回收。
- 【L4】失效场景:MQ 建单积压超容量 → 用户"抢购成功但无订单",需积压监控 + 提前告知;答题 / 验证码被脚本破解 → 漏斗失效,需风控实时升级难度。
- 【L4】方案权衡:Redis + Lua 性能最高但引入缓存与 DB 双存储一致性问题;纯 DB 乐观锁无一致性问题但性能差;分段库存解决热点但增加管理复杂度。
- 【L4】未支付订单的库存回补:建单后超时未支付需把库存还回 Redis 与 DB,通常用 MQ 延时消息(或定时任务扫描超时订单)触发;回补必须按订单号幂等去重,否则重复回补会造成"多卖";回补时序要保证在 MQ 建单消费完成之后,避免回补先于扣减落库执行。
🏭 实战场景
详情
某平台周年庆秒杀:100 个 SKU,每个库存 1000 件,预计峰值 50 万 QPS 持续 3 秒。
设计要点:漏斗设计——页面静态化 + CDN 挡掉 95% 读流量,答题 / 验证码滤掉 90% 无效请求,剩余约 2 万 QPS 进应用层;单 SKU 库存 1000 件无需分段,Redis Lua 原子扣减即可;扣减成功的 10 万件请求进 MQ 异步建单,DB 写入被平滑到几分钟。独立部署秒杀集群 + 独立库表与主站隔离;全链路限流兜底,DB 前置 3000 QPS 保护。
⚠️ 常见误区
详情
常见误区:
- ❌ "应用层先查库存再扣减就能防超卖" → 查与扣不是原子操作,高并发下必然超卖,必须用条件更新 SQL 或 Redis + Lua 原子脚本。
- ❌ "Redis 扣减后直接返回结果,不需要 MQ" → Redis 是内存库,扣减成功但 Redis 宕机(无持久化)则库存数据丢失,必须 MQ + DB 落库保证持久化。
- ❌ "对账是多余的,代码逻辑正确就不会不一致" → 网络分区、Redis 主从切换、MQ 重复投递等异常都可能导致不一致,对账是最后一道防线。
🔀 发散问题
Q:如何设计缓存与数据库的一致性方案?
→ 延迟双删(先删缓存 → 更新 DB → 延迟再删缓存)或 Canal 订阅 binlog 异步删缓存,详见本文档「如何设计缓存、数据库与消息队列的三者一致性方案?」
Q:分段库存如何设计?
→ 把总库存 N 等分拆为 N 个子 key,请求随机路由到某段,Lua 原子扣减,某段扣完可跳转其他段或触发再平衡。
【中等】亿级用户签到系统如何兼顾并发写入与连续天数计算?⭐⭐⭐
🎯 目标等级:L3 | ⏱ 建议用时:15 min | 🏷 标签:Redis / Bitmap / 异步 / 幂等
💎 关键结论
签到系统的核心矛盾是"亿级 DAU 的并发写入"与"连续天数的实时计算"。方案是 Redis Bitmap 做当日签到的原子写入,Hash 存储月度签到记录用于连续天数计算,异步 MQ 落库让 DB 只承担持久化不承担实时写入。签到幂等通过"用户 ID + 日期"唯一键保证。
⚡ 记忆卡片
- 口诀:Bitmap 签、Hash 算、MQ 落、幂等保
- 关键词:亿级 DAU / Bitmap / 连续天数 / 异步落库 / 幂等
- 链路:签到请求 → 幂等校验(SETNX) → Bitmap 标记 → 连续天数更新 → MQ 异步落库
📖 核心知识
签到系统的设计难点:超高并发写入(亿级 DAU,早晚高峰签到 QPS 可达数十万)+ 连续天数实时计算(需要支撑签到奖励逻辑)+ 不丢签到记录。
1. 三层架构
- 接入层:幂等校验(user_id + date 唯一键)+ 快速响应。
- 计算层:Redis 中完成签到标记 + 连续天数计算。
- 持久层:MQ 异步消费落库,DB 只做持久化。
2. Redis 数据结构
- Bitmap:
sign:{uid}:{yyyyMM}— 每位代表一天,1 表示已签到。 - 连续天数计数器:
sign:consecutive:{uid}— 整数,签到 +1,断签归零。 - 签到记录 Hash:
sign:record:{uid}— field 为日期,value 为签到时间戳。
3. 签到操作(Lua 原子脚本)
local bitmapKey = KEYS[1] -- sign:{uid}:{yyyyMM}
local consecutiveKey = KEYS[2] -- sign:consecutive:{uid}
local recordKey = KEYS[3] -- sign:record:{uid}
local day = tonumber(ARGV[1]) -- 当月第几天
local today = ARGV[2] -- yyyyMMdd
if redis.call('GETBIT', bitmapKey, day - 1) == 1 then
return -1 -- 已签到(幂等)
end
redis.call('SETBIT', bitmapKey, day - 1, 1)
redis.call('INCR', consecutiveKey)
redis.call('HSET', recordKey, today, ARGV[3]) -- 签到时间戳
return redis.call('GET', consecutiveKey)4. 连续天数恢复
- 用户首次访问时从 DB 加载上月最后签到记录,恢复 Redis 连续天数计数器。
- 后续请求直接读 Redis。
🔬 扩展知识
详情
- 【L3】Bitmap vs Hash 存储签到:Bitmap 空间效率高(1 用户 1 月仅 4 字节),但只能记录"签 / 未签";Hash 可存签到时间等附加信息但空间更大。亿级 DAU 用 Bitmap 存签到状态(1 亿用户 × 4 字节 ≈ 400MB / 月),Hash 只存当月签到时间(按需查询时加载)。
- 【L3】连续天数计算优化:维护
consecutive:{uid}计数器,签到时 +1,断签时归零。跨月时从 DB 恢复上月最后一天状态。 - 【L3】签到数据对账:MQ 异步落库存在丢失或重复风险,需定时任务比对 Redis Bitmap 与 DB 签到记录(按日抽样或全量),差异以幂等补写修复;奖励发放同样要按"用户 + 奖励规则"唯一约束防重,避免 MQ 重试导致重复发奖。
- 【L4】月度签到数据迁移:月末将 Bitmap 序列化后落库(
sign:{uid}:{yyyyMM}→ DB),Redis 只保留当月 + 上月数据,控制内存占用。
⚠️ 常见误区
详情
常见误区:
- ❌ "用数据库直接签到就行" → 亿级 DAU 下数据库直接被写爆,必须 Redis 前置 + DB 异步持久化。
- ❌ "签到不需要幂等" → 网络重试会导致重复签到,连续天数计算错误,必须 SETNX 或唯一索引保证幂等。
- ❌ "Bitmap 太省空间不需要压缩" → 对于连续 30 天没签到的用户,Bitmap 全是 0,可以用 Roaring Bitmap 压缩长期全 0 的段。
🔀 发散问题
Q:签到系统的奖励如何发放?
→ 连续签到 N 天奖励在签到 Lua 脚本中判断,满足条件发 MQ 消息异步发放,保证发奖与签到解耦。
Q:如果用户量降到百万级,架构如何简化?
→ 去掉 MQ 异步落库,直接 Redis + 定时同步 DB;连续天数直接在 Redis 计算,无需恢复逻辑。
【困难】如何设计缓存、数据库与消息队列的三者一致性方案?⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:20 min | 🏷 标签:缓存 / DB / MQ / 一致性
💎 关键结论
缓存-DB-MQ 三者一致性的核心是"明确每层职责 + 按场景选策略":缓存负责加速读、DB 负责权威存储、MQ 负责异步解耦和事件通知。强一致场景用事务消息或 Saga,最终一致场景用延迟双删 + MQ 补偿,读多写少场景用缓存过期兜底。三者一致性没有银弹,关键是业务能容忍多大不一致窗口。
⚡ 记忆卡片
- 口诀:缓存加速读、DB 做权威、MQ 做异步、场景选策略
- 关键词:延迟双删 / Canal binlog / 事务消息 / 最终一致 / 不一致窗口
- 链路:写操作 → 更新 DB → 删缓存(或延迟双删)→ MQ 补偿通道 → 对账兜底
📖 核心知识
1. 三者角色与一致性挑战
- 缓存:加速读,但与 DB 存在时间差。
- DB:权威数据源,但高并发下成为瓶颈。
- MQ:异步解耦,但引入消息丢失 / 重复 / 乱序风险。
三者一致性的本质是:写操作涉及 DB + 缓存 + MQ 三个存储,如何保证它们最终一致?
2. 策略一:延迟双删 + MQ 补偿
写操作:
1. 删除缓存
2. 更新 DB
3. 延迟 500ms 再删缓存(覆盖步骤 1 和步骤 2 之间的脏读重建)
4. 如果步骤 3 删除失败 → MQ 发重试消息- 优点:简单,适合大多数场景。
- 缺点:500ms 窗口内可能读到脏数据;二次删除可能失败需 MQ 兜底。
3. 策略二:Canal 订阅 binlog + MQ 触发缓存更新
写操作:
1. 只更新 DB(不操作缓存)
2. Canal 监听 binlog 变更
3. 变更事件发 MQ
4. 消费者删缓存(或更新缓存)- 优点:业务代码不关心缓存,DB 为唯一写入点,一致性好。
- 缺点:引入 Canal 组件,运维复杂度增加;binlog 到缓存更新有延迟。
4. 策略三:事务消息(强一致场景)
写操作:
1. 发送半消息到 MQ
2. 执行本地事务(更新 DB)
3. 提交 / 回滚消息
4. 消费者处理消息(更新缓存 / 触发下游)- 优点:DB 与 MQ 强一致。
- 缺点:只保证 DB-MQ 一致,缓存仍需额外策略。
🔬 扩展知识
详情
- 【L3】缓存与 DB 不一致的根因:写操作先删缓存再更新 DB → 并发读请求在两步之间重建缓存(旧值);先更新 DB 再删缓存 → 删缓存失败导致旧值残留。两种顺序都有窗口,延迟双删和 Canal 分别缩小这个窗口。
- 【L3】MQ 消息丢失处理:生产者确认(publisher confirm)+ 消费者手动 ACK + 死信队列 + 定时对账。
- 【L3】事务消息的回查机制:半消息提交后若 Broker 长时间未收到 commit/rollback(生产者宕机、网络分区),会定期回查生产者的本地事务状态,生产者必须实现可幂等重入的回查接口(通常查本地事务表 / 订单状态),否则消息会悬在半事务态。
- 【L4】失效消息乱序与重试滞后:走 MQ 删缓存时,乱序或重试可能让"旧值回填"覆盖新值。对策:缓存统一"只删不更",回填时对比数据版本号 / 更新时间戳,旧版本拒绝写入;这也是 Canal 链路必须携带 binlog 位点或时间戳的原因。
- 【L4】分库分表下的 Canal:每个分片独立订阅 binlog,汇总后统一处理缓存更新,注意跨分片事务的 binlog 合并。
🏭 实战场景
详情
电商商品详情页:商品信息的写操作(运营改价格 / 库存)需要实时更新到缓存,同时通知搜索服务更新索引、通知推荐服务更新特征。
方案:写操作只更新 DB → Canal 监听 binlog → 变更事件发 MQ → 三个消费者分别处理:① 删商品缓存 ② 更新 ES 索引 ③ 更新推荐特征。对账任务每小时比对 DB 与缓存 / ES 数据,不一致则修复。
⚠️ 常见误区
详情
常见误区:
- ❌ "先删缓存再更新 DB 就能保证一致" → 并发读请求在两步之间重建缓存(旧值),需延迟双删或 Canal。
- ❌ "缓存过期就够了,不需要主动删" → 过期窗口内读到脏数据,一致性要求高的业务不能接受。
- ❌ "MQ 只是用来异步解耦的,跟一致性无关" → MQ 是一致性补偿通道:删缓存失败时 MQ 重试,Canal 订阅 binlog 通过 MQ 触发缓存更新。
🔀 发散问题
Q:如何设计缓存与数据库的一致性方案?
→ 见本文档「如何设计缓存、数据库与消息队列的三者一致性方案?」的策略一和策略二。
Q:MQ 消息重复消费如何处理?
→ 消费者做幂等:数据库唯一索引、Redis SETNX 记录已处理消息 ID。
【中等】如何设计一个跨机房部署的本地缓存一致性方案?⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:15 min | 🏷 标签:多级缓存 / 跨机房 / 广播 / 版本号
💎 关键结论
跨机房本地缓存一致性的核心矛盾是"网络延迟不可消除"。方案是"版本写入 + 广播失效":写入时给数据打版本号,读取时比较版本号,失效时通过 MQ 广播让各机房本地缓存主动失效。不追求强一致,接受短暂不一致 + 版本检测让过期数据自然淘汰。
⚡ 记忆卡片
- 口诀:版本写入、广播失效、短 TTL 兜底
- 关键词:跨机房 / 本地缓存 / 版本号 / MQ 广播 / 最终一致
- 链路:写操作 → 更新 DB + 中心 Redis + 发 MQ 失效消息 → 各机房订阅 MQ → 本地缓存失效
📖 核心知识
1. 多级缓存架构
请求 → 本地缓存(Caffeine) → 分布式缓存(Redis 集群) → DB- 本地缓存:毫秒级响应,但跨机房无法实时同步。
- 分布式缓存:跨机房共享,但有网络延迟(同城机房 RTT 约 1~2ms;异地如北京—上海约 20~30ms、北京—深圳约 35~40ms,光纤路径绕行时更高)。
2. 写入流程
写操作:
1. 更新 DB
2. 更新中心机房 Redis 集群(带版本号 version+1)
3. 发 MQ 失效消息(key + new_version)
4. 各机房本地缓存订阅 MQ,收到消息后失效对应 key3. 读取流程
读操作:
1. 查本地缓存 → 命中 → 比较版本号与 Redis 版本号
- 版本一致 → 返回
- 版本过期 → 视为未命中
2. 未命中 → 查中心 Redis → 回填本地缓存4. 版本冲突处理
- 写入时生成单调递增版本号(DB 自增 / Redis INCR)。
- 读取时本地缓存版本号 < Redis 版本号 → 主动失效。
- 广播消息包含
{key, new_version, source_datacenter},各机房收到后对比版本号决定是否失效。
🔬 扩展知识
详情
- 【L3】脑裂场景:两个机房同时认为自己是主 → 各自接受写入 → 数据分叉。处理:各机房独立对外服务,通过版本号机制让旧数据自然淘汰;脑裂恢复后以版本号最大的机房数据为准同步。
- 【L3】广播消息延迟:如果 MQ 广播延迟超过业务容忍度,可以缩短本地缓存 TTL 作为兜底(TTL < 业务不一致容忍窗口)。
- 【L4】本地缓存容量管理:本地缓存不能存所有数据,需要 LFU 或 W-TinyLFU 淘汰策略,只缓存热点数据。
- 【L4】与单元化部署的配合:多活单元化架构下,用户请求被路由规则封闭在单元内,本地缓存 + 单元内 Redis 构成单元闭环的多级缓存,跨单元只同步权威数据(DB 层)而不互相同步缓存;此时广播失效消息也按单元隔离,避免全局广播风暴。设计本地缓存一致性方案前,先要回答"流量是否单元封闭"这一前置架构问题。
⚠️ 常见误区
详情
常见误区:
- ❌ "跨机房同步本地缓存就能保证强一致" → 网络延迟不可消除,必须接受最终一致 + 版本机制兜底。
- ❌ "本地缓存不需要版本号,广播失效就行" → 广播本身可能延迟 / 丢失,版本号让读取方能主动检测过期数据。
- ❌ "本地缓存和分布式缓存重复了,只需要一个" → 本地缓存应对超高频读(百万 QPS 级),分布式缓存应对高频读(十万 QPS 级),两者分层互补。
🔀 发散问题
Q:如果业务要求跨机房强一致怎么办?
→ 放弃本地缓存,每次读都查中心 Redis(延迟上升),或用分布式锁串行化写入(吞吐下降),需权衡业务是否真的需要强一致。
【中等】如何设计一个支撑十万 QPS 的限流与配额管理系统?⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:15 min | 🏷 标签:限流 / 令牌桶 / 配额 / Redis
💎 关键结论
限流与配额的核心是"多维令牌桶 + 分层配额":限流从用户 / 接口 / 全局三个维度做,配额通过管理后台分配、Redis 原子扣减执行。限流规则和配额解耦——规则定义"怎么限",配额定义"限多少",规则变更实时生效不影响配额存量。
⚡ 记忆卡片
- 口诀:多维桶、分层额、热更新、Redis 扣
- 关键词:令牌桶 / 多维度限流 / 配额池 / Redis Lua / 规则热更新
- 链路:请求 → 多维限流检查(用户/接口/全局) → 配额扣减 → 放行/拒绝
📖 核心知识
1. 限流算法选型
| 算法 | 特点 | 适用场景 |
|---|---|---|
| 固定窗口 | 简单,有边界突刺问题 | 粗粒度限流 |
| 滑动窗口 | 精确,开销稍大 | 接口级限流 |
| 令牌桶 | 允许突发,平滑速率 | API 限流(主流) |
| 漏桶 | 严格平滑,不允许突发 | 保护下游系统 |
2. 多维度限流
请求 → 全局维度(总 QPS ≤ 10w)
→ 接口维度(/api/order ≤ 5w QPS)
→ 用户维度(单用户 ≤ 100 QPS)三个维度全部通过才放行,任一维度超限即拒绝。
3. 配额管理
- 二级分配:全局配额池 → 分配到租户 → 租户分配到用户。
- 配额扣减:Redis Lua 原子操作,保证不超分配。
-- key 不存在时 redis.call('GET') 返回 false,tonumber(nil) 会直接报错,需用 or 兜底
local tenantQuota = tonumber(redis.call('GET', 'quota:tenant:' .. tenantId) or '0')
local userQuota = tonumber(redis.call('GET', 'quota:user:' .. userId) or '0')
if tenantQuota > 0 and userQuota > 0 then
redis.call('DECR', 'quota:tenant:' .. tenantId)
redis.call('DECR', 'quota:user:' .. userId)
return 1 -- 放行
end
return 0 -- 配额耗尽4. 限流规则热更新
- 规则存配置中心(Nacos / Apollo),变更时推送到应用实例。
- 应用收到变更后更新本地令牌桶参数,新请求立即生效。
🔬 扩展知识
详情
- 【L3】分布式限流的性能瓶颈:所有请求都查 Redis 做限流计数 → Redis 成为瓶颈。解决:按维度分片(不同租户的计数器在不同 Redis key),或本地预分配 + 全局同步(每个实例预分配 N 个配额,本地扣完再向 Redis 申请)。
- 【L3】限流 vs 降级:限流是保护系统自身(控制流速),降级是保护核心功能(关闭非核心),两者配合使用——限流触发后进入降级模式。
- 【L4】本地限流与弹性伸缩的联动:纯本地限流的单机阈值 = 全局阈值 / 实例数,扩缩容后若不同步调整,实际总限流值会随实例数漂移。集群限流(如 token server 模式)能精确控总量,但引入中心节点的可用性问题——token server 挂掉时必须明确降级语义(退化为本地限流还是全部放行),这个失败模式要在设计时定义,而不是留给运行时。
- 【L4】限流后的处理策略分级:直接拒绝(返回 429 并携带 Retry-After)只是最粗的一档;更细的做法是匀速排队(漏桶语义削峰)、冷启动预热(避免刚恢复的实例被打垮)、以及对内部调用方按优先级配额裁剪——保核心链路先于保总量。
⚠️ 常见误区
详情
常见误区:
- ❌ "令牌桶和漏桶是一回事" → 令牌桶允许一定突发(桶内有令牌时可一次性取多个),漏桶严格平滑(固定速率处理)。
- ❌ "分布式限流就是所有实例共享一个计数器" → 单计数器性能瓶颈,需按维度分片或本地预分配 + 全局同步。
- ❌ "限流规则硬编码在代码里" → 规则必须放配置中心,支持热更新 + 灰度 + 回滚。
🔀 发散问题
Q:限流和降级的区别?
→ 限流控制入口流速保护系统不过载,降级在系统压力大时关闭非核心功能保核心链路。
Q:如何防止配额被扣成负数?
→ Lua 脚本中先判断 > 0 再 DECR,或用
DECRBY配合max(0, current - n)逻辑。
数据与存储架构
【困难】分库分表场景下的全局 ID 与数据同步如何协同设计?⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:20 min | 🏷 标签:分库分表 / 全局 ID / 数据同步 / binlog
💎 关键结论
分库分表的全局 ID 与数据同步必须协同设计:全局 ID 保证唯一且趋势递增(号段模式 / Snowflake 变体),数据同步通过订阅 binlog 异步同步到异构索引表 / 从库 / ES。两者协同点是"全局 ID 作为分片路由键的补充索引"——分片键决定数据在哪个库,全局 ID 决定如何跨片查询。
⚡ 记忆卡片
- 口诀:号段生 ID、binlog 做同步、路由靠分片键、跨片查映射表
- 关键词:全局唯一 / 趋势递增 / 号段模式 / Canal / 异构索引 / 在线迁移
- 链路:写入 → 全局 ID 生成 → 分片路由 → 写入目标库 → Canal 订阅 binlog → 同步异构索引
📖 核心知识
1. 全局 ID 设计
- 要求:全局唯一、趋势递增(B+ 树友好)、不含敏感信息、高可用。
- 号段模式:从 DB 批量获取 ID 号段(如 [1, 1000]),应用本地分配,用完再取。DB 压力 = 请求数 / 号段大小,极低。
- Snowflake 变体:64 bit = 时间戳(41) + 机器 ID(10) + 序列号(12)。工程化关键是机器 ID(workerID)的自动分配与回收——美团 Leaf-Snowflake 用 ZooKeeper 顺序节点分配 workerID 并在启动时校验,避免人工配置冲突;两大固有缺陷是时钟回拨可能产生重复 ID、ID 只保证趋势递增不保证连续。
2. 数据同步架构
主库(分片) → Canal 订阅 binlog → MQ → 消费者
→ 异构索引表(order_id → user_id 映射)
→ ES 索引(搜索场景)
→ 从库(读写分离)3. 分片路由与跨片查询
- 路由:
user_id % N决定数据在哪个分片。 - 跨片查询:根据
order_id查订单 → 先查异构索引表获取user_id→ 路由到目标分片。
4. 在线分片迁移
1. 新分片建表
2. 双写:写入同时写旧分片和新分片
3. 历史数据迁移:批量同步旧分片数据到新分片
4. 数据校验:比对旧分片和新分片数据一致性
5. 切换读:读流量切到新分片
6. 停写旧分片:完成迁移🔬 扩展知识
详情
- 【L3】ID 生成器高可用:号段模式下应用从 ID 服务批量获取号段,ID 服务短暂不可用时本地号段仍可用。美团 Leaf 的双 buffer 优化:当前号段消耗到一定比例(如 10%)时异步预取下一号段,消除取号段瞬间的毛刺;重启会跳号,ID 不连续但业务无感。
- 【L3】Snowflake 时钟回拨缺陷:NTP 校时或虚拟机迁移导致时钟回退时,可能生成与历史重复的 ID。对策:回拨幅度小则等待时钟追平;幅度大则拒绝服务并告警,或切换到预留的备用 workerID;这也是"Snowflake 直接可用"这一印象的主要坑点。
- 【L3】binlog 同步延迟处理:业务层提供"最近 N 天订单"(走从库)和"全部订单"(走主库)两种查询模式。
- 【L4】分片键选择原则:高频查询字段、不频繁更新、基数足够大避免数据倾斜。
🏭 实战场景
详情
订单系统分库分表:1024 张表分 64 库,每库 16 表。分片键 user_id,全局 ID 用号段模式生成。
设计要点:全局 ID 生成器独立部署(3 节点 + 主备切换),号段大小 1000(DB 每 1000 次请求才分配一次号段);Canal 订阅 64 个主库 binlog,同步到异构索引表(order_id → user_id)支持根据 order_id 查询;在线迁移采用双写 + 校验 + 灰度切换,迁移期间业务无感知。
⚠️ 常见误区
详情
常见误区:
- ❌ "用 UUID 做全局 ID" → UUID 无序导致 B+ 树频繁分裂,写入性能差,应用趋势递增 ID。
- ❌ "分库分表后用自增 ID 就行" → 自增 ID 在多个分片会冲突,必须全局 ID。
- ❌ "分片后跨片查询只能广播" → 通过异构索引表(order_id → user_id)避免广播查询。
🔀 发散问题
Q:如何避免数据倾斜?
→ 一致性哈希 + 虚拟节点均衡分布,或对倾斜 key 单独路由到专用分片。
Q:binlog 同步延迟导致查不到数据怎么办?
→ 关键业务写后读走主库,非关键走从库 + 提示"数据同步中"。
【中等】如何设计一个多源异构数据的实时同步与冲突处理机制?⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:15 min | 🏷 标签:CDC / 数据同步 / 冲突处理 / ETL
💎 关键结论
多源异构数据同步的核心是"统一 Schema + 冲突仲裁 + 幂等消费":CDC 捕获各源库变更,统一为标准化变更事件(Schema 映射),冲突按"时间戳优先 / 源优先级 / 业务规则"三级仲裁,消费端幂等保证重复事件不产生副作用。
⚡ 记忆卡片
- 口诀:CDC 捕获、Schema 映射、冲突仲裁、幂等消费
- 关键词:CDC / Schema 映射 / LWW / 向量时钟 / 幂等 / 死信队列
- 链路:多源 DB → CDC 捕获 → Schema 映射 → 冲突仲裁 → 幂等写入目标库
📖 核心知识
1. CDC 捕获层
- MySQL → Canal / Debezium 订阅 binlog。
- PostgreSQL → logical decoding 订阅 WAL。
- MongoDB → oplog tailing。
各源变更统一为标准事件:{source, table, pk, operation, data, timestamp}。
2. Schema 映射
不同源的同义字段名不同(如 user_name vs username vs name),需要 Schema 映射层统一为目标 Schema。映射规则存配置中心,支持热更新。
3. 冲突处理策略
| 策略 | 原理 | 适用场景 |
|---|---|---|
| LWW | 时间戳最大的赢 | 简单场景,要求时钟同步 |
| 源优先级 | 预定义源的优先级 | 主从明确场景 |
| 字段级合并 | 不同字段取不同源 | 多系统各管各字段 |
| 业务规则 | 自定义合并逻辑 | 复杂业务场景 |
4. 幂等消费
- 目标库唯一索引防止重复插入。
- 变更事件带版本号,目标库只接受版本号 > 当前版本的事件。
🔬 扩展知识
详情
- 【L3】向量时钟:当 LWW 无法判断因果序(两个事件时间戳相同但因果相关),用向量时钟检测并发冲突。
- 【L3】死信队列:冲突无法自动仲裁的事件进死信队列,人工介入处理。
- 【L3】存量初始化与增量衔接:"实时同步"上线时源库已有存量数据,需先按主键分片做全量快照,记录快照起始位点,再从该位点续接增量 CDC;快照期间发生的变更依赖版本号 / 唯一键幂等写入去重,实现全量 + 增量无缝衔接(Debezium 的 snapshot + streaming 即此模型)。
- 【L3】MongoDB 变更捕获优先用官方 Change Streams(提供 resume token 断点续传、原生覆盖分片集群),直接 tailing oplog 需自行处理分片遍历与位点管理,容易丢事件。
- 【L4】时钟依赖的根治:LWW 依赖物理时钟可比,跨机房时钟漂移会导致错误仲裁。生产级方案用逻辑时钟或混合逻辑时钟(HLC,物理时间 + 逻辑计数,CockroachDB 等采用)为事件定序,让冲突仲裁不依赖各源库本机时钟的准确性。
⚠️ 常见误区
详情
常见误区:
- ❌ "多源同步不需要冲突处理,只要保证最终一致就行" → 两个源同时修改同一记录的不同字段,不处理会丢数据。
- ❌ "LWW 足够解决所有冲突" → LWW 依赖时钟同步,跨机房时钟漂移可能导致错误仲裁。
🔀 发散问题
Q:如何处理 Schema 演进(源库加字段)?
→ Schema Registry 管理版本,映射层按版本适配,目标库用宽表或 JSON 字段兼容新字段。
【中等】如何设计一个冷热数据自动分离与分层存储系统?⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:15 min | 🏷 标签:数据生命周期 / 分层存储 / 成本优化
💎 关键结论
冷热分离的核心是"按访问频率自动分层 + 透明查询":热数据(近 7 天)留 SSD / 内存,温数据(7~90 天)迁移到 HDD / 对象存储标准层,冷数据(90 天+)归档到对象存储低频 / 冰川层。迁移规则按策略自动执行,查询层屏蔽存储差异。
⚡ 记忆卡片
- 口诀:热 SSD、温 HDD、冷归档、查询透明
- 关键词:数据生命周期 / 分层存储 / 自动迁移 / 透明查询 / 成本优化
- 链路:写入 → 热存储 → 定时扫描 → 温存储 → 定时扫描 → 冷归档
📖 核心知识
1. 冷热定义
- 热数据:近 7 天,日访问量 > 1000 次 / 条,存 SSD。
- 温数据:7~90 天,日访问量 < 100 次 / 条,存 HDD。
- 冷数据:90 天+,几乎不访问,存对象存储(S3 低频 / 冰川)。
2. 迁移策略
- 定时任务:每天凌晨扫描,按最后访问时间 + 业务规则判断冷热。
- LRU 淘汰:基于访问日志统计,自动迁移低频数据。
- 手动标记:业务方标记某些数据永不迁移(如法律要求的审计日志)。
3. 查询透明
- 统一查询入口:应用层不感知数据在哪层,查询引擎自动路由。
- 热数据:直接查 DB。
- 温数据:查 DB(已迁移到 HDD 表空间)或查 ES。
- 冷数据:对象存储低频层可直接查询(按取回量计费、延迟略高);归档 / 冰川层必须先 restore 解冻(分钟级到数小时)才能读取,查询入口要对用户明确提示延迟。
🔬 扩展知识
详情
- 【L3】MySQL 表空间迁移:
ALTER TABLE ... TABLESPACE可将表 / 分区迁移到不同表空间(SSD → HDD)。 - 【L3】分区表 + 冷热分离:按时间分区,过期分区直接
EXCHANGE PARTITION迁移到归档库。 - 【L3】冷热判定的工程现实:MySQL 不记录行级"最后访问时间",按访问频率判定冷热需要访问日志 / binlog 采样统计,成本高且有滞后。生产上更常用业务时间属性(订单创建时间、日志时间)在表 / 分区粒度判定——"访问频率"是概念模型,"时间 + 业务规则"才是可落地的判据。
- 【L4】分层不只按冷热,还按访问模式:美团将 KV 存储拆为内存型(Squirrel,低延迟高成本)与持久化型(Cellar,SSD 存储、成本更低),推动对延迟不敏感的业务从内存 KV 下沉到持久化 KV——本质是按延迟 SLA 分级而非单纯按访问频率分级,这是存储成本治理的代表性工业实践。
⚠️ 常见误区
详情
常见误区:
- ❌ "冷热分离就是手动删旧数据" → 旧数据有审计和回溯价值,应归档而非删除。
- ❌ "冷数据查询不需要优化" → 冷数据查询虽少但延迟敏感(如合规审计),需要预热机制 + 查询加速。
🔀 发散问题
Q:如何评估冷热分离的成本收益?
→ 对比分层前后的存储成本(SSD vs HDD vs 对象存储单价)+ 查询性能变化(冷数据查询延迟增加),计算 ROI。
【困难】如何设计一个支持多模存储的统一查询引擎?⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:20 min | 🏷 标签:ES / Redis / HBase / 查询路由 / 联邦查询
💎 关键结论
多模存储的统一查询引擎核心是"元数据驱动路由 + 结果合并 + 下推优化":元数据层记录每类数据的存储位置,查询解析后路由到对应存储引擎(ES 做全文检索、Redis 做热点 KV、HBase 做大宽表),结果合并后统一返回。关键是让应用层不感知底层存储差异。
⚡ 记忆卡片
- 口诀:元数据路由、结果合并、下推优化、存储透明
- 关键词:联邦查询 / 元数据目录 / 查询路由 / 结果合并 / 下推优化
- 链路:统一 SQL → 解析 → 元数据查路由 → 分发到各存储引擎 → 结果合并 → 返回
📖 核心知识
1. 多模存储的角色分工
| 存储引擎 | 擅长场景 | 不擅长 |
|---|---|---|
| MySQL | 事务、关联查询 | 全文检索、海量写入 |
| ES | 全文检索、聚合分析 | 事务、关联查询 |
| Redis | 热点 KV、计数器 | 复杂查询、大数据量 |
| HBase | 海量行存储、范围扫描 | 关联查询、事务 |
2. 查询引擎架构
应用层 → 统一 SQL 接口
→ 查询解析器(AST 生成)
→ 元数据目录(表 → 存储引擎映射)
→ 查询路由器(按表 / 条件分发)
→ 各存储引擎适配器
→ 结果合并器(排序 / 分页 / 聚合)3. 查询路由策略
- 表级路由:不同表存不同引擎(用户表 → MySQL,日志表 → ES)。
- 条件级路由:同一表的不同查询条件路由到不同引擎(主键查 → Redis,全文检索 → ES,范围查 → HBase)。
- 混合路由:一个查询涉及多个表 / 引擎,拆分为子查询分别执行后合并。
4. 结果合并
- 排序合并:各引擎返回各自排序结果,归并排序。
- 分页合并:各引擎返回前 N 条,全局取前 N 条(深分页需特殊处理)。
- 聚合合并:各引擎返回局部聚合结果,全局二次聚合。
🔬 扩展知识
详情
- 【L3】下推优化:将过滤条件下推到各存储引擎,减少返回数据量。如 ES 支持全文检索 + 过滤,MySQL 支持 WHERE 条件,在各自引擎内完成过滤后再返回。
- 【L3】跨引擎深分页是开放性难题:全局第 10000 页意味着每个引擎都要返回前 10 万条再归并,代价不可接受。工程做法是限制分页深度(如只允许前 100 页)、改用游标式分页(seek method,按上一页末尾的排序键继续取),或用 ES 的 search_after 语义统一各引擎的翻页协议。
- 【L4】跨引擎快照一致性:一个查询先后读 ES 与 MySQL 时,两个引擎的数据版本可能不同(ES 索引落后于 DB),合并结果会出现"列表有、详情无"或价格不一致。对策:接受并声明弱一致语义,或以查询开始时间为界过滤掉更新的数据(各引擎返回写入时间戳),关键链路(下单、支付)不走联邦查询。
- 【L4】Apache Calcite / Trino 等开源联邦查询引擎可参考,但生产环境通常需要定制适配层。
🏭 实战场景
详情
电商商品搜索:用户输入"红色连衣裙",查询引擎拆分为:① ES 全文检索商品名 / 描述 → 返回商品 ID 列表 + 相关性分数 ② Redis 查商品实时库存 → 过滤库存为 0 的商品 ③ MySQL 查商品详情 → 补全价格 / 图片等信息。结果合并后按相关性排序返回。
⚠️ 常见误区
详情
常见误区:
- ❌ "统一查询引擎就是把所有数据同步到一个引擎" → 数据同步成本高且有延迟,应该让数据留在最适合的引擎,查询引擎做路由和合并。
- ❌ "跨引擎查询性能一定差" → 通过下推优化 + 并行查询 + 结果缓存,跨引擎查询可以做到秒级响应。
🔀 发散问题
Q:如何处理跨引擎的事务?
→ 跨引擎强一致事务不现实,通常用 Saga 模式保证最终一致,或在业务层做补偿。
【中等】如何设计一个高吞吐的消息持久化系统?⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:15 min | 🏷 标签:顺序写 / 零拷贝 / 副本 / 刷盘
💎 关键结论
高吞吐消息持久化的核心是"顺序写 + 零拷贝 + 副本异步复制":消息追加写入顺序日志(避免随机 IO 的寻道开销),消费端用 sendfile 零拷贝减少内核态拷贝,副本采用异步复制保证写入延迟不受跨机房影响。三者协同:顺序写保证写入速度,零拷贝保证读取速度,异步副本保证持久性不影响延迟。
⚡ 记忆卡片
- 口诀:顺序写、零拷贝、异步副本、分组刷盘
- 关键词:顺序 IO / mmap / sendfile / ISR / 刷盘策略
- 链路:生产者 → 顺序追加写 PageCache → 异步刷盘 → 异步复制到副本 → 消费者零拷贝读取
📖 核心知识
1. 顺序写入
- 磁盘顺序写远快于随机写:单盘 HDD 顺序写约 150~200MB/s(RAID 阵列可更高),SATA SSD 约 500~600MB/s,NVMe SSD 达 GB/s 级;而单盘 HDD 随机写受 IOPS 与寻道限制只有不足 1MB/s 到几 MB/s,与顺序写差距可达百倍。
- 消息追加写入日志文件(类似 WAL),避免随机 IO。
- PageCache 利用:写入先落 PageCache,由 OS 异步刷盘,应用层不直接 fsync。
2. 零拷贝
- 传统读取:磁盘 → 内核缓冲区 → 用户缓冲区 → Socket 缓冲区 → 网卡(4 次拷贝)。
- sendfile:磁盘 → 内核缓冲区 → Socket 缓冲区 → 网卡(2 次拷贝),跳过用户态。
- mmap + write:适合小数据量,减少一次拷贝但页表维护有开销。
3. 副本策略
| 策略 | 延迟 | 持久性 | 适用场景 |
|---|---|---|---|
| 无副本 | 最低 | 单点丢失即丢 | 日志等非关键数据 |
| 异步复制 | 低 | 主挂可能丢少量 | 大多数 MQ 场景 |
| 同步复制 | 高 | 不丢数据 | 金融级消息 |
| 半同步(ISR) | 中 | ISR 集合内副本全部确认(配合最小 ISR 数控制下限) | Kafka acks=all 模式 |
注意:Kafka 的 ISR(同步副本集合)机制不是 Raft 式"多数派确认"——acks=all 要求当前 ISR 中的全部副本确认,min.insync.replicas 限定 ISR 的最小数量(低于则该分区拒绝写入);多数派(quorum)语义是 Pulsar(BookKeeper quorum write)、Raft 类系统的模型,两者不可混为一谈。
4. 刷盘策略
- 异步刷盘:消息写 PageCache 即返回成功,OS 定期刷盘。吞吐高但宕机丢数据。
- 同步刷盘:消息写磁盘才返回成功。不丢数据但吞吐下降 10~100 倍。
- 分组刷盘:每 N 条消息或每 T 毫秒刷一次盘,折中方案。
🔬 扩展知识
详情
- 【L3】Kafka 高吞吐的秘密:顺序写 + PageCache + 零拷贝 + 分区并行 + 批量压缩发送。单 Broker 可达百万 TPS。
- 【L3】消息堆积处理:消费者跟不上时消息堆积在 Broker 磁盘,顺序读性能不受影响(磁盘顺序读 ~100MB/s),但需注意磁盘空间告警。
- 【L4】sendfile 的精确语义:传统 read + write 路径是 4 次拷贝(2 次 DMA + 2 次 CPU 拷贝);sendfile 配合 DMA gather 后数据不经过用户态,CPU 拷贝次数为 0,只剩 DMA 拷贝。这是"零拷贝"一词的准确含义——零的是 CPU 拷贝,不是所有拷贝。
- 【L4】刷盘与丢失窗口的精确定义:异步刷盘下,应用进程崩溃不丢数据(PageCache 属内核,仍会被刷盘);只有整机宕机 / 掉电才丢未刷盘部分。因此"异步刷盘 = 不可靠"是过度简化,真实的丢失窗口 = 上次刷盘到宕机时刻的间隔,可用分组刷盘(N 条或 T 毫秒)把窗口控制在可量化范围,并配合副本机制把"单机丢窗口"降级为"多副本同时故障才丢"。
⚠️ 常见误区
详情
常见误区:
- ❌ "SSD 不需要顺序写优化" → SSD 随机写性能虽好但顺序写仍快 2~5 倍,且顺序写对 GC 友好。
- ❌ "异步刷盘进程一挂消息就全丢" → 已写入 PageCache 的数据属内核空间,应用进程崩溃不会丢,OS 仍会刷盘;只有整机宕机 / 掉电才丢未刷盘部分。
- ❌ "同步复制才能保证不丢消息" → 异步复制 + 生产者确认(publisher confirm)+ 本地消息表可实现"近似不丢",性能远优于同步复制。
🔀 发散问题
Q:消息顺序性如何保证?
→ 分区内有序(同 key 路由到同分区),全局有序需单分区(牺牲并发度)。
可靠性与容灾
【困难】如何设计一个支持多活架构的数据一致性保证方案?⭐⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:25 min | 🏷 标签:多活 / 单元化 / 数据同步 / 冲突处理
💎 关键结论
多活架构数据一致性的核心是"单元封闭 + 双向同步 + 冲突仲裁":按用户维度切分流量,每个用户的读写都封闭在一个单元内(避免跨单元冲突),单元间只同步元数据和关键状态(双向同步),冲突通过 GTID 去重 + LWW 仲裁。不追求跨单元强一致,追求单元内强一致 + 跨单元最终一致。
⚡ 记忆卡片
- 口诀:单元封闭、双向同步、GTID 去重、LWW 仲裁
- 关键词:多活 / 单元化 / 流量路由 / 数据同步 / 冲突处理 / 容灾切换
- 链路:用户请求 → DNS/GSLB 路由到归属单元 → 单元内读写 → 跨单元同步(异步)→ 冲突仲裁
📖 核心知识
1. 单元封闭
- 按 user_id 哈希划分单元归属,user_id % N 决定归属哪个单元。
- 该用户的所有读写都在归属单元内完成,不跨单元写。
- 好处:避免跨单元写冲突,一致性模型简化为"单元内强一致 + 跨单元最终一致"。
2. 数据分类与同步策略
| 数据类型 | 同步策略 | 示例 |
|---|---|---|
| 用户数据 | 不跨单元同步(路由保证唯一写入点) | 用户信息、订单 |
| 全局数据 | 主单元写入,其他单元订阅 | 配置、商品 |
| 跨单元数据 | 双向同步 + 冲突仲裁 | 库存(超卖场景) |
3. 双向同步防循环
- A 单元变更同步到 B → B 处理后再同步回 A → 无限循环。
- 解决:每条变更附带 GTID(全局事务 ID),同步时检查 GTID 来源,源自自己的跳过。
4. 容灾切换
- 正常:流量按 user_id 路由到归属单元。
- 故障:将故障单元的 user_id 范围切换到其他单元。
- 切换期间:禁写(或排队),保证数据不丢。
- 切换完成:新单元接管读写,数据从旧单元最后同步位点补齐。
🔬 扩展知识
详情
- 【L3】单元封闭的局限:某些业务必须跨单元(如 A 单元用户给 B 单元用户转账),需要"跨单元消息路由"——A 单元发消息到 B 单元的消息队列,B 单元消费处理。
- 【L4】阿里单元化实践:按用户 ID 划分单元,每个单元包含完整的接入层、服务层、数据层,单元间通过消息队列异步同步。
- 【L4】共享热点数据无法单元封闭:全国一盘货的库存是典型的跨单元资源,双向同步同一行必然冲突。两种主流解法:① 库存分片——按单元拆子额度各自扣减,售罄时跨单元调拨再平衡;② 集中写——库存写请求路由回中心单元串行处理,读可单元本地缓存。选型取决于库存量级与超卖容忍度。
- 【L4】RTO/RPO 指标驱动的方案推演:跨单元异步复制的延迟(通常百毫秒到秒级)决定了 RPO 的下界——切换时禁写追赶数据可以做到 RPO≈0 但拉长 RTO;允许直接切写则 RTO 短但 RPO 窗口内的数据可能永久丢失。P8 级回答要能把"禁写时长、同步延迟、丢失容忍度"三个变量和具体业务(资金类 RPO=0、内容类可容忍秒级丢失)对应起来推演,并给出"1 分钟发现、5 分钟定位、10 分钟恢复"(业界通用口径)依赖的工程路径:健康检查自动化、单元级监控聚合、预案化的一键切流。
🏭 实战场景
详情
某电商平台异地多活:3 个单元(上海 / 北京 / 深圳),按 user_id % 3 划分归属。
设计要点:用户数据单元封闭(每个用户只在一个单元写入),全局配置主单元(上海)写入、其他订阅;MySQL 双向同步用 DTS 实现,GTID 去重防循环;容灾切换通过 GSLB 调整 DNS 权重,故障单元流量逐步切到其他单元。注意 DNS 切流的收敛时间受 TTL 与运营商 / 客户端本地缓存影响,实际是分钟级而非秒级——评估切换窗口必须按 DNS 收敛时间而不是配置下发时间;要压缩到秒级需依赖客户端 HTTPDNS 或接入层主动断开长连接迫使重解析。
⚠️ 常见误区
详情
常见误区:
- ❌ "多活就是每个单元都能处理所有用户的请求" → 多活是流量分片,每个用户只在一个单元写入,否则双向同步必然冲突。
- ❌ "数据库双向同步工具能自动解决冲突" → 工具只保证数据最终同步,不保证业务语义正确(如两个单元同时修改用户余额,工具只能保留一个值)。
- ❌ "切换时用户无感知" → 路由切换需要时间,期间少量请求可能失败或数据短暂不一致,需提前告知用户。
🔀 发散问题
Q:如何验证多活架构的容灾能力?
→ 定期混沌工程演练:模拟单元故障,验证切换时间、数据一致性、业务恢复情况。
【困难】如何设计一个全链路压测的影子库表与流量染色体系?⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:20 min | 🏷 标签:全链路压测 / 影子库 / 流量染色 / 监控
💎 关键结论
全链路压测的核心是"流量染色 + 影子体系 + 实时监控":压测流量从入口打标(染色),全链路透传标记,所有数据操作路由到影子库 / 影子表 / 影子缓存,与正式数据物理隔离。监控区分压测 / 正式流量,压测异常时自动熔断。
⚡ 记忆卡片
- 口诀:入口染色、全链路透传、影子隔离、监控熔断
- 关键词:流量染色 / 影子库 / 影子表 / 影子缓存 / 压测熔断 / 数据隔离
- 链路:压测入口 → 染色标记 → 全链路透传 → 影子库/表/缓存 → 监控区分 → 异常熔断
📖 核心知识
1. 流量染色
- 入口标记:压测请求在网关层打标记(HTTP Header
X-Stress-Test: true)。 - 透传:标记在 RPC Context / MQ Header / ThreadLocal 中全链路透传。
- 染色维度:按用户 ID 范围、按请求比例、按接口维度。
2. 影子体系
| 组件 | 影子方案 | 隔离级别 |
|---|---|---|
| DB | 影子库(独立实例)或影子表(_shadow 后缀) | 物理 / 逻辑隔离 |
| Redis | 影子缓存(key 前缀 stress: 或独立 DB) | 逻辑 / 物理隔离 |
| MQ | 影子 Topic(_stress 后缀) | 逻辑隔离 |
| ES | 影子索引(stress_ 前缀) | 逻辑隔离 |
3. 数据路由
if (StressContext.isStressTest()) {
// 路由到影子库/表
dataSource = stressDataSource;
tableName = originalTable + "_shadow";
} else {
dataSource = productionDataSource;
tableName = originalTable;
}4. 实时监控与熔断
- 监控面板区分压测 / 正式流量(通过染色标记分组)。
- 自动熔断:压测流量 RT > 阈值 或错误率 > 阈值 → 自动停止压测。
🔬 扩展知识
详情
- 【L3】压测数据清理:压测结束后批量清理影子库 / 表数据,避免长期占用存储。
- 【L3】压测准确性验证:比对影子表和正式表的数据分布,确认压测流量行为与正式流量一致。
- 【L3】染色标记的透传断点:ThreadLocal 标记经过异步线程池、MQ 异步消费后会丢失,需用 TransmittableThreadLocal 或在消息 Header 显式携带;任何一个环节丢标,压测数据就会写进正式库——丢标是全链路压测最典型的事故来源,透传链路的每一跳都要有覆盖率检测。
- 【L3】单机容量 → 集群容量的折算:不能简单乘以实例数,要扣除冗余系数(N+1 / 多 AZ 容灾预留)并考虑热点不均(分片倾斜、大租户集中导致集群整体水位受最热节点限制)。
- 【L4】从"压测"到"容量运营"的常态化:美团 2025-10 发布的数据库容量评估系统用线上流量回放构建沙盒环境,实现变更验证、容量上探与常态化运营。P8 级回答应体现把容量评估做成持续体系(容量水位看板、变更前容量验证卡点、与自动扩缩容联动),而不是大促前的一次性压测。
📚 延伸阅读:美团技术团队 · 数据库容量评估系统
🏭 实战场景
详情
某电商平台双 11 全链路压测:目标 100 万 QPS。
设计要点:压测流量从网关染色(按用户 ID 范围 1~10w 为压测用户),全链路透传标记;DB 用影子表(order_shadow),Redis 用影子 DB(DB 15),MQ 用影子 Topic(ORDER_STRESS);监控面板实时展示压测 / 正式两组指标,RT > 500ms 自动熔断压测。
⚠️ 常见误区
详情
常见误区:
- ❌ "全链路压测就是压单接口" → 单接口压测无法发现链路瓶颈(如 A 服务正常但 B 服务被打挂),必须全链路。
- ❌ "影子库是可选的" → 不用影子库,压测数据会污染正式数据(创建真实订单、扣减真实库存),后果严重。
- ❌ "压测环境可以和生产不一样" → 压测必须在生产环境做(或 1:1 镜像环境),否则发现的瓶颈不真实。
🔀 发散问题
Q:压测中如何防止压测数据泄露到正式环境?
→ 影子库隔离 + 染色标记透传 + 压测结束后清理影子数据。
【中等】如何设计一个分布式系统的对账与差错自动补偿机制?⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:15 min | 🏷 标签:对账 / 最终一致 / 幂等 / 补偿
💎 关键结论
对账系统的核心是"定时全量 + 实时增量 + 自动补偿":增量对账通过 MQ 实时比对关键业务字段,全量对账每天 T+1 跑批全量比对,发现差异后自动补偿(可幂等重试的自动重试,需人工审核的转人工)。
⚡ 记忆卡片
- 口诀:增量实时对、全量 T+1、差异分类、自动补偿
- 关键词:对账 / 差错处理 / 幂等补偿 / T+1 跑批 / 差异分类
- 链路:业务操作 → MQ 事件 → 增量对账 → 差异记录 → 自动补偿/人工审核
📖 核心知识
1. 对账层次
- 实时对账:关键交易链路中,上下游同步比对(如支付回调后立即比对订单状态)。
- T+1 对账:每日凌晨跑批,全量比对上下游数据(如支付流水 vs 银行回单)。
- 专项对账:针对特定场景(如退款、充值)的定向对账。
2. 对账流程
1. 数据拉取:从上下游系统拉取 T 日数据
2. 数据对齐:按业务主键关联(如订单号)
3. 差异比对:逐条比对关键字段(金额、状态、时间)
4. 差异分类:
- 上游有下游无(长款)→ 补发
- 上游无下游有(短款)→ 冲正
- 金额不一致 → 人工审核
5. 自动补偿:幂等重试 / 冲正
6. 人工审核:复杂差异转人工3. 分布式对账的数据拉取
- 数据量大时分布式拉取:MapReduce 模型,按 user_id 范围分片,各 Worker 并行拉取比对。
- 数据对齐:上下游时间窗口可能不对齐(如支付成功时间与回调时间差),需要"双窗口比对"(T 日和 T-1 日都比对一遍)。
🔬 扩展知识
详情
- 【L3】对账差异的根因分析:网络超时(上游成功但下游未收到)、重复投递(MQ 重复)、部分失败(分布式事务中间节点失败)。
- 【L4】自动补偿的幂等设计:补偿操作必须幂等(数据库唯一索引、Redis 记录已补偿 ID),防止重复补偿。
- 【L4】资损防控的三道防线:对账只是事后防线。资金类操作(提现、退款)还要有事前防线(资金类变更强制评审对账规则与限额配置)和事中防线(实时核对 + 单笔/累计金额阈值拦截,超限先挂起人工审核);差错金额先进挂账科目再补偿,禁止直接改账——电商/金融背景 P8 面试中"提现失败处理"类题目必考这套幂等、对账、资损防控组合。
⚠️ 常见误区
详情
常见误区:
- ❌ "对账只需要比对金额" → 还需比对状态、时间戳、关联单据号等,金额一致但状态不一致也是问题。
- ❌ "对账可以实时做" → 全量实时对账性能成本高,通常增量实时 + 全量 T+1。
- ❌ "补偿操作不需要幂等" → 补偿可能因网络超时重试,不幂等会导致重复补偿(如重复退款)。
🔀 发散问题
Q:对账数据量太大(10 亿级)如何优化?
→ 分层 Hash 聚合:先按 user_id hash 分组比对聚合值(总金额、总笔数),不一致再展开明细比对,减少比对次数。
【中等】如何设计一个多级熔断与限流协同的降级体系?⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:15 min | 🏷 标签:熔断 / 限流 / 降级 / 恢复
💎 关键结论
多级熔断限流的核心是"分层防护 + 统一配置 + 独立决策":网关层做用户级限流 + 黑白名单,服务层做熔断 + 并发数限制,数据层做连接池隔离 + 降级。三层协同:上层熔断触发时下层自动降级,但下层不能拒绝上层放行的请求(避免上层放行下层拒绝的不一致)。
⚡ 记忆卡片
- 口诀:网关限流、服务熔断、数据降级、统一配置
- 关键词:熔断器 / 限流 / 降级 / 半开探测 / 舱壁隔离
- 链路:请求 → 网关限流 → 服务熔断检查 → 调用下游 → 数据层降级 → 返回
📖 核心知识
1. 三层防护
| 层级 | 职责 | 实现 |
|---|---|---|
| 网关层 | 用户级限流、黑白名单、认证 | Sentinel / Nginx |
| 服务层 | 熔断、并发数限制、超时控制 | Resilience4j / Sentinel |
| 数据层 | 连接池隔离、读写分离、降级 | HikariCP / 缓存降级 |
2. 熔断器状态机
关闭(正常)→ 失败率 > 阈值 → 打开(熔断)
打开 → 等待超时 → 半开(放一个探测请求)
半开 → 探测成功 → 关闭
半开 → 探测失败 → 打开3. 限流与熔断协同
- 限流:控制入口流速,防止系统过载(主动防护)。
- 熔断:检测下游故障,快速失败释放资源(被动响应)。
- 协同:限流在熔断之前——先限流保证不超载,再熔断隔离故障下游。
4. 降级策略
- 缓存降级:DB 不可用时返回缓存数据(可能过期)。
- 简化降级:非核心功能关闭(如推荐 → 返回默认列表)。
- 异步降级:同步写改为 MQ 异步写(接受延迟)。
🔬 扩展知识
详情
- 【L3】熔断器参数调优:失败率阈值、慢调用阈值、半开探测数、打开等待时间需按业务 SLA 调整(各框架默认值不同,如 Resilience4j 默认失败率阈值 50%、打开态等待 60s、半开态放行 10 个探测请求;不能照搬默认值上生产)。
- 【L3】舱壁隔离:不同下游服务用独立线程池,一个服务故障不会耗尽全局线程。
- 【L3】强弱依赖治理决定降级范围:先梳理依赖强弱(弱依赖故障必须能降级不影响主链路,强依赖故障只能快速失败 + 预案切换),预案要提前注册并定期演练——没有演练过的降级开关,故障时大概率不敢按、按了也不生效。
- 【L4】重试风暴防放大:重试会沿调用链逐层放大流量(3 层链路每层重试 3 次 = 最多 27 倍请求),下游瞬时抖动会被重试放大成全链路雪崩。防放大设计:重试预算(retries 不超过正常请求量的一定比例,如 10%)、指数退避 + 随机抖动(避免同时重试的同步脉冲)、与熔断联动(熔断打开即停止重试)、入口层总量控制兜底。
⚠️ 常见误区
详情
常见误区:
- ❌ "熔断和限流是一回事" → 熔断是检测下游故障并快速失败(保护调用方),限流是控制入口流速(保护被调用方)。
- ❌ "熔断器打开后就不需要恢复了" → 必须半开探测恢复,否则一直走降级逻辑。
- ❌ "所有服务共用一个熔断规则" → 不同服务 SLA 不同,熔断阈值应独立配置。
🔀 发散问题
Q:熔断和重试如何协同?
→ 熔断开启时不重试(快速失败),半开时只放一个探测请求不重试,关闭时正常重试(有上限 + 退避)。
【中等】如何设计一个云原生应用的灾备体系(RTO/RPO 分级)?⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:15 min | 🏷 标签:灾备 / RTO / RPO / 多 region / 混沌工程
💎 关键结论
灾备体系的核心是"按 RTO/RPO 分级 + 成本效益平衡":RTO/RPO 要求越高成本越高,不能一刀切。核心业务 RTO 秒级 + RPO=0(多活强同步),重要业务 RTO 分钟级 + RPO 秒级(主备半同步),一般业务 RTO 小时级 + RPO 小时级(冷备定时同步)。同时通过混沌工程定期验证灾备能力。
⚡ 记忆卡片
- 口诀:分级定 RTO、成本效益平衡、混沌验证、自动切换
- 关键词:RTO / RPO / 多活 / 主备 / 冷备 / 混沌工程
- 链路:业务分级 → 灾备策略 → 数据同步 → 流量切换 → 混沌验证
📖 核心知识
1. RTO/RPO 分级策略
| 级别 | RTO | RPO | 灾备方案 | 成本 |
|---|---|---|---|---|
| 核心(支付/交易) | 秒级 | 0 | 同城双活(强/半同步);异地要 RPO=0 须两地三中心 + 多数派协议 | 3x+ |
| 重要(用户/订单) | 分钟级 | 秒级 | 同城主备(半同步复制) | 2x |
| 一般(日志/报表) | 小时级 | 小时级 | 异地冷备(定时同步) | 1.1x |
| 非核心(内部工具) | 天级 | 天级 | 无灾备(可重建) | 1x |
注意:普通异地多活(异步复制)做不到 RPO=0——跨地域 RTT 有 20~40ms,若每次写入都等异地确认(强同步),写延迟不可接受;跨地域 RPO=0 的现实解法是两地三中心或基于 Paxos/Raft 多数派确认的复制(如 OceanBase 的三地五副本模式,只需多数派同城/近地确认即可容忍单地故障),且只对资金核心数据值得付出这个成本。
2. 数据备份策略
- 强同步:主库写入 + 备库确认才返回成功(RPO=0,延迟上升)。
- 半同步:主库写入 + 至少一个备库确认(RPO 秒级,性能折中)。
- 异步:主库写入即返回,备库延迟追(RPO 分钟级,性能最优)。
- 定时备份:每小时 / 每天全量备份到对象存储(RPO 小时 / 天级)。
3. 应用层容灾
- 无状态设计:应用层不存状态,K8s 多集群部署,随时可扩容。
- 配置同步:配置中心跨 region 同步,保证多环境配置一致。
- 流量切换:DNS 加权路由 / GSLB(全局负载均衡),支持按 user_id / 地域灰度切换。
4. 混沌工程
- 故障注入:随机杀 Pod、模拟网络延迟、磁盘故障注入。
- 演练频率:核心业务每周、重要业务每月、一般业务每季度。
- 生产演练:在生产环境做(非测试环境),低峰期执行,一键回滚。
🔬 扩展知识
详情
- 【L3】RTO/RPO 量化:RTO 包含检测时间 + 决策时间 + 切换时间 + 验证时间,每个阶段都要有明确的耗时目标。
- 【L3】灾备成本优化:弹性资源(Serverless / 容器化)+ 共享灾备(多个业务共用灾备资源)+ 保险兜底。
- 【L3】云原生灾备的具体落点:Pod 拓扑分布约束(跨 AZ 反亲和)防止单 AZ 故障团灭、PodDisruptionBudget 防止滚动运维同时驱逐过多副本、有状态服务用云盘跨 AZ 快照或 Operator 多副本;K8s 本身不解决跨 region 流量调度,需依赖上层 GSLB / DNS 或服务网格。
⚠️ 常见误区
详情
常见误区:
- ❌ "灾备就是在另一个机房部署一套" → 灾备还包括数据同步、流量切换、配置一致性、演练验证,缺一个都切不过去。
- ❌ "有云服务商的 SLA 就不需要灾备" → 云 SLA 只保证基础设施可用性,应用层故障(如误删数据、配置错误)云不管。
- ❌ "灾备只需要文档不需要演练" → 不演练的灾备等于没有灾备,切换时一定出问题。
🔀 发散问题
Q:如何验证灾备方案的有效性?
→ 混沌工程定期演练 + 灾备切换演练(季度级)+ 事后复盘。
架构演进与工程化
【中等】如何设计一个从单体到微服务的渐进式拆分方案?⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:15 min | 🏷 标签:DDD / 微服务 / 数据迁移 / 灰度
💎 关键结论
渐进式拆分的核心是"绞杀者模式 + 数据双写 + 灰度切换":不一次性拆完,而是从边缘服务开始逐步剥离,每个服务拆分都经历"双写 → 数据同步 → 灰度读 → 全量切换 → 下线旧代码"五个阶段。DDD 指导拆分边界(限界上下文),灰度保证切换安全。
⚡ 记忆卡片
- 口诀:绞杀者模式、双写迁移、灰度切换、DDD 划界
- 关键词:绞杀者 / 限界上下文 / 数据双写 / 灰度路由 / 渐进式
- 链路:DDD 识别边界 → 选边缘服务先拆 → 双写 → 数据同步 → 灰度读 → 全量切换
📖 核心知识
1. 拆分策略:绞杀者模式
不是"推倒重来",而是在单体旁边逐步建新服务,新功能直接写新服务,旧功能逐步迁移,最终单体变成空壳。
2. 拆分顺序
1. 最独立的服务先拆(如通知服务、日志服务)
2. 读多写少的服务次拆(如查询服务)
3. 核心交易服务最后拆(如订单、支付)3. 数据迁移五阶段
阶段 1:双写 — 写入同时写旧库和新库(以旧库为准)
阶段 2:数据同步 — 旧库数据通过 Canal 同步到新库
阶段 3:灰度读 — 10% 读流量切到新库,比对结果
阶段 4:全量切换 — 读写全部切到新库,旧库降级为备
阶段 5:下线旧库 — 观察期过后下线旧库4. 灰度策略
- 按 user_id 灰度:指定用户先走新服务。
- 按比例灰度:10% → 30% → 50% → 100%。
- 灰度期间新旧服务并行,对比结果一致性。
🔬 扩展知识
详情
- 【L3】拆分中的分布式事务:拆分后跨服务调用引入分布式事务,优先用最终一致(MQ + 本地消息表),避免 2PC。
- 【L3】拆分后的运维复杂度:服务数量增加 → 需要服务网格(Istio)+ 统一监控 + 链路追踪。
- 【L3】什么时候不该拆(P8 常考的反向追问):微服务用运维复杂度换组织自治——团队规模小(如 <10 人、无并发交付冲突)、单体没有明确的扩展性或发布效率瓶颈、模块间交互高频强耦合时,拆分是负优化(网络开销、分布式事务、排障复杂度全部上升)。拆分的驱动力首先是组织与交付效率问题,其次才是技术问题。
- 【L4】边界识别的可操作方法:DDD 限界上下文不能只停在概念,落地用事件风暴(Event Storming)从领域事件出发聚类出上下文,再以"上下文间通信频率最低、上下文内数据自治(不跨服务 join、不共享库表)"检验拆分方案的合理性——数据无法自治的边界就是错误的边界。
⚠️ 常见误区
详情
常见误区:
- ❌ "一步到位全拆完" → 风险极高,拆分过程中业务不能停,必须渐进式。
- ❌ "先拆核心服务" → 核心服务依赖最多、最复杂,应先拆边缘服务积累经验。
- ❌ "数据迁移可以停机做" → 核心业务不能停机,必须在线迁移(双写 + 灰度)。
🔀 发散问题
Q:拆分后服务间调用延迟增加怎么办?
→ 合并频繁交互的服务(避免过度拆分),用 gRPC 替代 HTTP,引入服务网格优化网络层。
【中等】如何设计一个技术债的量化管理与偿还策略?⭐⭐
🎯 目标等级:L3 | ⏱ 建议用时:15 min | 🏷 标签:技术债 / 度量 / 优先级 / 偿还
💎 关键结论
技术债管理的核心是"量化 + 分级 + 纳入迭代":用代码质量指标(圈复杂度 / 重复率 / 测试覆盖率)+ 业务影响指标(故障频率 / 变更耗时)量化债务,按"利息"(维护成本)排序优先级,每个迭代固定 20% 容量偿还技术债。
⚡ 记忆卡片
- 口诀:量化度量、利息排序、纳入迭代、定期审计
- 关键词:技术债 / 圈复杂度 / 重复率 / 变更耗时 / 20% 容量
- 链路:度量指标 → 量化评分 → 按利息排序 → 纳入迭代计划 → 偿还验证
📖 核心知识
1. 技术债分类
| 类型 | 来源 | 示例 |
|---|---|---|
| 代码债 | 编码不规范 | 重复代码、过长方法、魔法数字 |
| 架构债 | 设计不合理 | 循环依赖、分层混乱、无抽象 |
| 测试债 | 测试不足 | 覆盖率低、无集成测试 |
| 文档债 | 文档缺失 | 接口无文档、架构无图 |
| 基础设施债 | 运维手工操作 | 无 CI/CD、无监控、手动部署 |
2. 量化指标
- 代码质量:圈复杂度(>15 为高风险)、重复率(>5% 需重构)、测试覆盖率(<60% 为高风险)。
- 业务影响:模块故障频率(月均 >1 次为高债务)、需求变更耗时(同功能修改耗时逐月增加)。
- 综合评分:
债务分 = 代码质量分 × 0.4 + 业务影响分 × 0.6。
3. 偿还策略
- Boy Scout Rule:每次修改代码时顺手改善一点("离开时比来时更干净")。
- 专项偿还:每个迭代固定 20% 容量用于技术债偿还。
- 架构重构:每季度一次专项重构周期(1~2 周)。
4. 优先级排序
按"利息"排序——维护成本最高的先还:
优先级 = 故障频率 × 影响范围 × 修改难度🔬 扩展知识
详情
- 【L3】技术债可视化:建立技术债看板(Jira / 飞书),每个债务项有明确的"本金"(偿还工作量)和"利息"(不偿还的额外成本)。
- 【L3】工业标准量化口径:SonarQube 的可维护性评级用"技术债比率 = 修复所有问题的预估时间 / 从零开发该代码的预估时间"分 A~E 级,可作为自建量化模型的参照,避免自造公式缺乏横向可比性。
- 【L4】技术债与业务节奏的平衡:业务高速期少还债(接受债务增长),业务平稳期多还债(降低债务存量)。
⚠️ 常见误区
详情
常见误区:
- ❌ "技术债是开发自己的事,不需要产品知道" → 技术债影响需求交付速度,产品需要知道"为什么这个需求要 2 周而不是 3 天"。
- ❌ "等技术债积累到一定程度再统一重构" → 统一重构意味着长时间不能交付业务需求,应该持续小步偿还。
🔀 发散问题
Q:如何说服管理层投入资源偿还技术债?
→ 用数据说话:技术债导致的故障损失、需求交付延迟天数、新人上手时间,转化为金额。
【困难】如何设计一个全链路可观测性体系(Metrics/Logs/Traces 三支柱)?⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:20 min | 🏷 标签:可观测性 / Metrics / Logs / Traces / SLO
💎 关键结论
可观测性体系的核心是"三支柱协同 + SLO 驱动":Metrics 做实时告警(聚合指标),Traces 做链路定位(找到哪个服务慢),Logs 做根因分析(找到为什么慢)。三者通过 traceId + timestamp 关联。SLO 定义可观测的质量目标,Error Budget 驱动告警和改进。
⚡ 记忆卡片
- 口诀:Metrics 告警、Traces 定位、Logs 分析、SLO 定标
- 关键词:三支柱 / traceId / SLO / Error Budget / 关联分析
- 链路:Metrics 告警 → Dashboard 定位域 → Traces 定位服务 → Logs 分析根因
📖 核心知识
1. Metrics(指标)
- 类型:Counter(累加)、Gauge(瞬时值)、Histogram(分布)。
- 关键指标:
- 四个黄金信号(Google SRE):延迟(RT p50/p99/p999,且要区分成功请求与失败请求的延迟)、流量(QPS、并发数)、错误(错误率)、饱和度(资源水位)。
- RED 系列(服务):Rate(请求速率)、Errors(错误率)、Duration(耗时分布)。
- USE 系列(基础设施):Utilization(利用率)、Saturation(饱和度)、Errors(错误数)。
- 工具:Prometheus + Grafana。
2. Traces(链路追踪)
- 核心概念:Trace(完整请求链路)→ Span(单个服务处理段)→ traceId 关联所有 Span。
- 关键能力:调用链可视化、瓶颈定位、跨服务依赖分析。
- 工具:OpenTelemetry + Jaeger / Tempo。
3. Logs(日志)
- 结构化日志:JSON 格式,包含 traceId、spanId、service、level、message。
- 日志关联:通过 traceId 将日志与 Traces 关联,从链路直接跳转到日志。
- 工具:ELK / Loki + Grafana。
4. SLO 与 Error Budget
- SLO:定义服务质量目标(如 99.9% 请求 RT < 200ms)。
- Error Budget:1 - SLO = 允许的错误预算(99.9% SLO → 0.1% 预算 = 每月 43 分钟)。
- 告警策略:基于 Error Budget 消耗速率告警(1 小时消耗 > 10% → P1 告警)。
5. 三支柱关联
告警触发(Metrics)
→ Dashboard 定位问题域(哪个服务 / 哪个接口)
→ 展开 Traces(找到具体请求的完整链路)
→ 查看各 Span 的 Logs(分析根因)🔬 扩展知识
详情
- 【L3】OpenTelemetry 统一 SDK:同时采集 Metrics + Traces + Logs,自动注入 traceId,避免三套 SDK 的集成成本。
- 【L3】Traces 采样策略:日均十亿级请求全采 trace 成本不可承受。头部采样(入口按比例决定,实现简单但会漏掉异常链路)vs 尾部采样(全量收集后在收集器按结果决定保留,能确保错误/慢链路被留下,但收集器需要缓冲全量数据);生产常用组合:错误与慢请求尾部采样全保留 + 正常流量头部采样低比例。
- 【L4】指标基数治理:把 user_id、原始 URL 等高基数值放进 Prometheus 标签会导致时间序列爆炸(内存/存储激增、查询超时),必须有标签设计规范与基数监控。这也是 Metrics(低基数聚合、便宜、可告警)与 Logs/Traces(高基数明细、贵、供下钻)分工的根本原因——可观测性体系设计一半是架构问题,一半是成本问题。
- 【L4】可观测性平台选型:开源码(Prometheus + Grafana + Tempo + Loki)成本低但运维重;商业方案(Datadog)开箱即用但贵;自建方案(OpenTelemetry + 自研后端)灵活但工程投入大。
🏭 实战场景
详情
某 SaaS 平台可观测性体系:200+ 微服务,日均 10 亿请求。
设计要点:OpenTelemetry SDK 统一采集三支柱数据;Metrics 用 Prometheus + Thanos 长期存储;Traces 用 Tempo(S3 后端,成本低);Logs 用 Loki(与 Grafana 原生集成);SLO 定义核心接口 99.9% RT < 200ms,Error Budget 消耗速率驱动告警。
⚠️ 常见误区
详情
常见误区:
- ❌ "可观测性就是打日志 + 看监控" → 三支柱各有分工,必须协同:Metrics 发现异常、Traces 定位瓶颈、Logs 分析根因。
- ❌ "traceId 透传太麻烦不想做" → 没有 traceId 关联,三支柱是割裂的数据,定位问题效率低 10 倍。
- ❌ "告警越多越好" → 过多告警导致告警疲劳,真正的问题被淹没。应基于 SLO Error Budget 告警,减少无效告警。
🔀 发散问题
Q:如何评估可观测性体系的 ROI?
→ 对比 MTTR(平均修复时间)变化:有可观测性体系前后,故障定位时间从小时级降到分钟级。
【中等】如何设计一个自动化容量规划与弹性伸缩系统?⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:15 min | 🏷 标签:容量规划 / 弹性伸缩 / 压测 / 预测
💎 关键结论
容量规划的核心是"压测校准 + 利特尔法则推算 + 增长预测":通过压测获取单实例承载能力,用利特尔法则(并发数 = QPS × RT)推算集群规模,结合业务增长趋势预测未来容量。弹性伸缩基于实时监控指标自动扩缩容,设置多级水位线(预警 → 扩容 → 限流 → 降级)。
⚡ 记忆卡片
- 口诀:压测校准、法则推算、预测规划、水位伸缩
- 关键词:压测 / 利特尔法则 / 增长预测 / HPA / 水位线
- 链路:压测 → 单实例容量 → 利特尔法则推算集群规模 → 增长预测 → 弹性伸缩
📖 核心知识
1. 压测校准
- 逐步加压找到拐点(RT 开始急剧上升的点)。
- 记录拐点处的 QPS、CPU、内存、GC 指标。
- 单实例容量 = 拐点 QPS × 安全系数(0.7)。
2. 利特尔法则推算
并发数 = QPS × 平均 RT(秒)
实例数 = 总 QPS / 单实例 QPS例:目标 10 万 QPS,单实例承载 2000 QPS,需 50 实例。
3. 增长预测
- 从产品 / 运营获取增长目标(如 DAU 年增长 50%)。
- 历史数据拟合增长曲线(线性 / 指数 / 季节性)。
- 预测未来 3~6 个月的容量需求。
4. 弹性伸缩策略
| 水位 | 动作 | 触发条件 |
|---|---|---|
| 60% | 预警 | 通知运维关注 |
| 70% | 扩容 | HPA 自动扩容(增加实例) |
| 85% | 限流 | 入口限流保护系统 |
| 95% | 降级 | 非核心功能降级 + 紧急扩容 |
5. 缩容策略
- 低水位持续 N 分钟后缩容(避免抖动)。
- 缩容步长小于扩容步长(保守缩容)。
- 保留最小实例数(保证可用性)。
🔬 扩展知识
详情
- 【L3】弹性伸缩的延迟问题:扩容需要时间(启动 Pod + 预热缓存 + 建立连接),突发流量可能来不及扩容。解决:预留缓冲实例 + 预热机制。
- 【L3】HPA 指标选型与抖动控制:基于 CPU 的 HPA 对 IO 密集 / JVM 服务常常滞后(GC 压力、线程池饱和时 CPU 未必高),更贴近负载的做法是用自定义指标(QPS、RT、队列长度)驱动;扩缩容要设稳定窗口(如缩容前观察 5 分钟)并控制步长,避免指标抖动引发扩缩容震荡。
- 【L4】容量规划三连问:"这个系统能扛多少 QPS?你怎么知道的?扩一倍要改什么?"——P8 面试的标准考法。三个答案分别是:容量模型(单机拐点容量 × 实例数 × 冗余系数,且明确瓶颈假设)、校准手段(压测 + 线上流量回放沙盒,参见本文档全链路压测一题)、扩容路径(识别先到的瓶颈点:连接池 / 线程池 / 分库分表 / 缓存命中率,水平加机器解决不了的瓶颈要提前架构改造)。
- 【L4】成本优化:弹性伸缩 + 竞价实例(Spot Instance)组合,非核心服务用竞价实例降低成本。
⚠️ 常见误区
详情
常见误区:
- ❌ "容量规划就是加机器" → 先优化代码和架构(减少资源消耗),再水平扩展(加机器)。
- ❌ "弹性伸缩可以应对一切" → 有状态服务(如 DB)弹性伸缩困难,需要提前做分片。
- ❌ "CPU 利用率低就是资源浪费" → 可能是 IO 密集型服务,需综合 CPU + IO + 内存多指标评估。
🔀 发散问题
Q:如何评估弹性伸缩的效果?
→ 对比扩缩容前后的资源利用率、成本、RT 变化。
【困难】如何设计一个灰度发布与全链路流量路由系统?⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时:20 min | 🏷 标签:灰度发布 / 流量路由 / 版本兼容 / 回滚
💎 关键结论
灰度发布的核心是"流量标记 + 版本路由 + 兼容性保证 + 监控对比 + 一键回滚":灰度流量在入口打标,全链路透传标记,每个服务根据标记路由到对应版本。关键是版本兼容性(新旧版本数据格式兼容)和监控对比(灰度 vs 正式的核心指标对比),异常时一键回滚。
⚡ 记忆卡片
- 口诀:入口打标、全链路透传、版本路由、监控对比、一键回滚
- 关键词:灰度发布 / 流量标记 / 版本路由 / 兼容性 / 监控对比 / 回滚
- 链路:灰度入口 → 流量标记 → 全链路透传 → 各服务版本路由 → 监控对比 → 异常回滚
📖 核心知识
1. 流量标记与透传
- 标记维度:按 user_id、地域、设备类型、请求比例。
- 标记传递:HTTP Header
X-Canary: v2,在 RPC Context / MQ Header / ThreadLocal 中全链路透传。
2. 版本路由
请求 → 网关(检查灰度标记)
→ 路由规则匹配
→ 灰度流量 → v2 服务实例
→ 正式流量 → v1 服务实例- 路由规则存配置中心,支持动态调整灰度比例。
- 多版本并行:v1 / v2 / v3 可同时运行,按比例分配流量。
3. 版本兼容性
- API 兼容:新版本必须兼容旧版本 API(向后兼容),新增字段用可选参数。
- 数据兼容:DB Schema 变更采用"扩展-迁移-收缩"模式(先加字段 → 双写迁移 → 删旧字段)。
- 消息兼容:消息格式变更用版本号标识,消费者兼容新旧格式。
4. 灰度可观测性
- 灰度 vs 正式的指标对比:成功率、RT、错误率。
- 灰度用户链路追踪:查看灰度流量在各服务的处理情况。
- 自动回滚:灰度版本错误率 > 阈值 → 自动切回正式版本。
5. 回滚策略
- 一键回滚:将灰度流量全部切回正式版本。
- 数据回滚:如果灰度版本写入了不兼容数据,需要数据修复脚本。
- 回滚后验证:确认正式版本恢复正常。
🔬 扩展知识
详情
- 【L3】特性开关(Feature Flag):灰度发布的一种轻量实现,通过配置中心控制功能开关,不需要部署多版本实例。
- 【L3】变更三板斧(可灰度、可监控、可回滚):灰度发布只是"可灰度"的落点,业界普遍把三板斧作为生产变更的强制检查项——任何变更上线前必须回答"怎么灰度、看哪些指标判断好坏、出问题几分钟内怎么回滚",三问答不全的变更不允许执行。
- 【L4】灰度发布的组织协同:跨团队服务的灰度需要各团队协同发布,通过特性开关控制功能可见性,避免版本间不兼容。
- 【L4】全链路灰度的兜底路由:多版本共存依赖灰度标记全链路透传,任何一跳丢标(异步 MQ 消费、定时任务、第三方回调这些没有上游 Header 的入口是重灾区)都会让灰度流量回落到基线版本。必须定义"缺标时的默认路由规则"(通常回落基线)并监控丢标率,否则灰度组混入基线流量,A/B 对比结论会被污染,自动回滚的判断依据也随之失真。
🏭 实战场景
详情
某电商平台订单服务灰度发布:v2 版本重构了订单状态机。
设计要点:灰度 5% 用户(按 user_id 尾号),灰度流量路由到 v2 实例;DB Schema 兼容(v2 新增字段 cancel_reason,v1 不读该字段);监控对比 v1 vs v2 的成功率和 RT,v2 错误率 > 0.1% 自动回滚;灰度 3 天后全量切换。
⚠️ 常见误区
详情
常见误区:
- ❌ "灰度发布就是切 1% 流量就行" → 还需要数据兼容、监控对比、回滚机制,否则灰度出问题比全量发布更危险。
- ❌ "灰度时间越短越好" → 灰度需要覆盖完整业务周期(至少 1 天),否则可能遗漏低频场景的问题。
- ❌ "数据库变更不需要灰度" → DB Schema 不兼容会导致灰度版本读写报错,必须向后兼容变更。
🔀 发散问题
Q:灰度发布中如何处理长连接服务(如 WebSocket)?
→ 灰度切换时逐步断开旧连接,新连接路由到新版本,旧连接自然过期后全量切换。