Skip to content

第六章 经典系统设计(第123-162题) ​

123. 短信营销链接太长被截断,要生成短链接(短链系统) ​

【考察内容】短链是系统设计入门必考题

【题目】运营要在短信里放推广链接,但原始 URL 太长(带一堆参数),短信会被截断。需要做短链服务:长 URL 生成短码、访问时跳转还原。要求:短码不能重复、支持高并发跳转(读多写少)、要能统计点击。怎么设计?

【参考答案】

  1. 需求分析:读(跳转)远高于写(生成),要求低延迟(毫秒级)、高可用、防冲突;
  2. 短码生成方案:
    • 哈希截断:MD5/MurmurHash 长链取前 6~8 位(62 进制:大小写字母+数字),冲突检测+追加/重哈希;简单但需查重;
    • 发号器(推荐):分布式 ID(号段/雪花)转 62 进制得到短码——唯一、无冲突、趋势递增;
    • 自定义短码(运营指定):数据库唯一索引兜底;
  3. 存储:MySQL 表(short_code 唯一索引、long_url、过期时间、创建时间);读写比例高——Redis 缓存短码→长链映射(TTL 设长);
  4. 跳转流程:用户访问短链 → 网关/服务查 Redis → 命中 302 跳转;未命中查 DB → 回填缓存 → 302 跳转;
  5. 扩展:过期清理(惰性删除+定时任务)、访问统计(异步记录)、防恶意生成(限流+黑白名单)、长链去重(同一长链复用短码);
  6. 容量:短码 6 位=568 亿组合,足够;单表千万级可分表(按短码哈希)——但去重会跨片失效:long_url_hash 的唯一索引只在单个分片内生效,主表按短码哈希后,同一条长链的两次请求会落到不同分片各生成一个短码,第 3 条「生成前先查」还要广播全部 64 张表。正解是另建一张以 long_url_hash 为分片键的映射表(或独立去重表/Redis Set)专门承担去重,主表才按短码哈希分片;
  7. 容量估算(步进):假设日点击 5000 万(营销短信),峰值系数 10,读峰值 ≈ 5000万/86400×10 ≈ 6000 QPS;写(生成)日 50 万条、峰值约 50 QPS,读写约 100:1。存储:5 亿条映射 × 200B ≈ 100GB(按同题“单表千万级”口径,5 亿÷1e7 ≈ 50 张表,实践取 64 张;只分 8 表会让单表到 6000 万行);Redis 热映射按 20% 热点 ≈ 1 亿个 key,只按 value 算是 1亿×200B ≈ 20GB;但 Redis 每个 key 还有 SDS 头、dictEntry、过期字典项等约 60B 量级开销,实占要按 26~35GB 起估,需按热点比例分片或单实例扛。带宽:每次跳转响应极小(302+Location),6000 QPS × 1KB ≈ 6MB/s,网关层无压力;
  8. 失败与降级:Redis 故障时读 DB(限流+本地缓存兜底);发号器不可用则该实例短时只读,或切备用号段;统计 MQ 挂了不影响 302 主路径(统计异步可丢);恶意刷生成触发限流/黑名单;过期短链返回 410 或引导页,不静默跳到错误目标。

【原理溯源】

  • 为什么推荐发号器而不是哈希截断? 哈希是“输入空间→输出空间”的压缩映射,生日悖论决定冲突概率随样本数平方上升——6 位 62 进制约 568 亿空间,生成几亿条时冲突已不可忽略。发号器(号段/雪花)本质是在单调递增整数上做进制转换,每个号只出现一次,冲突在生成端就被消除了,不需要“查重→重试”回路。代价是号段服务要高可用,但这比“全表查重”便宜得多。
  • 为什么短码读路径必须打 Redis 缓存? 跳转是超高频读(短信点击瞬间涌入),而映射关系一旦生成几乎不变(读多写少、读远大于写)。MySQL 单点查询毫秒级尚可,但热点短链会形成单行热点;Redis 单机 10 万+ QPS 且 O(1) 查找,把 99% 流量挡在 DB 之外。缓存 TTL 要长(甚至永不过期+惰性删除),因为映射关系几乎不变更。
  • 为什么用 302 而不是 301? 301 是永久重定向,浏览器/中间层会缓存,统计点击会丢(后续请求不再打到短链服务);302 是临时重定向,每次访问都回到服务端,才能记 UV/PV、做 A/B 跳转、随时改目标 URL。若某条短链要永久固定且无需统计,才考虑 301。
  • 为什么同一长链要去重复用短码? 不去重会让同一条营销链接生成成千上万个短码,表膨胀、缓存击穿风险上升、统计口径被拆散。去重后同一目标共享一个短码,点击可聚合统计。实现:长链做哈希索引(long_url_hash 唯一),生成前先查,命中则直接返回已有短码。
  • 为什么要过期清理? 营销短链有生命周期(活动结束),不过期会让表无限膨胀、陈旧链接指向失效活动。清理用“惰性删除(访问时发现过期即删)+ 定时扫描”,避免大事务批量删。

【选型判断树】

短码怎么生成?
├─ 能接受“查重+重试”吗?
│   ├─ 不能(高并发生成、强唯一)→ 发号器:号段/雪花 → 62 进制(推荐)
│   └─ 能(低 QPS 生成)→ 哈希截断 6~8 位 + 冲突检测重哈希
└─ 运营要自定义短码?
    └─ 是 → 用户输入 + 唯一索引兜底(占用冲突则提示换)

读路径:
短链访问 → 查 Redis → 命中 302
                └─ 未命中 → 查 MySQL → 回填缓存 → 302
统计:异步 MQ 记 click 日志,不阻塞跳转

判断口诀: 生成端用发号器保唯一,读端用缓存扛量,统计走异步不拖跳转。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“短链是读多写少的映射服务,核心是短码唯一性 + 跳转低延迟 + 点击统计”
0:30–1:30短码生成对比哈希截断(有冲突需检测)与发号器转 62 进制(天然无冲突,推荐)
1:30–2:30存储与读路径MySQL 存映射;Redis 缓存扛读;访问 → Redis → 命中 302,未命中回填
2:30–3:30统计与扩展异步记 click;长链去重;过期惰性+定时清理;防刷限流
3:30–4:30容量与细节6 位 62 进制 568 亿组合;主表按短码哈希分表、去重另建 long_url_hash 分片表;302 而非 301(保统计)
4:30–5:00收尾“一句话:发号器保唯一,缓存扛读,异步统计,302 保链路可运营”

【关键数字】

参数经验值说明
短码长度6~8 位(62 进制)6 位 ≈ 568 亿,够用
跳转 RT< 10ms(缓存命中)短信点击场景用户无感
读写比读 : 写 ≈ 100 : 1 以上决定缓存优先架构
缓存 TTL长 TTL 或永不过期+惰性删映射几乎不变
302 vs 301统计场景必须 302301 会被浏览器缓存导致丢统计
单表容量千万级考虑分表主表按 short_code 哈希;去重表要按 long_url_hash 分片,否则唯一索引跨片失效
点击统计异步 MQ,允许秒级延迟不阻塞跳转主路径
读峰值估算日点击×峰值系数/86400例:5000万日点击→约6000QPS
写峰值日生成量/日秒数×峰值日50万生成→约50QPS
映射存储条数×单行约200B5亿条约100GB,可分表

【追问链】(三层)

L1|“短码撞了怎么办?” → 发号器方案天然不撞:每个整数只转出一个 62 进制串。哈希方案则必须查库/查缓存,撞了就重哈希或在尾部追加盐再哈希,直到唯一;生产上用唯一索引做最后兜底,插入冲突则重试。

L2|“同一个长链接生成一百万次短码,表会不会爆?” → 会,所以要做长链去重:对 long_url 算哈希并建唯一索引,生成前先查,命中直接复用已有短码。但主表按短码哈希分片后,这个唯一索引只在单个分片内生效——同一条长链的两次请求会落到不同分片各生成一码,「生成前先查」还要广播 64 张表;所以去重要靠一张以 long_url_hash 为分片键的映射表(或独立去重表/Redis Set),主表才按短码哈希分片。这样同一营销链接只占一行,统计也能聚合成“这条活动链接总点击”。

L3|“热点短链把 Redis 打爆了怎么办?” → 三层:① 本地缓存(进程内 Caffeine)挡最热的几条映射;② Redis 多副本/读写分离;③ 极端时网关直接返回 302(映射可预热进网关配置)。本质是“不可变映射 + 极高读”,本地缓存几乎零代价。

【评分标准】

档位答案特征
60 分能说出“生成短码 + 存映射 + 跳转”,知道用 Redis 缓存
80 分对比发号器 vs 哈希截断;讲清读写分离与 302 选择;提到去重与过期清理
95 分能解释生日悖论下哈希冲突必然性;301/302 对统计的影响;热点本地缓存;分表策略;防刷限流

【关联题】

  • 同簇: 第 121 题(分布式 ID)——发号器是短码生成的上游依赖
  • 同构: 第 162 题(计数系统)——同样是“读多写少 + 缓存扛读”
  • 下游延伸: 第 137 题(爬虫 URL 去重)——海量 URL 去重的另一解(布隆)

【自测】

  1. 判断对错:短链跳转用 301 更好,因为永久重定向更快。 参考答案: 错。301 会被客户端缓存,后续访问不再打到服务端,点击统计会丢;需要统计的场景必须 302。
  2. 6 位 62 进制短码大约有多少组合?哈希生成时要注意什么? 参考答案: 62⁶ ≈ 568 亿。哈希压缩有冲突概率,必须做冲突检测与重试,或改用发号器。
  3. 为什么短链映射缓存 TTL 可以很长? 参考答案: 映射一旦生成几乎不变(读多写少),变更极少;长 TTL 可最大化命中率,过期用惰性删除+定时任务兜底。

124. 视频点赞量级千万,点赞/取消/计数怎么做(点赞系统) ​

【考察内容】点赞系统是高并发设计最高频题

【题目】视频 App 要做点赞功能:用户点赞/取消、展示总点赞数、判断我是否点过赞、查看点赞用户列表。单条视频可能被点几百万赞,热点视频点赞瞬时并发高。存储与计数怎么设计?

【参考答案】

  1. 需求拆解:①用户点赞/取消(写);②作品点赞总数(高并发读);③某用户是否点赞过(查状态);④用户点赞列表;
  2. 存储设计:
    • Redis:作品点赞数用 String INCR/DECR(或 Hash 字段);“谁点过赞”用 Set(sadd/srem,支持交集计算);用户维度点赞列表用 ZSet(score=时间,可排序);
    • DB 落库:点赞明细表(user_id, target_id, create_time,唯一索引 user_id+target_id 防重复点赞);点赞计数表(target_id, count);
  3. 一致性:点赞先写 Redis(实时反映)→ 异步落库(MQ 消费,批量合并更新计数,DB 为准)→ 对账兜底(Redis 计数与 DB 计数核对);
  4. 热点处理:大 V 作品点赞量巨大——计数拆分(多个计数 key 分片,读时求和);状态查询走缓存(Set)避免 DB 压力;但题干预设「单条视频几百万赞」,作品维度的 Set/ZSet 本身就是 5e6 成员级大 Key(整数集编码也要 ~40MB,字符串成员按 ~68B/元素 ≈0.34GB),SMEMBERS/DEL/主从同步都会阻塞——状态层必须同样治理:只保留近 N 天、或按判定维度分片成 liked:{vid}:{uid%K}(保证「同一用户+同一视频」恒落一片),全量「谁点过赞」以 DB 明细表为准,Redis 只作热态加速;
  5. 防重复/防刷:唯一索引防重复点赞;频控限流(同用户短时间内重复操作合并);
  6. 扩展:评论/转发/浏览计数同架构复用(通用计数服务);
  7. 容量估算(步进):假设 DAU 1000 万,约 20% 产生点赞动作、人均 10 次/日 → 日点赞事件约 2000 万;高峰 4 小时占 70%(2000万×0.7÷14400 ≈ 970/s 的时段均值),再乘瞬时系数 5~10 → 写峰值约 5000~1 万 QPS,Redis INCR+Set 可扛,分片后再乘 8~32 倍余量。存储:2000 万/日 × 明细 50B ≈ 1GB/日,一年约 300~400GB(明细可按月冷热分层)。“我是否赞过”列表页:一页 20 视频 = 20 次 SISMEMBER,可 pipeline 合并为 1 次 RTT。读总数接口通常被 CDN/本地缓存挡一层,回源 QPS 远低于展示 QPS;
  8. 失败与降级:Redis 计数/状态不可用时降级读 DB 计数表(限流),写侧本地缓冲重试;MQ 持续积压则暂停非核心计数、优先保点赞明细;对账发现 Redis 与 DB 偏差超阈值时以明细重算为准并告警;展示允许秒级不一致,结算/风控场景走权威路径。

【原理溯源】

  • 为什么点赞写路径必须走 Redis 而不是直接写 DB? 热点视频瞬时点赞可达数万 QPS,MySQL 单行 UPDATE count=count+1 会形成行锁热点,RT 飙升甚至拖垮实例。Redis INCR 是单线程内存原子操作,10 万+ QPS 无压力。DB 只承担“最终落账”,用 MQ 削峰后批量合并写入,把同步热点变成异步平滑流量。
  • 为什么要“Redis 实时 + DB 最终”的双层结构? 用户点赞后要立刻看到“已赞”和数字变化(体验强需求),但点赞明细是审计与对账的真源(业务强需求)。两者 SLA 不同:展示允许秒级延迟,账目必须不丢不错。双层结构用“实时层扛体验、权威层保正确”,中间靠 MQ + 幂等消费 + 对账收敛。
  • 热点计数为什么要分片? 单个 count:{videoId} key 所有写打到同一 Redis 节点的同一 slot,单线程下形成串行瓶颈。拆成 count:{videoId}:0~N,写入时随机/按用户哈希选分片,读时 SUM。分片把单点串行变成多 key 并行,代价是读要聚合——所以只对超热 key 分片,普通 key 不拆。
  • 为什么“谁点过赞”用 Set 而不是每次查 DB? 判断“我是否赞过”是列表页批量操作(一页 20 条视频都要标红心),查 DB 会放大 20 倍查询。Set 的 SISMEMBER O(1),且天然支持“共同好友点赞”这类交集运算。DB 侧仍保留唯一索引作为最终防线。
  • 取消点赞怎么保证不把计数减成负数? 先 SISMEMBER 判断是否已赞,再 SREM+DECR;DB 侧用“明细存在才删+计数减”的条件更新。更稳的做法是把“赞/取消”都当成事件,消费端按明细对账重算计数,而不是盲目 INCR/DECR。

【选型判断树】

点赞功能怎么拆?
├─ 写(赞/取消)
│   → Redis:SADD/SREM 状态 + INCR/DECR 计数(实时)
│   → MQ 异步 → DB 明细+计数(唯一索引兜底)
├─ 读总数
│   ├─ 普通视频 → 单 Redis key 直接 GET
│   └─ 热点视频 → 计数分片 0~N,读时 SUM;**状态 Set 也要治理**(几百万成员=大 Key,只留近 N 天或按 liked:{vid}:{uid%K} 分片,全量以 DB 明细为准)
├─ 读“我是否赞过”
│   → Redis Set SISMEMBER(列表页批量;**单条几百万赞时该 Set 本身是大 Key,要分片或只留热态**)
└─ 读“谁赞的”
    → ZSet(score=时间)分页;超大列表走 DB 游标

判断口诀: 状态用 Set、计数用 String/Hash、列表用 ZSet;写走 Redis,账走 DB,中间 MQ + 对账。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“点赞是高并发读写 + 最终一致计数题,核心是 Redis 数据结构选型与异步落库”
0:30–1:30需求拆解写赞/取消、读总数、读是否赞过、读点赞列表——四类访问模式不同
1:30–2:30存储设计Set 存状态、String 存计数、ZSet 存用户列表;DB 明细+计数表
2:30–3:30一致性Redis 实时 → MQ 异步落库 → 对账兜底;唯一索引防重
3:30–4:30热点治理计数分片抗单 key;状态层也是待治理对象(百万成员 Set 分片/只留近 N 天,全量走 DB);频控合并
4:30–5:00收尾“一句话:结构按访问模式选,写实时账最终,热点靠分片”

【关键数字】

参数经验值说明
Redis 单 key INCR10 万+ QPS热点仍需分片
热点分片数8~32按实际热点程度调
MQ 落库批量数百~数千条/批降低 DB 写压力
对账周期分钟级~小时级差异告警+自动修正
列表缓存分页每页 10~20按业务 UI 定
唯一索引(user_id, target_id)防重复点赞的最后防线
日点赞事件DAU×点赞率×人均次数例:1000万×20%×10≈2000万/日
写峰值日事件按高峰小时+瞬时系数折算约 0.5~1 万 QPS(970/s × 5~10)
明细存储增速日事件×单行约50B约1GB/日,可按月归档

【追问链】(三层)

L1|“取消点赞怎么处理?” → Redis:SREM 移除状态 + DECR 计数(先判断是否已赞);DB:异步删明细 + 计数减一。消费端幂等,用明细状态保证不多减不少减。

L2|“Redis 和 DB 计数不一致怎么办?” → 定时对账:从点赞明细重算 count,与 Redis/DB 计数比对,差异则修正并告警。对账是最终一致的最后防线,不能省。

L3|“大 V 视频点赞 key 成了单点热点,分片后读变慢了怎么办?” → 分片只对超热 key 做;读侧用“本地缓存 1~5 秒的汇总值”挡展示路径;真正需要精确值的场景(运营后台)再实时 SUM。展示允许秒级延迟,这是产品可接受的取舍。

【评分标准】

档位答案特征
60 分知道用 Redis 计数 + DB 落库
80 分能按访问模式选 Set/String/ZSet;讲清异步落库与唯一索引
95 分热点分片、对账闭环、取消点赞的状态机、防刷频控、通用计数服务抽象

【关联题】

  • 同簇: 第 162 题(计数系统)——点赞是计数的业务版
  • 上游: 第 13 题(热 Key)、第 30 题(大 Key)
  • 同构: 第 126 题(关注粉丝)——同样用 Set 做关系+计数
  • 综合题: 第 152 题(社区系统)——点赞是其中子模块

【自测】

  1. 判断对错:点赞数可以直接 UPDATE like_count = like_count + 1 写 MySQL,热点也没问题。 参考答案: 错。热点行锁会成为瓶颈,必须 Redis 扛写、DB 异步落账。
  2. “谁点过赞”用什么结构?为什么不用每次查 DB? 参考答案: 中小体量用 Redis Set(SISMEMBER O(1)),列表页批量判断查 DB 会放大 N 倍压力;但题干预设单条几百万赞时,作品维 Set 本身就是 5e6 成员的大 Key(SMEMBERS/DEL/主从同步都会阻塞),要按 liked:{vid}:{uid%K} 分片或只保留近 N 天热态,全量「谁点过赞」以 DB 明细表为准。
  3. 热点计数为什么要拆成多个 key? 参考答案: 避免单 key/单 slot 串行瓶颈;写随机分片、读 SUM,用聚合换并行。

125. 评论区一级/二级评论、热评、分页,怎么支撑(评论系统) ​

【考察内容】内容社区核心模块设计

【题目】内容平台要上线评论功能:支持一级/二级评论(楼中楼)、评论分页(按时间/热度)、热门评论置顶、评论计数。日活千万,热点内容评论区瞬时流量大。表结构、分页方案、热评排序怎么设计?

【参考答案】

  1. 需求:发表评论、评论列表分页(按时间/热度)、楼中楼(二级评论)、评论数统计、删除/审核;
  2. 存储:
    • MySQL:评论表(comment_id、parent_id、root_id、target_id、user_id、content、like_count、status、create_time),索引(target_id+create_time)查列表、(root_id)查楼中楼;
    • 热评:评论表加 like_count 排序字段或单独热评缓存;
  3. 列表读取:评论列表先查一级评论(分页),再批量查二级评论(按 root_id IN 查询,组装树)——避免 N+1;或用冗余字段(一级评论的二级评论缓存成 JSON 片段);
  4. 缓存:热帖评论列表缓存 Redis(分页缓存/整页缓存+失效);评论计数缓存(INCR);
  5. 写路径:评论先写 DB(事务),再更新缓存/计数(异步);审核状态(先审后发或先发后审);
  6. 高并发(热帖):评论写入用 MQ 削峰;列表读走缓存;评论按页拆分 key(comments:postId:page),避免单 key 过大;
  7. 扩展:敏感词过滤(写前校验)、排序策略(热度=点赞+时间衰减)、楼中楼深度限制(如最多两级);
  8. 容量估算(步进):假设 DAU 1000 万,约 5% 发评论、人均 2 条 → 日评论约 100 万;热帖可占全日 30% 流量(≈30 万条);全天均值只有 ≈11.6 条/s,爆款窗口把 30 万压在 2–3 分钟内才是 数千 QPS(MQ 削峰后 DB 按批消费数百 TPS 即可)。存储:单帖评论上限产品约束(如 1 万条),全量日增 100 万 × 300B ≈ 300MB/日。读:热帖列表 QPS 可达数万,必须整页/分页缓存,DB 只兜底冷帖。接口示例:GET /comments?target_id=&tab=hot|new&cursor=、GET /comments/{root_id}/replies?cursor=、POST /comments;
  9. 失败与降级:Redis 故障时列表降级直查 DB(限流);MQ 积压则评论“提交成功、稍后展示”,展示侧用本地缓存旧页;审核服务超时可降级为先发后审+异步复审;计数与列表短暂不一致以明细表为准,定时对账。

【原理溯源】

  • 为什么要冗余 root_id 而不是递归 parent_id? 纯 parent_id 链要递归向上找根,深度不确定时查询次数不可控,且无法“按根批量取子树”。冗余 root_id 后,一次 WHERE root_id IN (...) 就能取出所有楼中楼,把树查询变成两次平铺查询(一级分页 + 二级批量),复杂度从“每层一次”变成“固定两次”。
  • 为什么楼中楼深度要限制(通常两级)? 无限深度会带来三个问题:① 展示层无法分页折叠;② 查询必须递归或多次往返;③ 存储与索引难以优化。微信/知乎/抖音评论区都只做两级(一级评论 + 其回复),更深层回复扁平化到二级并 @用户,用产品约束换工程简单。
  • 为什么热帖评论要按页拆缓存 key? 单个 comments:postId 存全量列表会变成大 Key(百万评论的帖子),内存与网络传输都扛不住,且任何一条新评论都要失效整键。按 comments:postId:page 拆分后,新评论只失效相关页,热读页可独立缓存,大 Key 风险消失。
  • 为什么热评和最新评论要两套视图? 排序维度冲突:热度是全局相对量(随时变),时间是单调序。混在一个索引里要么放弃实时热度,要么放弃高效时间分页。工程上拆成“热评缓存(ZSet/物化列表,定期刷新)+ 最新评论索引(target_id+create_time)”,各走各的最优路径。
  • 为什么要 MQ 削峰写入? 明星发帖后评论区秒级涌入数千写请求,直接打 DB 会打爆连接池并拖垮读路径。MQ 把写解耦成“接收极快、消费按节奏”,配合敏感词过滤在消费端做,用户侧可先返回“提交成功、稍后展示”。

【选型判断树】

评论列表怎么读?
├─ 普通帖
│   → MySQL 索引分页(target_id + create_time / like_count)
│   → 可加短 TTL 缓存
└─ 热帖
    → Redis 整页/分页缓存
    → 按 page 拆 key,避免大 Key
    → 写走 MQ 削峰

楼中楼怎么查?
├─ 先查一级(分页)
└─ 再 root_id IN 批量查二级(禁止 N+1 逐条查)

排序:
├─ 最新 → create_time 索引
└─ 热评 → like_count + 时间衰减,单独缓存视图

判断口诀: 一级分页、二级批量、热帖缓存、写削峰、深度限两级。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“评论系统是树形内容+双排序+热帖高并发的综合题”
0:30–1:30表结构parent_id + root_id 双字段;索引 target_id+time / root_id
1:30–2:30读路径一级分页 → 二级批量组装;防 N+1;热帖分页缓存
2:30–3:30写路径先审后发/先发后审;MQ 削峰;计数 INCR 异步
3:30–4:30热评与治理热度=点赞+时间衰减;敏感词;深度限两级
4:30–5:00收尾“一句话:冗余 root 扁平化树,热帖缓存按页拆,双视图解决排序冲突”

【关键数字】

参数经验值说明
楼中楼深度2 级主流产品均限深
一级分页10~20 条/页按 UI
二级预取每个一级带 3~5 条回复其余点开再拉
热帖缓存 TTL10s~1min允许短暂旧数据
热度公式点赞×w1 + 评论×w2,再时间衰减score/(1+age^1.2) 一类
写削峰MQ,消费批量热点帖必须
敏感词写前同步过滤 + 异步复审合规底线
日评论量估算DAU×发帖评论率×人均例:1000万×5%×2=100万/日
热帖读峰值首页强曝光×刷新次数数万QPS,必须缓存

【追问链】(三层)

L1|“为什么不用递归查所有子评论?” → 深度不可控、每层一次往返,热点帖会拖垮 DB。冗余 root_id 后固定两次查询(一级+二级批量),性能可预期。

L2|“热评和最新评论怎么共存?” → 两套视图:最新走 target_id+create_time 索引;热评走缓存的排序列表(定期按热度重算)。前端 Tab 切换,互不影响。

L3|“帖子被删了,缓存和热榜里的评论怎么办?” → 删除事件走 MQ:失效评论缓存页、从热榜 ZSet 移除、ES 标记删除。或读时校验帖子状态兜底。事件驱动清理 + 状态过滤双保险。

【评分标准】

档位答案特征
60 分能说出评论表 + 分页 + 点赞数
80 分root_id 扁平化、批量防 N+1、热帖缓存、写削峰
95 分双排序视图、按页拆 key 防大 Key、审核策略、深度限制原因、删除事件联动

【关联题】

  • 上游: 第 124 题(点赞)——评论点赞复用计数架构
  • 综合: 第 152 题(社区系统)——评论是子模块
  • 同构: 第 128 题(Feed)——列表分页与缓存策略相似

【自测】

  1. 判断对错:二级评论用 parent_id 递归查询即可,不需要 root_id。 参考答案: 错。递归深度不可控且无法批量取子树;冗余 root_id 才能固定两次平铺查询。
  2. 热帖评论缓存为什么要按页拆 key? 参考答案: 避免单 key 过大(大 Key)和整键失效;新评论只失效相关页。
  3. 热度排序为什么不能只用 like_count? 参考答案: 旧评论会永久霸榜;必须叠加时间衰减,让新内容有机会上来。

126. 关注列表/粉丝列表/互相关注判断,关系链怎么做(关注粉丝系统) ​

【考察内容】关系链数据建模

【题目】社交 App 需要关注关系:用户关注/取关别人、查看我的关注列表/粉丝列表、判断 A 和 B 是否互相关注。大 V 粉丝千万级。存储模型(关注关系怎么存)、分页、互相关注判断怎么设计?

【参考答案】

  1. 数据结构:两套 Set——关注表(user:{uid}:following 存关注的人)、粉丝表(user:{uid}:follower 存粉丝);Redis Set 天然支持交集/并集(共同关注、互相关注);
  2. 操作:关注=A 的 following 加 B + B 的 follower 加 A(两步,可放 Lua/事务);取关反向;判断 A、B 是否互相关注:SINTER user:A:following user:A:follower 后看 B 在不在结果里,等价于两次 SISMEMBER(B∈A.following 且 A∈B.following);
  3. 存储分层:Redis Set 承担高频读写(关注列表、粉丝数 SADD/SCARD);DB 落库关注关系表(user_id, follow_id, create_time,唯一索引,分表按 user_id);
  4. 粉丝数:Redis 计数(SCARD/单独计数 key)+ 异步落库(对账);
  5. 热点:大 V 粉丝千万级——Set 元素多是大 key:粉丝列表拆 hash 分片(follower:{uid}:{0~N})或只存“粉丝数+精选列表”,全量粉丝走 DB;
  6. 扩展:关注 Feed 联动(发帖推送给粉丝——见 Feed 流题);僵尸粉清洗、批量关注限制(频控);
  7. 容量估算(步进):假设 DAU 500 万,人均关注/取关 5 次/日 → 日关系写约 2500 万次(双写 Set 则 ×2);峰值约 2000~3000 QPS 关系写。存储:关注明细 10 亿行 × 40B ≈ 40GB;分表数按本库统一口径「单表千万级评估、5000 万是评估节点而非魔数」——1e9÷1e7=100 为下限,取 2 的幂就是 128 张(每表约 781 万行)或 256 张(391 万行),与第 123 题「5 亿行分 50~64 张」同一算法;只分 16~64 张则每表 1562 万~6250 万行,16 张就已经越线(每表 6250 万),64 张每表 1562 万虽在线内但仍属「千万级评估」的粗档,只有读多写少且有大段冷数据可归档时才勉强可接受——分片数后期难改,宁多勿少;普通用户 Set 元素少,大 V 不进全量 Set。读:列表页“是否互关”批量 SINTER/SISMEMBER,一页 20 人可 pipeline;粉丝数展示被前端短缓存,回源可控。
  8. 失败与降级:双写一半失败时以 DB 明细为准,后台对账重建 Set;Redis 大 V 粉丝 key 预警时立刻降级为“计数+最近 N 个粉丝”,全量列表走 DB 游标;关注接口超时返回明确错误,禁止静默半成功;Feed 推送失败与关注关系解耦,漏推靠 Feed 对账补偿。

【原理溯源】

  • 为什么要同时维护 following 和 follower 两套结构? 只存单向会导致另一侧查询退化成全表扫描:只有 following 时查“我的粉丝”要 SELECT * WHERE follow_id=me,大 V 会扫出千万行;只有 follower 时查“我关注了谁”同样惨。双写冗余把双向查询都变成 O(1) 集合访问,代价是写路径要维护两份一致——用 Lua/本地事务保证同一次关注的原子性。
  • 为什么互相关注用集合运算而不是查 DB? SINTER following:A follower:A(取 A 自己两个集合的交集)是 Redis 原生集合操作,毫秒级返回;更省的是两次 SISMEMBER。注意不能取 A.following ∩ B.following——那是「共同关注」,只说明两人关注了同样的人,判不出互关。若走 DB,要么多次查询要么 JOIN,在社交图谱的高频“是否互关”判断(列表页每条都要标)场景下完全不可接受。
  • 为什么大 V 粉丝 Set 必须治理? 千万级元素的 Set 是典型大 Key:① 内存单点占用过高;② SMEMBERS/全量传输会打爆网卡;③ 主从同步阻塞。治理手段:分片存储(按粉丝 uid 哈希到 N 个 key)、只在 Redis 存“计数 + 前 N 个精选/最近粉丝”,全量列表走 DB 游标分页。大 V 的粉丝列表几乎从不全量展示,产品形态天然允许降级。
  • 为什么关注要频控? 关注是关系写入,脚本可批量关注/取关制造关系污染(刷粉、爬虫)。频控(如每分钟最多 20 次)+ 设备指纹 + 异常检测,是社交图谱干净度的必要成本。
  • 为什么粉丝数可以“不准但要快”? 展示场景允许秒级延迟甚至短暂误差,所以用 Redis 计数实时展示,DB 异步落账 + 对账。真正需要精确的是结算/风控场景,那些再走权威路径。

【选型判断树】

关系怎么存?
├─ 双向都要高频查 → Redis 双 Set(following + follower)+ DB 明细
└─ 只单向业务(如订阅)→ 单 Set 即可

大 V 粉丝怎么处理?
├─ 千万级 → 不存全量 Set
│   ├─ 计数 key + 精选/最近 N 个
│   └─ 全量走 DB 游标
└─ 普通用户 → Set 直接存

互关/共同关注:
→ 互关、共同关注都用 SINTER(取的集合不同);SUNION 只用于并集推荐;不查 DB

判断口诀: 双写保双向,大 V 拆分片或降级,集合运算做关系判断。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“关系链核心是双向查询与大 V 大 Key 治理”
0:30–1:30双 Set 模型following + follower;Lua 保证双写原子
1:30–2:30集合运算互关与共同关注都是 SINTER(互关取自己两集合、共同关注取两人 following 交集);SUNION 用在「朋友关注的人」这类并集推荐;列表页批量判断
2:30–3:30存储分层Redis 扛读写,DB 唯一索引落明细,对账
3:30–4:30大 V 治理分片 / 计数+精选 / DB 游标;不全量进 Redis
4:30–5:00收尾“一句话:双写冗余换双向 O(1),大 V 用降级换稳定”

【关键数字】

参数经验值说明
大 V 阈值粉丝 > 10 万~100 万开始治理大 Key
精选粉丝缓存最近 100~1000展示够用
关注频控10~30 次/分钟防刷
Set 分片8~64 个 key按粉丝量级
Feed 推模式阈值粉丝 < 1 万~10 万超过转拉/混合
对账小时级计数 vs 明细
日关系写DAU×人均关注动作例:500万×5=2500万/日(双写×2)
关系明细存储行数×约40B10亿行约40GB,按uid分表

【追问链】(三层)

L1|“只存关注表不行吗?粉丝列表现查。” → 大 V 查粉丝会全表扫描,千万级不可接受。必须冗余 follower 侧,用双写一致性换查询性能。

L2|“关注的两步写失败了一半怎么办?” → Lua 脚本保证原子;仍失败则对账任务修复(从 DB 明细重建 Set)。DB 唯一索引是最终真相。

L3|“大 V 粉丝 Set 太大,主从同步卡住了怎么办?” → 立刻拆分片或降级为“计数+精选列表”;全量粉丝迁 DB。监控大 Key(redis-cli --bigkeys),建立大 Key 治理规范,禁止业务无脑 SADD 千万级。

【评分标准】

档位答案特征
60 分知道用 Set 存关注关系
80 分双 Set + 集合运算 + DB 落库分层
95 分大 V 大 Key 分片/降级、双写一致性与对账、频控防刷、与 Feed 的联动

【关联题】

  • 下游: 第 128 题(Feed)——关注关系是推模式的 fan-out 依据(126 是 128 的上游,故此处站在 126 视角是下游)
  • 同构: 第 124 题(点赞)——同样 Set+计数+异步落库
  • 治理: 第 30 题(大 Key)

【自测】

  1. 判断对错:只存 following,粉丝列表用 SQL 查 follow_id 即可。 参考答案: 错。大 V 会全表扫,必须冗余 follower 或接受慢查询。
  2. 互相关注怎么判断? 参考答案: SINTER user:A:following user:A:follower 看 B 是否命中,或直接两次 SISMEMBER(B∈A.following 且 A∈B.following)。A.following ∩ B.following 得到的是共同关注,不是互关。
  3. 大 V 粉丝为什么不能全量放一个 Set? 参考答案: 大 Key:内存、网络、主从同步风险;应分片或只存计数+精选,全量走 DB。

127. 热搜榜单实时更新、去重、排序,怎么算出来的(热榜系统) ​

【考察内容】实时统计与 TopN 设计

【题目】平台要上线热榜:根据用户行为(搜索/点击/讨论量)实时计算热门话题,榜单分钟级更新、要去重合并(同一事件不同词条要归并)、要防刷。数据怎么采集、热度分怎么算、榜单怎么存储与推送?

【参考答案】

  1. 需求:统计单位时间(分钟/小时)内关键词/视频的热度,实时 TopN,防刷;
  2. 热度计算:score = 权重组合(搜索量×系数+讨论量×系数+原创加权),时间衰减(score 随时间衰减,如 score/(1+age^1.2)),用 ZSet 的 score 表达;
  3. 架构:
    • 埋点:搜索/点击/转发行为上报(MQ 异步);
    • 聚合:Flink/Spark 流式计算或定时任务(分钟级)聚合关键词计数 → 写 Redis ZSet(key=hot:{date}:{hour});
    • 读取与推送(题干问「怎么推送」,拉与推要分开答):拉=ZREVRANGE 取 TopN,缓存榜单 JSON(短 TTL,如 10s),读多写少用缓存兜底;推=榜单是「整体快照」而不是逐条事件,所以不做逐条下发——榜更新后只推一个「榜单已更新,请拉一次」的轻量 diff 信号(长连接只给小范围活跃用户),其余客户端按「版本号+轮询(周期≈更新间隔)」获取;把整榜逐条推给百万连接是最常见的错法;
  4. 去重与合并:同义词/近义词合并(“王宝强”与“王宝强 电影”合并);重复条目按实体归一;
  5. 防刷:同一 IP/用户加权降权、频控、机器识别(无行为特征的刷量剔除);
  6. 扩展:地域榜/分类榜(分 key)、新旧榜切换(榜单 key 按时间滚动)、历史榜单归档查询;
  7. 容量估算(步进):假设 DAU 2000 万,搜索/点击行为约 30 次/人日 → 日行为事件约 6 亿,埋点峰值按 20% 在高峰小时 → 上报峰值约 3~5 万条/s(MQ 削峰,流式聚合消费)。热榜存储:ZSet 只保留 Top 200~500 条,单 key 内存极小;历史榜按 {date}:{hour} 滚动,30 天 × 24 key 可归档到 DB/对象存储。读:热榜接口首页强曝光,峰值可 数万 QPS,10s JSON 缓存 + 本地缓存后回源 Redis 可忽略。
  8. 数据与接口(口述可带):榜单项字段 = {term_id, display_name, score, rank, snapshot_count, category};核心接口 GET /hot/list?scene=all|tech&n=50、GET /hot/detail?term_id=;后台 POST /hot/ban(人工下架/置顶)。失败与降级:流计算延迟时读旧榜(允许滞后 1~2 个刷新周期);Redis/聚合任务失败则切换到上一小时快照;防刷规则引擎超时可先放行再异步复核,但人工置顶/下架开关必须始终可用。

【原理溯源】

  • 为什么热度必须加时间衰减? 不衰减时,历史累积量永远压过新事件,榜单变成“历史总榜”而非“当前热榜”。衰减函数让旧条目分数随时间下降,新事件有机会上榜。常见形式:线性衰减 score/(1+age)、指数衰减 score·e^(-λt)、或滑动窗口内重新计数(只统计最近 N 分钟)。
  • 为什么用 ZSet 存榜? ZSet 天然按 score 排序,ZADD 更新分数 O(log N),ZREVRANGE 取 TopN O(log N + M)。热榜本质是“带分数的有序集合 TopN”,ZSet 是该抽象的原生实现,比“每次全量排序”便宜几个数量级。
  • 为什么要去重合并词条? 同一事件会被用户用不同词搜索/讨论(“某明星 恋情” vs “某明星官宣”),不合并会把热度拆散,榜上出现多个条目稀释权重,真实热度被低估。归一化靠同义词典 + 实体识别 + 编辑距离/向量相似度,离线词典 + 在线匹配结合。
  • 为什么要防刷? 热榜是舆论场,有动机被刷。不防刷会导致假热点占据公共资源,伤害平台公信力。手段:同 IP/设备/账号降权、异常增速检测(突然 100 倍增长告警)、无互动行为的纯点击剔除。
  • 为什么榜单要短 TTL 缓存而不是每次现算? ZREVRANGE 本身不慢,但热榜接口 QPS 极高(首页强曝光)。缓存 10s 的 JSON 榜单,把 Redis 查询也挡掉,读路径变成纯内存/本地。代价是最多 10s 延迟,对热榜可接受。

【选型判断树】

热度怎么算?
├─ 实时性要求高(秒级)→ Flink 滑动窗口 → ZSet
└─ 分钟级可接受 → 定时任务批量聚合 → ZSet

榜单怎么读、怎么推?
├─ TopN 接口 → 缓存 JSON(TTL 10s)
└─ 历史榜 → 按 {date}:{hour} 归档 key

去重:
同义词典 + 实体归一 → 合并到主词条再计热度

防刷:
IP/设备/账号降权 + 增速异常检测

判断口诀: 权重组合 + 时间衰减得分数,ZSet 存 TopN,缓存挡读,词典去重,风控防刷。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“热榜是实时 TopN + 去重 + 防刷的统计系统”
0:30–1:30热度公式多信号加权 + 时间衰减;举例说明衰减必要性
1:30–2:30计算链路埋点 → MQ → 流式/定时聚合 → Redis ZSet
2:30–3:30拉与推拉=ZREVRANGE TopN → JSON 缓存短 TTL;推=只发「榜已更新」的轻量 diff 信号+客户端按版本号轮询(整榜逐条推是错法)
3:30–4:30去重防刷同义词合并;IP/设备降权;增速异常
4:30–5:00收尾“一句话:衰减保新鲜,ZSet 保排序效率,缓存保读性能”

【关键数字】

参数经验值说明
榜单更新10s~1min产品可接受延迟
缓存 TTL5~30s挡 TopN 接口
时间衰减score/(1+age^1.2) 或 e^(-λt)防旧词霸榜
接口展示条数Top 50~100首页展示够用(接口示例 n=50)
滑动窗口最近 10min~1h按业务热点半衰期
防刷降权同 IP/设备计数封顶异常增速告警
行为上报峰值日事件×高峰占比/秒例:6亿×20%/3600≈3万+/s
ZSet 保留条数Top200~500多留一档供分类榜/去重回填;内存可忽略
热榜读峰值首页曝光×DAU刷新数万QPS,靠短TTL缓存

【追问链】(三层)

L1|“为什么不直接用数据库 ORDER BY 计数?” → 每次请求全量排序,热榜 QPS 下 DB 必挂;且难以表达时间衰减。ZSet 把排序成本摊到写入时的 O(log N),读 TopN 极快。

L2|“同一事件多个词条怎么合并?” → 同义词典 + 实体识别归一到主词条;离线挖掘近义词对,在线匹配后统一计分。合并后再入 ZSet,避免热度被拆散。

L3|“有人恶意刷榜怎么发现和处置?” → 实时:增速异常检测(单位时间涨幅超阈值告警+降权);特征:无点击/无停留的纯搜索、设备农场。处置:剔除刷量、封禁账号、榜单回滚。事后:样本回流模型。

【评分标准】

档位答案特征
60 分知道用 Redis ZSet 做 TopN
80 分热度公式含时间衰减;流式聚合链路;缓存读;并答到「推送」这一问(diff 信号/版本号轮询)
95 分去重归一、防刷策略、滑动窗口 vs 累计、历史榜归档、异常增速处置

【关联题】

  • 上游: 第 124 题(计数/点赞)——热度信号来源
  • 同构: 第 146 题(BI 看板)——实时聚合架构相似
  • 综合: 第 152 题(社区)——热榜是子模块
  • 对比: 第 151 题(弹幕)——可丢失消息 vs 必须准确的榜

【自测】

  1. 判断对错:热榜只需累计搜索次数,不用时间衰减。 参考答案: 错。不衰减会变历史总榜,旧词永久霸榜。
  2. 热榜存储为什么选 ZSet? 参考答案: 天然按分数排序,更新 O(log N),取 TopN 高效,匹配“带分数的有序 TopN”抽象。
  3. 去重合并失败会怎样? 参考答案: 热度被拆散到多个近义词条,真实热度被低估,榜上出现重复条目。

128. 关注了 1000 人,刷新要看到他们的最新动态(Feed 流) ​

【考察内容】Feed 流是系统设计最高频大题之一

【题目】用户关注了 1000 个账号,每次刷新都要看到这些账号的最新动态,而且要求秒开。大 V 一条动态有千万粉丝要看到。推(写扩散)、拉(读扩散)、推拉结合三种模式各自怎么实现?你们的场景选哪种?

【参考答案】

  1. 两种模式:
    • 推模式(Fan-out on write):发帖时把帖子 ID 写入所有粉丝的 feed 缓存(list);读时直接取自己列表——读快;但大 V 千万粉丝=写千万次(写放大);
    • 拉模式(Fan-out on read):发帖只写自己的帖子表;读时合并所有关注人的帖子(查关注列表→查各自最新帖→合并排序)——写简单;但读慢(多查询+合并);
    • 混合模式(生产主流):普通用户推(粉丝少,写成本低),大 V(粉丝超阈值如 10 万)拉——粉丝读时把“自己关注的大 V 帖子”与“推的普通帖子”合并;
  2. 存储:feed 缓存用 Redis List(每用户一个 key,存帖子 ID,LRANGE 分页,容量限制如 1000 条,超出淘汰);帖子详情放 Redis/DB;发帖表(user_id, create_time 索引);
  3. 排序:按时间倒序(ID 有序)或热度;分页用游标(避免深分页);
  4. 一致性:发帖先落 DB(事务)→ 异步推送给粉丝(MQ);推送失败的重试+对账(漏推补推);
  5. 扩展:feed 缓存降级(拉模式兜底)、僵尸粉清理、分组/不看某人等过滤规则;
  6. 容量估算(步进):假设 DAU 1000 万,约 2% 发帖 → 日新帖 20 万;读刷新人均 20 次/日 → 日 Feed 读 2 亿次,高峰折算读峰值约 2~3 万 QPS。推模式写放大:普通用户均粉 200,则日推写 = 20万×200 = 4000 万次 list 写,峰值数百~数千 Redis OPS,可接受;若某大 V 1000 万粉丝纯推,单次发帖 = 1000 万写,必须走拉/混合。存储:每用户 Feed 缓存 500~1000 个帖子 ID × 8B ≈ 4~8KB/用户,活跃用户 1000 万 × 8KB ≈ 40~80GB 级(1e7×8KB=8e10B),单集群放不下,要按用户分片或只保留最近 100~200 条。
  7. 失败与降级:推失败重试+对账补推;Redis Feed 列表故障时自动降级为拉模式现算(限流+只取最近 N 关注人);帖子详情缓存击穿用互斥锁/空值缓存;深分页禁止 offset,统一游标。

【原理溯源】

  • 为什么推模式读快写爆? 推把计算成本挪到写入时:发帖一次,写入 N 个粉丝的 feed 列表。读路径 O(1)(只取自己列表)。当 N=千万(大 V),一次发帖产生千万次写,MQ 消费链路被放大,缓存与带宽都扛不住。推的本质是“预计算读结果”,用写放大换读性能。
  • 为什么拉模式写简读慢? 拉只写一份帖子,读时要:查关注列表(1000 人)→ 批量取各人最新帖 → 归并排序。关注人数多时读放大严重,且每次刷新都重复计算。拉的本质是“读时现算”,用读成本换写简单。
  • 为什么混合是生产主流? 普通用户粉丝少(几十~几百),推的成本可忽略且读体验最好;大 V 粉丝千万,推会打爆写入。混合按“粉丝数阈值”分流:小 V 推、大 V 拉。读时合并“推来的列表”与“关注的大 V 最新帖”,两边都取 TopN 归并。阈值由压测定(常见 1 万~10 万)。
  • 为什么 feed 缓存只存帖子 ID 而不是整帖内容? 帖子可能被编辑/删除/审核下架,内容冗余进 feed 会导致不一致与存储膨胀。存 ID、读时查详情缓存,删除只需失效详情,feed 列表读时过滤即可。容量也小一个数量级。
  • 为什么要游标分页? Feed 是无限流,深分页(offset 10000)要扫描大量元素。游标(上一页最后一条的 ID/时间戳)让每次只取“比游标更旧的 N 条”,复杂度 O(N),与页深无关。

【选型判断树】

选推/拉/混合?
├─ 粉丝数少(< 1 万)且读 QPS 高 → 推
├─ 粉丝数巨大(> 10 万)或写频高 → 拉
└─ 社交产品(大 V + 普通人并存)→ 混合(阈值分流)

feed 缓存:
├─ 存帖子 ID(推荐)+ 详情独立缓存
├─ 容量限制 500~1000 条,LRU/截断
└─ key 按用户,大 V 粉丝不预推

分页:游标(max_id / 时间戳),禁止 offset 深分页

判断口诀: 小 V 推、大 V 拉、混合合并;存 ID 不存内容;游标翻页。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“Feed 是推拉取舍题,核心是写放大与读放大的交换”
0:30–1:30推模式发帖 fan-out 写粉丝列表;读 O(1);大 V 写爆
1:30–2:30拉模式只写一份;读时合并关注人最新帖;读慢
2:30–3:30混合(推荐)阈值分流;读时归并推列表与大 V 帖
3:30–4:30存储细节Redis List 存 ID;容量截断;游标分页;MQ 异步+对账
4:30–5:00收尾“一句话:用混合在写放大和读放大之间取平衡”

【关键数字】

参数经验值说明
推/拉阈值粉丝 1 万~10 万压测定
feed 缓存容量500~1000 条超出截断
分页大小10~20 条游标
推送延迟秒级(MQ)可接受
大 V 发帖写放大千万级必须拉模式
对账补推分钟级扫描漏推最终一致
Feed读峰值日读总次数/86400×峰均比例:2亿/日÷86400≈2315,×10≈2.3万QPS
推写放大发帖数×平均粉丝数20万×200=4000万list写/日
单用户Feed内存500~1000个ID×8B约4~8KB/用户

【追问链】(三层)

L1|“混合模式阈值怎么定?” → 看“单次发帖的 fan-out 成本”与“读合并成本”的交叉点,结合发帖频率压测。经验值 1 万~10 万粉丝,还要考虑该用户发帖频次(发帖狂魔阈值要更低)。

L2|“feed 缓存丢了怎么办?” → 降级到拉模式:按关注列表现算最新帖。缓存是优化不是权威,DB 帖子表才是真相。也可只对活跃用户预推缓存。

L3|“关注了 1000 人,其中 800 个是大 V,读时合并会不会很慢?” → 限制同时合并的大 V 数量(取最近活跃的 Top 50~100);对大 V 帖子做“热门摘要缓存”;或产品上限制关注大 V 数量。工程上大 V 帖子详情缓存命中率高,合并主要是 ID 归并,可接受。

【评分标准】

档位答案特征
60 分能说出推和拉的区别
80 分讲清写放大/读放大;给出混合模式与阈值
95 分feed 存 ID 策略、游标分页、MQ 异步+对账、降级拉模式、大 V 合并优化

【关联题】

  • 上游: 第 126 题(关注粉丝)——fan-out 的关系依据
  • 同构: 第 152 题(社区信息流)
  • 对比: 第 127 题(热榜)——另一种内容组织方式
  • 存储: 第 35 题(集群分片)——feed key 按用户分片

【自测】

  1. 判断对错:Feed 用推模式总比拉模式好,因为读更快。 参考答案: 错。大 V 千万粉丝写放大会打爆系统,必须混合或拉。
  2. feed 缓存为什么存帖子 ID 而不存正文? 参考答案: 正文可能变更/删除,存 ID 避免不一致与存储膨胀,详情独立缓存。
  3. 关注 1000 人时拉模式读路径是什么? 参考答案: 查关注列表 → 批量取各人最新帖 → 归并排序 → 游标分页返回。

129. 单聊/群聊/多端同步/消息不丢,IM 怎么设计(IM 系统) ​

【考察内容】IM 是系统设计经典大题

【题目】公司要做即时通讯(类钉钉):单聊、群聊(万人群)、在线状态展示、消息不丢不重、手机/电脑多端同步已读未读。长连接怎么管理?消息存储与同步(多端)怎么做?群消息扩散怎么写?

【参考答案】

  1. 架构:客户端 → 接入层(WebSocket 长连接集群,保持在线会话)→ 消息服务(路由/存储)→ 推送服务(离线推送 APNs/FCM);
  2. 在线状态:Redis 维护(userId→连接节点+心跳),在线列表/最后在线时间;
  3. 消息流:发送方 → 消息服务(写消息存储)→ 路由到接收方连接节点 → 长连接推送;接收方离线 → 离线消息存储 + 通知推送;
  4. 存储:消息表(msg_id 全局唯一(雪花)、会话 ID(单聊=双方 id 有序拼接;群聊=groupId)、from/to、content、时间),按会话分表;消息序号(seq):每会话递增,客户端按 seq 拉取增量(多端同步基础);
  5. 可靠性:消息必达——客户端 ack 机制(收到回执),超时重传(幂等:msg_id 去重);服务端持久化+客户端本地缓存;
  6. 多端同步:每端维护“已同步 seq”,登录后从服务端拉取缺失消息(增量同步);
  7. 群聊:群成员列表(Redis/DB)、消息写一份存储+广播在线成员(推送风暴:大群用“拉模式/分片推送”);
  8. 扩展:已读回执、消息撤回(发送“撤回指令”消息)、敏感词、端到端加密(可选);
  9. 容量估算(步进):假设同时在线 100 万连接,单机 WebSocket 承载 2~5 万连接 → 接入层约 20~50 台(100万÷5万=20,100万÷2万=50;按保守的 2 万/台取 50 台);消息峰值:日消息 2 亿条,高峰小时 30% → 约 1.5~2 万条/s 写入+路由。存储:2 亿/日 × 200B ≈ 40GB/日,按会话分表并冷热分层(热 7 天 Redis/SSD,冷进归档)。群聊广播:500 人普通群可推;万人超大群改拉模式,推送只发摘要。
  10. 失败与降级:接入节点宕机,客户端重连到其他节点并按本地 seq 补拉;消息服务写超时则本地持久化重发(客户端 outbox);离线推送通道(APNs/FCM)失败重试+降级为站内信;多端同步以服务端 seq 为准,冲突以服务端顺序覆盖;存储抖动时先保证在线投递,异步补持久化并告警。

【原理溯源】

  • 为什么要 WebSocket 长连接而不是 HTTP 轮询? IM 要求消息秒级到达。HTTP 轮询延迟高(等于轮询间隔)、浪费带宽(大部分请求无新消息)、服务端无法主动推送。WebSocket 全双工、一条连接持续复用,服务端可在消息产生瞬间推送,延迟降到百毫秒级。代价是连接有状态,需要接入层会话路由。
  • 为什么每会话要维护 seq(序号)? 多端同步、乱序重传、断线补拉都需要“消息全序”。没有 seq 时,客户端只能靠时间戳(不可靠,时钟漂移)或“最后一条 ID”(断线期间多条无法定位缺口)。每会话递增 seq 让客户端知道“我同步到哪了”,缺口一目了然,增量拉取天然幂等。
  • 为什么消息必达要 ack + 幂等重传? 网络不可靠:推送可能丢。客户端收到后必须 ack;服务端超时未收到 ack 则重传。重传会导致重复,所以消费端按 msg_id 去重。这是“至少一次投递 + 消费端幂等”的经典组合,保证最终必达且不重复上屏。
  • 万人群为什么要特殊处理? 一条消息写 N 份离线 + 广播 N 个在线连接,N=1 万时写放大与推送风暴都严重。手段:消息只存一份,群成员按需拉取(拉模式);或分片推送(按连接节点分批);在线广播合并、抽样(如只推最近活跃成员的摘要)。产品上万人群通常降级为“进入会话才拉历史”。
  • 为什么单聊会话 ID 用“双方 ID 有序拼接”? 保证 A↔B 的会话 ID 唯一且与方向无关(min(idA,idB)_max(idA,idB)),避免“我发给你”和“你发给我”变成两个会话,也便于按会话分表路由。

【选型判断树】

消息通道:
├─ 在线 → WebSocket 长连接直推
└─ 离线 → 离线消息存储 + APNs/FCM 推送

同步:
├─ 多端 → 每端记已同步 seq,登录增量拉
└─ 断线重连 → 从本地 seq+1 拉缺口

群聊:
├─ 小群(< 500)→ 写一份 + 广播在线
└─ 大群(> 1000)→ 拉模式 / 分片推送 / 抽样

可靠性:
ack + 超时重传 + msg_id 幂等

判断口诀: 长连接管在线,seq 管同步,ack 管必达,大群拉模式。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“IM 核心是长连接、seq 同步、必达、大群扩散四件事”
0:30–1:30接入架构WebSocket 集群 + 会话路由 + 在线状态 Redis
1:30–2:30消息流与存储发送→存储→路由推送;seq 递增;按会话分表
2:30–3:30必达与多端ack+重传+幂等;每端已同步 seq 增量拉
3:30–4:30群聊与扩展大群拉/分片;已读回执;撤回指令消息
4:30–5:00收尾“一句话:长连接保实时,seq 保有序可补,ack 保必达”

【关键数字】

参数经验值说明
心跳30~60s掉线检测
消息 ack 超时5~30s超时重传
单机长连接数万~十万看内存与推送能力
大群阈值> 1000 人转拉/分片
离线消息保留7~30 天按产品
seq每会话单调递增多端同步基石
分页拉历史20~50 条/页游标 seq
在线连接估算同时在线/单机承载例:100万÷2~5万≈20~50 台
消息写入峰值日消息×高峰占比例:2亿×30%/3600≈1.7万/s
消息存储增速日消息×单条约200B约40GB/日,需冷热分层

【追问链】(三层)

L1|“消息乱序怎么处理?” → 服务端保证同会话 seq 递增;客户端按 seq 排序展示,缺口触发补拉。网络层乱序不影响上屏顺序。

L2|“用户手机在线、电脑也在线,消息怎么不重复不丢失?” → 服务端推给所有在线端;每端独立 ack 与已同步 seq。一端已读可同步已读位点到其他端(多端已读同步靠服务端统一的 read_seq)。

L3|“接入节点挂了,上面的长连接全断,怎么办?” → 客户端自动重连(退避)并路由到健康节点;重连后按 seq 补拉。服务端会话路由要能快速摘除故障节点;可配合多活接入。

【评分标准】

档位答案特征
60 分知道 WebSocket 长连接 + 消息表
80 分seq 增量同步、ack 必达、在线状态存储
95 分大群拉/分片、多端已读同步、撤回实现、节点故障重连补拉、幂等重传

【关联题】

  • 对比: 第 151 题(弹幕)——可丢失 vs 必达的架构取向
  • 上游: 第 126 题(关系)——群成员/好友关系
  • 存储: 第 153 题(延迟任务)——消息重试可复用
  • 安全: 第 130 题(扫码登录)——IM 常配合登录体系

【自测】

  1. 判断对错:IM 用 HTTP 轮询完全可以,实现更简单。 参考答案: 错。轮询延迟高、浪费大,IM 必须长连接才能秒级推送。
  2. seq 的作用是什么? 参考答案: 会话内全序,支撑多端增量同步、断线补拉、乱序重排。
  3. 万人群一条消息为什么不能简单广播? 参考答案: 写放大与推送风暴;应存一份、拉模式或分片推送、必要时抽样。

130. 网页版要扫码登录,整个流程怎么闭环(扫码登录) ​

【考察内容】登录态与状态机设计

【题目】产品要求 PC 网页支持扫码登录:手机扫码→确认→网页自动进入登录态。二维码怎么生成与轮询?手机确认后如何通知网页?安全上怎么防二维码被冒用?请设计完整流程与状态机。

【参考答案】

  1. 核心流程(二维码承载的是“登录票据”):
    • PC 生成二维码:服务端创建 login_ticket(唯一 ID+状态=待扫描+过期时间,存 Redis),二维码内容=票据 ID;
    • PC 端轮询/长连接查询票据状态(或服务端推送到 PC 的长连接);
    • 手机端扫码:App 读取票据 ID → 唤起确认页(显示设备信息)→ 用户确认 → App 调服务端“确认登录”接口(携带用户身份+票据 ID)→ 服务端更新票据状态=已确认,生成登录态(Token/Session)写入;
    • PC 端轮询到“已确认”→ 拿到登录态 → 跳转登录完成;
  2. 状态机:待扫描 → 已扫描待确认 → 已确认/已取消/已过期;Redis key=login:{ticket},状态+手机端会话信息;
  3. 安全:
    • 票据一次性(确认后即失效)、短过期(2~5 分钟);
    • 防劫持:二维码携带随机数,手机确认时校验设备指纹/登录态;HTTPS;
    • 轮询频控(如 1 秒 1 次);
  4. 替代方案:WebSocket 长连接推送状态(减少轮询);
  5. 扩展:扫码后免确认(可信设备直接登录)、扫码登录统计;
  6. 容量估算(步进):扫码登录是低频写、中频读——假设日登录 100 万次(扫码占 30%)→ 日票据约 30 万(全天均值仅 3.5/s),登录集中在上班时段按 5~10 倍峰均比 → 峰值约 10~30 QPS 生成;PC 轮询按 1s/次、人均扫码确认约 20s → 并发轮询可达 数百~数千 QPS,Redis 状态查询无压力。存储:票据 TTL 2~5 分钟,Redis 常驻量 = QPS×TTL ≈ 30×180 ≈ 5000~1 万 key,可忽略。
  7. 失败与降级:Redis 票据丢失/过期 → PC 提示二维码失效,引导刷新;手机确认接口超时 → 幂等重试(ticket+status 条件更新);轮询风暴时服务端对 ticket 维度限流;确认服务故障可降级为“短信验证码登录”兜底入口;Token 签发失败必须回滚票据状态或置为失败,避免 PC 卡死在轮询。

【原理溯源】

  • 为什么二维码里只能放“票据 ID”而不能放用户信息? 二维码会被截图转发。若含用户凭证,拿到图的人即可登录。票据是服务端签发的一次性、短时、待确认的占位符,本身不携带权限;权限只在“手机端已登录用户确认”后才注入。这是把身份验证与凭证传递解耦。
  • 为什么要状态机而不是直接写 Token? 扫码登录是异步多方协作(PC 生成、手机确认、PC 感知)。状态机让每一方只关心“当前状态能否迁移到我需要的态”,避免竞态:手机确认时必须检查仍是“已扫描”,PC 只在“已确认”时取 Token。没有状态机就会出现“过期码被确认”“二次确认”等问题。
  • PC 怎么知道手机确认了? 两种:① 轮询票据状态(简单,1s 一次可接受);② PC 也持有一条长连接,确认后服务端推送。小规模用轮询足够,大规模/体验敏感用长连接。本质是“状态变更的通知通道”选择。
  • 为什么要短过期 + 一次性? 限制攻击窗口:二维码被偷拍后,攻击者只有几分钟且必须在用户确认前使用;一旦确认,票据立即作废,重放无效。一次性是防重放的核心。
  • 为什么要校验设备指纹/已登录态? 防止“把二维码贴到钓鱼页让用户扫”。手机 App 扫码后应展示目标站点信息,并要求用户已在 App 登录;服务端确认时校验手机会话有效,降低“未登录设备确认”的风险。

【选型判断树】

PC 如何感知确认?
├─ 规模小/体验可接受 → 轮询票据状态(1s)
└─ 规模大/要秒级 → WebSocket 推送

状态机:
待扫描 → 已扫描待确认 → 已确认 | 已取消 | 已过期
(非法迁移一律拒绝)

安全:
票据一次性 + 2~5 分钟过期 + 绑定用户 + HTTPS

判断口诀: 码里只放票据,状态机管流转,确认才发 Token,一次性防重放。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“扫码登录是票据状态机 + 异步通知问题”
0:30–1:30主流程生成票据→二维码→手机确认→服务端发态→PC 感知
1:30–2:30状态机五态与非法迁移拒绝
2:30–3:30通知机制轮询 vs 长连接推送
3:30–4:30安全一次性、短过期、设备校验、防劫持
4:30–5:00收尾“一句话:票据是占位符,确认才赋权,状态机管竞态”

【关键数字】

参数经验值说明
票据过期2~5 分钟过短体验差,过长风险高
轮询间隔1s频控防刷
票据一次性确认后即毁防重放
状态数5(待扫/已扫/确认/取消/过期)标准状态机
Token 有效期小时~天级与普通登录一致
票据生成峰值日扫码量×峰均比/86400例:30万/日≈3.5/s,×5~10→约 10~30QPS
票据Redis常驻QPS×TTL秒数数千~1万key,可忽略
PC轮询频次约1次/秒/票据需按ticket限流防风暴

【追问链】(三层)

L1|“二维码被别人扫了怎么办?” → 票据未确认时只是占位;别人扫了若确认,需要该手机已登录且用户点确认。展示设备信息让用户知情;异常可风控。真正危险是“诱导用户扫攻击者的码”,所以要展示目标域名。

L2|“轮询和长连接怎么选?” → 轮询实现简单,1s 间隔对登录场景可接受;长连接实时省流量,适合高 QPS。可按规模升级。

L3|“用户扫了但一直不点确认,PC 端怎么办?” → 等待超时后票据过期,PC 提示刷新二维码。服务端定时清理过期票据。可展示“已扫描待确认”状态提升体验。

【评分标准】

档位答案特征
60 分能描述扫码→确认→登录的大致流程
80 分票据状态机、轮询/推送、一次性过期
95 分安全细节(防劫持、设备指纹、重放)、票据一次性消费与短 TTL 依据、免确认扩展

【关联题】

  • 同簇: 第 155 题(SSO)——票据机制同源
  • 上游: 登录态体系(Token/Session)
  • 安全: 第 147 题(风控)——异常扫码检测

【自测】

  1. 判断对错:二维码内容可以直接放用户 Token,扫码即登录。 参考答案: 错。二维码可被转发,必须放一次性票据,确认后才发 Token。
  2. 扫码登录的状态机有哪几个态? 参考答案: 待扫描 → 已扫描待确认 → 已确认/已取消/已过期。
  3. 为什么要短过期? 参考答案: 缩小攻击窗口,过期码无法被利用。

131. 电商下单到支付到发货,订单系统全貌(订单系统) ​

【考察内容】订单系统是电商领域最高频设计题

【题目】电商平台要做订单系统:用户下单→支付→发货→完成,中间可能取消/退款/超时关闭。订单状态机怎么设计?下单幂等怎么防重?超时未支付怎么自动关单?订单数据量大了怎么存储?

【参考答案】

  1. 下单流程:校验(用户/商品/价格)→ 生成订单(状态=待支付)→ 扣库存(预占/扣减)→ 返回订单号;支付回调更新状态;
  2. 状态机:待支付 → 已支付 → 已发货 → 已完成;取消/退款分支(已取消、已退款);非法流转禁止(已支付不能直接完成);
  3. 关键设计:
    • 订单号:分布式 ID(雪花),订单表分库分表(按 user_id);
    • 幂等:请求级幂等键(客户端幂等号或服务端预生成订单号)建唯一索引,重复请求返回首单;不能在订单表按「用户+商品」建唯一索引——那会挡住正常复购,它只适用于限购活动(user_id+sku+活动期);支付回调幂等(支付流水号唯一);
    • 超时关单:MQ 延迟消息 + 定时扫表兜底(见延时任务题);
    • 库存:下单预占(Redis 预扣+DB 条件更新),支付成功转正式扣减,超时释放;
    • 金额:分存储,快照商品信息;
  4. 并发保障:下单走乐观锁/Redis 预扣防超卖;「一人一单」只在限购活动成立(唯一键=user_id+sku+活动期),不能当通用并发保障——本条第 3 项刚论证过按「用户+商品」建唯一索引会挡住正常复购;
  5. 一致性:订单-支付-库存跨服务用“事务消息+对账”(最终一致);支付成功事件驱动发货;
  6. 扩展:拆单(多仓)、订单列表分页(user_id+create_time 索引+游标)、售后流程;
  7. 容量估算(步进):假设日订单 50 万单,大促峰值系数 20~50 → 下单峰值 100~300 TPS(极端秒杀另走独立链路);订单表 50 万/日 × 500B ≈ 250MB/日,一年约 100GB,按 user_id 分 16~64 库/表。支付回调峰值与下单同量级;超时关单扫描量 = 活跃待支付订单 × 扫描频率,MQ 延迟消息承担主路径,扫表仅兜底(分钟级、分页)。
  8. 失败与降级:创建订单时库存服务超时 → 快速失败,禁止“无库存快照落单”;支付成功但订单更新失败 → 状态对账补偿任务扫描;DB 主库故障读从库可能读到旧状态,支付回调必须写主库;大促时订单列表可降级(只展示近 N 个月),详情仍保证可查。

【原理溯源】

  • 为什么订单必须是状态机而不是随意 UPDATE? 订单生命周期有多条合法路径(正常、取消、退款、超时)。没有状态机约束时,非法更新(如已取消直接变已发货)会导致资损与脏数据。状态机把“允许的迁移”编码成规则,每次更新校验 current_status 与目标态,从模型层杜绝非法流转。
  • 为什么要库存预占而不是支付时才扣? 支付有延迟(用户可能不付),若支付时才扣,会出现“超卖后支付失败”的补偿复杂问题。预占(下单锁库存)→ 支付确认扣减 / 超时释放,把冲突前置到下单瞬间,用条件更新保证不超卖。Redis 预扣扛并发,DB 条件更新兜底。
  • 超时关单为什么要“延迟消息 + 扫表”双保险? 延迟消息(RocketMQ/RabbitMQ)到点触发,精度高;但消息可能丢。扫表兜底扫描“超时仍待支付”的订单,保证最终关闭。双保险是“高精度通道 + 最终一致兜底”的标准组合。
  • 为什么支付回调必须幂等? 渠道可能重发回调(网络超时重试)。不幂等会重复入账、重复发货。用渠道流水号唯一索引 + 状态机(已支付则直接返回成功)保证只处理一次。
  • 为什么订单要按 user_id 分库分表? 订单查询绝大多数是“我的订单列表”,按 user_id 路由让单用户订单在同一分片,列表查询无需跨库。全局查(运营)走离线/ES。

【选型判断树】

下单链路:
校验 → 生成订单(待支付)→ 库存预占 → 返回订单号
支付回调(幂等)→ 已支付 → 发货 → 完成

超时:
MQ 延迟消息关单 + 定时扫表兜底

库存:
Redis 预扣(并发)+ DB 条件更新(正确)
支付成功转正式扣减;超时释放

分表:按 user_id
幂等:下单唯一索引 + 回调流水号唯一

判断口诀: 状态机管流转,预占管库存,双保险管超时,幂等管重复。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“订单核心是状态机 + 幂等 + 库存预占 + 超时关单”
0:30–1:30状态机主路径与分支;非法迁移禁止
1:30–2:30下单与库存预占/正式扣;Redis+DB 双层
2:30–3:30幂等与超时唯一索引;延迟消息+扫表
3:30–4:30一致性与分表最终一致+对账;user_id 分片;游标分页
4:30–5:00收尾“一句话:状态机保正确,预占保不超卖,双保险保超时,幂等保不重复”

【关键数字】

参数经验值说明
支付超时15~30 分钟电商常见
库存预占超时与支付超时一致超时释放
延迟消息精度秒级主通道
扫表兜底分钟级最终一致
分表键user_id列表查询
分页游标避免深分页
金额整数分避免浮点
下单峰值日订单×大促系数/折算例:50万×20~50→约100~300TPS
订单存储增速日订单×单行约500B约250MB/日,按uid分表

【追问链】(三层)

L1|“为什么支付回调要幂等?” → 渠道重发是常态;不幂等会重复入账。用渠道流水号唯一索引 + 状态机短路。

L2|“库存预占和 DB 扣减怎么配合?” → 下单 Redis 预扣扛并发,DB 条件更新保正确;支付成功转正式扣减;超时/取消释放预占。对账修正偏差。

L3|“订单表上亿行,列表页怎么保证快?” → 按 user_id 分库分表,单用户订单集中;联合索引 (user_id, create_time);游标分页;热用户订单可缓存。

【评分标准】

档位答案特征
60 分能描述下单支付发货流程
80 分状态机、幂等、超时关单、库存预占
95 分双保险超时、分表与游标、跨服务最终一致+对账、金额快照、拆单

【关联题】

  • 上游: 第 153 题(延迟任务)——超时关单依赖
  • 同簇: 第 132 题(支付)、第 133 题(对账)
  • 库存: 第 134 题(优惠券)、第 157 题(预约资格)
  • 分布式: 第 99 题(分布式事务)

【自测】

  1. 判断对错:订单状态可以直接从“待支付”改成“已完成”。 参考答案: 错。必须经过已支付、已发货等合法迁移,状态机拦截非法流转。
  2. 为什么库存要预占? 参考答案: 把超卖冲突前置到下单,支付失败/超时再释放,避免支付时才发现库存不足。
  3. 超时关单为什么用双保险? 参考答案: 延迟消息精度高但可能丢;扫表保证最终关闭。

132. 余额支付,钱不能多一分少一分(支付系统) ​

【考察内容】支付系统是金融/电商最高频设计题

【题目】产品要做钱包余额支付:用户充值、消费、退款。要求:钱一分不能多一分不能少,并发扣款不能超扣,每笔钱都有流水可查,账要平。账户模型、流水设计、并发控制、日终对账怎么设计?

【参考答案】

  1. 核心原则:账务系统不能“改余额”,只能“记流水”(流水驱动:余额是流水的物化结果。注意本方案是单账户流水,严格的复式记账需为平台/渠道建对手账户并要求 Σ借=Σ贷,单侧流水只能靠日终对账兜住不平);
  2. 核心表:
    • 账户表(user_id、balance,供查询);余额 = Σ流水(可定期对账);
    • 流水表(流水号唯一、user_id、方向(+/-)、金额(分)、关联业务单号、状态、create_time)——追加写,不更新;
    • 支付单表(支付单号、订单号、金额、渠道、状态机:待支付→支付中→成功/失败/退款);
  3. 支付流程:创建支付单 → 调渠道(微信/支付宝)→ 异步回调(幂等:渠道流水号唯一索引)→ 记账(加流水+更新余额,同一本地事务)→ 通知业务方(MQ 事件);
  4. 一致性保障:
    • 幂等:支付单号/渠道流水号唯一约束,重复回调只处理一次;
    • 金额校验:回调金额与支付单金额比对(防止篡改);签名验证;
    • 对账:每日与渠道对账文件比对(我方支付单 vs 渠道流水),差异人工/自动处理;
    • 退款:原路退回(复用原支付单)、退款单独立+幂等;
  5. 安全:金额用分(整数)、并发扣款用行锁/乐观锁(余额>=金额 条件更新)、审计日志、敏感操作风控;
  6. 高可用:账务是核心链路——限流、幂等、数据库高可用、对账兜底;账户分片(按 user_id);
  7. 容量估算(步进):假设电商日支付成功 30 万笔,峰值系数 10 → 支付回调/记账峰值约 30~50 TPS,账务库完全可扛;若含红包/余额高频场景可达数百 TPS,仍远低于互联网 C 端峰值,重点是正确性而非纯吞吐。存储:流水 30 万/日 × 200B ≈ 60MB/日,账户表按 user_id 分片;渠道对账文件日增 MB 级。带宽与 RT 敏感点在第三方渠道(外部 RT 数百 ms),必须异步化回调,不能同步等渠道。
  8. 失败与降级:渠道超时 → 支付单置“支付中”,靠回调/查单收敛,禁止本地直接判失败后用户重复付;回调处理失败 → 渠道重试+我方查单补偿;DB 记账失败但回调已收 → 本地消息表/事务消息保证最终入账;风控误杀可人工放行;限流优先保支付与回调,营销扣款类可排队。

【原理溯源】

  • 为什么不能直接 UPDATE balance? 直接改余额会丢历史、无法审计、并发下易超扣,且出错后无法回溯。流水驱动模型把余额降级为“流水的物化视图”,任何变动都有不可变流水记录,对账可从流水重算。这是会计学复式记账思想在系统中的落地。
  • 为什么金额要用整数分? 浮点数有精度误差(0.1+0.2≠0.3),资金场景一分都不能差。用分作单位,全部整数运算,杜绝精度问题。展示时再除 100。
  • 并发扣款为什么要条件更新而不是先查后改? 先查后改在并发下会超扣:两请求同时读到余额 100,各扣 80。条件更新 UPDATE ... SET balance=balance-80 WHERE user_id=? AND balance>=80 让数据库原子判断+更新,影响行数为 0 即余额不足。
  • 支付回调为什么要验签+金额比对? 回调来自公网,可能被伪造或篡改。验签保证来自渠道;金额比对保证与我方支付单一致,防止“付 1 元回调说付 1000”类攻击。
  • 为什么要每日对账? 网络、回调、重试都可能造成最终不一致(掉单、重复、金额差)。对账是发现这些问题的最后防线,把“运行时不确定”收敛到“日终确定”。资金场景对账不是可选项。

【选型判断树】

账户怎么建?
流水表(追加写)+ 账户表(物化余额)+ 支付单(状态机)

扣款:
条件更新 balance>=amount,禁止先查后改

回调:
验签 + 金额比对 + 流水号幂等 → 记账(流水+余额同一事务)

对账:
日终我方流水 vs 渠道账单 → 差异分类 → 人工/自动

判断口诀: 流水驱动、余额只读、条件更新、回调幂等、日终对账。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“支付核心是流水驱动的账务模型 + 幂等 + 对账”
0:30–1:30账户模型流水表追加写;账户余额物化;支付单状态机
1:30–2:30支付流程创建→调渠道→回调→记账→通知
2:30–3:30并发与安全条件更新;金额分;验签;幂等
3:30–4:30对账与退款日终对账;原路退回;差异处理
4:30–5:00收尾“一句话:钱不可直接改,只能记账,账平靠对账”

【关键数字】

参数经验值说明
金额单位分(整数)杜绝浮点误差
回调幂等键渠道流水号唯一防重复入账
对账周期T+1 日终资金类可 T+0
退款原路、独立退款单幂等
账户分片user_id水平扩展
审计全流水留痕可追溯
支付峰值日支付×峰值系数/折算例:30万×10→约30~50TPS
流水存储增速日支付×单行约200B约60MB/日
渠道外部RT数百ms级必须异步回调,勿同步等待

【追问链】(三层)

L1|“为什么余额不能直接改?” → 丢历史、无法审计、并发不安全。流水驱动让每笔变动可追溯,余额可重算。

L2|“并发扣款怎么防超扣?” → WHERE balance >= amount 条件更新,数据库原子保证;或行锁/乐观锁。禁止先 SELECT 再 UPDATE。

L3|“对账发现差 1 分钱怎么办?” → 绝不自动改账。进差异池:分类(掉单/重复/金额差),查日志与渠道核对,人工或规则化补偿。资金安全优先于自动修复。

【评分标准】

档位答案特征
60 分知道要记流水、防重复
80 分流水驱动模型、条件更新、回调幂等
95 分验签与金额校验、日终对账闭环、退款设计、审计与风控

【关联题】

  • 同构: 第 159 题(积分)——虚拟资产流水模型
  • 下游: 第 133 题(对账系统)
  • 上游: 第 131 题(订单)——支付单关联订单
  • 分布式: 第 99 题(分布式事务)

【自测】

  1. 判断对错:余额支付可以直接 UPDATE balance = balance - 100。 参考答案: 错。必须流水驱动,条件更新防超扣,且可审计。
  2. 金额为什么用分? 参考答案: 浮点精度问题,资金场景必须整数。
  3. 支付回调重复怎么办? 参考答案: 渠道流水号唯一索引 + 状态机短路,只处理一次。

133. 我方支付记录和银行结算单对不上(对账系统) ​

【考察内容】对账系统是支付/金融岗高频题

【题目】每天凌晨银行会回传结算文件,和系统内的支付记录经常对不上(掉单、金额差、状态不一致)。对账系统怎么设计?对账流程(文件拉取→核对→差异分类→自动处理)怎么走?差异怎么处理?

【参考答案】

  1. 对账流程(日终批量):
    • 数据准备:渠道侧下载结算文件(对账单:交易流水、金额、手续费);我方导出当日支付流水;
    • 比对:按“唯一键(渠道交易号/我方支付单号)+金额+状态”逐笔匹配,分为:双方一致(通过)、我方有渠道无(渠道漏单/在途)、渠道有我方无(我方漏记/回调丢失)、金额不一致(差异);
    • 差异处理:
      • 在途/延迟(23:55~00:05 边缘交易):入缓冲池,次日(T+2)再核对;
      • 渠道有我方无:主动向渠道查单(查单接口),确认后补记/入账;
      • 我方有渠道无:人工核查(可能渠道退款/撤销);
      • 金额不一致:告警人工介入,绝不自动调整(资金安全);
    • 对账报告:差异分类汇总、金额核对(总额=明细和)、生成报表;
  2. 架构:定时任务(日终)→ 拉取文件 → 解析 → 流式/分批比对 → 差异库 → 查单补偿 → 人工处理平台;
  3. 进阶:实时对账(流式比对:每笔支付完成即与渠道结果比对,提前发现异常);
  4. 保障:对账是“最终一致的最后防线”,必须自动化+告警+人工工单闭环;
  5. 容量估算(步进):假设日支付 30 万笔,对账文件约 30 万行 × 200B ≈ 60MB/日,解析+比对单机分钟级可完成;差异率经验通常 0.01%~0.1%(数十~数百笔/日),必须有人工工单容量。存储:差异记录+报告保留 180 天~数年(合规),日增很小。实时对账则每笔支付完成后订阅 MQ 与渠道结果比对,峰值与支付 TPS 同量级。
  6. 数据与接口(口述可带):差异单字段 = {biz_no, channel_no, diff_type, our_amount, channel_amount, status, owner};接口 POST /recon/run?date=、GET /recon/diffs?date=&type=、POST /recon/diff/{id}/handle。失败与降级:渠道文件拉取失败自动重试并告警,T+1 文件缺失则挂起当日对账而非空跑;解析坏行进死信人工;查单接口超时任务延后重试;对账任务自身失败必须告警到值班,禁止静默跳过。

【原理溯源】

  • 为什么对账是必须的而不是可选的? 分布式系统中,我方状态与渠道状态通过不可靠网络同步,掉单、延迟、重复、篡改都可能发生。运行时机制(幂等、重试)降低概率但无法归零。对账用“双方独立记录事后核对”发现残余不一致,是最终一致的最后防线。没有对账的支付系统等于没有安全网。
  • 为什么要有“缓冲池”处理跨日交易? 日切点附近的交易(23:59 下单,00:01 回调)在我方记在 T 日、渠道记在 T+1 日,直接比对会误报。缓冲池让边缘交易再等一天,T+2 仍不一致才算真差异。这是用时间换准确率。
  • 为什么金额不一致绝不自动调整? 资金差异可能来自欺诈、篡改、严重 bug。自动调账会掩盖问题甚至扩大资损。正确做法是告警+人工,查清根因后再修复。资金场景的自动化边界必须保守。
  • 为什么要“渠道有我方无”主动查单? 可能是回调丢失导致我方漏记。查单接口向渠道要真相,确认支付成功则补记入账。这比等用户投诉快得多。
  • 为什么还要实时对账? 日终对账延迟 24h,大额异常发现太晚。实时对账(支付完成即比对)可把发现时间从 T+1 压到分钟级,适合核心资金链路。日终仍是全量兜底。

【选型判断树】

对不上怎么分类?
├─ 双方一致 → 通过
├─ 我方有渠道无 → 人工核查(可能渠道撤销)
├─ 渠道有我方无 → 查单接口确认 → 补记
├─ 金额不一致 → 告警人工,绝不自动改
└─ 跨日边缘 → 缓冲池 T+2 再对

架构:
定时拉文件 → 解析 → 分批比对 → 差异库 → 补偿/人工
进阶:实时流式比对

判断口诀: 四类差异、缓冲池跨日、查单补偿、金额差人工。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“对账是支付系统的最终一致最后防线”
0:30–1:30流程拉文件→导流水→逐笔比对→差异分类
1:30–2:30差异处理四类差异的不同策略;缓冲池;查单
2:30–3:30架构定时任务+差异库+人工平台
3:30–4:30实时对账与原则分钟级发现;金额差绝不自动改
4:30–5:00收尾“一句话:对账发现残余不一致,处理要保守”

【关键数字】

参数经验值说明
对账周期日终 T+1可加实时
缓冲池跨日 1~2 天防误报
差异类型4 类一致/我方多/渠道多/金额差
查单渠道 API补记依据
金额差人工绝不自动
对账文件规模日支付笔数×约200B例:30万行≈60MB/日
差异率经验0.01%~0.1%日数十~数百笔,需人工容量
文件保留合规180天~数年差异单与报告长期可查

【追问链】(三层)

L1|“为什么需要对账?” → 网络/回调不可靠,运行时机制无法归零不一致;对账用双方独立记录事后核对,是最后防线。

L2|“跨日交易怎么避免误报?” → 缓冲池,T+2 再核对;或按时间窗模糊匹配。

L3|“对账发现大量掉单,系统性问题怎么处理?” → 立即告警,查回调链路/查单接口/消息积压;批量补记走人工审核;根因修复后补跑对账。建立差异率监控,超过阈值熔断相关业务。

【评分标准】

档位答案特征
60 分知道要和渠道对账
80 分四类差异、缓冲池、查单补偿
95 分金额差人工原则、实时对账、差异率监控与熔断、与账务模型呼应

【关联题】

  • 上游: 第 132 题(支付)——对账的数据来源
  • 同构: 第 159 题(积分对账)
  • 下游: 第 219 题(日终对账,国企/金融版)

【自测】

  1. 判断对错:对账发现金额差,应自动调整账目保证平。 参考答案: 错。资金差异必须人工核查,自动调整会掩盖欺诈与严重 bug。
  2. 缓冲池解决什么问题? 参考答案: 跨日边缘交易的时间归属差,避免误报。
  3. “渠道有我方无”怎么处理? 参考答案: 调渠道查单接口确认,成功则补记入账。

134. 大促发券 1 秒被抢光,核销还要防重复(优惠券系统) ​

【考察内容】优惠券是电商高频设计题(与秒杀结构类似但细节不同)

【题目】大促要发 100 万张满减券,用户 0 点准时抢,要求不超发、不重复领、核销时不能重复使用。券的库存、领取资格、核销三个环节分别怎么设计?并发和一致性怎么保证?

【参考答案】

  1. 核心流程:发券(运营创建活动+券模板+库存)→ 领券(用户领取)→ 核销(下单使用)→ 过期;
  2. 关键设计:
    • 券模板表 + 用户券表(user_id、券模板 id、状态:未用/已用/过期、领取时间、过期时间);
    • 一人一券防重复领取:用户券表唯一索引(user_id+模板 id);
    • 防超发:券库存 Redis 预扣(DECR 后若 <0 判超发并回补;语义上等价于「先判 ≥1 再扣」,但两步必须在同一条原子命令/Lua 里完成)+ DB 条件更新兜底(stock>0)+ 对账。≥100 万 QPS 时单 key 必被打穿:库存按 stock:{模板id}:{0~N} 分 ≥10 片(单实例 10 万 QPS 口径),售罄后由后台借调/回收,对账以 DB 为准;
    • 高并发抢券:领券请求先 Redis 校验+扣库存 → MQ 异步发券落库(削峰)→ 用户收到结果;
  3. 核销:下单时校验券(状态+有效期+使用条件)→ 标记已用(原子 UPDATE 状态,乐观锁防并发重复使用)→ 金额计算(订单金额-券金额,金额放分);
  4. 过期处理:定时任务扫描批量标记过期(分页批量,避免大事务);或懒更新(用券时校验过期);
  5. 扩展:券码兑换(预生成券码批量导入)、组合优惠、风控(黄牛领券:限频+设备指纹)、数据统计(领取率/核销率);
  6. 分布式事务:领券与下单用券是不同服务——事件驱动+幂等+对账;
  7. 容量估算(步进):假设大促发券 100 万张、1 秒抢完 → 峰值领券请求要按 ≥100 万 QPS 设计(100 万张 1 秒发完,光是成交就是 1e6 次扣减/s,含重复点击与脚本只会更高);若按 10 万 QPS 设计,100 万张至少要 10 秒才发得完,与题干「1 秒被抢光」矛盾;DB 异步落库按消费能力 1~3 万 TPS 批量写,积压用 MQ 削峰。存储:用户券 5000 万张 × 100B ≈ 5GB DB + Redis 状态;券模板表很小。核销峰值与日常下单同量级(数百 TPS)。
  8. 失败与降级:Redis 预扣成功但 MQ/落库失败 → 对账任务按预扣流水补发券;Redis 与 DB 库存不一致以 DB 条件更新为准并告警;抢券接口过载时降级为排队页(返回排队号),避免雪崩到交易主链路;风控规则超时可先放行热门券再异步标记可疑订单;核销服务故障时订单侧禁止用券直接下单(宁可不用券也不能错扣)。

【原理溯源】

  • 为什么库存要用 Redis 预扣而不是直接打 DB? 0 点瞬时抢券可达数十万 QPS,DB 行锁 stock>0 条件更新会成为瓶颈。Redis DECR 原子且内存级,把并发挡在 DB 之外。DB 仍做条件更新兜底,防止 Redis 与 DB 不一致导致超发。
  • 为什么要一人一券唯一索引? 防重复领是业务硬约束。前端按钮置灰可被绕过,必须服务端唯一约束:(user_id, template_id) 唯一索引,重复插入失败即已领过。
  • 核销为什么要原子状态流转? 同一张券可能被两个订单并发使用(用户双开)。用 UPDATE ... SET status=used WHERE id=? AND status=unused,影响行数为 0 即已被用,保证一券一单。
  • 为什么要 MQ 异步发券? 抢券峰值打 DB 会挂。Redis 扣成功即返回“排队中”,MQ 消费异步落库发券。用户体验是“抢到了稍后到账”,系统用异步换稳定。需对账保证 Redis 扣了的最终都能落库。
  • 过期为什么推荐“懒更新+定时扫描”结合? 纯定时扫描有延迟且大表扫描重;纯懒更新则过期券长期占库存展示。结合:用券时校验过期(懒),定时批量标记(扫描),兼顾实时性与成本。

【选型判断树】

领券高并发:
Redis DECR 预扣 → 成功则 MQ 异步落库
DB 唯一索引(user_id, template_id)防重复
对账:Redis 库存 vs DB

核销:
条件更新 status: unused→used
校验有效期+使用条件

过期:
懒校验 + 定时批量标记

判断口诀: Redis 扛抢、唯一索引防重、条件更新核销、对账兜底。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“优惠券是库存预扣+资格校验+核销状态机”
0:30–1:30发券与库存Redis 预扣+DB 兜底+对账
1:30–2:30领取资格唯一索引一人一券;MQ 削峰
2:30–3:30核销条件更新防重复使用;金额分
3:30–4:30过期与风控懒更新+扫描;限频防黄牛
4:30–5:00收尾“一句话:抢用 Redis,资格用唯一索引,核销用状态机”

【关键数字】

参数经验值说明
抢券峰值按「发放量÷可容忍秒数」算;本题 100 万张/1s → 1e6 级必须 Redis 分片预扣
库存兜底DB stock>0防 Redis 漂移
一人一券唯一索引硬约束
核销状态unused→used 原子防并发
金额分精度
过期懒+定时平衡
落库消费DB批量TPS约1~3万TPS,MQ削峰
用户券存储张数×约100B5000万张约5GB

【追问链】(三层)

L1|“Redis 扣成功但落库失败怎么办?” → MQ 重试+死信;对账任务把“Redis 已扣但无用户券记录”的补发或回滚库存。

L2|“同一张券两个订单同时核销怎么办?” → 条件更新 WHERE status=unused,只有一个成功。

L3|“黄牛用脚本抢光怎么办?” → 设备指纹、IP/账号限频、验证码、风控模型;库存分批发放;预约制(见 157 题)。

【评分标准】

档位答案特征
60 分知道要控制库存防超发
80 分Redis 预扣+唯一索引+核销状态机
95 分对账闭环、MQ 削峰、过期懒更新、风控防黄牛;能说清领券与秒杀的三点差异(券有核销态、多模板、先抢资格再抢量)

【关联题】

  • 同构: 第 150 题(抽奖)、第 156 题(红包)、第 157 题(预约资格)
  • 上游: 第 131 题(订单)——核销发生在下单
  • 治理: 第 147 题(风控)

【自测】

  1. 判断对错:抢券可以先查库存再扣减。 参考答案: 错。非原子会超发;必须原子 DECR 或条件更新。
  2. 一人一券怎么保证? 参考答案: 用户券表 (user_id, template_id) 唯一索引。
  3. 核销怎么防重复使用? 参考答案: UPDATE status=used WHERE status=unused 条件更新。

135. 加购/改数量/跨端同步,购物车要稳定可靠(购物车设计) ​

【考察内容】购物车业务建模

【题目】电商 App 购物车:用户加购、改数量、勾选部分结算,且手机/网页购物车要同步。未登录用户的购物车怎么办?存储用 Redis 还是 DB?结算时的价格/库存校验怎么做?

【参考答案】

  1. 数据结构:Redis Hash(key=cart:{userId},field=SKU ID,value=数量+勾选状态)——支持 HINCRBY 增减、HGETALL 读取、原子操作;DB 持久化购物车表(user_id、sku_id、quantity、checked、create_time、唯一索引 user_id+sku_id);
  2. 读写:商品信息(价格、库存、图片)查缓存(商品服务),购物车列表=购物车条目+实时商品信息组装;价格实时校验(加入购物车时价格 vs 结算时价格,取低或提示);
  3. 一致性:购物车以 Redis 为读写主存储(高频),异步落库(MQ/定时),对账合并;登出/跨端:以 DB 为准合并(Redis 丢失恢复);
  4. 结算:勾选商品生成订单(批量校验库存与价格 → 创建订单 → 清理购物车已购项);购物车结算幂等(防重复提交);
  5. 并发:加购频控(防刷);大促购物车数量限制(如 200 件);
  6. 扩展:失效商品提示(下架/降价)、凑单推荐、未登录购物车(本地→登录后合并);
  7. 容量估算(步进):假设 DAU 1000 万,40% 活跃购物车、人均 20 SKU → 活跃 Hash key 约 400 万;单 key Hash 20 个 field × 约 50B ≈ 1KB → Redis 内存约 4~5GB 量级(加过期与复制约 ×2)。加购峰值:大促浏览高峰,人均加购动作峰值后约 数千~上万 QPS HINCRBY。DB 持久化异步批量,写放大可控。结算峰值与下单同量级。
  8. 失败与降级:Redis 故障 → 从 DB 恢复购物车(允许丢失未落库的最近几秒加购,产品可接受);商品服务超时 → 列表展示缓存快照并标记“价格/库存待刷新”;结算校验失败逐项剔除并提示,不整单失败;跨端合并冲突以数量较大者或 DB 权威合并,合并接口幂等;大促限购物车条数与结算商品数,防大 Key 与结算超时。

【原理溯源】

  • 为什么购物车主存储用 Redis Hash? 购物车是“用户维度的 KV 集合”:key=用户,field=SKU,value=数量。Hash 的 HGET/HSET/HINCRBY/HGETALL 与业务操作一一对应,原子且 O(1)~O(N)。比 String 存 JSON 省空间、比 Set 能存数量。这是数据结构与访问模式的匹配。
  • 为什么 Redis 为主、DB 为辅? 加购/改数量是高频写(浏览即加购),读也是高频(每次进购物车)。Redis 扛住读写;DB 做持久化与跨端合并的权威。Redis 丢失可从 DB 恢复,体验上可容忍“极端情况丢购物车”(低价值数据)。
  • 为什么结算时必须实时校验价格和库存? 购物车里的价格是加购时的快照,可能已涨价/降价/下架。用旧价格结算会造成资损或用户投诉。结算瞬间向商品服务要最新价与库存,价格取低或提示用户,库存不足则剔除。
  • 未登录购物车怎么办? 本地存储(Cookie/LocalStorage)或临时 Redis key(设备 ID)。登录后合并:以登录用户 DB 为准,本地条目并入(数量相加或取较大)。合并要幂等,防重复。
  • 为什么购物车要限数量(如 200)? 防滥用(脚本塞满)、防大 Key、控制结算复杂度。产品上限即可。

【选型判断树】

存储:
登录用户 → Redis Hash 为主 + DB 异步落库
未登录 → 本地/临时 key → 登录后合并

结算:
勾选 SKU → 实时校验价格库存 → 创建订单 → 清理已购
幂等防重复提交

跨端:以 DB 为准合并

判断口诀: Hash 存购物车,Redis 扛读写,结算实时校验,DB 做权威合并。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“购物车是用户维度高频读写的 KV 集合”
0:30–1:30数据结构Redis Hash;DB 唯一索引;为什么选 Hash
1:30–2:30读写与一致性Redis 主 DB 副;异步落库;对账
2:30–3:30结算实时校验价格库存;幂等;清理
3:30–4:30未登录与跨端本地暂存;登录合并;DB 为准
4:30–5:00收尾“一句话:Hash 匹配访问模式,结算必须实时校验”

【关键数字】

参数经验值说明
购物车上限100~200 SKU防滥用
Redis TTL7~30 天活跃用户续期
落库异步批量降低写压力
价格校验结算时实时防资损
合并策略DB 为准跨端
活跃购物车KeyDAU×活跃率例:1000万×40%=400万
Redis内存key数×单key约1KB约4~5GB(复制后×2)
加购峰值大促浏览放大数千~上万QPS HINCRBY

【追问链】(三层)

L1|“Redis 购物车丢了怎么办?” → DB 兜底恢复;极端丢失可提示重新加购(低价值容忍)。关键路径保证 DB 定期落库。

L2|“结算时价格变了怎么办?” → 实时校验,涨价提示、降价取低(或按业务规则);不可用旧价结算。

L3|“手机和网页同时改数量,以谁为准?” → 以服务端最后写入为准(时间戳/版本);或冲突时提示用户。简单场景 DB 权威合并。

【评分标准】

档位答案特征
60 分知道购物车要存起来
80 分Hash 结构、Redis+DB 分层、结算校验
95 分未登录合并、跨端一致性、幂等结算、失效商品提示

【关联题】

  • 上游: 第 131 题(订单)——结算生成订单
  • 同构: 第 148 题(分片上传)——用户维度状态管理
  • 缓存: 第 29 题(缓存策略选型)、第 27 题(缓存一致性)

【自测】

  1. 判断对错:购物车结算可以用加购时的快照价格。 参考答案: 错。必须实时校验,否则资损或投诉。
  2. 为什么用 Hash? 参考答案: 用户→SKU→数量 的天然映射,支持原子增减与全量读取。
  3. 未登录购物车怎么并入登录账户? 参考答案: 本地暂存,登录后与 DB 合并(数量相加或取大),DB 为准。

136. 千万商品要按关键词/价格/分类搜,还要排序(搜索引擎) ​

【考察内容】搜索系统是互联网核心设计题

【题目】电商要上搜索:千万商品,用户按关键词搜、按价格区间/分类过滤、按销量/价格排序,要求毫秒级返回。搜索链路(分词、索引、查询、排序)怎么设计?和数据库 LIKE 查询比强在哪?

【参考答案】

  1. 架构:数据同步(MySQL binlog → Canal → ES)→ 搜索服务(Query 解析 → 检索 → 排序)→ 结果组装(查商品详情缓存);
  2. 倒排索引(核心):词项 → 文档列表;分词(中文:IK 分词器;拼音、同义词扩展);检索时按词查倒排表合并(AND/OR);
  3. 多条件过滤:Filter 缓存(价格区间、分类、库存)用 BKD 树/倒排做过滤,过滤后再相关性排序(BM25);
  4. 排序:默认相关性(BM25 分数)、按价格/销量/时间排序(ES sort)、加权排序(销量×权重+相关性);
  5. 性能:ES 集群分片(按商品 ID 路由)、查询缓存、索引分页用 search_after(深分页性能问题同 MySQL);
  6. 一致性:Canal 同步延迟秒级(可接受),删除/下架商品即时同步(MQ 强制刷新);
  7. 扩展:搜索建议(suggest)、同义词、纠错(编辑距离)、个性化(用户偏好加权)、价格排序稳定性(加 ID 次级排序防抖动);
  8. 容量估算(步进):假设商品总量 5000 万 SKU,标题+属性+描述约 2KB/文档 → 原始文本约 100GB 级(5000 万 × 2KB = 1e11 B)(ES 倒排+正排膨胀后按副本另计,通常 1~2 倍,可按分片水平扩);在线索引热数据可只保留在售 2000 万。搜索峰值:DAU 500 万 × 10 次搜索/日 → 日搜索 5000 万 → 均值 5e7÷86400 ≈ 579 QPS,再按搜索类业务晚高峰集中 8~14 倍折算 → 5000~8000 QPS(这个峰均比是假设,答题要先给口径再落数),ES 集群按分片分散,单分片 QPS 经验数百。同步:Canal 延迟秒级,日变更商品 100 万级对 ES 写入压力可控。
  9. 失败与降级:ES 集群故障 → 搜索降级为分类浏览/热门推荐兜底页,或只读缓存的热搜词结果;Canal 延迟变大时强依赖上下架的场景走 MQ 主动刷新;排序服务超时退回 BM25 默认序;大促时关闭重个性化/聚合分析,保核心检索路径。

【原理溯源】

  • 为什么 MySQL LIKE 不行? LIKE '%关键词%' 无法走 B+ 树索引,必然全表扫描,千万级商品毫秒级不可能。且 LIKE 无相关性排序、无分词、无同义词、无聚合分析。搜索引擎的倒排索引把“文档→词”变成“词→文档”,检索变成词项定位+列表合并,复杂度与库大小弱相关。
  • 倒排索引为什么快? 建索引时对每个词项记录出现的文档列表(posting list)。查询“手机 壳”时,分别取两个词的 posting list 做交集,只命中同时包含两词的文档。这避免了扫描全部文档。配合压缩与跳表,交集也高效。
  • 为什么 Filter 和 Query 要分离? Query(关键词相关性)需要打分,成本高;Filter(价格区间、分类、库存)是布尔过滤,结果可缓存。ES 中 filter context 不算分且可缓存 bitset,先过滤后打分,性能好一个数量级。
  • 为什么要 Canal 同步? 业务库是 MySQL,搜索库是 ES,两者数据模型不同。Canal 订阅 binlog 准实时同步,业务无侵入。延迟秒级对商品搜索可接受;下架等强一致需求可走 MQ 主动失效。
  • 深分页为什么慢? from=10000&size=20 要在每个分片取 10020 条再全局排序,协调节点内存与 CPU 爆炸。search_after 用上一页最后一条的排序值作游标,每页只取 size 条,与页深无关。

【选型判断树】

全文检索 → ES 倒排(必须)
精确过滤(价格/分类)→ filter context,可缓存
排序:
├─ 相关性 → BM25
├─ 价格/销量 → ES sort
└─ 综合 → 加权公式;价格排序加 ID 次级键防抖

分页:search_after,禁止深分页 from+size
同步:Canal 准实时 + 强一致 MQ 失效

判断口诀: 倒排管检索,filter 管过滤,search_after 管深分页,Canal 管同步。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“搜索核心是倒排索引+分词+过滤排序分离”
0:30–1:30倒排与分词词→文档;IK;同义词扩展
1:30–2:30查询链路Query 解析→检索→Filter→BM25 排序
2:30–3:30同步Canal binlog;下架即时失效
3:30–4:30性能分片、缓存、search_after
4:30–5:00收尾“一句话:倒排解决 LIKE 不可扩展,filter 与 sort 分离保性能”

【关键数字】

参数经验值说明
商品量级千万~亿ES 集群分片
检索延迟< 50ms用户无感
同步延迟秒级 Canal可接受
深分页from+size 建议 < 1 万超出用 search_after
分词IK 中文+拼音+同义词
缓存热 Query 结果短 TTL挡重复查询
索引规模SKU数×单文档约2KB例:5000万→原始约100GB(5e7×2KB=1e11B)
搜索峰值日搜索量/86400×峰均比例:5000万/日→5000~8000QPS
单分片QPS经验数百按分片水平扩展

【追问链】(三层)

L1|“为什么不用 MySQL 全文索引?” → 中文分词弱、相关性差、扩展性与聚合能力不足;千万级性能不够。ES 是专用搜索引擎。

L2|“商品下架了搜索还能搜到怎么办?” → Canal/MQ 主动删除或更新文档;读时校验商品状态兜底。

L3|“价格排序时同价商品顺序乱跳怎么办?” → 加唯一键(商品 ID)作次级排序,保证稳定。

【评分标准】

档位答案特征
60 分知道用 ES
80 分倒排索引原理、filter 分离、Canal 同步
95 分search_after、同义词纠错、个性化加权、下架即时失效、与推荐结合

【关联题】

  • 同构: 第 140 题(日志检索)、第 160 题(知识库检索)
  • 上游: 第 131 题(商品/订单)
  • 对比: 数据库索引章节

【自测】

  1. 判断对错:千万商品用 LIKE '%手机%' 加索引就能毫秒返回。 参考答案: 错。前置通配无法走索引,必须倒排搜索引擎。
  2. Filter 和 Query 分离的好处? 参考答案: Filter 可缓存不算分,先过滤后打分,性能更好。
  3. 深分页怎么优化? 参考答案: search_after 游标分页,避免 from+size 全量排序。

137. 全网爬取数据,URL 去重和限速怎么做(爬虫系统) ​

【考察内容】海量数据处理与分布式任务设计

【题目】公司要爬全网公开数据(新闻/商品)。全网上亿 URL:怎么去重(布隆/一致性哈希分片)、怎么限速不把对方站点打挂、怎么分布式调度与失败重试、怎么解析抽取。请设计整体架构。

【参考答案】

  1. 流程:URL 队列(种子)→ DNS/抓取(HTTP 下载)→ 解析(提取链接+内容)→ 内容存储 → 新链接入队;
  2. URL 去重:海量 URL(十亿级)用布隆过滤器(内存友好,容忍小误判)或 Redis Set 分片;
  3. URL 队列:分布式队列(Kafka/RabbitMQ)分发;按域名/优先级分队列(热点站优先级高);
  4. 分布式:多 Worker 抓取,任务分配(一致性哈希/按域名分片);抓取节点无状态,可水平扩展;
  5. 限速与礼貌爬取:每域名并发限制+抓取间隔(robots.txt 遵守),防止被封/给目标站造成压力;
  6. 健壮性:请求重试(退避)、代理池(反爬:IP 轮换)、User-Agent 伪装、异常处理;
  7. 存储:原始 HTML 存对象存储(按 URL hash 分桶)、解析后结构化数据入 DB/ES;
  8. 监控:抓取速率、失败率、去重率、队列积压;
  9. 容量估算(步进):假设全网目标 URL 规模 10 亿级、日新发现 5000 万 URL。布隆过滤器:误判率 1% 时约 10 bit/元素 → 10 亿 URL ≈ 1.2GB 内存,可多机分片或 Redis bitmap。发现量≠必抓量:日新发现 5000 万 URL,去重+价值筛选后实抓约 1000 万页/日(其余进待抓队列按优先级放量——这里必须同时给出队列的收口口径:日发现 5000 万、实抓 1000 万意味着每天净增 4000 万,不设 TTL/丢弃口径一年就积压 146 亿。正解:待抓队列按时效分层(新闻类 7 天过期即丢、泛站按配额丢弃),并在发现端就限流,把「放量」改成「按配额与时效丢弃」);若要 5000 万全抓,对象存储要按 5000 万×200KB≈10TB/日 规划,抓取也要 579 页/s,两者差 5 倍。抓取吞吐:单 Worker 数十~数百 QPS,按礼貌限速(单域名 0.5~1 QPS)扩展 Worker 数,集群总抓取约 数千页/分钟(日 1000 万页 ÷ 1440 ≈ 7000 页/分)可配。原始 HTML 均页 200KB → 日抓 1000 万页 ≈ 2TB/日 对象存储,需生命周期策略。
  10. 失败与降级:目标站封禁/反爬升级 → 任务进冷却队列,不硬闯;布隆误判导致漏抓 → 定期全量重扫低价值域补偿;抓取队列积压时只降低优先级并发会让积压更长(分母没变、分子变小),要配套「按配额丢弃低价值待抓项+提高实抓吞吐」才收敛;解析服务故障时原始页已落对象存储,可回放补解析;合规变更(robots/法律)必须支持一键停抓。

【原理溯源】

  • 为什么用布隆过滤器而不是 Set? 十亿级 URL 若用 Redis Set,每个 URL 几十字节,内存要几十 GB 且难以水平扩展。布隆用位数组+多个哈希,空间极省(如 1% 误判率约每元素 10 bit),代价是假阳性(把没抓过的判成已抓过→漏抓)和不支持删除。爬虫可接受少量漏抓,用定期重建/全量重扫补偿。
  • 为什么要按域名限速? 不限速会把目标站打挂或触发封禁,既不道德也导致抓取失败。robots.txt 与每域名 QPS 限制是“礼貌爬取”的底线,也是法律/合规要求。
  • 为什么按域名分片调度? 同一域名的抓取要串行限速,按域名哈希到固定 Worker,保证同一站点的并发可控,也便于维护每域名的抓取状态(限速计数、robots 缓存)。
  • 为什么要代理池与 UA 伪装? 反爬会封 IP、识别 UA。代理池轮换出口 IP,UA/指纹伪装降低被识别概率。但核心仍是限速与合规,伪装是辅助。
  • 原始 HTML 为什么要存对象存储? 原始页体积大、访问频率低(通常只解析一次),对象存储成本低且容量近乎无限。结构化结果才进 DB/ES 供检索。

【选型判断树】

URL 去重:
├─ 十亿级、可容少量漏抓 → 布隆过滤器
└─ 精确、量级可控 → Redis Set 分片

调度:
按域名哈希到 Worker + 每域名限速 + robots

存储:
原始 HTML → 对象存储
结构化 → DB/ES

判断口诀: 布隆省内存,按域名限速与分片,原始存对象存储。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“爬虫是海量 URL 去重 + 分布式调度 + 礼貌限速”
0:30–1:30主流程种子→抓取→解析→存储→新链入队
1:30–2:30去重布隆原理与假阳性;Set 对比
2:30–3:30调度与限速域名分片;限速;robots
3:30–4:30健壮性重试、代理池、监控
4:30–5:00收尾“一句话:空间换时间的布隆去重,域名维度的礼貌抓取”

【关键数字】

参数经验值说明
URL 量级亿~十亿布隆必要
布隆误判~1%可接受漏抓
每域名并发1~3礼貌
抓取间隔秒级防封
重试指数退避 2~3 次健壮
待抓队列Kafka/持久队列削峰解耦,但必须带 TTL/配额丢弃:日发现 5000 万、实抓 1000 万=净增 4000 万/日,不收口一年积压 146 亿
布隆内存URL数×约10bit(1%误判)10亿URL≈1.2GB
单域名限速0.5~1 QPS礼貌爬取+防封
原始页存储日抓页数×均页200KB例:1000万页≈2TB/日

【追问链】(三层)

L1|“布隆误判会漏抓吗?” → 会。假阳性把新 URL 判成已抓过而跳过。用定期重建布隆、全量重扫、或布隆+白名单补偿。

L2|“被目标站封了 IP 怎么办?” → 代理池轮换;降低并发;遵守 robots;必要时退避重试。根本是限速不要触发反爬。

L3|“抓取任务失败率突然升高怎么排查?” → 看是全局(网络/DNS)还是单域名(对方反爬/变更);检查代理池健康、UA 被识别、页面结构变化导致解析失败。监控按域名维度的失败率。

【评分标准】

档位答案特征
60 分知道要 URL 去重和队列
80 分布隆过滤器、域名限速、分布式 Worker
95 分假阳性影响与补偿、robots 合规、代理池、监控维度、存储分层

【关联题】

  • 上游: 第 123 题(短链)——URL 处理
  • 同构: 第 153 题(延迟任务)——分布式任务调度
  • 存储: 第 140 题(日志/对象存储)

【自测】

  1. 判断对错:用 Redis Set 存全部待抓 URL 最简单可靠。 参考答案: 十亿级内存爆炸;应布隆或分片+可接受误判。
  2. 为什么要按域名限速? 参考答案: 礼貌与合规,避免打挂目标站或被封。
  3. 布隆的缺点是什么? 参考答案: 假阳性导致漏抓;不支持删除;需补偿机制。

138. 几十个服务统一入口,路由/鉴权/限流都在这层(API 网关) ​

【考察内容】网关是微服务架构高频题

【题目】公司几十个微服务暴露给 App 调用,要求统一入口:路由转发、统一鉴权、限流、灰度、日志。网关怎么设计?核心能力有哪些?怎么避免网关自身成为瓶颈和单点?

【参考答案】

  1. 定位:所有请求的统一入口,承担横切关注点,业务服务专注业务;
  2. 核心能力:
    • 路由转发:按路径/服务名路由到下游(负载均衡、重试、超时);
    • 统一鉴权:Token 校验(JWT 验签/OAuth)、白名单、权限校验(RBAC);
    • 限流熔断:全局限流(Redis 计数器)、按用户/接口限流;下游故障熔断降级;
    • 灰度发布:按用户/流量比例路由到新旧版本服务(权重/标签路由);
    • 协议转换:HTTP ↔ RPC(Dubbo)、请求/响应改写、格式校验;
    • 日志与监控:全链路 TraceID 生成与透传、访问日志、指标埋点;
  3. 实现:Spring Cloud Gateway(Reactive,性能好)/ Zuul / Kong / APISIX / 自研(Netty);
  4. 高可用:网关集群部署(无状态)、自身限流保护(防被打爆)、降级(返回兜底页);
  5. 注意:网关不能放业务逻辑(变胖网关);认证信息(用户 ID)通过 header 透传给下游,下游不重复鉴权——前提是网关先把入站同名 header 剥离再写入:请求从公网进来,客户端完全可以自带 X-User-Id,若网关只在鉴权成功后「追加/覆盖不彻底」,下游就会照单全收,等于任何人都能冒充。规范做法:鉴权成功后先 remove 再 set 内部头;内部服务间再加一层 HMAC 签名或 mTLS+服务身份,别把「内网隔离」当成 header 不可伪造的保证;
  6. 容量估算(步进):假设全站业务峰值 20 万 QPS,网关集中入口 → 单实例(Reactive/Netty)经验 1~3 万 QPS(视鉴权/插件复杂度),集群至少 10~20 实例 并 N+1 冗余。延迟预算:网关自身 CPU 转发+鉴权宜 ≤5~10ms(P99),为下游留足时间。连接数:入口连接 ≈ 业务 QPS × 处理时延,长连接客户端场景另计。限流阈值按下游容量反推,网关全局限流略低于下游总和。
  7. 失败与降级:下游超时/熔断打开 → 网关返回统一错误页或静态兜底 JSON;配置中心故障时网关使用本地缓存的路由与限流规则;单实例 OOM/僵死由负载均衡摘除;灰度路由异常立即切回全量旧版本;网关自身成为热点时,可按域名/路径拆分多组网关(独立扩缩容),鉴权服务故障时白名单路径降级放行并告警。

【原理溯源】

  • 为什么需要网关而不是每个服务自己做鉴权限流? 几十个服务各自实现会重复开发、策略不一致、变更困难。网关把横切关注点(认证、限流、日志、灰度)集中,业务服务只关心业务。这是关注点分离在微服务入口层的应用。
  • 为什么网关要做成无状态集群? 有状态(会话粘在本机)会导致扩容困难、单点故障。无状态意味着任意实例都能处理任意请求,靠负载均衡水平扩展,挂一个不影响整体。会话状态外置到 Redis。
  • 为什么网关不能塞业务逻辑? 业务逻辑会拖慢入口、让网关变成单点瓶颈、发布耦合。网关只做“转发前的通用处理”,业务判断留给下游。一旦网关变胖,它就成了最脆弱的单点。
  • 鉴权后为什么要透传用户 ID 而不是下游再验一次? 下游重复验签浪费 CPU、且可能策略不一致。网关验完写入可信 header(如 X-User-Id),内网下游信任该 header。前提是内网隔离,header 不可被外部伪造。
  • 为什么网关自身也要限流? 网关是全站入口,被打爆则全站不可用。必须对总 QPS、单用户 QPS 设上限,超出返回 429/排队,保护自己和下游。

【选型判断树】

要不要上网关?
├─ 服务少(< 5)且简单 → 可 Nginx/Ingress 凑合
└─ 服务多、要统一鉴权限流灰度 → API 网关

选型:
├─ Java 技术栈、Reactive → Spring Cloud Gateway
├─ 多语言/高性能 → Kong / APISIX / 自研 Netty
└─ 云原生 → Ingress / Envoy

高可用:无状态集群 + 自身限流 + 降级页

判断口诀: 横切进网关,业务在下游;无状态集群,自身也要限流。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“网关是微服务统一入口,承担横切关注点”
0:30–1:30核心能力路由、鉴权、限流、灰度、日志
1:30–2:30实现与高可用SCG/Kong;无状态集群;自身限流
2:30–3:30身份透传验签后先剥离入站同名头再写 header,下游才信任
3:30–4:30反模式不塞业务;不变胖
4:30–5:00收尾“一句话:横切集中、业务下沉、入口无状态”

【关键数字】

参数经验值说明
网关实例≥ 2,多可用区消除单点
自身限流容量的 70%~80%保护
鉴权JWT 验签,毫秒级不查库
灰度按 header/比例标签路由
超时网关总超时 > 下游超时预留重试
单实例QPSReactive/Netty经验值约1~3万QPS,视插件复杂度
网关延迟预算自身P99宜≤5~10ms
集群规模全站峰值/单实例QPS×N+1例:20万→10~20实例

【追问链】(三层)

L1|“网关如何透传用户身份?” → 解析 Token 验签后先剥离入站同名 header、再写入 X-User-Id,下游才可信;「内网隔离」并不保证 header 不可伪造——请求从公网进来时客户端完全可以自带 X-User-Id,网关只追加不覆盖就会被冒充;内部服务间再加 HMAC 签名或 mTLS+服务身份。

L2|“网关挂了怎么办?” → 多实例+健康检查+L4 LB 兜底;极端时返回降级页;网关变更要灰度。

L3|“限流在网关做还是服务做?” → 两者都要:网关做全局限流与用户维度;服务做接口/资源维度细粒度限流。分层限流,互为兜底。

【评分标准】

档位答案特征
60 分知道网关做路由和鉴权
80 分题干五项能力齐全(鉴权/限流/路由/熔断降级/可观测);无状态集群
95 分身份透传、自身限流、不变胖原则、灰度实现;能说明网关与 sidecar 的职责边界

【关联题】

  • 上游: 第 155 题(SSO)、第 154 题(权限)
  • 同构: 第 3 题(限流设计)、第 39 题(分布式限流)——本题自身就是统一入口层
  • 对比: Service Mesh(sidecar)

【自测】

  1. 判断对错:网关里可以写一些简单的业务判断,方便统一处理。 参考答案: 错。网关塞业务会变胖、成瓶颈、发布耦合;业务放下游。
  2. 为什么要无状态? 参考答案: 任意实例可处理任意请求,易扩容、抗单点。
  3. 下游为什么信任 X-User-Id? 参考答案: 网关统一写入+入站先剥离同名 header;前提不是「内网隔离」,而是「外部值进不了内部头」——要么网关先 remove 再 set,要么内层加 HMAC 签名/mTLS 服务身份,否则客户端自带 X-User-Id 就能冒充。

139. 营销/订单/站内信多通道通知,统一推送中心(通知中心) ​

【考察内容】通用通知能力设计

【题目】业务方都要发通知:订单提醒(App 推送)、营销活动(Push+短信)、风控告警(站内信+短信)。需要一个统一通知中心:通道管理、模板、频率限制(防骚扰)、失败重试。怎么设计?

【参考答案】

  1. 需求:多渠道(App 推送 APNs/FCM/厂商通道、短信、站内信、邮件)、模板化、限频、优先级;
  2. 架构:业务方 → 通知中心 API(校验+模板渲染)→ 消息入库 → MQ 分发 → 各渠道发送服务 → 回执;
  3. 关键设计:
    • 模板管理:消息模板(短信签名/字数校验、推送标题),占位符渲染,防注入;
    • 去重与幂等:相同业务(订单号+通知类型)只发一次——Redis 去重+DB 唯一键;
    • 限频:同一用户渠道限频(如短信 1 条/分钟)、全局渠道限流(短信通道成本高);
    • 优先级:高优(支付/验证码)插队直发,低优(营销)错峰批量;
    • 失败重试:发送失败重试(退避)+死信人工;回执回调(送达/失败);
    • 退订管理:营销类需退订(合规);
  4. 高可用:发送服务独立部署(短信通道故障不影响主业务)、MQ 削峰(大促营销批量);
  5. 扩展:用户偏好(渠道开关)、A/B 文案、发送统计报表;
  6. 容量估算(步进):假设 DAU 1000 万,人均触发通知 5 条/日(含营销)→ 日通知 5000 万条;营销大促可瞬时放大 10~50 倍,发送峰值按 数万条/s 打 MQ,各渠道按自身配额消费(短信通道往往只开数百~数千 TPS)。存储:通知记录 5000 万/日 × 150B ≈ 7.5GB/日,站内信可按用户分表/分库。成本敏感:短信单条成本显著,必须全局限频与预算熔断。
  7. 失败与降级:某渠道(如短信供应商)故障 → 自动切换备用供应商或降级为站内信+推送;推送回执丢失则状态超时置失败并重试有限次;高优(验证码)队列与营销队列隔离,营销故障不影响验证码;限频服务故障时 fail-safe(可降级为只允许高优通道);模板渲染失败进死信人工,禁止带未渲染占位符外发。

【原理溯源】

  • 为什么要统一通知中心而不是业务各发各的? 各发各的会导致:模板不统一、无限频(用户被刷屏投诉)、通道故障互相影响、无统一监控退订。中心化后模板/限频/重试/退订一处治理,业务只调 API。这是能力平台化。
  • 为什么要模板化? 同类通知文案结构固定,模板+占位符可复用、可 A/B、可审核(防注入/敏感词)。直接拼字符串易出 XSS/注入且难管理。
  • 为什么要限频? 防骚扰是合规要求(尤其短信),也是用户体验底线。同用户同渠道限频 + 全局通道限流(短信有成本与通道配额)双层控制。
  • 为什么要优先级? 验证码/支付结果必须秒达;营销可延迟。混在一个队列会让高优被低优阻塞。分级队列或插队机制保证关键通知 SLA。
  • 为什么要回执? 发送成功≠送达。回执(送达/失败/退订)更新状态,支撑统计、重试与用户查询。无回执则失败不可见。

【选型判断树】

通知类型?
├─ 高优(验证码/支付)→ 直发通道,插队,短重试
├─ 中优(订单状态)→ MQ,标准重试
└─ 低优(营销)→ 错峰批量,限频,可退订

通道故障:
独立部署 + 多通道备份 + 死信人工

幂等:业务键唯一(订单号+类型)

判断口诀: 模板统一、限频防骚扰、优先级分队、回执闭环。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“通知中心是多通道、模板化、可治理的消息平台”
0:30–1:30架构API→入库→MQ→渠道服务→回执
1:30–2:30模板与幂等模板渲染;业务键去重
2:30–3:30限频与优先级用户限频+通道限流;高优插队
3:30–4:30可靠性重试死信;回执;退订合规
4:30–5:00收尾“一句话:中心化治理模板频控重试,业务只管调用”

【关键数字】

参数经验值说明
短信限频1 条/分钟,日上限合规
验证码 TTL5 分钟过期重发
重试指数退避 2~3 次死信
优先级至少 2 级(高/低)插队
回执必须统计与重试
日通知量DAU×人均触发条数例:1000万×5=5000万/日
发送峰值营销放大10~50倍数万条/s进MQ,渠道按配额
通知记录存储日量×约150B5000万×150B ≈ 7.5GB/日

【追问链】(三层)

L1|“短信和推送失败怎么处理?” → 重试+死信+日志;关键消息(验证码)有 TTL,超时提示重发;可切备用通道。

L2|“用户投诉被短信骚扰怎么办?” → 限频+退订+发送记录可查;营销类必须可退订;建立黑白名单。

L3|“大促营销要发百万条,怎么不打挂通道?” → MQ 削峰+批量接口+按通道 QPS 限速;错峰发送;预热通道配额。

【评分标准】

档位答案特征
60 分知道要调短信/推送 API
80 分统一中心、模板、限频、重试
95 分优先级分队、回执闭环、退订合规、通道容灾、与 158 题呼应

【关联题】

  • 同构: 第 158 题(邮件发送)——批量可靠发送
  • 上游: 第 131 题(订单事件)、第 147 题(风控告警)
  • 通道: 第 151 题(弹幕)——另一种实时推送

【自测】

  1. 判断对错:业务方可以直接调短信通道 API,更快。 参考答案: 错。缺模板/限频/重试/退订治理;应统一通知中心。
  2. 为什么要限频? 参考答案: 防骚扰合规+用户体验+控制通道成本。
  3. 发送成功但用户没收到,怎么发现? 参考答案: 回执机制更新状态;失败重试或提示。

140. 每天 TB 级日志,采集/存储/检索怎么做(日志系统) ​

【考察内容】大数据链路架构(ELK)

【题目】每天产生 TB 级日志:采集(怎么不丢不阻塞业务)、传输、存储(低成本可检索)、查询(按关键字/时间快速检索)。ELK 架构怎么搭?海量日志存储怎么省钱?

【参考答案】

  1. 采集:应用打日志(logback/log4j,格式统一 JSON 带 TraceID)→ Agent(Filebeat)采集本地文件 → Kafka(缓冲削峰,解耦);
  2. 传输:Kafka 按日志类型分 topic(access/error/business),分区按应用/机器;
  3. 处理:Logstash/Fluentd 清洗、解析、脱敏(手机号/身份证)、字段标准化;
  4. 存储与检索:ES(倒排索引,按天建索引,冷热分层:热 3 天 SSD、温 30 天、冷归档 OSS);元数据(大小、条数)入 DB 统计;
  5. 查询:Kibana(全文检索、聚合分析、Dashboard);按 TraceID 全链路检索(应用日志带 TraceID);
  6. 容量与治理:磁盘清理策略(按保留期删索引)、日志采样(高 QPS 接口降采样)、日志级别动态调整(线上开 ERROR);
  7. 可靠性:Kafka 高吞吐扛峰值;「可丢」只能写给 access/debug,而且要先把不可丢的边界划出来(题干问的正是「采集怎么不丢」):error/审计/合规类必须 at-least-once——本地文件先落盘+Agent 记录 offset(重启续传)+Kafka acks>=1(关键通道 acks=all+幂等 producer),拥塞时靠背压降速而不是丢弃;监控:采集延迟、Kafka 积压、ES 写入速率;
  8. 容量估算(步进):假设集群 500 台应用、单机日志 20GB → 日志量约 10TB/日(与题面“TB 级”吻合)。写入峰值:先做「日→集中时段」折算——10TB/日若集中在 6 小时,平均 1e13÷21600 ≈ 0.46GB/s;故障期日志放大 3~5 倍 → 1.4~2.3GB/s,故采集侧按 ≥2.5GB/s 峰值设计(覆盖上面算出的 1.4–2.3GB/s 上界并留余量)(少了「集中时段」这层,直接 10TB÷86400×3~5 只有 0.35~0.58GB/s,对不上) Kafka 分区数(单 partition 经验数十~百 MB/s,按 topic 拆分)。ES 热层:保留 3 天 × 10TB × 2 份(主分片 + 1 副本)≈ 60TB SSD 量级(再计倒排膨胀 1.2~2 倍,需按压测校准);温层 30 天机械盘/大容量节点;冷层 OSS 按 GB 级成本归档。
  9. 失败与降级:Kafka 积压告警后自动降采样/丢弃 debug 日志(丢弃白名单只允许 access/debug/info;error 与审计通道在任何降级档位都不丢,只允许延后);ES 写入拒绝时丢弃低优先级 access 日志保 error;磁盘打满前强制按保留期删索引;日志链路故障不影响业务(异步、可丢),但 error 日志通道要独立 topic 提高优先级;脱敏规则失败时 fail-closed(宁可丢弃含敏感字段的原始行)。

【原理溯源】

  • 为什么日志要先写本地文件再采集,而不是直接发远端? 直接发远端会在网络抖动时阻塞业务线程或丢日志。本地文件是缓冲,Agent 异步 tail 采集,业务无感。这是用本地磁盘换业务无侵入。
  • 为什么要 Kafka 缓冲? 日志洪峰(故障时暴增)会打爆 ES 写入。Kafka 高吞吐缓冲,削峰填谷,且解耦采集与消费(多消费者可重复消费做不同分析)。ES 按自己的节奏消费。
  • 为什么要按天建索引? 时间是日志最自然的分区维度。按天索引便于:① 过期直接删整个索引(高效);② 查询可限定索引范围;③ 冷热分层按索引粒度迁移。
  • 为什么要冷热分层? 热数据(近 3 天)查询频繁,放 SSD;温数据(30 天)机械盘;冷数据(归档)对象存储成本极低。90% 查询集中在近期,分层后成本可降一个数量级。
  • 为什么要脱敏? 日志可能含手机号、身份证、密码。合规(GDPR/个保法)要求脱敏。在采集/处理层统一脱敏,避免敏感数据进 ES。

【选型判断树】

链路:
应用本地文件 → Filebeat → Kafka → Logstash → ES → Kibana

存储分层:
热 3 天 SSD → 温 30 天 HDD → 冷归档 OSS

治理:
采样、级别动态调、按保留期删索引
脱敏:处理层统一

判断口诀: 本地缓冲、Kafka 削峰、按天索引、冷热分层、统一脱敏。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“日志系统是采集-缓冲-存储-检索的大数据链路”
0:30–1:30采集本地文件+Agent,不阻塞业务
1:30–2:30传输Kafka 削峰解耦;topic 分类
2:30–3:30存储ES 按天索引;冷热分层省钱
3:30–4:30检索与治理Kibana;TraceID;采样;脱敏
4:30–5:00收尾“一句话:本地保业务、Kafka 保峰值、分层保成本”

【关键数字】

参数经验值说明
日志量TB/天量级感
热数据3~7 天 SSD高频查
温数据30 天 HDD偶尔查
冷数据归档 OSS极少查
采样高 QPS 接口 1%~10%降成本;但采样/丢弃白名单只允许 access/debug/info,error 与审计通道不采不丢
脱敏采集/处理层合规
日志总量估算机器数×单机日志量例:500×20GB=10TB/日
采集峰值集中时段日均+故障放大 3~5 倍按 ≥2.5GB/s 设计(正文口径:10TB/日集中在少数时段算出 1.4~2.3GB/s 再留余量;只按 10TB÷86400×3~5 会算出 0.35~0.58GB/s,对不上)
热层容量保留天数×日志量×(1+副本数)3天×10TB×2≈60TB级(副本1=共2份)

【追问链】(三层)

L1|“为什么用 Kafka 不直接写 ES?” → 削峰+解耦+重放;ES 写入承受不住突发峰值,故障时日志暴增会打挂 ES。

L2|“TB 级日志存储成本怎么降?” → 冷热分层、按天删过期索引、采样、压缩、只索引必要字段。

L3|“怎么按 TraceID 查全链路?” → 日志统一格式带 TraceID;网关生成并透传;ES 按 TraceID 检索串联各服务日志。

【评分标准】

档位答案特征
60 分知道 ELK 三个字母
80 分完整链路、Kafka 缓冲、按天索引;并能画出「不可丢边界」(error/审计=本地落盘+Agent offset 续传+acks,靠背压不靠丢弃)
95 分冷热分层、采样治理、脱敏、TraceID 全链路、与监控联动

【关联题】

  • 同构: 第 141 题(监控告警)、第 136 题(搜索)
  • 上游: 全部微服务——日志来源
  • 排查: 第 163-169 题(故障排查)——靠日志定位

【自测】

  1. 判断对错:日志可以同步写 ES,保证不丢。 参考答案: 会阻塞业务且峰值打挂 ES;应本地缓冲+Kafka。
  2. 冷热分层的依据是什么? 参考答案: 访问频率随时间衰减;热 SSD、温 HDD、冷 OSS 降成本。
  3. 为什么要脱敏? 参考答案: 合规要求,防止敏感信息泄露。

141. 指标采集/告警/通知,一套监控系统怎么做(监控告警) ​

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

【题目】公司要建监控系统:采集各服务的指标(CPU/RT/QPS/错误率)、存储、阈值告警、通知到人。指标怎么采集(agent/埋点)与存储(时序库)?告警规则与防误报(收敛、静默)怎么设计?

【参考答案】

  1. 采集:Agent(Prometheus node_exporter/自研)采集指标(CPU、内存、QPS、RT、错误率、JVM、DB、MQ 积压)→ 推/拉模式(Prometheus 拉取,或推送到时序库);
  2. 存储:时序数据库(Prometheus/InfluxDB/OpenTSDB)——按时间+标签维度存储,高压缩、聚合查询快;
  3. 告警规则:阈值(RT>500ms)、趋势(连续 3 次)、同比环比(今日 vs 昨日同期);多指标组合(CPU 高+错误率高=异常);
  4. 告警引擎:规则评估(周期执行)→ 触发 → 去重/聚合(同一故障合并告警,防告警风暴)→ 通知(钉钉/短信/电话,按级别:P0 电话、P1 短信、P2 IM);
  5. 处理闭环:告警 → 认领 → 处理 → 恢复 → 复盘(事故记录);告警降噪(静默窗口、抑制规则);
  6. 可视化:Dashboard(Grafana)展示大盘;
  7. 进阶:智能告警(基线预测:流量波动检测,节假日自动调阈值)、根因分析(关联 TraceID/日志);
  8. 容量估算(步进):假设 500 服务实例 × 每实例约 200 个时间序列(CPU/JVM/接口/中间件)≈ 10 万活跃时间序列;15s 采集间隔 → 约 7000 samples/s(1e5÷15)写入 Prometheus/时序库,单机 Prometheus 可扛,超大规模需分片/联邦。存储:每 sample 压缩后约 1~2B,10 万序列 × 每日采样点 86400÷15=5760 个 → 约 5.8 亿 sample/日 × 1~2B ≈ 日增 0.6~1.2GB,保留 15 天热数据约 10~20GB。告警规则评估:每 15s~1min 扫一轮,规则数千条时注意分片评估。
  9. 失败与降级:监控系统自身故障必须告警到带外通道(短信/电话网关独立);指标写入失败时降采集频率或丢弃非关键指标;告警通知通道失败自动 failover 到备用 IM/电话;发布/压测窗口静默但 P0 相关规则可豁免静默;指标基数(label 基数)失控会打爆时序库,需 cardinality 限额与规范化 label。

【原理溯源】

  • 为什么用时序库而不是 MySQL 存指标? 指标是“时间戳+标签+值”的高频写入(每服务每秒多条)、按时间范围聚合查询。时序库按时间压缩、自动降采样、标签索引,写入与聚合都比关系库快几个数量级。MySQL 存指标会写爆且查不动。
  • 为什么告警要去重聚合? 一次故障可能触发几十条告警(同服务多实例、多指标)。不去重会“告警轰炸”,运维麻木(狼来了)。聚合把同一 label 维度的告警合并成一条,抑制规则在上级告警已发时抑制下级。
  • 为什么要静默窗口? 发布、压测、已知维护期间告警无意义且干扰。静默按时间/服务屏蔽,避免误报消耗值班精力。
  • 为什么阈值要“连续 N 次”而不是单次? 单次抖动(网络毛刺、GC)会误报。连续 3 次超阈值才告警,用时间换准确率。也可用滑动窗口 P99。
  • 为什么要分级通知? P0(全站不可用)必须电话叫醒;P1 短信;P2 IM 留言。分级保证紧急问题被立刻响应,非紧急不打扰。

【选型判断树】

采集:
├─ 基础设施 → node_exporter 拉取
└─ 业务埋点 → SDK 推送

存储:时序库(Prometheus/InfluxDB)

告警:
阈值+连续 N 次+组合条件
去重聚合+静默+抑制
分级通知:P0 电话 / P1 短信 / P2 IM

判断口诀: 时序库存指标,连续 N 次防抖,聚合防轰炸,分级保响应。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“监控是采集-存储-规则-通知-闭环”
0:30–1:30采集与存储Agent/埋点;时序库
1:30–2:30告警规则阈值、趋势、组合
2:30–3:30降噪去重聚合、静默、抑制
3:30–4:30闭环认领→处理→恢复→复盘;Grafana
4:30–5:00收尾“一句话:指标进时序库,告警要收敛,响应要分级”

【关键数字】

参数经验值说明
采集间隔15~30s生产常用自配值(Prometheus 出厂默认 scrape_interval 为 1m)
连续次数3 次防抖
P0 通知电话必须响应
告警聚合同 label 合并防风暴
静默发布/维护窗口防误报
时间序列规模实例数×每实例序列数例:500×200=10万活跃序列
采集写入序列数/采集间隔10万/15s≈7000 samples/s
指标存储增速samples×压缩1~2B日增约 0.6~1.2GB(10 万序列 × 5760 点/日 ≈ 5.8 亿 sample/日)

【追问链】(三层)

L1|“告警风暴怎么防?” → 相同 label 聚合、依赖抑制(上游挂了抑制下游)、静默窗口、分级。

L2|“阈值怎么定不拍脑袋?” → 基线(历史 P99)+ 业务 SLA;节假日/大促动态调;智能基线预测。

L3|“告警来了怎么定位根因?” → 关联 TraceID/日志/依赖拓扑;先看变更(发布/配置);Dashboard 下钻。

【评分标准】

档位答案特征
60 分知道采集指标+阈值告警
80 分时序库、规则引擎、通知分级
95 分降噪全套、闭环复盘、智能基线、与日志/链路追踪联动

【关联题】

  • 同构: 第 140 题(日志)——可观测性三支柱
  • 下游: 第 163-169 题(故障排查)——告警触发排查
  • SRE: 第 1 题(容量)、第 2 题(突增)

【自测】

  1. 判断对错:指标可以存 MySQL,简单通用。 参考答案: 高频写+时间聚合场景时序库更优;MySQL 会写爆。
  2. 为什么要连续 3 次才告警? 参考答案: 过滤单次抖动误报,用时间换准确率。
  3. P0 告警为什么用电话? 参考答案: 必须立刻叫醒值班,IM/短信可能被忽略。

142. 用户叫车,怎么把订单派给最合适的司机(派单系统) ​

【考察内容】派单是 LBS+实时系统代表题

【题目】打车 App 用户叫车,几公里内几百个司机在线,派给谁?要求接单率高、乘客等待短、司机不空跑。派单策略怎么设计(距离、方向、评分、负载均衡)?实时位置怎么维护?订单和司机如何匹配(分桶/索引)?

【参考答案】

  1. 数据:司机实时位置(GPS 上报,每 3~5 秒)→ 写入 Redis GEO(司机位置);订单(起点、终点、类型);
  2. 派单流程:用户下单 → 范围检索(Redis GEO 半径搜索附近司机,如 3km 内空闲司机)→ 候选司机按“距离+评分+接单率+方向+负载均衡(题干点了这四个维度,负载均衡不能漏:按该司机近时段已派数与空闲时长做惩罚/激励,否则热门司机被连续派单、其余司机空跑,接单率与「司机不空跑」两个题干目标同时掉)”打分排序 → 派单(推送给 Top 司机);
  3. 并发抢单与防冲突:
    • 一个订单多个司机同时收到——先到先得:司机接单时用 Redis 分布式锁/订单状态机(订单状态=待接单 → 原子更新为已接单,防多司机重复接单);
    • 司机同时收到多个订单——司机侧串行(一次只能处理一个);
  4. 状态机:待派单 → 派单中(司机确认超时重新派)→ 已接单 → 服务中 → 已完成/已取消;超时重派(30 秒无司机接单,扩大范围或加价);
  5. 实时性:司机位置用 WebSocket 长连接上报;派单结果推送(WebSocket/推送);
  6. 扩展:调度策略(顺路单、预约单)、地图服务(路径/距离计算 ETA)、热力图(供需预测)、风控(刷单检测);
  7. 容量估算(步进):假设城市同时在线司机 2 万、GPS 5s 上报 → 位置写入约 4000 GEOADD/s(订单中司机更高频时再上浮),Redis GEO 单实例可扛,超大城市按城市分片。日订单 20 万、高峰 15% 集中在 2 小时 → 这 2 小时的均值只有约 4.2 单/s(2e5×0.15÷7200);再乘瞬时突发系数 ×10~×20 才得到派单峰值 40~80 单/s 的量级(两个数之间存在一层折算,别把它们当成同一个口径),每单半径检索+排序毫秒级。存储:Redis GEO 仅存在线/近期司机(2 万点),订单库按日增量 20 万 × 300B ≈ 60MB/日。
  8. 数据与接口(口述可带):司机状态 {driver_id, status(idle|busy|offline), lng, lat, score, heading};订单 {order_id, status, start, end, est_distance, assigned_driver};接口 POST /orders、POST /orders/{id}/accept(CAS 接单)、GET /orders/{id}/nearby-drivers(调试)。失败与降级:附近无司机 → 排队+加价+扩大半径循环;派单服务超时 → 订单进入重试队列而非失败;Redis GEO 不可用降级为按网格缓存的司机列表;定位漂移用地理围栏与停留点过滤;司机端 WS 断连时仍可轮询接单。

【原理溯源】

  • 为什么司机位置用 Redis GEO? 派单需要“半径范围内的空闲司机”,这是地理空间查询。Redis GEO 底层是 GeoHash+ZSet,GEOSEARCH O(log N) 返回范围内成员,毫秒级。若用 DB 经纬度范围查,索引与计算都更重。
  • 为什么接单要原子状态流转? 多个司机可能同时点接单。用 UPDATE orders SET status=taken WHERE id=? AND status=pending,影响行数为 1 的那个成功,其余失败。这避免一单被多司机接走。
  • 为什么要超时重派? 派出的司机可能拒单或超时未响应。不重派则订单卡死。30 秒无响应扩大半径或加价激励,保证乘客最终被服务。
  • GPS 为什么要节流? 每秒上报百万司机位置会打爆写入。订单中高频(3~5 秒)、空闲低频(30 秒+),或客户端按移动距离触发。成本与实时性的平衡。
  • 为什么要综合打分而不是纯距离? 最近司机可能评分低、接单率低、方向相反。综合分(距离+服务分+接单率+方向)提升全局效率与体验,这是调度算法的核心。

【选型判断树】

位置存储:Redis GEO
检索:GEOSEARCH 半径 + 空闲过滤
打分:距离 + 评分 + 接单率 + 方向 + 负载均衡(近时段已派数/空闲时长)
接单:订单状态机原子更新
超时:30s 重派,扩大范围/加价

判断口诀: GEO 检索、综合打分、状态机防冲突、超时重派。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“派单是 LBS 实时检索+调度优化+并发控制”
0:30–1:30位置与检索GPS→Redis GEO;半径搜索
1:30–2:30打分策略距离/评分/接单率/方向/负载均衡(近时段已派数、空闲时长)
2:30–3:30并发与状态机原子接单;防多司机
3:30–4:30超时与扩展重派;顺路单;热力图
4:30–5:00收尾“一句话:GEO 找人,打分选人,状态机定人”

【关键数字】

参数经验值说明
GPS 上报订单中 3~5s,空闲 30s+节流
派单半径1~5km按城市
超时重派30s扩大/加价
候选司机Top 5~10推送
状态机待派→派中→已接→服务→完成原子迁移
GPS写入在线司机/上报间隔例:2万/5s=4000 GEOADD/s
派单峰值日订单×高峰占比÷秒数,再乘瞬时系数20万×15%/7200≈4.2 单/s(时段均值)→ ×10~20 ≈ 40~80 单/s
无司机策略排队+加价+扩大半径超时重派状态机闭环

【追问链】(三层)

L1|“两个司机同时接单怎么办?” → 订单状态原子更新,只有一个成功。

L2|“附近没有司机怎么办?” → 扩大半径、加价激励、进入排队、通知乘客等待时间。

L3|“司机位置更新延迟导致派给已离开的司机?” → 接单时再校验实时位置与状态;拒单/超时则重派。允许一定误差,靠状态机兜底。

【评分标准】

档位答案特征
60 分知道找附近司机
80 分GEO、综合打分、状态机接单
95 分超时重派、GPS 节流、顺路单/热力图、与地图服务集成

【关联题】

  • 同构: 第 143 题(LBS)、第 131 题(订单状态机)
  • 上游: 第 126 题(关注粉丝关系)——派单要读用户关系;位置检索的同类题是第 143 题
  • 扩展: 第 147 题(风控)——刷单检测

【自测】

  1. 判断对错:派给最近的司机就是最优。 参考答案: 错。还要看评分、接单率、方向、负载均衡(否则热门司机被连续派单、其余空跑,接单率与「不空跑」两个题干目标同时掉);综合打分更优。
  2. 接单为什么用状态机? 参考答案: 防止多司机同时接同一单;原子更新保证唯一。
  3. 没司机接单怎么办? 参考答案: 超时扩大范围/加价/排队;通知乘客。

143. 外卖 App 要按距离列出附近 100 家店(LBS 设计) ​

【考察内容】LBS 检索分层设计

【题目】外卖 App:用户打开要看到附近门店按距离排序,且要能按销量/评分再排。门店几十万家、位置实时变化不大。地理位置索引怎么做(GeoHash/网格/ES geo)?距离计算与排序的性能怎么保证?

【参考答案】

  1. 数据:门店表(id、经纬度、营业状态),用户查询带当前位置;
  2. 检索方案:
    • Redis GEO:门店经纬度 GEOADD 到 city key,查询 GEOSEARCH(半径/矩形范围+按距离排序+分页)——快,适合百万级;
    • GeoHash 索引(MySQL):门店经纬度转 GeoHash 字符串存列(建索引),查询时按“当前点周围 9 格前缀”匹配再精确过滤——适合千万级,DB 方案;
    • ES geo 查询:海量+复杂过滤(营业中+评分>4)用 ES geo_distance 查询,性能好;
  3. 排序(题干要「按销量/评分再排」,这与「按距离排」是两件事,要分开答):距离排序=GEOSEARCH 天然按距离由近到远返回;属性排序(销量/评分/推广)=GEO 结构只能按距离排、不能按销量排,所以要么把半径内的候选集(门店 ID+销量+评分)取回后在内存重排,要么走 ES:geo_distance 过滤+function_score(销量/评分加权)或直接 sort:[{sales:desc}];纯 GeoHash 前缀方案则要在 DB 侧配 ORDER BY sales DESC 的复合索引。一句话:「附近 + 按销量」=先地理过滤、再属性排序,两步不能合成一步;
  4. 优化:按城市分片(门店归属城市,查询先定位城市)、网格缓存(热门区域的门店列表缓存,TTL 短)、范围查询限制(如 10km 内);
  5. 动态数据:营业状态、实时排队数——查缓存/状态服务;
  6. 扩展:多边形区域(配送范围判断:点在多边形内用射线法+空间索引)、热力图;
  7. 容量估算:假设全国门店 50 万、单城 1~5 万;查询高峰全国 2 万 QPS,单城约 200~2000。GEO 点均约 60B,50 万点≈30MB,可按城市 key 全量进内存;网格缓存按命中 30% 估,可挡约 6000 QPS;
  8. 失败与降级:Redis GEO / ES 故障时降级返回「城市热门门店」缓存列表(标注非精确距离);状态服务超时默认按营业中展示;10km 半径限制仍作为硬约束,避免降级时扫全城。

【原理溯源】

  • 为什么按城市分片? 全国门店 GEO 放一个 key 会成大 Key,且用户只关心本地。按城市路由后,单 key 规模可控,查询也快。这是业务局部性在存储上的体现。
  • GeoHash 的原理是什么? 把二维经纬度递归四分/八分,编码成字符串;前缀相同表示空间邻近。查询时取当前点周围 8 个邻接格的前缀做范围检索,再精确算距离。本质是空间降维成字符串前缀匹配。
  • 为什么门店位置变化不大却仍要处理动态状态? 位置准静态,可进 GEO/索引;营业状态、排队人数是动态的,放缓存/状态服务实时查。准静态进索引,动态进缓存,避免频繁更新索引。
  • 为什么要网格缓存? 热门商圈(如国贸)查询高度重复。缓存该网格的门店列表,短 TTL,把重复计算挡掉。冷门区域不缓存。
  • 为什么限制查询半径? 10km 外的门店用户不会点;不限制会返回海量无用数据。产品约束换性能。

【选型判断树】

量级?
├─ 百万级 → Redis GEO(按城市 key)
├─ 千万级 DB → GeoHash 列+索引
└─ 复杂过滤 → ES geo

排序:先按距离圈候选(GEO),**销量/评分是第二步**——取回候选集后内存重排或走 ES function_score,GEO 本身不能按销量排
动态状态:缓存实时查
优化:城市分片 + 网格缓存 + 半径限制

判断口诀: 按城分片、GeoHash 降维、动态走缓存、限制半径。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“LBS 是空间索引+分层缓存问题”
0:30–1:30三方案GEO / GeoHash+MySQL / ES
1:30–2:30GeoHash 原理空间→字符串前缀;9 宫格
2:30–3:30分片与缓存城市分片;网格缓存
3:30–4:30动态与排序状态实时查;候选集取回后按销量/评分重排(两步不能合成一步)
4:30–5:00收尾“一句话:静态进索引,动态进缓存,按城分片”

【关键数字】

参数经验值说明
门店量级几十万~百万GEO 够用
查询半径≤ 10km产品约束
GeoHash 精度6~7 位约 1km~百米
城市分片按 city避免大 Key
网格缓存 TTL10s~1min热点
门店 GEO 内存50 万点 ≈ 30MB点均约 60B
查询 QPS全国峰值 2 万单城更低

【追问链】(三层)

L1|“GeoHash 和 Redis GEO 区别?” → Redis GEO 是 GeoHash 的封装(底层 ZSet);MySQL 方案自己存 GeoHash 字符串+索引。本质同源。

L2|“为什么不全放一个 GEO key?” → 大 Key、查询范围无意义;按城市分片后规模可控且匹配业务局部性。另外单 key 会把写(门店上下架)和读(附近查询)都压到同一 slot,热点城市(如上海)易打满节点;分片后可独立扩容。

L3|“门店打烊了还能被搜到怎么办?” → 动态状态实时查缓存;或索引里标记,读时过滤。位置准静态,状态动态。

【评分标准】

档位答案特征
60 分知道用 GEO 或经纬度
80 分三方案对比、城市分片
95 分GeoHash 原理、网格缓存、动态状态分离、多边形配送范围

【关联题】

  • 同构: 第 142 题(派单)——LBS 实时版
  • 上游: Redis GEO 命令章节
  • 扩展: 配送范围多边形见本题正文第 6 条(点在多边形内:射线法 + 空间索引);风控视角见第 147 题

【自测】

  1. 判断对错:全国门店放一个 Redis GEO key 最简单。 参考答案: 大 Key 且查询无局部性;应按城市分片。
  2. GeoHash 前缀相同表示什么? 参考答案: 空间邻近;可用前缀匹配做范围检索。
  3. 营业状态为什么单独存? 参考答案: 动态数据频繁变,不宜进空间索引;走缓存实时查。

144. 视频上传要转码、播放要流畅、分发要快(短视频系统) ​

【考察内容】短视频是内容平台最高频设计题

【题目】短视频产品:用户上传视频(几百 MB)要转码成多清晰度、播放要秒开流畅、还要支撑千万级并发播放。上传链路(分片/断点)、转码(异步任务/转码集群)、播放(CDN/预加载/缓存)怎么设计?

【参考答案】

  1. 上传链路:客户端分片上传 → 对象存储(OSS/S3)→ 消息触发转码流水线(多清晰度:720p/1080p、封面截取、水印)→ 审核(机审+人审)→ 上线;
  2. 存储:原始文件+转码产物存对象存储(冷热分层:热视频 CDN 边缘缓存,冷数据低频访问回源);元数据(视频信息、作者、标签)入 DB/缓存;
  3. 播放:播放请求 → CDN(边缘节点缓存视频分片,就近分发)→ 未命中回源;首屏优化(首帧快速加载:低清晰度先出+渐进式播放);HLS/DASH 切片(分段拉流,自适应码率);
  4. 推荐分发:见推荐系统——上传后先小流量测试(分发到小池子测数据)→ 数据好则加大分发(冷启动策略);
  5. 计数与互动:播放量、点赞、评论(见计数/点赞题,播放量异步累加+定期落库);
  6. 扩展:内容审核(色情/违规——图片/视频 AI 审核+人工复核)、版权识别(指纹)、删除/下架(CDN 失效);
  7. 容量估算:日上传假设 100 万条×均 300MB(题干「几百 MB」取下沿)→ 原始约 300TB/天;若按 100MB 算是压缩后/短视频口径,须写明,转码 3 档后存储约 2~3 倍;播放峰值假设千万级 QPS,CDN 命中 >95% 时回源约 50 万 QPS,仍需中心带宽与对象存储扛;
  8. 失败与降级:转码失败自动重试 3 次后进死信人工;CDN 节点故障靠调度切换边缘;播放失败降级更低清晰度或返回封面;审核服务超时默认「先不可见」,避免违规内容漏出。

【原理溯源】

  • 为什么必须对象存储+CDN 而不是应用服务器? 视频文件大(几百 MB)、播放并发高(千万级)。应用服务器磁盘与带宽都是瓶颈。对象存储海量容量、CDN 边缘节点就近分发,把带宽压力从中心分散到边缘。这是内容分发的物理最优解。
  • 为什么要转码成多清晰度? 不同网络环境(4G/WiFi)需要不同码率。多清晰度+自适应码率(HLS/DASH)让播放器按带宽切换,保证流畅。同时降低存储与分发成本(冷门视频只保留低清晰度)。
  • 为什么要分片/切片播放? 整文件下载失败要重头再来。HLS 切成几秒的小分片,可独立请求、可切换码率、可预加载下一秒。首屏只需第一个分片,秒开。
  • 为什么要异步转码? 转码 CPU 密集(几分钟到几十分钟),同步等待不可接受。上传成功即返回,转码流水线异步跑,完成后通知。这是用异步换用户体验。
  • 为什么要先小流量测试再全量分发? 新视频质量未知,直接全量推荐会浪费流量且可能推垃圾内容。小池子测 CTR/完播率,数据好再放大。这是推荐系统的冷启动。

【选型判断树】

存储:OSS/S3 + CDN
上传:分片+断点续传(见 148 题)
转码:异步流水线,多清晰度
播放:HLS/DASH 切片,CDN 边缘,低清晰度先出
审核:机审+人审

判断口诀: 对象存储管容量,CDN 管分发,切片管流畅,异步管转码。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“短视频是上传-转码-分发的媒体流水线”
0:30–1:30上传与存储分片上传;OSS;元数据分离
1:30–2:30转码异步流水线;多清晰度
2:30–3:30播放CDN;HLS 切片;首屏优化
3:30–4:30审核与分发机审人审;冷启动小流量
4:30–5:00收尾“一句话:CDN 边缘分发,切片保证流畅,异步保证体验”

【关键数字】

参数经验值说明
分片大小1~10MB上传
转码耗时分钟级异步
HLS 切片2~10s流畅
CDN 命中> 95%热门视频
首屏< 1s体验
审核机审秒级+人审合规
日上传存储约 300TB 原始(100 万×300MB,题干「几百 MB」)转码多清晰度后再翻倍
回源 QPSCDN 命中 95% 时约峰值 5%中心仍要容量

【追问链】(三层)

L1|“视频直接存应用服务器行不行?” → 不行。带宽与磁盘瓶颈;必须对象存储+CDN。

L2|“播放量怎么统计防刷?” → 客户端上报+服务端抽样+异步累加;同用户/设备/时间窗去重,异常增速离线清洗;见第 162 题计数系统。

L3|“视频下架后 CDN 还有缓存怎么办?” → CDN 主动失效(invalidate)+版本号/URL 变更;读时校验状态兜底。

【评分标准】

档位答案特征
60 分知道要转码和 CDN
80 分完整链路、异步转码、HLS
95 分冷启动分发、审核、CDN 失效、冷热分层存储、与推荐联动

【关联题】

  • 上游: 第 148 题(分片上传)
  • 同构: 第 162 题(计数)——播放量
  • 分发: 第 128 题(Feed)——内容分发

【自测】

  1. 判断对错:视频可以同步转码完成后才返回上传成功。 参考答案: 转码耗时长,必须异步;上传成功即返回。
  2. 为什么用 HLS 切片? 参考答案: 小分片可独立请求、切换码率、预加载,首屏秒开。
  3. 为什么要多清晰度? 参考答案: 适配不同带宽,自适应切换保证流畅。

145. 多人同时编辑一个文档,不冲突、实时同步(协作文档) ​

【考察内容】协作文档是高级设计题(阿里/字节/飞书系高频)

【题目】要做在线协作文档:10 个人同时编辑同一文档,每个人都能看到别人的修改、不能互相覆盖。OT(操作转换)和 CRDT 两种并发控制方案是什么?服务端怎么合并、怎么存储版本?

【参考答案】

  1. 架构:客户端(编辑器+本地状态)→ WebSocket 长连接 → 文档服务(版本管理+广播);
  2. 实时同步:OT(操作转换)或 CRDT(无冲突复制数据类型):
    • OT:每个编辑操作(插入/删除+位置)带操作 ID,服务端对并发操作做转换(transform),保证所有客户端最终一致;Google Docs 用 OT;
    • CRDT:每个字符带唯一 ID,合并时按 ID 排序天然收敛,无需中心转换;适合离线优先;
  3. 协作协议:客户端发送 op → 服务端应用 op(校验)→ 广播给其他在线客户端 → 应用;版本号(doc version)递增,客户端 op 带 base version,版本不匹配先同步最新;
  4. 存储:文档内容按版本存储(增量 op 日志 + 定期快照),可回溯历史版本;
  5. 在线状态:用户光标位置(广播)、在线成员列表(Presence);
  6. 一致性保证:服务端是权威(op 序号)、冲突解决策略统一(OT/CRDT 保证收敛)、离线编辑(本地暂存,重连后同步——CRDT 更优);
  7. 扩展:权限(查看/编辑/评论)、评论批注(锚定文本)、历史回滚;
  8. 容量估算:单文档 10 人协作、op 峰值按 50/s 设计;公司级假设 10 万文档、日均约 1000 万 op,增量日志可承受,快照按 100~1000 op 一次摊薄恢复成本;
  9. 失败与降级:断线本地暂存 op,重连按 base version 补齐;op 持久化失败则拒绝应用;文档服务主备切换,客户端重放日志收敛,避免静默丢编辑。

【原理溯源】

  • 为什么不能用“整个文档加锁”? 加锁串行编辑,10 人轮流等锁,体验极差,且离线不可用。协作编辑的本质是“允许并发、事后收敛”,不是“禁止并发”。
  • OT 的“转换”在解决什么? A 在位置 5 插入 “X”,B 同时在位置 3 插入 “Y”。若 B 先到,A 的位置 5 实际已变成 6。OT 的 transform 函数把 A 的操作基于 B 的已应用状态做位置修正,保证两边应用后一致。本质是把并发操作变成可交换的等价序列。
  • CRDT 为什么能无中心收敛? 每个字符/操作有全局唯一 ID(如 lamport timestamp + site id)。合并时按确定性规则(如按 ID 排序)应用,无论顺序如何,最终状态相同。代价是 ID 元数据存储开销大。
  • 为什么要有版本号? 客户端可能基于旧版本发 op。服务端发现 base version 落后,先让客户端同步最新再重发。版本号是乐观并发控制的载体。
  • 为什么要 op 日志+定期快照? 全量存每一版文档空间爆炸。增量 op 日志省空间,定期快照加速恢复与历史版本加载。这是日志+快照的经典折中(同 Redis AOF+RDB 思想)。

【选型判断树】

OT vs CRDT?
├─ 中心化、可控、Google Docs 模式 → OT
└─ 离线优先、去中心、存储可换 → CRDT

存储:op 日志 + 定期快照
版本:base version 乐观校验
在线:Presence 广播光标

判断口诀: OT 靠转换,CRDT 靠唯一 ID;op 日志+快照;版本号防乱序。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“协作文档核心是并发操作收敛”
0:30–1:30OT操作转换;transform 例子
1:30–2:30CRDT唯一 ID;天然收敛;离线
2:30–3:30协议与版本op 广播;base version
3:30–4:30存储op 日志+快照;历史版本
4:30–5:00收尾“一句话:OT 转换换一致,CRDT 元数据换收敛”

【关键数字】

参数经验值说明
同时编辑10~100 人规模
op 延迟< 100ms实时感
快照间隔每 100~1000 op恢复加速
版本保留按天/按版数历史
离线CRDT 更优本地暂存
日 op 量(公司级)约 1000 万10 万文档×编辑频率
单文档 op 峰值~50/s10 人协作上限量级

【追问链】(三层)

L1|“OT 和 CRDT 怎么选?” → OT 中心化、需转换逻辑、可控;CRDT 去中心、适合离线、存储开销大。产品离线需求强选 CRDT。

L2|“服务端挂了文档会不会丢?” → op 日志持久化+快照;多副本;客户端本地也有缓存可重放。恢复顺序:先加载最近快照,再重放其后 op;若持久化副本全丢,则以最后成功快照为权威版本,提示用户核对。

L3|“10 人同时改同一段怎么办?” → OT/CRDT 保证收敛;产品层可显示他人光标;冲突极少时人工提示。

【评分标准】

档位答案特征
60 分知道要实时同步
80 分OT/CRDT 区别、WebSocket、版本号
95 分transform 例子、op 日志+快照、离线同步、Presence、与 160 题权限联动

【关联题】

  • 同构: 第 160 题(知识库版本)、第 129 题(IM 实时)
  • 上游: 第 128 题(Feed)——实时推送
  • 存储: 日志+快照模式(同 Redis 持久化)

【自测】

  1. 判断对错:协作文档可以给整个文档加锁保证一致。 参考答案: 错。串行编辑体验差;应 OT/CRDT 允许并发收敛。
  2. OT 的 transform 解决什么? 参考答案: 并发操作的位置修正,保证多端最终一致。
  3. CRDT 为什么适合离线? 参考答案: 唯一 ID+确定性合并,离线编辑重连后可自动收敛。

146. 老板要看实时 GMV/订单量/转化率看板(BI 看板) ​

【考察内容】数仓与实时计算架构

【题目】老板要求实时看板:GMV、订单量、转化率、各省份分布,秒级刷新。数据从订单库实时流出,怎么聚合(实时计算)、怎么存储(OLAP/预聚合)、看板接口怎么支撑高频查询?

【参考答案】

  1. 数据链路:业务埋点/订单事件 → MQ(Kafka)→ 实时计算(Flink 窗口聚合:秒级滚动窗口或每条事件累加——题干要秒级,窗口宽度决定数值新鲜度,缓存 TTL 只决定前端拉取频率,两件事别混)→ 结果写 Redis/ClickHouse;离线计算(Hive/Spark 定时跑日/周报)→ 数仓;
  2. 存储:明细数据入数仓(Hive/OSS);聚合结果入 ClickHouse(列式、聚合查询快)或 Redis(实时 TopN/计数);
  3. 查询层:看板接口查 ClickHouse/Redis → 前端 ECharts 渲染;预聚合(按维度组合物化视图)避免大查询;
  4. 关键设计:
    • 维度建模:事实表(订单事实:金额、数量、时间)+ 维度(渠道、商品、地区),支持任意维度组合下钻;
    • 转化率(题干四个指标里唯一有分母陷阱的,必须单独答)=成交订单数 ÷ 曝光(或 UV):分母不在订单库里,要补前端埋点流(曝光/UV 事件),与订单流在同一窗口、同一维度对齐后再算;分子分母各自按窗口预聚合(Flink 每 5s/1min 累加写 Redis),看板只读比值,不在查询时跨表现算;口径要写明分母是 UV、PV 还是曝光次数——否则老板看到的「转化率」和你算的不是同一个数;
    • 实时 vs 离线一致性:实时看板与离线报表口径对齐(同源数据,允许差异并在 T+1 校准);
    • 查询性能:大数据量用 ClickHouse(亿级秒级返回)、按日期分区、物化视图;
    • 缓存:高频看板缓存(秒级 TTL);
  5. 扩展:指标口径管理(指标字典)、告警(指标异常波动)、自助报表;
  6. 容量估算:订单事件假设日 500 万单、每单约 6 条状态事件 → 3000 万事件/日;高峰 4 小时占 70% 时段的均值为 3000万×0.7÷14400 ≈ 1460/s,再乘瞬时系数 5~10 → 流式写入峰值约 0.7~1.5 万 QPS(取 1 万计),大促再 ×5~10;ClickHouse 明细按日分区、亿级聚合秒级返回;看板本身读 QPS 通常百级,用 Redis/结果缓存扛,不直接打明细;
  7. 失败与降级:Flink 故障时看板降级读离线 T+1 结果并显著标注延迟;ClickHouse 不可用退回 Redis 预聚合/上一期快照;口径对不上时以离线数仓为准,实时盘标“近似值”。

【原理溯源】

  • 为什么要实时+离线双链路? 实时要秒级(老板刷新),离线要全量准确(对账、历史)。实时链路(Flink)延迟低但可能丢细节;离线(Spark/Hive)全量但 T+1。双链路各司其职,T+1 校准口径。
  • 为什么用 ClickHouse 而不是 MySQL? 看板查询是“亿级行按维度聚合”,MySQL 行存+事务模型不适合。ClickHouse 列存+向量化执行+稀疏索引,聚合比 MySQL 快几个数量级。这是为 OLAP 场景特化的存储。
  • 为什么要预聚合/物化视图? 明细查询亿行慢;预聚合把常用维度组合(日期×渠道×地区)提前算好,查询 O(聚合结果)。空间换时间。
  • 为什么要维度建模? 事实表(度量:金额、数量)+维度表(渠道、地区、时间)让任意维度组合下钻成为可能(星型/雪花模型)。没有维度建模,每个报表都要定制 SQL,无法自助分析。
  • 实时和离线口径为什么会不一致? 实时可能丢迟到数据、乱序;离线全量。所以要“同源数据+口径定义统一+T+1 校准”。口径不统一会导致老板看到的数对不上,失去信任。

【选型判断树】

实时:订单事件 → Kafka → Flink 窗口聚合 → Redis/CH
离线:明细入仓 → Spark 日批 → 数仓
查询:看板 → CH 预聚合/物化视图 → 缓存
维度:事实+维度建模,支持下钻;转化率要先定分母(UV/PV/曝光)并与订单流同窗对齐

判断口诀: 实时 Flink、离线 Spark、存储 ClickHouse、预聚合保查询、口径要统一。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“实时看板是流批一体+OLAP 查询”
0:30–1:30双链路Flink 实时;Spark 离线
1:30–2:30存储ClickHouse 列存;Redis 计数
2:30–3:30查询优化预聚合、物化视图、缓存
3:30–4:30维度与口径事实维度建模;口径统一
4:30–5:00收尾“一句话:流批双链路,列存扛聚合,口径保信任”

【关键数字】

参数经验值说明
实时延迟秒~分钟级老板可接受
离线T+1对账
CH 查询亿级秒出列存优势
缓存 TTL1~10s看板
物化视图常用维度组合预聚合
事件峰值按正文折算 0.7~1.5 万 QPS(日常)/ 大促再 5~10 倍驱动 Flink 容量
看板读 QPS百级缓存足够

【追问链】(三层)

L1|“为什么用 ClickHouse?” → 列存+向量化+稀疏索引,聚合比 MySQL 快数量级;专为 OLAP。

L2|“实时和离线数字对不上怎么办?” → 同源数据、口径字典统一、T+1 校准;差异先查迟到消息/重复消费/过滤条件,再决定是否回刷实时盘;展示层对实时数标注“含近实时估算”。

L3|“老板要看任意维度组合怎么办?” → 维度建模+物化视图+OLAP 引擎;自助分析平台。

【评分标准】

档位答案特征
60 分知道要实时算+存起来
80 分Flink+ClickHouse、预聚合
95 分流批一体、维度建模、转化率分母口径(UV/曝光,需补埋点流并与订单流同窗对齐)、口径管理、物化视图、与 127 题热榜呼应

【关联题】

  • 同构: 第 127 题(热榜)、第 162 题(计数)
  • 上游: 第 131 题(订单事件)
  • 存储: 第 140 题(大数据链路)

【自测】

  1. 判断对错:看板直接查订单 MySQL 明细即可。 参考答案: 亿级聚合慢;应 ClickHouse 预聚合。
  2. 为什么要实时+离线双链路? 参考答案: 实时要快、离线要准;T+1 校准口径。
  3. 什么是维度建模? 参考答案: 事实表(度量)+维度表(渠道/地区/时间),支持任意下钻。

147. 刷单/薅羊毛/盗号,怎么识别和拦截(风控系统) ​

【考察内容】风控是金融/电商高级设计题

【题目】电商平台被薅羊毛团伙盯上了:批量注册小号领券、刷单骗补贴、盗号下单。风控系统怎么设计?规则引擎与机器学习怎么配合?实时拦截(毫秒级决策)怎么实现?误杀与漏杀怎么权衡?

【参考答案】

  1. 数据采集:行为埋点(登录、下单、支付、领券)、设备指纹(设备 ID、IP、浏览器)、账户信息;
  2. 规则引擎(第一层,实时):黑白名单(IP/设备/账号)、频控规则(同设备多账号、短时间高频)、阈值规则(金额、频率)——可配置、热更新(Drools/自研规则平台);
  3. 模型层(第二层):机器学习模型(风险评分:XGBoost/GBDT 打分 0~100,欺诈/盗号/薅羊毛分类模型)——离线训练、在线推理(实时特征:行为序列特征);
  4. 决策引擎:规则+模型分组合决策(评分>80 直接拒绝,60~80 人工审核/二次验证码,<60 放行);人工审核平台(可疑订单人工复核);
  5. 特征:实时特征(近 5 分钟下单次数)、离线特征(历史行为画像)、图特征(社交关系/设备关联图——团伙检测);
  6. 闭环:处置(拦截/验证码/限额)→ 反馈(处置结果回流标注)→ 模型迭代(样本更新重训);
  7. 延迟要求:毫秒级决策(不影响正常下单)——规则引擎内存化+模型 ONNX/自研推理+特征缓存;
  8. 误杀控制:分级处置(先验证码/限额,再拒绝),白名单通道;
  9. 容量估算:下单峰值假设 5 万 QPS,风控决策同量级;规则内存 O(1),特征缓存命中 >95%,整段决策 P99 < 50ms;
  10. 失败与降级:按业务风险选 fail-open(放行+补扫)或 fail-closed(拒绝)——支付倾向 fail-closed,营销领券可短时 fail-open;特征超时用默认画像,避免死等。

【原理溯源】

  • 为什么规则和模型要配合而不是二选一? 规则快、可解释、拦截确定风险(黑名单、频控),但对抗性差(黑产改手法即失效)。模型能发现未知模式,但慢、黑盒、可能误杀。组合:规则先挡确定风险,模型处理复杂/未知,输出融合进决策引擎。这是可解释性与泛化能力的互补。
  • 为什么要毫秒级决策? 风控在下单关键路径上,超时会拖垮用户体验甚至超时失败。规则内存化、模型轻量推理(ONNX/TensorRT)、特征预计算缓存,保证 P99 < 50ms。
  • 为什么要分级处置(验证码/限额/拒绝)? 一刀切拒绝误杀严重(正常用户被挡)。分级:可疑先验证码/二次验证,再限额,最后拒绝。白名单通道保护 VIP。这是误杀与漏杀的权衡——宁可多验证,不可错杀。
  • 为什么要图特征? 团伙作案设备/IP/社交关联紧密。单点特征难发现,图上“同设备多账号”“资金环”等模式一目了然。图计算离线做,在线查子图特征。
  • 为什么要闭环反馈? 处置结果(真实欺诈/误杀申诉)是最好标注。回流训练集,模型迭代。没有闭环的风控会逐渐失效。

【选型判断树】

第一层:规则引擎(快、可解释)
  黑名单、频控、阈值 → 直接拦截
第二层:模型评分(泛化)
  >80 拒绝 / 60~80 验证码或人审 / <60 放行
特征:实时+离线+图
闭环:处置结果回流训练

判断口诀: 规则挡确定,模型挡未知,分级降误杀,闭环保迭代。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“风控是规则+模型双层、毫秒级、分级处置”
0:30–1:30数据与特征埋点、设备指纹、实时/离线/图特征
1:30–2:30双层引擎规则快拦;模型评分
2:30–3:30决策与处置分级:拒绝/验证码/人审/放行
3:30–4:30闭环与性能反馈训练;毫秒级推理
4:30–5:00收尾“一句话:规则可解释、模型可泛化、分级可控误杀”

【关键数字】

参数经验值说明
决策延迟< 50ms P99关键路径
模型评分0~100阈值分级
高危>80拒绝
中危60~80验证码/人审
实时特征窗口近 1/5/30 分钟行为序列
闭环日级重训样本回流
决策 QPS跟随下单峰值(如 5 万)与交易同级
特征缓存命中> 95%保 P99

【追问链】(三层)

L1|“规则和模型怎么配合?” → 规则快速拦截确定风险,模型处理复杂/未知风险,融合进决策引擎。

L2|“误杀怎么控制?” → 分级处置(验证码→限额→拒绝)、白名单、申诉通道;监控误杀率与人工翻案率,超阈值回滚规则/降阈值;新规则先影子流量再全量。

L3|“黑产不断变招怎么办?” → 闭环反馈快速迭代;图特征发现团伙;对抗训练;蜜罐/探针。

【评分标准】

档位答案特征
60 分知道要做规则拦截
80 分规则+模型双层、实时特征
95 分分级处置、图特征、闭环迭代、毫秒级性能、与 134/150/157 呼应

【关联题】

  • 下游: 第 134 题(优惠券防黄牛)、第 150 题(抽奖防刷)、第 157 题(预约资格)
  • 上游: 第 132 题(支付风控)
  • 安全: 第 154 题(权限越权)

【自测】

  1. 判断对错:风控只用规则引擎就够了。 参考答案: 规则对抗性差;需模型泛化+闭环迭代。
  2. 为什么要分级处置? 参考答案: 降低误杀;可疑先验证再拒绝。
  3. 决策为什么要毫秒级? 参考答案: 在下单关键路径,超时影响体验与成功率。

148. 1GB 视频上传失败要重传,体验太差(分片上传) ​

【考察内容】文件上传是通用高频设计题

【题目】用户上传 1GB 视频,传了 80% 网络断了,全部重来,用户骂街。怎么设计大文件上传:分片上传、断点续传、秒传(服务端已有相同文件直接跳过)、并发分片与校验?

【参考答案】

  1. 秒传:上传前客户端计算文件 MD5 → 服务端查重(已存在则直接返回“上传完成”,引用已有文件)——省流量;
  2. 分片上传:文件切成固定大小分片(如 5MB/片),客户端逐个上传(并发 3~5 片),服务端存分片(临时目录/OSS 分片)并记录每片状态;
  3. 断点续传:客户端查询已上传分片列表(uploadId 维度),只重传缺失分片;
  4. 合并:全部分片上传完成后,服务端发起合并(OSS CompleteMultipartUpload/自研合并),校验总大小与 MD5;
  5. 流程与状态:创建上传任务(返回 uploadId+分片数)→ 上传分片(每片带 uploadId+index,幂等:同 index 覆盖)→ 完成合并 → 文件入库(file_id、地址、大小);
  6. 存储:对象存储(OSS/S3)天然支持分片;权限(上传凭证:STS 临时凭证,防越权);
  7. 下载优化:CDN 加速、Range 请求(断点续传下载)、大文件流式返回;
  8. 清理:超时未完成的分片定时清理(防存储浪费);
  9. 容量估算:1GB / 5MB ≈ 200 片,并发 3~5 片;日上传假设 10 万文件×均 200MB → OSS 约 20TB/天;秒传命中率按 20% 估可省约 4TB 带宽与存储;
  10. 失败与降级:单片失败指数退避重试 3 次,仍失败则整任务标失败可续传;合并失败可对同一 uploadId 重试 Complete;分片元数据服务不可用时提示稍后续传,OSS 分片仍按 24h 保留。

【原理溯源】

  • 为什么要分片? 大文件一次传输失败要全部重来,且无法并发。分片后单片小、可并发、失败只重传该片。这是把大任务拆成可重试的小任务,同分布式任务思想。
  • 为什么要有 uploadId? 标识一次上传会话,关联所有分片状态。客户端凭 uploadId 查询已传分片,实现断点续传;服务端凭 uploadId 管理临时分片与超时清理。
  • 为什么分片要幂等(同 index 覆盖)? 网络重试可能重复上传同片。同 index 直接覆盖,简单幂等,无需复杂去重。
  • 秒传的 MD5 为什么可行? 同一文件内容 MD5 相同。服务端维护“MD5→文件”映射,命中则无需真传,只建引用。省带宽与存储(去重)。代价是客户端要先算 MD5(大文件耗时),且不同用户共享同一物理文件需权限隔离。
  • 为什么要 STS 临时凭证? 直接给 AK/SK 危险。STS 是限定时间、限定 bucket/路径的临时凭证,客户端直传 OSS,服务端不承载文件流量。这是最小权限原则。

【选型判断树】

文件大小?
├─ 小文件(< 10MB)→ 直传
└─ 大文件 → 分片上传
    ├─ 秒传:先算 MD5 查重
    ├─ 断点续传:uploadId 查已传分片
    └─ 合并:CompleteMultipartUpload + MD5 校验

存储:OSS 分片 + STS 临时凭证
清理:超时未完成分片定时删

判断口诀: 小文件直传,大文件分片;MD5 秒传,uploadId 续传。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“大文件上传核心是分片+断点+秒传”
0:30–1:30分片切片、并发、uploadId
1:30–2:30断点续传查已传分片,只传缺失
2:30–3:30秒传MD5 查重,引用已有
3:30–4:30合并与安全合并校验;STS 临时凭证
4:30–5:00收尾“一句话:拆小可重试,MD5 可去重,uploadId 可续传”

【关键数字】

参数经验值说明
分片大小1~10MB平衡并发与请求开销
并发分片3~5带宽利用
秒传MD5 查重省流量
STS 有效期分钟~小时最小权限
超时清理24h防存储浪费
1GB 分片数~200 片(5MB)续传粒度
日写入量(例)20TB(10 万×200MB)OSS 容量

【追问链】(三层)

L1|“并发上传分片乱序怎么办?” → 分片按 index 存储,合并时按 index 顺序组装,与上传顺序无关。

L2|“MD5 算大文件很慢怎么办?” → 客户端抽样 MD5 或分片级 MD5 拼接;或边传边算;秒传收益要覆盖计算成本,冷门文件可跳过预计算。

L3|“用户传了 99% 断网,再进来怎么续?” → 携带 uploadId 查询已传分片列表,只重传缺失片;进度条准确显示。

【评分标准】

档位答案特征
60 分知道要分片
80 分分片+断点续传+uploadId
95 分秒传 MD5、STS 安全、合并校验、超时清理、与 144 题短视频联动

【关联题】

  • 上游: 第 144 题(短视频)——上传是其第一步
  • 同构: 第 135 题(购物车)——用户状态管理
  • 安全: 第 154 题(权限)——STS 最小权限

【自测】

  1. 判断对错:大文件应该一次传完,简单可靠。 参考答案: 失败重传成本高;必须分片。
  2. uploadId 的作用? 参考答案: 标识上传会话,管理分片状态,支撑断点续传。
  3. 秒传原理? 参考答案: 文件 MD5 查重,命中则引用已有文件,无需真传。

149. 连续签到/补签卡/签到日历,怎么实现(签到系统) ​

【考察内容】签到是 BitMap 经典应用(Redis 场景题高频)

【题目】App 要做签到:每天签到领积分、连续签到 7 天额外奖励、支持补签卡、展示当月签到日历。连续天数怎么算(跨月/断签)?高并发(0 点大量签到)怎么扛?存储用什么?

【参考答案】

  1. 数据结构:Redis BitMap(key=sign:{userId}:{month},bit 位=日期,1=已签到)——内存极小(一个用户一月才 4 字节)、支持位运算统计(连续签到天数计算):
    • 签到:SETBIT(key, day-1, 1)(防重复:SETBIT 的返回值就是该位旧值,返回 1 即当日已签,一条命令原子判重;先 GETBIT 再 SETBIT 是 check-then-act,0 点并发/重试会重复);
    • 本月签到天数:BITCOUNT;
    • 连续签到:从今天往前位运算(BITFIELD 取一段,从「今天对应的 bit 位(day-1)」向低位数连续 1 的个数);
  2. DB 落库:签到记录表(user_id、date、唯一索引 user_id+date 防重复签到)——对账/历史查询;
  3. 奖励:连续签到 N 天发奖励(积分/优惠券)——签到事务后发奖励(MQ 异步+幂等:签到记录唯一);
  4. 时区/日期:按服务端日期计算(跨天:凌晨 0 点重置),避免客户端本地时间作弊;
  5. 补签卡:补签=SETBIT 补位+消耗补签卡(事务);
  6. 扩展:签到日历查询(BITFIELD 取出当月 bit 转前端日历)、签到提醒(定时任务/推送)、活动(翻倍签到);
  7. 容量估算:假设 DAU 1000 万,0 点前后 1 分钟集中签到,峰值 QPS 可达 5~10 万;BitMap 一人一月约 4 字节,但每用户一个 key 时 key 自身的结构开销(robj+SDS+dictEntry+过期项,约 60–80B)远大于 4B 值:1000 万 key ≈ 0.6–0.8GB,不是 40MB。要省内存须把多用户挤进同一 key(offset=userId×31,1e6 用户/key → 约 4MB/key、全量约 40MB),此时压力才主要在网卡与连接——省内存的代价是大 key 风险,要不要合并得按运维口径定:4MB 级 key 在 Cluster 迁移(按 slot 搬迁会长时间阻塞)、删除(必须 UNLINK/分批)、主从全量重传上都是隐患,12 个月历史就是 12 个这种 key;内存确实吃紧且能接受这些运维动作被拖慢才选合并,内存不紧张就保持「一人一月一 key」(0.6–0.8GB)。这笔取舍要在答题时说明,别默认「极轻」;
  8. 失败与降级:Redis 不可用时降级写 DB(唯一索引防重),接口限流排队;奖励 MQ 丢失靠「签到记录 vs 已发奖励」对账补发;客户端时间仅作展示,判定一律走服务端时钟。

【原理溯源】

  • 为什么用 BitMap 而不是 String/Set? 签到是“用户×日期”的布尔状态,BitMap 用 1 bit 表示一天,一月仅 4 字节。String 存日期列表浪费空间;Set 元素开销大。BitMap 还支持位运算(BITCOUNT、连续 1 统计),天然匹配“连续签到”计算。
  • 为什么连续签到要用位运算而不是循环查? 从今天往前逐天 GETBIT 是 O(N) 且多次网络往返。BITFIELD 一次取出当月 bit,在应用层从今日位(day-1)向低位数连续 1 的个数,O(字长)。这是用位运算把多次查询变成一次。
  • 为什么要服务端日期? 客户端可改本地时间作弊签到。以服务端时钟为准,跨天用服务器 0 点(或业务时区)重置。
  • 为什么奖励要 MQ 异步+幂等? 签到高并发(0 点),发积分/券若同步会拖慢签到接口。异步发放,用签到记录唯一索引保证幂等(同一天只发一次)。
  • 为什么按月 key? 一月 31 bit 刚好;跨月自然滚动新 key;历史月可归档。避免单 key 无限增长。

【选型判断树】

存储:Redis BitMap(sign:{uid}:{month})
签到:SETBIT + 防重复判断
连续:BITFIELD 取位,找连续 1
奖励:MQ 异步 + 唯一索引幂等
日期:服务端时钟
补签:SETBIT + 消耗卡(事务)

判断口诀: BitMap 省空间,位运算算连续,服务端日期防作弊。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“签到是 BitMap 经典场景,核心是空间与位运算”
0:30–1:30数据结构BitMap 按月 key;SETBIT/BITCOUNT
1:30–2:30连续计算BITFIELD+找连续 1;跨月
2:30–3:30防重与奖励唯一索引;MQ 异步幂等
3:30–4:30补签与日历补签事务;日历查询
4:30–5:00收尾“一句话:1 bit 一天,位运算算连续,服务端日期防作弊”

【关键数字】

参数经验值说明
一月内存~4 字节/用户BitMap
连续奖励7 天常见活动
补签卡有限次活动道具
时区服务端业务时区防作弊
奖励幂等(user_id, date) 唯一防重复发
0 点峰值 QPS5~10 万(DAU 千万)集中触发
BitMap 内存每用户一 key:1000 万 key ≈ 0.6–0.8GB(key 结构开销 60–80B 远大于 4B 值);合并成少量大 key:≈40MB(约 4MB/key × 10)「极轻」只在合并口径成立,代价是大 key 的迁移/删除/阻塞风险

【追问链】(三层)

L1|“BitMap 跨年怎么办?” → 按月 key,自然滚动;历史月归档或删除。

L2|“连续签到跨月怎么算?” → 取上月末尾几天与本月开头,拼接位串再算连续;或维护「当前连续天数」计数器,断签/补签时同步修正,避免每次查询都跨 key 拼接。

L3|“0 点大量签到打挂 Redis 怎么办?” → 本地缓存当日已签标记;批量/合并写;限流排队;BitMap 本身极轻只在「多用户合并进少量大 key」的口径下成立(约 40MB);按主方案每用户一 key 是 0.6–0.8GB(key 结构开销 60–80B)。合并虽省内存,但 4MB 级大 key 在迁移/删除/主从重传上都是隐患,这笔取舍要先表态,再说瓶颈通常在网络与连接。

【评分标准】

档位答案特征
60 分知道用 Redis 存签到
80 分BitMap、连续位运算、唯一索引
95 分跨月拼接、服务端日期、补签事务、MQ 奖励幂等、日历查询

【关联题】

  • 同构: 第 124 题(点赞计数)、第 162 题(计数)
  • 上游: 第 159 题(积分)——签到发积分
  • 存储: Redis BitMap 章节

【自测】

  1. 判断对错:签到可以用 String 存日期列表。 参考答案: 浪费空间且难算连续;应用 BitMap。
  2. 连续签到怎么高效计算? 参考答案: BITFIELD 取当月 bit,从今日位(day-1)向低位数连续 1 的个数。
  3. 为什么用服务端日期? 参考答案: 防客户端改时间作弊。

150. 大转盘抽奖:概率/防超发/防作弊,怎么设计(抽奖系统) ​

【考察内容】概率与并发控制设计

【题目】运营要做大转盘抽奖:不同奖品概率不同、总奖品数量有限(大奖可能只有 1 个)、要防并发超发、防用户刷次数。概率怎么配置?中奖判定放哪层?库存扣减怎么保证不超发?

【参考答案】

  1. 核心:概率控制
    • 固定概率:每个奖品一个概率权重,随机数落在哪个区间中哪个奖;
    • 总量控制:每个奖品有库存,抽中时扣库存(Redis DECR/DB 条件更新),扣到 0 则改中其他奖品(按剩余权重重算)或返回未中;
  2. 防超发:库存 Redis 预扣(原子 DECR,小于 0 拒绝)→ 中奖记录落库(唯一索引 user_id+活动+轮次防重复参与)→ 对账;
  3. 防作弊:登录态校验、频控(每人每天次数 Redis 计数)、参与前验证(邀请/消费条件)、设备指纹风控;
  4. 并发:抽奖请求 Redis 扣库存+Lua 原子(判断库存→扣减→返回结果),MQ 异步发放奖品(发券/发货,幂等);
  5. 中奖概率动态:概率均分/保底机制(抽 N 次必中)、库存越少概率越低(按剩余库存调整权重);
  6. 审计:抽奖记录(谁、何时、中什么)留痕,活动结束后可对账复盘;
  7. 容量估算:活动峰值假设 10 万 QPS 抽奖;Redis Lua 单实例约 10 万 ops,需 Cluster;但「按活动/奖品随意分 key」会让 Lua 跨 slot 直接报 CROSSSLOT:Redis Cluster 要求一个脚本内的所有 key 落同一 slot,而一次抽奖要同时动「奖品库存+用户次数+保底计数+中奖记录」多个 key,主招就失效了。要么用 hash tag 把同一活动的 key 绑到同一 slot(act:{1001}:stock、act:{1001}:times,分片粒度按活动而非奖品),要么改成「单 key Lua+应用层重算」或「多次单 key 原子操作+补偿」;奖品库存与次数计数内存可忽略,瓶颈在单活动热点 key,可按奖品拆分;
  8. 失败与降级:Redis 故障时活动页降级为「排队/稍后再试」,禁止穿透 DB 抽奖;发放 MQ 失败重试+死信人工;日终对账发现超发则冻结异常中奖并补偿运营。

【原理溯源】

  • 为什么概率判定不能只靠前端或纯随机? 前端可被改;纯随机不控总量会超发。必须服务端:先随机算出目标奖品,再原子扣库存;扣失败则重算或未中。概率与库存解耦再耦合——概率决定“想给谁”,库存决定“能不能给”。
  • 为什么要 Lua 原子? “判断库存→扣减→返回结果”多步操作在并发下会超发。Lua 脚本在 Redis 单线程内原子执行,保证不超发。
  • 为什么要保底机制? 纯概率可能大量用户不中,体验差、活动失败。保底(抽 N 次必中)用确定性换取用户满意度,需消耗保底奖品库存。
  • 为什么要防刷? 脚本可无限抽,耗尽库存或薅羊毛。频控(次数上限)+设备指纹+行为风控是标配。
  • 为什么奖品发放要异步? 发货/发券可能慢或失败。MQ 异步+幂等(中奖记录唯一),失败重试,保证“中了必发到”。

【选型判断树】

抽奖:
随机权重选奖品 → Lua 原子扣库存(**Cluster 下多 key 脚本要用 hash tag 绑到同一 slot,否则 CROSSSLOT 直接报错**) → 成功则记录+异步发放
扣失败 → 重算其他奖/未中

防超发:Redis DECR + DB 兜底 + 对账
防刷:频控 + 设备指纹 + 风控
保底:次数计数达阈值强制中

判断口诀: 权重选奖、原子扣库存、异步发放、保底救体验。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“抽奖是概率+库存+防刷三合一”
0:30–1:30概率与库存权重随机;总量控制
1:30–2:30并发Lua 原子扣;防超发
2:30–3:30防刷与保底频控;N 次必中
3:30–4:30发放与审计MQ 异步幂等;留痕
4:30–5:00收尾“一句话:概率决定给不给,库存决定能不能给”

【关键数字】

参数经验值说明
库存扣减Redis 原子防超发
频控每人每日 N 次防刷
保底抽 10 次必中体验
发放MQ 异步可靠
对账Redis vs DB兜底
抽奖峰值约 10 万 QPS活动开抢瞬间
保底计数Redis INCR达阈值强制中

【追问链】(三层)

L1|“先查库存再扣可以吗?” → 不行,非原子会超发;必须 Lua 原子或条件更新——Cluster 里一次抽奖要动库存/次数/保底/记录多个 key,脚本内所有 key 必须同 slot(act:{aid}:*),否则主招失效,只能改「单 key Lua+应用层重算」或多次单 key 原子+补偿。

L2|“保底怎么实现?” → 参与次数计数(Redis INCR),达阈值强制中奖,消耗保底库存;次数与「是否已中过大奖」联合判断,防止保底逻辑被用来无限薅指定奖;发放仍走同一异步管道。

L3|“大奖只有 1 个,怎么保证不超发?” → 原子扣减到 0 即止;后续请求按剩余权重重算或未中;对账兜底。

【评分标准】

档位答案特征
60 分知道要控概率和库存
80 分权重随机+原子扣+防刷
95 分保底机制、异步发放、对账、与 134/156/157 同构

【关联题】

  • 同构: 第 134 题(优惠券)、第 156 题(红包)、第 157 题(预约)
  • 上游: 第 147 题(风控)
  • 并发: 第 124 题(计数)

【自测】

  1. 判断对错:抽奖概率可以只在前端控制。 参考答案: 前端可篡改;必须服务端判定+原子扣库存。
  2. 为什么用 Lua? 参考答案: 判断+扣减原子,防并发超发。
  3. 保底机制解决什么? 参考答案: 纯概率用户体验差;保底保证活跃用户必中。

151. 直播间 10 万人同时发弹幕,怎么实时展示(弹幕系统) ​

【考察内容】高并发实时消息架构

【题目】直播间 10 万人在线,弹幕峰值每秒几万条,要求实时上屏、不能卡顿、不能丢太多。弹幕通道怎么建(长连接/WebSocket)?广播(群发)怎么做?弹幕是否需要存储、存多久?

【参考答案】

  1. 链路:用户发弹幕 → 弹幕服务(校验+过滤)→ 广播到该直播间所有连接 → 客户端展示;
  2. 连接:WebSocket 长连接(客户端接入弹幕网关集群,按直播间路由);
  3. 广播方案:
    • Redis Pub/Sub:弹幕服务 publish 到直播间频道,弹幕网关订阅后推给本机连接——简单,但 Redis 单点消息能力有限(高并发下需分片);
    • Kafka:弹幕写入 topic(按直播间分区),网关消费后广播——可削峰、可回放,大直播主流;
    • 自研长连接网关:内存路由表(直播间→连接列表),直接内存广播(最快);
  4. 体验优化:弹幕限频+合并(同用户 1 秒最多 1 条;超高频弹幕抽样展示)、弹幕生命周期(窗口内展示后丢弃,不做持久化存储或只存抽样);
  5. 保障:弹幕丢失可容忍(不落库或异步落库);网关扩容(按直播间路由保证连接分布);全站弹幕洪峰(主播 PK 等)用限流+降级(只展示 VIP/部分弹幕);
  6. 扩展:弹幕审核(敏感词实时过滤)、礼物特效(高优消息通道);
  7. 容量估算:10 万人房间、弹幕峰值 3 万/s;若全量广播,fan-out 理论上可达 3 万×观看连接数,必须靠「限频+合并+抽样」把上屏量压到 UI 可承受(「几十条」要落成硬指标,不能停在形容词:设每连接上屏 R 条/s、消息体约 100B,则单机下行带宽=该机连接数×R×100B;10 万人分摊到 N=4 个网关时每机 2.5 万连接×30 条/s≈75MB/s≈600Mbps,千兆网卡下 R 就只能给到约 15 条/s——所以 R、N、网卡三者要一起给数并互相校验,否则「抽样」只是嘴上降级);Kafka 按房间分区时单房间恒落 1 个分区=1 个消费者,32~64 个分区只隔离房间之间、不给这个 10 万人房间提速;大房间要房间内二次散列(roomId+hash(uid)%N)或多分区扇出。同理「同房间连接落同一网关分片」与「网关按连接数水平扩」互斥,大房间须跨网关节点广播;
  8. 失败与降级:网关节点挂→连接按房间哈希漂移重连;Kafka 延迟升高→降级为只推高优礼物+合并弹幕;审核服务超时→临时只展示白名单/低风险词弹幕,宁可少不可违规。

【原理溯源】

  • 为什么弹幕和 IM 架构取向不同? IM 要求必达、有序、存储;弹幕高吞吐、可丢失、无需持久化。弹幕可以抽样展示、合并、丢弃,用“最终大部分用户看到大部分弹幕”换吞吐。这是产品语义决定架构取舍。
  • 为什么大直播用 Kafka 而不是 Pub/Sub? Redis Pub/Sub 无持久化、单点能力有限;Kafka 分区可水平扩展、可削峰、可回放(录播弹幕)。10 万人直播 Kafka 更稳。
  • 为什么要限频+合并? 防止刷屏与洪峰。同用户 1 秒 1 条;超高频时抽样/合并展示,保证 UI 不卡。这是用产品降级换系统稳定。
  • 为什么按直播间路由? 同一直播间的连接应落在同一网关分片,便于本机连接列表直接推(内存广播)。但这条与「网关按连接数水平扩」是互斥的:10 万人房间全落一个网关分片=单机连接数与带宽先被打满,所以大房间要按 roomId + hash(uid)%N 把同一房间的连接再散到 N 个分片,推送时跨节点扇出(Redis Pub/Sub 或网关间 RPC 广播)。口径分两档:小房间按房间集中=本地广播最省;大房间二次散列=跨节点扇出换扩展性。负载均衡时按房间一致性哈希只对前者成立。
  • 为什么要敏感词实时过滤? 合规要求。在弹幕服务入口同步过滤,延迟毫秒级;也可异步复审+撤回。

【选型判断树】

| 通道:WebSocket 长连接;小房间按直播间路由,大房间 roomId+hash(uid)%N 二次散列
广播:
├─ 小直播 → Redis Pub/Sub
└─ 大直播 → Kafka 分区
存储:可不落库或抽样;窗口内丢弃
治理:限频、合并、抽样、限流降级

判断口诀: 长连接接入,Kafka 削峰,限频合并,可丢失换吞吐。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“弹幕是高吞吐可丢失的实时广播”
0:30–1:30接入WebSocket 集群;小房间按房间路由、大房间房间内二次散列跨节点扇出
1:30–2:30广播Pub/Sub vs Kafka
2:30–3:30体验限频合并抽样;不卡 UI
3:30–4:30存储与治理可不落库;洪峰降级
4:30–5:00收尾“一句话:可丢失、可抽样、可合并,换十万并发”

【关键数字】

参数经验值说明
在线10 万单房间
弹幕峰值数万/秒需削峰
限频1 条/秒/人防刷屏
存储不落库或抽样可容忍丢
路由小房间按直播间哈希;大房间 roomId+hash(uid)%N 二次散列本地广播/跨节点扇出
Kafka 分区32~64(隔离房间之间)单房间恒落 1 分区=1 消费者,不给大房间提速;大房间靠房间内二次散列/多分区扇出

【追问链】(三层)

L1|“弹幕和 IM 消息区别?” → 弹幕高吞吐可丢、无需必达;IM 低吞吐必达、要存储。架构取向相反。

L2|“弹幕全落库行不行?” → 写入扛不住:3 万/s 持久化会打爆存储与主键索引;应不落库,或只抽样/高优礼物落库供回放。若合规要求留痕,异步进对象存储/冷存,不挡上屏链路。

L3|“主播 PK 全站弹幕洪峰怎么办?” → 限流+降级(只展示部分/VIP);Kafka 削峰;网关扩容。

【评分标准】

档位答案特征
60 分知道用 WebSocket
80 分广播方案、限频合并
95 分与 IM 对比、按房间路由(大房间须散列)、洪峰降级、敏感词、礼物高优通道

【关联题】

  • 对比: 第 129 题(IM)——必达 vs 可丢
  • 同构: 第 128 题(Feed 推模式)——同为大扇出广播;第 144 题是短视频(上传/转码/CDN 文件分发),机制不同
  • 上游: 第 128 题(Feed)——另一种推送

【自测】

  1. 判断对错:弹幕必须全部落库保证不丢。 参考答案: 可丢失;落库扛不住,抽样或不落。
  2. 为什么按直播间路由? 参考答案: 同房间连接集中便于本机内存广播(小房间适用);大房间 10 万人会让单机连接与带宽先打满,须 roomId+hash(uid)%N 二次散列、推送时跨节点扇出——与正文第 7 条口径一致。
  3. 大直播为什么用 Kafka? 参考答案: 分区扩展、削峰、可回放;Pub/Sub 能力有限。

152. 发帖/回答/点赞/热榜,社区系统全貌(社区系统) ​

【考察内容】内容社区综合设计

【题目】要做问答社区:用户发帖提问、回答问题、给回答点赞、关注话题、首页信息流与热榜。内容审核(敏感词)怎么做?热榜怎么算?海量内容的分页与检索怎么做?

【参考答案】

  1. 内容模型:帖子/问题表、回答表(问题下多条回答)、评论表(见评论系统);内容字段(正文、图片);
  2. 核心功能架构:
    • 发帖/回答:内容校验(敏感词)→ 落库 → 索引(ES 全文检索)→ 通知关注者(MQ);
    • 内容流(热榜/推荐):热度分=点赞×权重+评论×权重+时间衰减 → ZSet 存热榜 → 缓存榜单;
    • 关注话题流:关注话题的帖子 Feed(推拉结合,见 Feed 流);
    • 回答排序:按投票/时间/采纳(最佳答案置顶);
  3. 存储:内容表分表(按内容 ID)、正文大字段拆分(正文表/对象存储)、ES 建搜索索引;
  4. 高并发读:热帖详情缓存(Redis 整页缓存/JSON)、列表游标分页;
  5. 治理:审核(先审后发/先发后审)、举报处理、反垃圾(重复内容检测);
  6. 扩展:专栏、付费、私信(IM 能力);
  7. 容量估算:日发帖 10 万、回答 50 万、点赞 500 万;热榜每分钟重算一次 ZSet,单次扫描热池即可;ES 假设千万级文档,写入用 bulk,检索 P99 目标 < 200ms;详情读写比约 100:1,详情页强缓存;
  8. 失败与降级:ES 故障时搜索降级为「标签/最新列表」;热榜计算失败沿用上一期缓存并标延迟;审核服务不可用则 UGC 转先审后发或仅允许关注流,避免违规内容全站分发。

【原理溯源】

  • 为什么要拆正文大字段? 内容表若含长正文,行变大、索引变重、缓存浪费。拆出正文表或对象存储,列表页只查元数据,详情页才取正文。这是冷热字段分离。
  • 为什么热榜要时间衰减? 同 127 题:不衰减则历史内容永久霸榜,新内容无法冒头。
  • 为什么发帖要通知关注者? 关注关系驱动分发。发帖事件 MQ 推给粉丝(推拉结合),是社区活跃的关键回路。
  • 为什么要游标分页? 内容列表无限流,深分页慢。游标(最后一条 ID/时间)每次取 N 条,与页深无关。
  • 为什么要先审后发/先发后审? 合规与体验的权衡:高危场景(时政)先审后发;UGC 通常先发后审+机审过滤+举报兜底。

【选型判断树】

内容:元数据表 + 正文分离
检索:ES 倒排
热榜:ZSet + 时间衰减 + 缓存
Feed:推拉结合(见 128)
分页:游标
审核:机审+人审;先发后审或先审后发

判断口诀: 冷热分离、ES 检索、衰减热榜、游标分页、审核兜底。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“社区是内容模型+分发+治理的综合题”
0:30–1:30内容模型问题/回答/评论;正文分离
1:30–2:30分发热榜衰减;Feed 推拉;通知
2:30–3:30检索与分页ES;游标
3:30–4:30治理审核、举报、反垃圾
4:30–5:00收尾“一句话:模型支撑存储,衰减支撑热榜,审核支撑合规”

【关键数字】

参数经验值说明
正文拆分> 1KB 外置冷热分离
热榜 TTL10s~1min缓存
分页游标避免深分页
审核机审同步+人审异步平衡
去重编辑距离/指纹反垃圾
日发帖/回答10 万 / 50 万量级假设
点赞500 万/日热榜输入

【追问链】(三层)

L1|“帖子删除后缓存/搜索/热榜怎么同步?” → 删除事件 MQ 驱动清理缓存+ES+榜单;或读时状态过滤。

L2|“热榜无时间衰减会怎样?” → 老内容霸榜,新内容无法冒头,榜单失去分发价值;必须衰减(如 score × e^(-λt))或按时间窗重算。

L3|“海量内容怎么防重复灌水?” → 内容指纹(SimHash)、相似度检测、频控、举报。

【评分标准】

档位答案特征
60 分知道要发帖点赞评论
80 分内容模型、热榜、ES、游标
95 分正文分离、删除事件联动、审核策略、反垃圾、与 124/125/127/128 综合

【关联题】

  • 综合: 第 124 题(点赞)、第 125 题(评论)、第 127 题(热榜)、第 128 题(Feed)、第 136 题(搜索)
  • 治理: 第 147 题(风控)

【自测】

  1. 判断对错:帖子正文应存在内容主表。 参考答案: 大字段拖慢查询;应冷热分离。
  2. 热榜为什么要衰减? 参考答案: 防旧内容霸榜,让新内容有机会。
  3. 深分页怎么优化? 参考答案: 游标分页(last_id/时间戳)。

153. 30 分钟后执行/定时执行/失败重试,统一延迟任务平台(延迟任务系统) ​

【考察内容】任务调度平台设计

【题目】多个业务都要“延迟执行”:订单 30 分钟关单、红包 24 小时退回、任务定时重试。想做一个通用延迟任务平台(不绑死某个 MQ)。时间轮、Redis 有序集合、MQ 延迟消息、DB 轮询几种实现怎么选?任务持久化与失败重试怎么做?

【参考答案】

  1. 核心能力:任务注册(延迟时间/执行内容)、到点触发、失败重试、执行记录;
  2. 实现方案选型:
    • 时间轮(Netty HashedWheelTimer):内存级、毫秒精度,适合进程内短延迟任务;不支持持久化(重启丢失);
    • Redis ZSet:score=执行时间戳,扫描到点任务执行(见延时任务题);支持持久化、简单;
    • RabbitMQ 延迟队列/RocketMQ 延迟消息:可靠、有死信;
    • 数据库轮询(Quartz/XXL-JOB):任务表+定时扫描,支持持久化/分布式调度/失败重试记录;
  3. 平台化设计:
    • 任务表(task_id、执行时间、状态:待执行/执行中/成功/失败、重试次数、业务参数);
    • 分级存储:秒级任务用内存时间轮/Redis,分钟级以上落库扫描;
    • 触发:执行器(Worker 集群,分布式锁防重复执行,分片处理);
    • 重试:指数退避、最大次数、死信(人工处理);
    • 监控:任务成功率、积压、超时;
  4. 关键点:可靠性(任务持久化+执行记录+对账)、精准性(时间轮毫秒 vs 扫表分钟级,按 SLA 选)、幂等(执行器幂等,防重复触发);
  5. 容量估算:日任务假设 500 万,约 80% 为订单超时;均值到期约 58/s,整点可放大 10 倍至 500+/s;任务表按天/状态分片,Worker 按 task_id 哈希;
  6. 失败与降级(先分清哪一档能切,否则会切到空集合):按第 3 条分级存储,秒级任务在内存时间轮/Redis ZSet,分钟级以上只落库——DB 扫描挂时 Redis 里根本没有这批分钟级任务,直接「切 Redis ZSet」会让订单关单、红包退回全丢。所以:① 秒级档照常走 Redis;② 分钟级档改为延后扫描+积压告警+恢复后按「到期未执行」批量补偿,只有平时就双写(落库同时 ZADD,且仅对内存有余量的短任务)才允许热切 Redis;Worker 全挂则恢复后按「到期未执行」批量补偿;执行中状态超时回滚,避免卡死在执行中。

【原理溯源】

  • 为什么要分级存储? 秒级任务扫表精度不够且压力大;分钟级以上用时间轮内存又怕重启丢。分级:秒级 Redis/时间轮,分钟级 DB 扫描,各取所长。
  • 为什么执行器要幂等? 分布式调度可能重复触发(锁超时、重试)。任务本身必须幂等(业务唯一键),否则重复执行会出错(如重复关单、重复退款)。
  • 为什么用分布式锁防重复执行? 多 Worker 同时扫到同一任务会重复执行。锁保证同一任务同时只有一个 Worker 处理。
  • 为什么要死信? 重试到上限仍失败的任务不能无限重试,进死信队列人工处理,避免阻塞后续任务。
  • 为什么时间轮适合短延迟? 时间轮用循环数组+指针推进,插入 O(1),到期检测 O(1);但内存态,重启丢失。适合“连接超时”这类进程内短任务。

【选型判断树】

延迟精度?
├─ 毫秒级、进程内 → 时间轮
├─ 秒级、需持久化 → Redis ZSet
├─ 分钟级以上 → DB 扫表 / XXL-JOB
└─ 要可靠投递+死信 → RocketMQ 延迟消息

平台化:任务表+Worker 集群+分布式锁+重试死信
幂等:业务唯一键

判断口诀: 按精度选方案,分级存储;Worker+锁防重;死信兜底。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“延迟任务是精度、可靠性、幂等三角”
0:30–1:30四方案时间轮/ZSet/MQ/DB 对比
1:30–2:30分级秒级内存,分钟级 DB
2:30–3:30Worker集群+分布式锁+分片
3:30–4:30可靠性持久化、重试、死信、幂等
4:30–5:00收尾“一句话:精度定方案,幂等防重复,死信兜底”

【关键数字】

参数经验值说明
时间轮精度毫秒进程内
Redis ZSet秒级持久化
DB 扫描分钟级高可靠
重试3~5 次指数退避死信
订单超时15~30 分钟常见
日任务量数百万级平台化前提
到期峰值均值约 60/s,峰可 10 倍分桶分片

【追问链】(三层)

L1|“全部用扫表行不行?” → 秒级延迟不可控;高精度场景需时间轮/Redis。

L2|“任务量级大怎么办?” → 按执行时间分桶(减少扫描范围)、任务表分库分表、Worker 按 task_id 哈希分片;热点业务可独立队列,避免大促任务挤占普通延迟任务。

L3|“Worker 挂了任务会丢吗?” → 任务持久化在 DB/Redis,其他 Worker 可接管;执行中状态需超时回滚。

【评分标准】

档位答案特征
60 分知道延迟队列
80 分四方案对比、持久化、重试
95 分分级存储、分布式锁、幂等、死信、与 131 题超时关单呼应

【关联题】

  • 下游: 第 131 题(订单超时)、第 156 题(红包退回)
  • 同构: 第 137 题(爬虫调度)
  • 上游: MQ 延迟消息章节

【自测】

  1. 判断对错:延迟任务全部用内存时间轮最简单。 参考答案: 重启丢失;需持久化场景不能用。
  2. 为什么执行器要幂等? 参考答案: 分布式可能重复触发;幂等防业务错误。
  3. 死信的作用? 参考答案: 重试失败的任务人工处理,不阻塞后续。

154. 后台管理系统的菜单/按钮/数据权限,怎么建模(权限系统) ​

【考察内容】RBAC 是后台系统最高频设计题

【题目】后台管理系统要控制:谁能看哪个菜单、谁能点哪个按钮、谁能看哪些数据(比如大区经理只能看本大区)。RBAC 模型怎么建(用户-角色-权限)?数据权限(行级)怎么做?权限变更如何生效?

【参考答案】

  1. RBAC 模型:用户 ↔ 角色(多对多)↔ 权限(多对多);权限=资源+操作(如“订单-查看/导出”);
  2. 表设计:用户表、角色表、权限表(或菜单表)、用户-角色关联表、角色-权限关联表;
  3. 授权粒度:
    • 功能权限(菜单/按钮):用户登录后查权限列表 → 前端控制菜单显隐+按钮禁用,后端接口鉴权(注解/拦截器校验,防止绕过前端);
    • 数据权限(只能看本部门/本人数据):SQL 层注入条件(部门过滤、owner 过滤),或数据权限规则表;
  4. 鉴权实现:登录生成 Token(含用户 ID)→ 请求拦截器解析 → 查缓存(用户权限缓存 Redis,变更后失效)→ 校验接口所需权限 → 放行/403;
  5. 权限变更:改角色即生效(权限缓存失效);超级管理员(绕过校验);
  6. 扩展:组织架构(部门树)、用户组、权限继承(角色继承/组织继承)、操作审计(谁在何时做了什么);
  7. 安全:越权防护(水平越权:改 ID 查别人的数据——接口必须校验资源归属,不能只靠前端);
  8. 容量估算:用户假设 5 万、角色 200、权限点 2000;权限缓存约 5 万×2KB ≈ 100MB Redis,完全可全量缓存;鉴权拦截器单次 Redis GET 亚毫秒,不构成瓶颈;
  9. 失败与降级:权限服务挂→网关用本地最近成功缓存(短 TTL)短时放行只读,写操作拒绝;缓存击穿用互斥锁重建;变更风暴时批量失效而非逐用户删。

【原理溯源】

  • 为什么 RBAC 要“用户-角色-权限”三层而不是用户直接挂权限? 用户数量大、变动频繁;权限相对稳定。角色作为中间层,把“一组权限”打包,用户赋角色即可。千名员工只需维护少量角色,管理复杂度从 O(用户×权限) 降到 O(用户×角色)+O(角色×权限)。
  • 为什么前端隐藏按钮不够? 前端可被绕过(直接调 API)。必须后端接口鉴权,前端只是体验优化。安全必须在服务端强制。
  • 数据权限为什么难? 功能权限是“能不能调这个接口”,数据权限是“能看哪些行”。行级过滤要在 SQL 层注入条件(如 AND dept_id = ?),或用数据权限规则引擎。漏做会导致越权看别人数据。
  • 水平越权怎么防? 用户 A 改 URL 里的 ID 访问 B 的订单。接口内必须校验资源归属(order.user_id == current_user),不能只依赖“已登录”。这是 OWASP 越权漏洞的典型。
  • 权限变更为什么要失效缓存? 权限缓存在 Redis 提升性能;变更后不失效则用户仍持旧权限(离职员工仍可访问)。改角色时主动删缓存。

【选型判断树】

功能权限:RBAC 三表 + 后端接口鉴权
数据权限:SQL 注入条件 / 规则表
缓存:Redis 存用户权限,变更失效
越权:接口内校验资源归属
审计:操作日志

判断口诀: 角色打包权限,后端强制鉴权,数据行级过滤,归属校验防越权。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“权限是 RBAC+数据权限+越权防护”
0:30–1:30RBAC 模型三层多对多;表设计
1:30–2:30功能权限前端显隐+后端鉴权
2:30–3:30数据权限SQL 注入条件;部门过滤
3:30–4:30安全与生效缓存失效;水平越权;审计
4:30–5:00收尾“一句话:角色简化管理,后端强制安全,行级控制数据”

【关键数字】

参数经验值说明
角色数数十~数百(本题按 200 估)远少于用户
权限缓存Redis,变更失效性能
越权接口内校验归属安全底线
审计全操作留痕合规
权限缓存体积~100MB(5 万用户 × 约 2KB)Redis 全量放得下

【追问链】(三层)

L1|“只做前端菜单隐藏行不行?” → 不行,可绕过;必须后端接口鉴权。

L2|“水平越权怎么防?” → 接口内校验资源 owner 与当前用户/部门关系,或数据权限 SQL 兜底;不能只靠「已登录」。导出类接口同样要行级过滤,否则一键拖库。

L3|“权限改了用户要重新登录吗?” → 不需要;权限缓存主动失效,下次请求即新权限。

【评分标准】

档位答案特征
60 分知道用户-角色-权限
80 分RBAC 三层、后端鉴权
95 分数据权限行级、水平越权、缓存失效、审计、与 155/138 联动

【关联题】

  • 同簇: 第 155 题(SSO)、第 138 题(网关鉴权)
  • 安全: 第 147 题(风控)
  • 数据: 第 160 题(知识库权限)

【自测】

  1. 判断对错:前端隐藏按钮即可控制权限。 参考答案: 可绕过;必须后端鉴权。
  2. RBAC 三层的好处? 参考答案: 角色打包权限,简化用户-权限管理复杂度。
  3. 水平越权是什么?怎么防? 参考答案: 改 ID 访问他人数据;接口内校验资源归属。

155. 公司 10 个系统要一次登录全通(SSO) ​

【考察内容】SSO 是登录体系高级题

【题目】公司内部有 10 个系统(OA/财务/工单…),每个都登录太烦。要做 SSO:登录一次,所有系统免登。SSO 的流程(认证中心、票据、回调)怎么设计?跨域 Cookie 问题怎么处理?登出怎么全局生效?

【参考答案】

  1. 核心:统一认证中心(CAS/OAuth2/SSO 服务),各系统不再各自登录;
  2. 流程(基于 Cookie+票据):
    • 用户访问系统 A(未登录)→ 302 跳转认证中心 → 认证中心展示登录页 → 用户登录成功 → 认证中心生成全局票据(TGT/Ticket)写入认证中心 Cookie(域 *.company.com 或认证中心域名)→ 302 带 ticket 回跳系统 A → 系统 A 拿 ticket 到认证中心校验(换取用户信息+本地会话)→ 放行;
    • 访问系统 B:B 同样跳转认证中心 → 认证中心发现已有 TGT(已登录)→ 直接发 ticket 回跳 B → B 校验放行——一次登录,处处通行;
  3. 会话管理:认证中心持有全局会话(Redis,可主动踢人);各系统持有本地会话(Token/短时);
  4. 登出:单点登出——认证中心发登出通知,各系统清会话(消息/回调);
  5. 安全:ticket 一次性(用后即毁)、短过期、HTTPS、防 CSRF(state 参数)、回调地址白名单;
  6. 变体:OAuth2(第三方授权,支持 App 扫码)、JWT 无状态 SSO(各系统验签,无中心会话但踢人难);
  7. 容量估算:公司 5 万员工、日活登录 2 万,认证中心登录峰值通常 < 100 QPS,集群冗余足够;各系统 ticket 校验合计约千级 QPS,校验接口无状态易水平扩;
  8. 失败与降级:认证中心挂→新登录不可用,已签发的本地会话仍可短时工作;认证中心需集群+会话 Redis 高可用;可保留紧急本地登录白名单(仅运维),并告警。

【原理溯源】

  • 为什么要认证中心而不是各系统各自登录? 各自登录要维护多套账号体系、用户体验差、安全策略不一致。中心化后一处登录、统一策略、统一审计。这是身份认证的平台化。
  • TGT 和 Ticket 的区别? TGT(Ticket Granting Ticket)是认证中心的全局会话凭证,存在认证中心 Cookie;Ticket(Service Ticket)是发给具体业务系统的一次性凭证,业务系统拿它换用户信息。TGT 长期、Ticket 一次性。分层凭证降低泄露风险。
  • 跨域 Cookie 怎么处理? 认证中心与业务系统若不同主域,Cookie 无法共享。方案:① 统一父域(*.company.com);② 认证中心独立域,靠 302 重定向传递 ticket(不依赖 Cookie 共享);③ OAuth2 授权码模式。CAS 用 302+ticket 天然跨域。
  • 为什么 ticket 要一次性? 防重放。ticket 被截获后若可重复使用,攻击者可冒充。用后即毁+短过期,限制风险。
  • 单点登出为什么难? 认证中心清了 TGT,但各系统本地会话还在。需要通知所有系统清会话(回调/广播)。JWT 无状态方案更难踢人(token 有效期内无法作废,需黑名单)。

【选型判断树】

企业内部统一登录 → CAS/CAS-like(Cookie+Ticket)
第三方/App 授权 → OAuth2
无状态+可踢人难接受 → 需中心会话黑名单

安全:ticket 一次性、短过期、白名单、HTTPS
登出:中心通知各系统清会话

判断口诀: 中心管 TGT,业务管本地会话,ticket 一次性,登出要广播。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“SSO 是认证中心+票据+回跳校验”
0:30–1:30主流程302 跳转、TGT、ticket、校验
1:30–2:30第二系统免登已有 TGT 直接发 ticket
2:30–3:30跨域与安全父域/302;一次性;白名单
3:30–4:30登出与变体单点登出;OAuth2/JWT
4:30–5:00收尾“一句话:TGT 全局会话,ticket 一次性换本地会话”

【关键数字】

参数经验值说明
ticket 有效期1~10 分钟一次性
TGT 有效期小时~天全局会话
回调白名单必须防开放重定向
登出中心广播各系统清会话
登录峰值< 100 QPS(企业规模)易集群
ticket 校验千级 QPS 合计无状态可扩

【追问链】(三层)

L1|“把 JWT 当 SSO 全解行不行?” → JWT 无中心会话,踢人难;适合无状态 API,不适合需强制登出的企业 SSO。

L2|“ticket 被截获怎么办?” → 一次性+短过期+HTTPS+回调白名单;截获也难利用。再加:ticket 与目标系统绑定、校验失败即拉黑来源 IP、审计异常校验次数。

L3|“单点登出怎么实现?” → 认证中心通知各系统清本地会话(回调/消息);JWT 需黑名单辅助。

【评分标准】

档位答案特征
60 分知道要做统一登录
80 分认证中心+ticket 流程
95 分TGT/ticket 分层、跨域方案、单点登出、与 OAuth2/JWT 对比、与 130 题票据呼应

【关联题】

  • 同簇: 第 130 题(扫码登录)、第 154 题(权限)
  • 上游: 第 138 题(网关鉴权)
  • 安全: 第 147 题(风控)

【自测】

  1. 判断对错:各系统可以共享一个 Cookie 完成 SSO。 参考答案: 跨主域无法共享;靠 302+ticket 或 OAuth2。
  2. TGT 和 ticket 区别? 参考答案: TGT 全局会话;ticket 一次性业务凭证。
  3. 为什么 ticket 要一次性? 参考答案: 防重放;用后即毁。

156. 群红包金额拆分、并发抢、退回,怎么设计(抢红包) ​

【考察内容】抢红包是 Redis 场景最高频设计题(腾讯系必考)

【题目】群里发一个总金额 100 元、50 个的红包,几百人同时抢:金额怎么拆分(每个人拿多少)、并发抢怎么保证不超领、24 小时未领完的钱怎么自动退回。数据库/Redis 方案怎么设计?

【参考答案】

  1. 发红包:金额校验(分存储)→ 生成红包记录(总额、个数、状态)→ 拆红包算法(发时预拆分 or 抢时动态拆分):
    • 二次均值法:每次抢剩余金额/剩余个数×2 为上限随机(保证分布均匀且不超);
    • 预拆分:发时把金额拆好存 Redis List(pop 即得);
  2. 抢红包流程(并发核心):用户抢 → Redis Lua 原子(库存判断:红包是否还有 + 是否已抢过(Set 去重)→ 从 List 弹一个金额 / 或动态拆一个 → 记录)→ 入账(余额/零钱,MQ 异步落账+幂等);
  3. 防重复抢:Redis Set(redpack:{id}:users)SADD 幂等,已抢返回“已抢过”;
  4. 24 小时退回:发红包时写延时任务(24h 后检查剩余金额)→ 未领完自动退回原账户(幂等:退回状态机,防重复退);
  5. 并发控制:全部用 Redis 原子操作(Lua),DB 只做异步落账与对账(Redis 记录 vs DB 明细核对);
  6. 防刷:每人限领一次、频控、风控(异常账号);
  7. 容量估算:单个红包几百人抢只有 10^3 QPS 量级(500 人在 1 秒内点开=500 QPS,算上重试也到不了数万);会打到「数万 QPS 同一 key」的是全站级热点——除夕全站假设 50 万 QPS,同一时刻全民抢同类红包、按红包 ID 哈希后单个热点 key 会聚到 10^4 QPS 量级,这才是分片计数的动机;单红包 List/Set 体积可忽略,瓶颈在热点红包 key,可用分片计数或本地缓存挡重复请求;
  8. 失败与降级:Redis 挂→暂停领取并提示排队,禁止穿透 DB 抢;恢复后从 AOF/DB 明细对齐;入账 MQ 积压异步消化;退回任务失败则重试并告警,资金以状态机为准只退一次。

【原理溯源】

  • 为什么拆红包用“二次均值法”? 若每次在剩余金额内均匀随机,前面的人可能把钱拿光,后面全 0.01,体验差。二次均值法设上限为“剩余均值×2”,保证分布均匀且总额不超。这是用约束随机换公平感。
  • 为什么全程要 Redis Lua 原子? “判断是否有剩余→判断是否已抢→弹出金额→记录”多步操作,非原子会在并发下超发/重复抢。Lua 在 Redis 单线程内原子执行,天然无并发冲突。DB 无法承受这种高并发写。
  • 为什么 Set 能防重复抢? SADD 返回 1 表示新加入,返回 0 表示已存在。一次原子操作完成“去重+判断”,比先查再插可靠。
  • 为什么入账要异步? 抢红包峰值极高,同步写账户 DB 会挂。Redis 记录“谁抢了多少”即返回,MQ 异步入账+幂等(红包 ID+用户 ID 唯一),对账兜底。
  • 为什么要延时退回? 24 小时未领完的钱必须退回,否则资损。延时任务(见 153 题)到点检查剩余,状态机保证只退一次。这是资金闭环。

【选型判断树】

拆分:预拆分存 List(简单)或动态二次均值
抢:Lua 原子(剩余判断+去重+弹出+记录)
防重:Set SADD 幂等
入账:MQ 异步 + 唯一索引
退回:延时任务 24h + 状态机幂等
对账:Redis vs DB

判断口诀: Lua 保原子,Set 保不重,MQ 保异步,延时保退回。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“抢红包核心是 Lua 原子+拆分算法+资金闭环”
0:30–1:30拆分算法二次均值;预拆分 vs 动态
1:30–2:30抢流程Lua 原子;Set 防重
2:30–3:30入账MQ 异步;幂等
3:30–4:30退回与对账延时任务;状态机;对账
4:30–5:00收尾“一句话:原子抢、异步入账、延时退回、对账兜底”

【关键数字】

参数经验值说明
金额分(整数)精度
拆分上限剩余均值×2二次均值
退回24h常见
防重Set + 唯一索引双保险
对账Redis vs DB兜底
单红包峰值数百~数千 QPS(几百人 1 秒内点开)全站级热点聚合到同一 key 才到万级
全站除夕约 50 万 QPS 量级需 Cluster

【追问链】(三层)

L1|“为什么金额用分?” → 浮点精度问题;资金场景必须整数。

L2|“拆分算法为什么用 2 倍均值?” → 保证每次不超过剩余均值 2 倍,分布均匀且不会前面拿光;预拆分 List 则算法在发红包时定死,抢时只 pop,实现更简单。

L3|“Redis 挂了红包记录丢了怎么办?” → 持久化(AOF);DB 对账;业务上可容忍极端丢失(小额)或告警人工。

【评分标准】

档位答案特征
60 分知道要控库存防超发
80 分Lua 原子、Set 防重、MQ 异步
95 分二次均值拆分、延时退回、对账、金额分、与 134/150 同构

【关联题】

  • 同构: 第 134 题(优惠券)、第 150 题(抽奖)、第 153 题(延迟任务)
  • 上游: 第 132 题(支付账务)——入账
  • 并发: 第 124 题(计数)

【自测】

  1. 判断对错:抢红包可以先查库存再扣减。 参考答案: 错。必须 Lua 原子,否则并发超发。
  2. 二次均值法解决什么? 参考答案: 防止前面的人拿光,保证分布均匀。
  3. 24 小时退回怎么保证不重复退? 参考答案: 状态机幂等;延时任务触发。

157. 先预约才有抢购资格,资格怎么发放和校验(预约资格系统) ​

【考察内容】秒杀的资格预审设计

【题目】秒杀改预约制:用户先预约,预约成功才获得抢购资格,秒杀开始后只有有资格的人能下单。资格怎么发放(限量)?秒杀时资格怎么校验(不穿透 DB)?资格能否转让/过期?

【参考答案】

  1. 需求:预约(活动前)→ 抢购(活动开始,有资格才能抢)→ 防“未预约直接抢”;
  2. 预约阶段:用户点预约 → 记录预约(Redis Set/DB 表,唯一索引 user_id+活动)→ 预约人数统计(活动预热参考);可设预约上限(限量预约);
  3. 资格生成:活动开始时(或预约成功时)给预约用户发放资格 token(存 Redis,key=quota:{activity}:{userId},value=资格凭证);
  4. 抢购校验:抢购请求必须携带资格 token → 服务端校验(Redis 存在且未使用)→ 原子消费资格(SETNX/GETDEL 删除,防重复使用)→ 进入抢购流程(限流+预扣库存);
  5. 防绕过:资格校验在服务端(token 与用户绑定:Redis 存 userId 比对),前端无意义;
  6. 未预约用户:无资格 token,直接拒绝——减少抢购瞬间无效流量;
  7. 扩展:资格回收/过期清理(口径要与第 5 条一致:资格不可转让——token 绑 userId;若业务确实要「转赠」,只能走服务端重发资格+唯一索引改绑并全程留痕,绝不能在客户端换人)、预约提醒(推送)、资格使用统计;
  8. 容量估算:预约假设 100 万人,token 均 50B ≈ 50MB Redis;秒杀瞬间有资格用户假设 10 万,校验+原子消费 QPS 10 万级,Redis Cluster 可扛,无需查 DB;
  9. 失败与降级:资格 Redis 不可用时降级查预热 DB(需限流);token 服务异常则暂停抢购入口;活动结束定时清理残留 token,并与预约表/订单表对账,防止「无预约却下单」。

【原理溯源】

  • 为什么要预约制而不是直接秒杀? 直接秒杀时全部流量涌入,大量无效请求(未预约、黄牛)打挂系统。预约制把“筛选”前置到活动前,抢购瞬间只有有资格的用户进入,流量大幅下降。这是用时间换空间——把峰值压力摊到预约期。
  • 为什么要资格 token 而不是查 DB? 抢购瞬间 QPS 极高,查 DB 会挂。token 在 Redis,O(1) 校验+原子消费,把 DB 挡在外面。
  • 为什么 token 要与用户绑定? 防转让滥用。token 存 userId,校验时比对当前用户,即使 token 泄露也无法被他人使用。
  • 为什么要原子消费(GETDEL/SETNX)? 防止一个 token 被用两次。原子“读取并删除”保证一次性。
  • 预约人数超过库存怎么办? 预约只是筛选资格,不保证中签。抢购时库存决定成败,预约多说明热度高,可加大备货或加价。

【选型判断树】

预约:Redis Set / DB 唯一索引(user_id+活动)
资格发放:Redis token,绑定 userId
抢购校验:Redis 存在性 + 原子消费
未预约:直接拒绝
过期:定时清理

判断口诀: 预约筛人、token 资格、原子消费、服务端绑定。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“预约资格是秒杀的前置流量筛选”
0:30–1:30预约阶段记录、唯一索引、上限
1:30–2:30资格发放Redis token 绑定用户
2:30–3:30抢购校验原子消费;防绕过
3:30–4:30扩展过期清理、提醒、统计
4:30–5:00收尾“一句话:预约筛流量,token 保资格,原子防重用”

【关键数字】

参数经验值说明
预约上限可限量控热度
token 存储Redis毫秒校验
消费GETDEL 原子一次性
过期活动结束后清理防残留
预约 token 内存100 万×50B ≈ 50MB极轻
抢购校验 QPS约 10 万(有资格用户)Redis 扛

【追问链】(三层)

L1|“资格不绑定用户会怎样?” → token 可转让滥用;必须绑定 userId 校验。

L2|“一个 token 能抢两次吗?” → 不能;原子消费(GETDEL/SETNX)保证一次性。客户端重试会拿到「已使用」而非二次下单;订单侧再加用户+活动唯一索引双保险。

L3|“预约人数远超库存怎么办?” → 抢购限流+库存决定;预约只是资格筛选,可加大备货或营销造势。

【评分标准】

档位答案特征
60 分知道要预约才能抢
80 分token 资格、原子消费、服务端校验
95 分流量筛选价值、绑定防转让、过期清理、与 134/147/150 联动

【关联题】

  • 同构: 第 134 题(优惠券)、第 150 题(抽奖)
  • 上游: 第 147 题(风控)——防黄牛
  • 秒杀: 第 2 题(流量突增)

【自测】

  1. 判断对错:资格 token 可以放在前端,抢购时带上即可。 参考答案: 前端可伪造;必须服务端 Redis 校验+绑定用户。
  2. 为什么要原子消费? 参考答案: 防止一个 token 被用两次。
  3. 预约制的系统价值? 参考答案: 前置筛选,降低抢购瞬间无效流量。

158. 每天百万封邮件/短信,可靠发送与退订管理(邮件发送系统) ​

【考察内容】可靠批量任务系统设计

【题目】运营每天要发百万封营销邮件/短信,通知类也要发。要求:模板管理、失败重试、退订处理(合规)、发送频率控制(防骚扰投诉)、通道容灾。系统怎么设计?

【参考答案】

  1. 架构:业务方 → 发送 API(校验+模板渲染)→ 消息落库(发送任务表)→ MQ 分发 → 发送执行器(调用邮件/短信渠道)→ 回执更新;
  2. 关键设计:
    • 模板管理:模板+变量渲染(防注入);短信签名、字数、敏感词校验;
    • 批量发送:营销邮件批量任务(分片、限速——渠道 QPS 有限,防被渠道封);
    • 重试与死信:发送失败重试(指数退避)→ 死信人工;回执(投递成功/失败/退订)更新状态;
    • 幂等:发送任务唯一 ID(业务+类型),重复提交不重复发送;
    • 退订/黑名单:营销类必须支持退订(合规),发送前过滤黑名单;
  3. 性能:批量合并(邮件批量接口)、并发控制(渠道限速)、消息表分表(按发送时间/状态);
  4. 合规:授权发送(用户同意)、频率限制(每日上限)、敏感内容审核;
  5. 监控:发送量、成功率、失败原因分布、渠道延迟;
  6. 容量估算:日 100 万封,若 8 小时窗口则均值约 35 条/s,营销高峰可到 100+/s;短信渠道配额常见 500~2000 TPS,执行器必须按渠道令牌桶限速,宁慢勿被封;
  7. 失败与降级:主渠道挂自动切备用渠道并告警;模板/渲染服务挂时通知类降级固定文案,营销类延后;任务积压按优先级:验证码/交易通知 > 营销。

【原理溯源】

  • 为什么要任务表+MQ+执行器? 百万级发送不能同步调渠道 API(阻塞、超时、失败难追踪)。任务表落库保证不丢,MQ 削峰,执行器按渠道限速调用。这是批量可靠任务的标准架构。
  • 为什么要渠道限速? 短信/邮件渠道有 QPS 配额,打满会被封。执行器用令牌桶/信号量按渠道配置限速,宁慢勿封。
  • 为什么要退订? 合规要求(个保法/反垃圾邮件法)。营销类必须提供退订入口,发送前过滤黑名单。不合规有法律风险。
  • 为什么要回执? 发送成功≠送达。回执更新状态,支撑统计、重试、用户查询。
  • 为什么幂等用业务键? 业务方可能重复提交(重试)。用“业务单号+通知类型”唯一,重复提交不重复发送。

【选型判断树】

任务表(落库)→ MQ(削峰)→ 执行器(限速调渠道)
重试:指数退避 + 死信
幂等:业务键唯一
合规:退订 + 黑名单 + 频控
监控:成功率、失败分布

判断口诀: 落库保不丢,MQ 保削峰,限速保不封,退订保合规。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“批量发送是可靠任务+渠道限速+合规”
0:30–1:30架构任务表+MQ+执行器
1:30–2:30限速与重试渠道限速;退避;死信
2:30–3:30合规退订、黑名单、频控
3:30–4:30幂等与监控业务键;成功率
4:30–5:00收尾“一句话:可靠任务架构+渠道限速+合规退订”

【关键数字】

参数经验值说明
日发送百万级批量
渠道限速按配额防封
重试2~3 次退避死信
退订必须合规
频控日上限防骚扰
日发送均值~35/s(100 万/8h)峰可数倍
渠道配额常见 500~2000 TPS必须限速

【追问链】(三层)

L1|“渠道限速怎么实现?” → 令牌桶/信号量控制执行器并发,按渠道配置 QPS 上限。

L2|“用户退订后怎么生效?” → 黑名单实时更新;发送前过滤;已在队列中的存量任务消费时再查一次黑名单,避免“退订后仍收到”。

L3|“渠道挂了怎么办?” → 多渠道备份、故障转移、任务积压告警;死信人工。

【评分标准】

档位答案特征
60 分知道要调发送 API
80 分任务表+MQ+限速+重试
95 分退订合规、幂等、回执、渠道容灾、与 139 题呼应

【关联题】

  • 同构: 第 139 题(通知中心)
  • 上游: 第 131 题(订单事件)
  • 合规: 第 147 题(风控)

【自测】

  1. 判断对错:可以直接循环调短信 API 发百万条。 参考答案: 会超时、被封、失败难追踪;必须任务表+MQ+限速。
  2. 为什么要退订? 参考答案: 合规要求;防投诉与法律风险。
  3. 渠道限速怎么做的? 参考答案: 令牌桶/信号量按渠道 QPS 配额控制。

159. 积分获取/消耗/等级成长,会员体系怎么做(会员积分系统) ​

【考察内容】账户流水型业务设计

【题目】电商要做会员体系:消费得积分、积分抵现、成长值升级(普通→黄金→钻石)、不同等级不同权益。积分和成长值的流水怎么记?升级规则怎么设计、要不要降级?积分消耗和账户金额一样不能超扣,怎么保证?

【参考答案】

  1. 数据模型:用户积分账户(user_id、可用积分、累计积分)、积分流水表(流水号、类型:获取/消耗/过期、数量、关联业务单号、时间——追加写);等级表(等级、成长值区间、权益);成长值流水;
  2. 积分获取:下单/签到/活动 → 积分发放(MQ 异步+幂等:业务单号唯一,防重复发放)→ 写流水+更新账户(同一事务);
  3. 积分消耗:积分抵扣/兑换 → 扣减(原子性:条件更新 available >= 数量,或账户行锁)→ 写流水;防并发扣减(同一账户同时下单用积分——行锁/乐观锁);
  4. 积分过期:按获取时间先进先出(FIFO)过期——定期任务按批次过期或实时计算(流水队列);
  5. 等级(题干第二问「要不要降级」要先定口径,两种都要能答):保级制=成长值按自然年或滚动 12 个月统计,周期末重算,低于门槛即降档(多数电商会员用这套,否则等级只升不降、权益成本失控);不降级制=成长值只累计、等级永久(体验好,但权益成本随年限单调上涨,需财务确认)。选定后必须配齐四件:成长值有效期(与第 4 条积分 FIFO 同构)、降级前保级提醒+缓冲期、降档后权益回收(已领取的券不追回、未领取按新档发放)、等级变更事件通知下游刷新权益缓存。工程实现:成长值流水+每日/每周期结算作业重算,不在每次消费时实时判级(避免边界抖动);等级读走缓存;
  6. 对账:积分账户余额=流水和(定期核对);积分是“虚拟资产”,记录审计留痕;
  7. 风控:异常获取(刷单积分)监测、积分套现防护;
  8. 容量估算:日发放假设 100 万笔、消耗 20 万笔,流水日增约 120 万行,按月分表;账户读热点用户可缓存;扣减走条件更新,单账户串行可接受;
  9. 失败与降级:积分服务挂→下单主链路不依赖积分成功,发放 MQ 延迟补发;扣减失败提示积分不可用,不阻塞支付;过期任务失败次日重跑,以流水为准。

【原理溯源】

  • 为什么积分要“账户+流水”而不是直接改余额? 同支付账务(132 题):流水追加写可审计、可对账、可追溯;直接改余额丢历史、并发不安全。积分是虚拟资产,但账务纪律与真金白银一样。
  • 为什么发放要幂等? 下单事件可能重复消费。业务单号唯一索引保证同一订单只发一次积分。
  • 为什么扣减要条件更新? 并发下两个请求同时扣同一账户会超扣。WHERE available >= n 条件更新原子保证。
  • 为什么积分要过期? 控制负债规模、刺激消费。FIFO 按获取时间过期,公平且易实现(按批次)。
  • 成长值和积分为什么要分开? 积分可消耗(抵现),成长值只累计(升级)。混在一起会导致“消耗积分导致降级”的错误语义。分开建模,职责清晰。

【选型判断树】

账户:积分账户 + 流水(追加写)
发放:MQ 异步 + 业务单号幂等
扣减:条件更新防超扣
过期:FIFO 按批次
等级:成长值累计 → 等级 → 权益缓存
对账:余额=流水和

判断口诀: 流水驱动、幂等发放、原子扣减、FIFO 过期、成长值分离。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“积分是账户流水型业务,与支付同构”
0:30–1:30数据模型账户+流水+等级表
1:30–2:30获取与消耗幂等发放;条件更新扣减
2:30–3:30过期与等级FIFO;成长值分离;降级要选口径:保级制(周期末重算、低于门槛降档)或永久制(权益成本随年限涨)
3:30–4:30对账与风控余额=流水和;防刷
4:30–5:00收尾“一句话:流水保可审计,幂等保不重复,条件保不超扣”

【关键数字】

参数经验值说明
发放幂等业务单号唯一防重复
扣减条件更新 WHERE available>=n防超扣
过期FIFO 批次公平
对账日/周余额=流水和
等级成长值区间权益缓存
日流水~120 万笔(发 100 万+耗 20 万)按月分表

【追问链】(三层)

L1|“积分过期怎么算?” → 按批次(获取时间)先进先出,避免逐笔计算。

L2|“并发扣积分怎么防超扣?” → 条件更新 WHERE available >= n 或行锁/乐观锁;失败返回「积分不足」。同一账户热点时可本地排队串行,减少无效行锁冲突。

L3|“积分能套现吗?怎么防?” → 限制使用场景(不可提现)、风控监测异常获取与兑换、与支付隔离。

【评分标准】

档位答案特征
60 分知道要有积分账户
80 分流水驱动、幂等、条件扣减
95 分FIFO 过期、成长值分离、等级口径(保级/永久+缓冲期与权益回收)、对账、风控套现、与 132 题同构

【关联题】

  • 同构: 第 132 题(支付)、第 133 题(对账)
  • 上游: 第 149 题(签到发积分)
  • 风控: 第 147 题

【自测】

  1. 判断对错:积分可以直接 UPDATE balance。 参考答案: 错。流水驱动,可审计可对账。
  2. 为什么成长值和积分分开? 参考答案: 积分可消耗,成长值只累计;混用会导致降级错误。
  3. 积分过期用什么策略? 参考答案: FIFO 按获取时间批次过期。

160. 企业内部知识库:文档组织/全文检索/权限/版本(知识库系统) ​

【考察内容】知识管理类系统设计

【题目】公司要做内部知识库:文档按目录/空间组织、全文检索、按团队控制权限、文档有历史版本。文档存储(DB+对象存储)、全文检索(ES)、权限模型、版本管理(diff/回溯)怎么设计?

【参考答案】

  1. 核心实体:空间/知识库 → 目录树 → 文档;文档内容(结构化块/富文本/附件);
  2. 文档存储:文档表(doc_id、空间、标题、作者、状态)+ 内容存储(对象存储存大正文/附件,DB 存元数据与索引信息);版本管理(编辑生成版本快照,支持回滚);
  3. 全文检索:文档内容同步到 ES(增量:编辑事件触发/定时扫描),支持标题/正文/标签检索,高亮展示;
  4. 权限:空间级(成员/只读/管理员)+文档级(继承空间权限,可覆盖);RBAC+ACL 结合;权限校验在服务端;
  5. 并发编辑:编辑锁(悲观:编辑中锁定他人只读)或协作文档(OT/CRDT,见 145 题);
  6. 其他:目录树(树形结构存储 parent_id+排序)、评论/点赞(复用通用模块)、移动端同步、导入导出(Word/PDF 转换)、审计(谁改了文档);
  7. 扩展:AI 能力(文档摘要、问答 RAG);
  8. 容量估算:假设 10 万文档×均 50KB → 对象存储约 5GB,极小;ES 索引因分词膨胀约 2~3 倍正文;日编辑 5000 次增量同步完全可接受,检索读 QPS 内部系统通常不高,缓存热门文档元数据即可;
  9. 失败与降级:ES 挂→搜索降级为目录浏览+最近打开;对象存储故障→文档打开重试,版本快照仍可从 DB 元数据定位;权限服务超时默认拒绝,避免越权读。

【原理溯源】

  • 为什么正文要对象存储而不是 DB 大字段? 文档正文可能很大(图文混排),DB 大字段拖慢查询、备份、复制。对象存储存内容,DB 只存元数据与索引信息。这是冷热分离。
  • 为什么权限要“空间级+文档级”? 空间是团队边界,默认继承空间权限;个别文档需特殊控制(如 HR 文档仅管理员可见)则覆盖。继承+覆盖兼顾管理效率与灵活性。
  • 为什么要版本快照? 文档会被编辑,需回溯历史(误删恢复、审计)。每次编辑生成版本,可 diff、可回滚。存储上用“增量+定期快照”平衡空间。
  • 为什么检索要 ES? 全文检索(中文分词、高亮、相关性)MySQL 做不好。ES 倒排索引+分词器是标准方案。
  • 目录树为什么用 parent_id+path? 纯 parent_id 要递归查路径;冗余 path(物化路径)可一次查子树。深层目录频繁遍历时 path 更快。

【选型判断树】

存储:DB 元数据 + 对象存储正文
检索:ES 增量同步
权限:空间级继承 + 文档级覆盖;服务端校验
版本:快照+diff+回滚
目录:parent_id + path 冗余
并发:编辑锁或 OT/CRDT

判断口诀: 冷热分离、继承覆盖、快照回滚、path 加速。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“知识库是内容存储+检索+权限+版本”
0:30–1:30存储DB+对象存储分离
1:30–2:30检索ES 增量同步
2:30–3:30权限空间+文档两级;服务端
3:30–4:30版本与目录快照回滚;path 冗余
4:30–5:00收尾“一句话:冷热分离保性能,继承覆盖保灵活,快照保可回溯”

【关键数字】

参数经验值说明
正文外置> 1KB冷热分离
版本保留按需(30~90 天)回滚
检索同步秒级增量ES
权限空间+文档继承覆盖
目录path 冗余防递归
文档量级10 万篇×50KB ≈ 5GB对象存储
日编辑数千次ES 增量可行

【追问链】(三层)

L1|“正文存 DB 大字段行不行?” → 查询慢、备份重;应对象存储+ES 索引。

L2|“文档树深层嵌套怎么存?” → parent_id+path 物化路径,避免递归;移动目录时批量重写子树 path(或用闭包表)。

L3|“权限只做前端行不行?” → 不行;必须服务端校验,防越权。

【评分标准】

档位答案特征
60 分知道要存文档和搜索
80 分冷热分离、ES、权限两级
95 分版本快照回滚、path 冗余、并发编辑、与 145/154/136 联动

【关联题】

  • 同构: 第 136 题(搜索)、第 154 题(权限)
  • 版本: 第 145 题(协作文档)
  • 存储: 对象存储章节

【自测】

  1. 判断对错:文档正文应存 DB 主表。 参考答案: 大字段拖慢;应对象存储。
  2. 权限为什么要空间+文档两级? 参考答案: 空间默认继承高效,文档可覆盖灵活。
  3. 版本管理的价值? 参考答案: 回溯、审计、误删恢复。

161. 给大模型对话应用做后端:调用管理/限流/成本(LLM 后端) ​

【考察内容】LLM 工程是 2025+ 大热高频题

【题目】公司要做 AI 对话应用(接大模型 API),用户量上来后:调用费暴涨、上游限流、响应慢、Prompt 被刷。后端怎么设计?请求管理(缓存/路由)、限流与配额、成本控制(降级模型)、安全(Prompt 注入防护)怎么做?

【参考答案】

  1. 架构:业务应用 → LLM 网关 → 模型服务(自研部署/vLLM 或外部 API);
  2. 核心能力:
    • 统一接入:多模型(不同厂商/版本)统一 API 封装,路由(按模型能力/成本/负载选模型);
    • 限流与配额:按用户/应用维度限流(令牌桶),防止恶意调用烧钱;配额(免费额度/付费额度);
    • 缓存:相同 Prompt 结果缓存(Redis,TTL 短)——语义缓存(向量相似度命中)降本增效;
    • 成本控制:Token 计量(输入/输出)、按模型定价计费、预算告警(超预算熔断);
    • 重试与容错:模型超时/限流(429)重试(退避)、故障切换(主模型挂切备用模型);
    • 流式输出:SSE(Server-Sent Events)透传首字延迟(TTFT)优化;
  3. 可靠性:请求日志(Prompt/响应/耗时全记录,审计与调试)、敏感内容审核(入站/出站)、数据隔离(租户);
  4. 性能:KV Cache/前缀缓存(同前缀 Prompt 复用)、批处理(vLLM continuous batching)、模型路由(简单请求用快模型);
  5. 监控:Token 消耗、延迟(TTFT/TPOT)、错误率、成本大盘;
  6. 容量与成本:假设日调用 100 万次、均输入 800+输出 500 token;旗舰模型估算日成本量级数千美元起,语义缓存命中 30% 可省约三成;
  7. 失败与降级:上游 429/超时→退避后切备用模型;预算熔断后仅白名单可用;全挂时返回缓存结果或排队提示。

【原理溯源】

  • 为什么需要 LLM 网关而不是直连模型 API? 直连无法统一限流、计费、缓存、路由、审计。用户量上来后成本失控、被刷、上游 429 无应对。网关把横切能力集中,业务只调统一 API。这是AI 时代的 API 网关。
  • 为什么要做语义缓存? 相同/相似 Prompt 重复率高(客服 FAQ)。精确匹配缓存命中率低;语义缓存(embedding 相似度>阈值)命中率高,显著降本。代价是要向量检索与相似度计算。
  • 为什么要 Token 计量? LLM 按 token 计费,不计量则成本黑盒。计量后可按用户/应用分摊、设配额、预算告警熔断。
  • 为什么要模型路由? 不同任务对模型能力要求不同。简单任务用小模型(快、便宜),复杂任务用大模型。路由按 prompt 特征/用户等级选模型,平衡成本与效果。
  • 为什么要流式 SSE? LLM 生成慢(秒级),同步等待体验差。SSE 逐 token 推送,首字延迟(TTFT)短,用户感知快。

【选型判断树】

网关:统一接入、限流、缓存、计费、路由
缓存:精确匹配 + 语义缓存
限流:用户/应用维度令牌桶
成本:Token 计量 + 预算熔断
路由:按任务复杂度选模型
流式:SSE

判断口诀: 网关集中管控,语义缓存降本,路由平衡成本效果。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“LLM 后端核心是网关化管控”
0:30–1:30网关能力接入、限流、缓存、计费
1:30–2:30成本控制Token 计量;语义缓存;模型路由
2:30–3:30可靠性重试、故障切换、审计
3:30–4:30性能与安全流式 SSE;Prompt 注入防护
4:30–5:00收尾“一句话:网关集中管控,缓存与路由降本,计量防失控”

【关键数字】

参数经验值说明
语义缓存阈值余弦相似度 > 0.9按业务调
Token 计量输入/输出分开计费
限流用户/应用维度防刷
TTFT< 1s首字延迟
预算超限熔断防烧钱
日调用(例)100 万次成本基数
缓存命中收益命中 30% 省约 30% 费用语义缓存

【追问链】(三层)

L1|“语义缓存怎么实现?” → Prompt 转 embedding,向量相似度>阈值命中缓存;动态数据不能缓存。

L2|“上游模型 429 怎么办?” → 退避重试、故障切换备用模型、排队限流;熔断后返回缓存/降级文案,而不是把错误原样打给用户。

L3|“Prompt 注入怎么防?” → 输入过滤、输出审核、沙箱执行、权限最小化;不能完全依赖模型。

【评分标准】

档位答案特征
60 分知道要调大模型 API
80 分网关、限流、缓存、计费
95 分语义缓存、模型路由、流式 SSE、注入防护、成本熔断

【关联题】

  • 同构: 第 138 题(API 网关)
  • 上游: 第 3 题(限流设计)、第 136 题(搜索/向量)
  • 成本: 第 141 题(监控告警的存储与采样成本)

【自测】

  1. 判断对错:应用可以直接调 OpenAI API,不需要网关。 参考答案: 无统一限流/计费/缓存/审计;成本易失控。
  2. 语义缓存的价值? 参考答案: 提高缓存命中率,显著降低 LLM 调用成本。
  3. 为什么用 SSE? 参考答案: 流式输出降低首字延迟,改善体验。

162. 视频播放量亿级访问、实时展示、还要防刷(计数系统) ​

【考察内容】计数系统是超高并发设计代表题(抖音/微博高频)

【题目】视频的播放量要实时展示(几亿次播放),刷新一次就 +1 还要防脚本刷量。计数怎么存储(Redis 原子计数→异步落库)?防刷(IP/用户/设备维度去重)怎么做?数据一致性(展示值 vs 最终值)怎么处理?

【参考答案】

  1. 需求:计数写入(播放+1)高频、读取高频(展示)、数值最终一致即可、防刷;
  2. 写入链路:客户端上报(播放事件)→ 接入层校验(防刷:频控、IP/设备指纹、服务端抽检)→ MQ 异步 → 聚合服务(Flink/定时批量合并)→ 定期写 DB;
  3. 实时展示:Redis INCR(key=count:{id})实时累加,读取直接返回;定期落库(如每 5 分钟把增量写 DB,Redis 与 DB 对账)——播放量可容忍秒级延迟,不追求强一致;
  4. 防刷:同用户同视频去重(Redis Set/时间窗口)、频控(单位时间计数上限)、UA/设备校验、异常流量清洗(离线修正);
  5. 热点视频:单 key 热点——计数分片(count:{id}:{0~N},写入随机分片,读取 SUM 聚合)缓解单 key 竞争;
  6. 存储:Redis 承担实时,DB 落最终值(或 ClickHouse 统计),展示用缓存;亿级视频的计数 key 用Hash 批量存储(field=视频 ID,value=计数)节省内存;
  7. 对账:Redis 计数 vs DB 计数差异,定期修正(以事件明细为准);
  8. 容量估算:播放事件假设 1 亿/天 → 全天均值仅 1157/s,但晚间 2 小时集中 40% 时时段均值 ≈ 5600/s,再乘起播瞬时集中系数 10~20 → 峰值按 5.6~11 万 QPS(容量按 10 万规划);Redis INCR 单实例 10 万+,需 Cluster;Hash 批量时若一 Hash 放 1000 个视频,亿级视频约 10 万 key(1e8÷1000;若按千万视频才是 1 万 key);
  9. 失败与降级:Redis 挂→展示降级读 DB 最终值(允许略旧);MQ 积压异步消化不丢事件;对账失败次日重跑,极端可从明细重算。

【原理溯源】

  • 为什么播放量可以容忍最终一致? 播放量是展示型数据,差几次用户无感;强一致代价极高(每次 +1 写 DB)。用 Redis 实时累加+定期落库,把一致性成本降到最低。这是业务语义驱动的一致性取舍。
  • 为什么热点要计数分片? 明星视频播放量单 key 会成热点,所有写打同一 Redis 节点。拆成 count:{id}:0~N,写随机分片,读 SUM,把单点串行变多 key 并行。
  • 为什么要 Hash 批量存储? 亿级视频每个一个 key,key 数量爆炸。用 Hash(field=视频 ID)把多个视频计数放进一个 key,节省内存与 key 管理开销。
  • 为什么防刷要多维度? 单维度(IP)易被绕过(代理池)。IP+设备指纹+用户+时间窗口组合,提高绕过成本。异常流量离线清洗修正计数。
  • 为什么要对账? Redis 可能丢数据(极端),DB 是权威。定期对账以事件明细为准修正,保证最终正确。

【选型判断树】

写:防刷校验 → MQ → 聚合 → Redis INCR
读:Redis 直接返回
落库:定期批量写 DB
热点:计数分片 0~N
存储:Hash 批量(field=视频 ID)
对账:Redis vs DB,明细为准

判断口诀: Redis 扛实时、分片扛热点、Hash 省内存、对账保最终。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“计数是超高并发+最终一致+防刷”
0:30–1:30写入链路防刷→MQ→Redis INCR
1:30–2:30热点治理计数分片;Hash 批量
2:30–3:30防刷多维度去重;异常清洗
3:30–4:30一致性定期落库;对账
4:30–5:00收尾“一句话:实时靠 Redis,准确靠对账,防刷靠多维”

【关键数字】

参数经验值说明
写入亿级/天超高并发
落库周期1~5 分钟可容忍
热点分片8~32抗单 key
Hash 批量一 key 多视频省内存
防刷多维度IP+设备+用户
对账日级明细为准
播放事件1 亿/天,峰 10 万 QPS需 Cluster
Hash 分桶1 key ≈ 1000 field省 key 数

【追问链】(三层)

L1|“Redis 宕机计数丢了怎么办?” → 播放量可从事件明细重算;Redis 持久化+DB 兜底;业务容忍。

L2|“热点视频单 key 打挂 Redis 怎么办?” → 计数分片 0~N,写随机、读 SUM;极热视频可本地缓存展示值(秒级延迟)再回源聚合。

L3|“怎么防脚本刷量?” → 多维度去重(IP/设备/用户)、频控、异常增速检测、离线清洗修正。

【评分标准】

档位答案特征
60 分知道用 Redis 计数
80 分异步落库、防刷、最终一致
95 分计数分片、Hash 批量、对账、与 124 题点赞同构但更强调海量

【关联题】

  • 同构: 第 124 题(点赞计数)——业务版
  • 上游: 第 144 题(播放)、第 146 题(BI)
  • 防刷: 第 147 题(风控)

【自测】

  1. 判断对错:播放量必须强一致,每次 +1 写 DB。 参考答案: 展示型数据可最终一致;Redis 实时+定期落库。
  2. 热点计数为什么要分片? 参考答案: 避免单 key 串行瓶颈;写分片、读 SUM。
  3. Hash 批量存储解决什么? 参考答案: 亿级视频 key 数量爆炸;一 key 多 field 省内存。

第六章完,共 40 题(123–162)。

持续学习,持续积累。