134. 大促发券 1 秒被抢光,核销还要防重复(优惠券系统)
【考察内容】优惠券是电商高频设计题(与秒杀结构类似但细节不同)
【题目】大促要发 100 万张满减券,用户 0 点准时抢,要求不超发、不重复领、核销时不能重复使用。券的库存、领取资格、核销三个环节分别怎么设计?并发和一致性怎么保证?
【参考答案】
- 核心流程:发券(运营创建活动+券模板+库存)→ 领券(用户领取)→ 核销(下单使用)→ 过期;
- 关键设计:
- 券模板表 + 用户券表(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 异步发券落库(削峰)→ 用户收到结果;
- 核销:下单时校验券(状态+有效期+使用条件)→ 标记已用(原子 UPDATE 状态,乐观锁防并发重复使用)→ 金额计算(订单金额-券金额,金额放分);
- 过期处理:定时任务扫描批量标记过期(分页批量,避免大事务);或懒更新(用券时校验过期);
- 扩展:券码兑换(预生成券码批量导入)、组合优惠、风控(黄牛领券:限频+设备指纹)、数据统计(领取率/核销率);
- 分布式事务:领券与下单用券是不同服务——事件驱动+幂等+对账;
- 容量估算(步进):假设大促发券 100 万张、1 秒抢完 → 峰值领券请求要按 ≥100 万 QPS 设计(100 万张 1 秒发完,光是成交就是 1e6 次扣减/s,含重复点击与脚本只会更高);若按 10 万 QPS 设计,100 万张至少要 10 秒才发得完,与题干「1 秒被抢光」矛盾;DB 异步落库按消费能力 1~3 万 TPS 批量写,积压用 MQ 削峰。存储:用户券 5000 万张 × 100B ≈ 5GB DB + Redis 状态;券模板表很小。核销峰值与日常下单同量级(数百 TPS)。
- 失败与降级:Redis 预扣成功但 MQ/落库失败 → 对账任务按预扣流水补发券;Redis 与 DB 库存不一致以 DB 条件更新为准并告警;抢券接口过载时降级为排队页(返回排队号),避免雪崩到交易主链路;风控规则超时可先放行热门券再异步标记可疑订单;核销服务故障时订单侧禁止用券直接下单(宁可不用券也不能错扣)。
【原理溯源】
- 为什么库存要用 Redis 预扣而不是直接打 DB? 0 点瞬时抢券按第 7 条要按 百万级 QPS 设计(100 万张 1 秒发完,成交就是 1e6 次扣减/s),DB 行锁
stock>0条件更新会成为瓶颈。RedisDECR原子且内存级,把并发挡在 DB 之外。DB 仍做条件更新兜底,防止 Redis 与 DB 不一致导致超发。 - 为什么要一人一券唯一索引? 防重复领是业务硬约束。前端按钮置灰可被绕过,必须服务端唯一约束:
(user_id, template_id)唯一索引,重复插入失败即已领过。 - 核销为什么要原子状态流转? 同一张券可能被两个订单并发使用(用户双开)。用
UPDATE ... SET status=used WHERE id=? AND status=unused,影响行数为 0 即已被用,保证一券一单。 - 为什么要 MQ 异步发券? 抢券峰值打 DB 会挂。Redis 扣成功即返回“排队中”,MQ 消费异步落库发券。用户体验是“抢到了稍后到账”,系统用异步换稳定。需对账保证 Redis 扣了的最终都能落库。
- 过期为什么推荐“懒更新+定时扫描”结合? 纯定时扫描有延迟且大表扫描重;纯懒更新则过期券长期占库存展示。结合:用券时校验过期(懒),定时批量标记(扫描),兼顾实时性与成本。
- 与秒杀的三点差异(95 分档要求):① 券多了核销态——
unused → used才是资损点,秒杀支付即终态;② 多模板并行,每个模板独立库存与资格规则;③ 资格维度前置:已领标记/限领规则在 Redis 端先判,扣量仍走DECR预扣,「一人一券」的唯一索引在落库时才兜底;秒杀没有资格维度,是先抢量再支付判终态。
【选型判断树】
领券高并发:
Redis DECR 预扣 → 成功则 MQ 异步落库
DB 唯一索引(user_id, template_id)防重复
对账:Redis 库存 vs DB
核销:
条件更新 status: unused→used
校验有效期+使用条件
过期:
懒校验 + 定时批量标记判断口诀: Redis 扛抢、唯一索引防重、条件更新核销、对账兜底。
【口述骨架】(5 分钟;框架 B,骨架共性见文首第二节,本题只写特定内容)
| 时间 | 任务 | 内容 |
|---|---|---|
| 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削峰 |
| 用户券存储 | 张数×约100B | 5000万张约5GB |
【追问链】(三层)
L1|“Redis 扣成功但落库失败怎么办?” → MQ 重试+死信;对账任务把“Redis 已扣但无用户券记录”的补发或回滚库存。
L2|“同一张券两个订单同时核销怎么办?” → 条件更新 WHERE status=unused,只有一个成功。
L3|“黄牛用脚本抢光怎么办?” → 设备指纹、IP/账号限频、验证码、风控模型;库存分批发放;预约制(见 157 题)。
【评分标准】
| 档位 | 答案特征 |
|---|---|
| 60 分 | 知道要控制库存防超发 |
| 80 分 | Redis 预扣+唯一索引+核销状态机 |
| 95 分 | 对账闭环、MQ 削峰、过期懒更新、风控防黄牛;能说清领券与秒杀的三点差异(券有核销态、多模板、先抢资格再抢量) |
【关联题】
【自测】
- 判断对错:抢券可以先查库存再扣减。 参考答案: 错。非原子会超发;必须原子 DECR 或条件更新。
- 一人一券怎么保证? 参考答案: 用户券表 (user_id, template_id) 唯一索引。
- 核销怎么防重复使用? 参考答案:
UPDATE status=used WHERE status=unused条件更新。
135. 加购/改数量/跨端同步,购物车要稳定可靠(购物车设计)
【考察内容】购物车业务建模
【题目】电商 App 购物车:用户加购、改数量、勾选部分结算,且手机/网页购物车要同步。未登录用户的购物车怎么办?存储用 Redis 还是 DB?结算时的价格/库存校验怎么做?
【参考答案】
- 数据结构:Redis Hash(key=cart:{userId},field=SKU ID,value=数量+勾选状态)——支持 HINCRBY 增减、HGETALL 读取、原子操作;DB 持久化购物车表(user_id、sku_id、quantity、checked、create_time、唯一索引 user_id+sku_id);
- 读写:商品信息(价格、库存、图片)查缓存(商品服务),购物车列表=购物车条目+实时商品信息组装;价格实时校验(加入购物车时价格 vs 结算时价格,取低或提示);
- 一致性:购物车以 Redis 为读写主存储(高频),异步落库(MQ/定时),对账合并;登出/跨端:以 DB 为准合并(Redis 丢失恢复);
- 结算:勾选商品生成订单(批量校验库存与价格 → 创建订单 → 清理购物车已购项);购物车结算幂等(防重复提交);
- 并发:加购频控(防刷);大促购物车数量限制(如 200 件);
- 扩展:失效商品提示(下架/降价)、凑单推荐、未登录购物车(本地→登录后合并);
- 容量估算(步进):假设 DAU 1000 万,40% 活跃购物车、人均 20 SKU → 活跃 Hash key 约 400 万;单 key Hash 20 个 field × 约 50B ≈ 1KB → Redis 内存约 4~5GB 量级(加过期与复制约 ×2)。加购峰值:大促浏览高峰,人均加购动作峰值后约 数千~上万 QPS HINCRBY。DB 持久化异步批量,写放大可控。结算峰值与下单同量级。
- 失败与降级: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 分钟;框架 B,骨架共性见文首第二节,本题只写特定内容)
| 时间 | 任务 | 内容 |
|---|---|---|
| 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 TTL | 7~30 天 | 活跃用户续期 |
| 落库 | 异步批量 | 降低写压力 |
| 价格校验 | 结算时实时 | 防资损 |
| 合并策略 | DB 为准 | 跨端 |
| 活跃购物车Key | DAU×活跃率 | 例: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 分 | 未登录合并、跨端一致性、幂等结算、失效商品提示 |
【关联题】
【自测】
- 判断对错:购物车结算可以用加购时的快照价格。 参考答案: 错。必须实时校验,否则资损或投诉。
- 为什么用 Hash? 参考答案: 用户→SKU→数量 的天然映射,支持原子增减与全量读取。
- 未登录购物车怎么并入登录账户? 参考答案: 本地暂存,登录后与 DB 合并(数量相加或取大),DB 为准。
136. 千万商品要按关键词/价格/分类搜,还要排序(搜索引擎)
【考察内容】搜索系统是互联网核心设计题
【题目】电商要上搜索:千万商品,用户按关键词搜、按价格区间/分类过滤、按销量/价格排序,要求毫秒级返回。搜索链路(分词、索引、查询、排序)怎么设计?和数据库 LIKE 查询比强在哪?
【参考答案】
- 架构:数据同步(MySQL binlog → Canal → ES)→ 搜索服务(Query 解析 → 检索 → 排序)→ 结果组装(查商品详情缓存);
- 倒排索引(核心):词项 → 文档列表;分词(中文:IK 分词器;拼音、同义词扩展);检索时按词查倒排表合并(AND/OR);
- 多条件过滤:Filter 缓存(价格区间、分类、库存)用 BKD 树/倒排做过滤,过滤后再相关性排序(BM25);
- 排序:默认相关性(BM25 分数)、按价格/销量/时间排序(ES sort)、加权排序(销量×权重+相关性);
- 性能:ES 集群分片(按商品 ID 路由)、查询缓存、索引分页用 search_after(深分页性能问题同 MySQL);
- 一致性:Canal 同步延迟秒级(可接受),删除/下架商品即时同步(MQ 强制刷新);
- 扩展:搜索建议(suggest)、同义词、纠错(编辑距离)、个性化(用户偏好加权)、价格排序稳定性(加 ID 次级排序防抖动);
- 容量估算(步进):假设商品总量 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 写入压力可控。
- 失败与降级: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 分钟;框架 A,骨架共性见文首第二节,本题只写特定内容)
| 时间 | 任务 | 内容 |
|---|---|---|
| 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、同义词纠错、个性化加权、下架即时失效、与推荐结合 |
【关联题】
【自测】
- 判断对错:千万商品用
LIKE '%手机%'加索引就能毫秒返回。 参考答案: 错。前置通配无法走索引,必须倒排搜索引擎。 - Filter 和 Query 分离的好处? 参考答案: Filter 可缓存不算分,先过滤后打分,性能更好。
- 深分页怎么优化? 参考答案: search_after 游标分页,避免 from+size 全量排序。
137. 全网爬取数据,URL 去重和限速怎么做(爬虫系统)
【考察内容】海量数据处理与分布式任务设计
【题目】公司要爬全网公开数据(新闻/商品)。全网上亿 URL:怎么去重(布隆/一致性哈希分片)、怎么限速不把对方站点打挂、怎么分布式调度与失败重试、怎么解析抽取。请设计整体架构。
【参考答案】
- 流程:URL 队列(种子)→ DNS/抓取(HTTP 下载)→ 解析(提取链接+内容)→ 内容存储 → 新链接入队;
- URL 去重:海量 URL(十亿级)用布隆过滤器(内存友好,容忍小误判)或 Redis Set 分片;
- URL 队列:分布式队列(Kafka/RabbitMQ)分发;按域名/优先级分队列(热点站优先级高);
- 分布式:多 Worker 抓取,任务分配(一致性哈希/按域名分片);抓取节点无状态,可水平扩展;
- 限速与礼貌爬取:每域名并发限制+抓取间隔(robots.txt 遵守),防止被封/给目标站造成压力;
- 健壮性:请求重试(退避)、代理池(反爬:IP 轮换)、User-Agent 伪装、异常处理;
- 存储:原始 HTML 存对象存储(按 URL hash 分桶)、解析后结构化数据入 DB/ES;
- 监控:抓取速率、失败率、去重率、队列积压;
- 容量估算(步进):假设全网目标 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,但抓取并发由「同时在抓的活跃域名数」决定,不是由页/s 决定:礼貌限速是按域名的(单域名 0.5~1 QPS),Worker 数 ≈ 活跃域名数×每域名并发 ÷ 单 Worker 并发,集群总抓取约 数千页/分钟(日 1000 万页 ÷ 1440 ≈ 7000 页/分)可配。原始 HTML 均页 200KB → 日抓 1000 万页 ≈ 2TB/日 对象存储,需生命周期策略。
- 失败与降级:目标站封禁/反爬升级 → 任务进冷却队列,不硬闯;布隆误判导致漏抓 → 定期全量重扫低价值域补偿;抓取队列积压时只降低优先级并发会让积压更长(分母没变、分子变小),要配套「按配额丢弃低价值待抓项+提高实抓吞吐」才收敛;解析服务故障时原始页已落对象存储,可回放补解析;合规变更(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 分钟;框架 A,骨架共性见文首第二节,本题只写特定内容)
| 时间 | 任务 | 内容 |
|---|---|---|
| 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 合规、代理池、监控维度、存储分层 |
【关联题】
【自测】
- 判断对错:用 Redis Set 存全部待抓 URL 最简单可靠。 参考答案: 十亿级内存爆炸;应布隆或分片+可接受误判。
- 为什么要按域名限速? 参考答案: 礼貌与合规,避免打挂目标站或被封。
- 布隆的缺点是什么? 参考答案: 假阳性导致漏抓;不支持删除;需补偿机制。
138. 几十个服务统一入口,路由/鉴权/限流都在这层(API 网关)
【考察内容】网关是微服务架构高频题
【题目】公司几十个微服务暴露给 App 调用,要求统一入口:路由转发、统一鉴权、限流、灰度、日志。网关怎么设计?核心能力有哪些?怎么避免网关自身成为瓶颈和单点?
【参考答案】
- 定位:所有请求的统一入口,承担横切关注点,业务服务专注业务;
- 核心能力:
- 路由转发:按路径/服务名路由到下游(负载均衡、重试、超时);
- 统一鉴权:Token 校验(JWT 验签/OAuth)、白名单、权限校验(RBAC);
- 限流熔断:全局限流(Redis 计数器)、按用户/接口限流;下游故障熔断降级;
- 灰度发布:按用户/流量比例路由到新旧版本服务(权重/标签路由);
- 协议转换:HTTP ↔ RPC(Dubbo)、请求/响应改写、格式校验;
- 日志与监控:全链路 TraceID 生成与透传、访问日志、指标埋点;
- 实现:Spring Cloud Gateway(Reactive,性能好)/ Zuul / Kong / APISIX / 自研(Netty);
- 高可用:网关集群部署(无状态)、自身限流保护(防被打爆)、降级(返回兜底页);
- 注意:网关不能放业务逻辑(变胖网关);认证信息(用户 ID)通过 header 透传给下游,下游不重复鉴权——前提是网关先把入站同名 header 剥离再写入:请求从公网进来,客户端完全可以自带
X-User-Id,若网关只在鉴权成功后「追加/覆盖不彻底」,下游就会照单全收,等于任何人都能冒充。规范做法:鉴权成功后先 remove 再 set 内部头;内部服务间再加一层 HMAC 签名或 mTLS+服务身份,别把「内网隔离」当成 header 不可伪造的保证; - 容量估算(步进):假设全站业务峰值 20 万 QPS,网关集中入口 → 单实例(Reactive/Netty)经验 1~3 万 QPS(视鉴权/插件复杂度),集群至少 10~20 实例 并 N+1 冗余。延迟预算:网关自身 CPU 转发+鉴权宜 ≤5~10ms(P99),为下游留足时间。连接数:入口连接 ≈ 业务 QPS × 处理时延,长连接客户端场景另计。限流阈值按下游容量反推,网关全局限流略低于下游总和。
- 失败与降级:下游超时/熔断打开 → 网关返回统一错误页或静态兜底 JSON;配置中心故障时网关使用本地缓存的路由与限流规则;单实例 OOM/僵死由负载均衡摘除;灰度路由异常立即切回全量旧版本;网关自身成为热点时,可按域名/路径拆分多组网关(独立扩缩容),鉴权服务故障时白名单路径降级放行并告警。
【原理溯源】
- 为什么需要网关而不是每个服务自己做鉴权限流? 几十个服务各自实现会重复开发、策略不一致、变更困难。网关把横切关注点(认证、限流、日志、灰度)集中,业务服务只关心业务。这是关注点分离在微服务入口层的应用。
- 为什么网关要做成无状态集群? 有状态(会话粘在本机)会导致扩容困难、单点故障。无状态意味着任意实例都能处理任意请求,靠负载均衡水平扩展,挂一个不影响整体。会话状态外置到 Redis。
- 为什么网关不能塞业务逻辑? 业务逻辑会拖慢入口、让网关变成单点瓶颈、发布耦合。网关只做“转发前的通用处理”,业务判断留给下游。一旦网关变胖,它就成了最脆弱的单点。
- 鉴权后为什么要透传用户 ID 而不是下游再验一次? 下游重复验签浪费 CPU、且可能策略不一致。网关验完写入可信 header(如 X-User-Id),内网下游才敢信它。前提不是「内网隔离」,而是外部值进不了这个内部头:网关鉴权成功后必须先 remove 入站同名 header 再 set(否则客户端自带 X-User-Id 就能冒充),服务间再加 HMAC 签名或 mTLS 服务身份。
- 为什么网关自身也要限流? 网关是全站入口,被打爆则全站不可用。必须对总 QPS、单用户 QPS 设上限,超出返回 429/排队,保护自己和下游。
- 网关与 sidecar 的职责边界(95 分档要求):南北向(外部→内部)的路由、统一鉴权、全局限流、灰度打标归网关;东西向(服务间)的 mTLS、重试与超时、细粒度限流、指标采集归 sidecar / Service Mesh。同一次调用只在一层做身份鉴权、业务权限留给下游,两处都配必然策略漂移。
【选型判断树】
要不要上网关?
├─ 服务少(< 5)且简单 → 可 Nginx/Ingress 凑合
└─ 服务多、要统一鉴权限流灰度 → API 网关
选型:
├─ Java 技术栈、Reactive → Spring Cloud Gateway
├─ 多语言/高性能 → Kong / APISIX / 自研 Netty
└─ 云原生 → Ingress / Envoy
高可用:无状态集群 + 自身限流 + 降级页判断口诀: 横切进网关,业务在下游;无状态集群,自身也要限流。
【口述骨架】(5 分钟;框架 A,骨架共性见文首第二节,本题只写特定内容)
| 时间 | 任务 | 内容 |
|---|---|---|
| 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/比例 | 标签路由 |
| 超时 | 网关总超时 > 下游超时 | 预留重试 |
| 单实例QPS | Reactive/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 的职责边界 |
【关联题】
【自测】
- 判断对错:网关里可以写一些简单的业务判断,方便统一处理。 参考答案: 错。网关塞业务会变胖、成瓶颈、发布耦合;业务放下游。
- 为什么要无状态? 参考答案: 任意实例可处理任意请求,易扩容、抗单点。
- 下游为什么信任 X-User-Id? 参考答案: 网关统一写入+入站先剥离同名 header;前提不是「内网隔离」,而是「外部值进不了内部头」——要么网关先 remove 再 set,要么内层加 HMAC 签名/mTLS 服务身份,否则客户端自带 X-User-Id 就能冒充。
139. 营销/订单/站内信多通道通知,统一推送中心(通知中心)
【考察内容】通用通知能力设计
【题目】业务方都要发通知:订单提醒(App 推送)、营销活动(Push+短信)、风控告警(站内信+短信)。需要一个统一通知中心:通道管理、模板、频率限制(防骚扰)、失败重试。怎么设计?
【参考答案】
需求:多渠道(App 推送 APNs/FCM/厂商通道、短信、站内信、邮件)、模板化、限频、优先级;
架构:业务方 → 通知中心 API(校验+模板渲染)→ 消息入库 → MQ 分发 → 各渠道发送服务 → 回执;
关键设计: 通道管理(题干第一项):渠道注册表(类型/供应商/配额/单位成本/健康状态)+按消息类型的通道路由与降级顺序(Push→站内信→短信)+安卓厂商通道分列表维护(各家的推送配额与限流不同)+通道级配额与预算熔断。
- 模板管理:消息模板(短信签名/字数校验、推送标题),占位符渲染,防注入;
- 去重与幂等:相同业务(订单号+通知类型)只发一次——Redis 去重+DB 唯一键;
- 限频:同一用户渠道限频(如短信 1 条/分钟)、全局渠道限流(短信通道成本高);
- 优先级:高优(支付/验证码)插队直发,低优(营销)错峰批量;
- 失败重试:发送失败重试(退避)+死信人工;回执回调(送达/失败);
- 退订管理:营销类需退订(合规);
高可用:发送服务独立部署(短信通道故障不影响主业务)、MQ 削峰(大促营销批量);
扩展:用户偏好(渠道开关)、A/B 文案、发送统计报表;
容量估算(步进):假设 DAU 1000 万,人均触发通知 5 条/日(含营销)→ 日通知 5000 万条;日 5000 万条摊算只有约 580 条/s,营销大促瞬时放大 10~50 倍 → 约 0.6~2.9 万条/s,发送峰值按这个区间打 MQ,各渠道按自身配额消费(短信通道往往只开数百~数千 TPS)。存储:通知记录 5000 万/日 × 150B ≈ 7.5GB/日,站内信可按用户分表/分库。成本敏感:短信单条成本显著,必须全局限频与预算熔断。
失败与降级:某渠道(如短信供应商)故障 → 自动切换备用供应商或降级为站内信+推送;推送回执丢失则状态超时置失败并重试有限次;高优(验证码)队列与营销队列隔离,营销故障不影响验证码;限频服务故障时 fail-safe(可降级为只允许高优通道);模板渲染失败进死信人工,禁止带未渲染占位符外发。
【原理溯源】
- 为什么要统一通知中心而不是业务各发各的? 各发各的会导致:模板不统一、无限频(用户被刷屏投诉)、通道故障互相影响、无统一监控退订。中心化后模板/限频/重试/退订一处治理,业务只调 API。这是能力平台化。
- 为什么要模板化? 同类通知文案结构固定,模板+占位符可复用、可 A/B、可审核(防注入/敏感词)。直接拼字符串易出 XSS/注入且难管理。
- 为什么要限频? 防骚扰是合规要求(尤其短信),也是用户体验底线。同用户同渠道限频 + 全局通道限流(短信有成本与通道配额)双层控制。
- 为什么要优先级? 验证码/支付结果必须秒达;营销可延迟。混在一个队列会让高优被低优阻塞。分级队列或插队机制保证关键通知 SLA。
- 为什么要回执? 发送成功≠送达。回执(送达/失败/退订)更新状态,支撑统计、重试与用户查询。无回执则失败不可见。
【选型判断树】
通知类型?
├─ 高优(验证码/支付)→ 直发通道,插队,短重试
├─ 中优(订单状态)→ MQ,标准重试
└─ 低优(营销)→ 错峰批量,限频,可退订
通道故障:
独立部署 + 多通道备份 + 死信人工
幂等:业务键唯一(订单号+类型)判断口诀: 模板统一、限频防骚扰、优先级分队、回执闭环。
【口述骨架】(5 分钟;框架 A,骨架共性见文首第二节,本题只写特定内容)
| 时间 | 任务 | 内容 |
|---|---|---|
| 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 条/分钟,日上限 | 合规 |
| 验证码 TTL | 5 分钟 | 过期重发 |
| 重试 | 指数退避 2~3 次 | 死信 |
| 优先级 | 至少 2 级(高/低) | 插队 |
| 回执 | 必须 | 统计与重试 |
| 日通知量 | DAU×人均触发条数 | 例:1000万×5=5000万/日 |
| 发送峰值 | 营销放大10~50倍 | 数万条/s进MQ,渠道按配额 |
| 通知记录存储 | 日量×约150B | 5000万×150B ≈ 7.5GB/日 |
【追问链】(三层)
L1|“短信和推送失败怎么处理?” → 重试+死信+日志;关键消息(验证码)有 TTL,超时提示重发;可切备用通道。
L2|“用户投诉被短信骚扰怎么办?” → 限频+退订+发送记录可查;营销类必须可退订;建立黑白名单。
L3|“大促营销要发百万条,怎么不打挂通道?” → MQ 削峰+批量接口+按通道 QPS 限速;错峰发送;预热通道配额。
【评分标准】
| 档位 | 答案特征 |
|---|---|
| 60 分 | 知道要调短信/推送 API |
| 80 分 | 统一中心、模板、通道注册表+路由降级+配额熔断(题干第一项)、限频、重试 |
| 95 分 | 优先级分队、回执闭环、退订合规、通道容灾、与 158 题呼应 |
【关联题】
【自测】
- 判断对错:业务方可以直接调短信通道 API,更快。 参考答案: 错。缺模板/限频/重试/退订治理;应统一通知中心。
- 为什么要限频? 参考答案: 防骚扰合规+用户体验+控制通道成本。
- 发送成功但用户没收到,怎么发现? 参考答案: 回执机制更新状态;失败重试或提示。
140. 每天 TB 级日志,采集/存储/检索怎么做(日志系统)
【考察内容】大数据链路架构(ELK)
【题目】每天产生 TB 级日志:采集(怎么不丢不阻塞业务)、传输、存储(低成本可检索)、查询(按关键字/时间快速检索)。ELK 架构怎么搭?海量日志存储怎么省钱?
【参考答案】
- 采集:应用打日志(logback/log4j,格式统一 JSON 带 TraceID)→ Agent(Filebeat)采集本地文件 → Kafka(缓冲削峰,解耦);
- 传输:Kafka 按日志类型分 topic(access/error/business),分区按应用/机器;
- 处理:Logstash/Fluentd 清洗、解析、脱敏(手机号/身份证)、字段标准化;
- 存储与检索:ES(倒排索引,按天建索引,冷热分层:热 3 天 SSD、温 30 天、冷归档 OSS);元数据(大小、条数)入 DB 统计;
- 查询:Kibana(全文检索、聚合分析、Dashboard);按 TraceID 全链路检索(应用日志带 TraceID);
- 容量与治理:磁盘清理策略(按保留期删索引)、日志采样(高 QPS 接口降采样)、日志级别动态调整(线上开 ERROR);
- 可靠性:Kafka 高吞吐扛峰值;「可丢」只能写给 access/debug/info(与第 9 条的丢弃白名单同一档),而且要先把不可丢的边界划出来(题干问的正是「采集怎么不丢」):error/审计/合规类必须 at-least-once——本地文件先落盘+Agent 记录 offset(重启续传)+Kafka
acks>=1(关键通道 acks=all+幂等 producer),拥塞时靠背压降速而不是丢弃;监控:采集延迟、Kafka 积压、ES 写入速率;这几个自监控指标要接进监控告警体系(阈值→值班通知),并在告警消息里附带 TraceID 与日志查询链接(Kibana/Grafana Explore),做到「告警点开就能下钻到日志」——这才是 95 分档要的与监控联动,不是只把日志链路自己的数字放着看 - 容量估算(步进):假设集群 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,会低估约 4 倍——1.4~2.3 ÷ 0.35~0.58 ≈ 4,即 24h÷6h 那个系数,不是一个数量级)。据此定 Kafka 分区数(单 partition 经验数十~百 MB/s,按 topic 拆分)。ES 热层:保留 3 天 × 10TB × 2 份(主分片 + 1 副本)≈ 60TB SSD 量级(再计倒排膨胀 1.2~2 倍,需按压测校准);温层 30 天机械盘/大容量节点;冷层 OSS 按 GB 级成本归档。
- 失败与降级: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 分钟;框架 A,骨架共性见文首第二节,本题只写特定内容)
| 时间 | 任务 | 内容 |
|---|---|---|
| 0:00–0:30 | 定性 | “日志系统是采集-缓冲-存储-检索的大数据链路” |
| 0:30–1:30 | 采集 | 本地文件+Agent,不阻塞业务;同一句要把「不可丢边界」划出来(error/审计=本地落盘+Agent offset 续传+acks,靠背压不靠丢弃) |
| 1:30–2:30 | 传输 | Kafka 削峰解耦;topic 分类 |
| 2:30–3:30 | 存储 | ES 按天索引;冷热分层省钱 |
| 3:30–4:30 | 检索与治理 | Kibana;TraceID;采样;脱敏 |
| 4:30–5:00 | 收尾 | “一句话:本地保业务、Kafka 保峰值、分层保成本” |
【关键数字】
| 参数 | 经验值 | 说明 |
|---|---|---|
| 日志量 | TB/天 | 量级感 |
| 热数据 | 3 天 SSD(第 4 条口径;60TB=3 天×10TB×2 份,改按 7 天要重算成约 140TB) | 高频查 |
| 温数据 | 30 天 HDD | 偶尔查 |
| 冷数据 | 归档 OSS | 极少查 |
| 采样 | 高 QPS 接口 1%~10% | 降成本;但采样/丢弃白名单只允许 access/ debug/ info,error 与审计通道不采不丢 |
| 脱敏 | 采集/处理层 | 合规 |
| 日志总量估算 | 机器数×单机日志量 | 例:500×20GB=10TB/日 |
| 采集峰值 | 集中时段日均+故障放大 3~5 倍 | 按 ≥2.5GB/s 设计(口径:10TB 集中在约 6 小时的写入窗 ≈0.46GB/s,再按故障期放大 3~5 倍得 1.4~2.3GB/s,取 2.5GB/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 全链路、与监控联动 |
【关联题】
【自测】
- 判断对错:日志可以同步写 ES,保证不丢。 参考答案: 会阻塞业务且峰值打挂 ES;应本地缓冲+Kafka。
- 冷热分层的依据是什么? 参考答案: 访问频率随时间衰减;热 SSD、温 HDD、冷 OSS 降成本。
- 为什么要脱敏? 参考答案: 合规要求,防止敏感信息泄露。
141. 指标采集/告警/通知,一套监控系统怎么做(监控告警)
【考察内容】可观测性平台设计
【题目】公司要建监控系统:采集各服务的指标(CPU/RT/QPS/错误率)、存储、阈值告警、通知到人。指标怎么采集(agent/埋点)与存储(时序库)?告警规则与防误报(收敛、静默)怎么设计?
【参考答案】
- 采集:Agent(Prometheus node_exporter/自研)采集指标(CPU、内存、QPS、RT、错误率、JVM、DB、MQ 积压)→ 推/拉模式(Prometheus 拉取,或推送到时序库);
- 存储:时序数据库(Prometheus/InfluxDB/OpenTSDB)——按时间+标签维度存储,高压缩、聚合查询快;
- 告警规则:阈值(RT>500ms)、趋势(连续 3 次)、同比环比(今日 vs 昨日同期);多指标组合(CPU 高+错误率高=异常);
- 告警引擎:规则评估(周期执行)→ 触发 → 去重/聚合(同一故障合并告警,防告警风暴)→ 通知(钉钉/短信/电话,按级别:P0 电话、P1 短信、P2 IM);
- 处理闭环:告警 → 认领 → 处理 → 恢复 → 复盘(事故记录);告警降噪(静默窗口、抑制规则);
- 可视化:Dashboard(Grafana)展示大盘;
- 进阶:智能告警(基线预测:流量波动检测,节假日自动调阈值)、根因分析(关联 TraceID/日志);
- 容量估算(步进):假设 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 扫一轮,规则数千条时注意分片评估。
- 失败与降级:监控系统自身故障必须告警到带外通道(短信/电话网关独立);指标写入失败时降采集频率或丢弃非关键指标;告警通知通道失败自动 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 分钟;框架 A,骨架共性见文首第二节,本题只写特定内容)
| 时间 | 任务 | 内容 |
|---|---|---|
| 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 分 | 降噪全套、闭环复盘、智能基线、与日志/链路追踪联动 |
【关联题】
【自测】
- 判断对错:指标可以存 MySQL,简单通用。 参考答案: 高频写+时间聚合场景时序库更优;MySQL 会写爆。
- 为什么要连续 3 次才告警? 参考答案: 过滤单次抖动误报,用时间换准确率。
- P0 告警为什么用电话? 参考答案: 必须立刻叫醒值班,IM/短信可能被忽略。
142. 用户叫车,怎么把订单派给最合适的司机(派单系统)
【考察内容】派单是 LBS+实时系统代表题
【题目】打车 App 用户叫车,几公里内几百个司机在线,派给谁?要求接单率高、乘客等待短、司机不空跑。派单策略怎么设计(距离、方向、评分、负载均衡)?实时位置怎么维护?订单和司机如何匹配(分桶/索引)?
【参考答案】
- 数据:司机实时位置(GPS 上报,每 3~5 秒)→ 写入 Redis GEO(司机位置);订单(起点、终点、类型);
- 派单流程:用户下单 → 范围检索(Redis GEO 半径搜索附近司机,如 3km 内空闲司机)→ 候选司机按“距离+评分+接单率+方向+负载均衡(题干点了这四个维度,负载均衡不能漏:按该司机近时段已派数与空闲时长做惩罚/激励,否则热门司机被连续派单、其余司机空跑,接单率与「司机不空跑」两个题干目标同时掉)”打分排序 → 派单(推送给 Top 司机);
- 并发抢单与防冲突:
- 一个订单多个司机同时收到——先到先得:司机接单时用 Redis 分布式锁/订单状态机(订单状态=待接单 → 原子更新为已接单,防多司机重复接单);
- 司机同时收到多个订单——司机侧串行(一次只能处理一个);
- 状态机:待派单 → 派单中(司机确认超时重新派)→ 已接单 → 服务中 → 已完成/已取消;超时重派(30 秒无司机接单,扩大范围或加价);
- 实时性:司机位置用 WebSocket 长连接上报;派单结果推送(WebSocket/推送);
- 扩展:调度策略(顺路单、预约单)、地图服务(路径/距离计算 ETA)、热力图(供需预测)、风控(刷单检测);
- 容量估算(步进):假设城市同时在线司机 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/日。
- 数据与接口(口述可带):司机状态
{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:按 GeoHash 区间定位起点是 O(log N),但
GEOSEARCH还要扫候选集并逐点精确算距离,官方复杂度 O(N)+O(M·logM)(N=外接矩形内元素数、M=命中数),所以实践中必须限制半径才谈得上毫秒级。若用 DB 经纬度范围查,索引与计算都更重。 - 为什么接单要原子状态流转? 多个司机可能同时点接单。用
UPDATE orders SET status=taken WHERE id=? AND status=pending,影响行数为 1 的那个成功,其余失败。这避免一单被多司机接走。 - 为什么要超时重派? 派出的司机可能拒单或超时未响应。不重派则订单卡死。30 秒无响应扩大半径或加价激励,保证乘客最终被服务。
- GPS 为什么要节流? 每秒上报百万司机位置会打爆写入。订单中高频(3~5 秒)、空闲低频(30 秒+),或客户端按移动距离触发。成本与实时性的平衡。
- 为什么要综合打分而不是纯距离? 最近司机可能评分低、接单率低、方向相反。综合分(距离+服务分+接单率+方向)提升全局效率与体验,这是调度算法的核心。
【选型判断树】
位置存储:Redis GEO
检索:GEOSEARCH 半径 + 空闲过滤
打分:距离 + 评分 + 接单率 + 方向 + 负载均衡(近时段已派数/空闲时长)
接单:订单状态机原子更新
超时:30s 重派,扩大范围/加价判断口诀: GEO 检索、综合打分、状态机防冲突、超时重派。
【口述骨架】(5 分钟;框架 B,骨架共性见文首第二节,本题只写特定内容)
| 时间 | 任务 | 内容 |
|---|---|---|
| 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 题(订单状态机)
- 同类: 第 143 题(位置检索)——同一套 GEO/索引与容量取舍。本题派单只读 Redis GEO 与订单状态机,不读用户关系链,第 126 题不是它的上游
- 扩展: 第 147 题(风控)——刷单检测
【自测】
- 判断对错:派给最近的司机就是最优。 参考答案: 错。还要看评分、接单率、方向、负载均衡(否则热门司机被连续派单、其余空跑,接单率与「不空跑」两个题干目标同时掉);综合打分更优。
- 接单为什么用状态机? 参考答案: 防止多司机同时接同一单;原子更新保证唯一。
- 没司机接单怎么办? 参考答案: 超时扩大范围/加价/排队;通知乘客。
143. 外卖 App 要按距离列出附近 100 家店(LBS 设计)
【考察内容】LBS 检索分层设计
【题目】外卖 App:用户打开要看到附近门店按距离排序,且要能按销量/评分再排。门店几十万家、位置实时变化不大。地理位置索引怎么做(GeoHash/网格/ES geo)?距离计算与排序的性能怎么保证?
【参考答案】
- 数据:门店表(id、经纬度、营业状态),用户查询带当前位置;
- 检索方案:
- Redis GEO:门店经纬度 GEOADD 到 city key,查询 GEOSEARCH(半径/矩形范围+按距离排序+分页)——快,适合百万级;
- GeoHash 索引(MySQL):门店经纬度转 GeoHash 字符串存列(建索引),查询时按“当前点周围 9 格前缀”匹配再精确过滤——适合千万级,DB 方案;
- ES geo 查询:海量+复杂过滤(营业中+评分>4)用 ES geo_distance 查询,性能好;
- 排序(题干要「按销量/评分再排」,这与「按距离排」是两件事,要分开答):距离排序=GEOSEARCH 天然按距离由近到远返回;属性排序(销量/评分/推广)=GEO 结构只能按距离排、不能按销量排,所以要么把半径内的候选集(门店 ID+销量+评分)取回后在内存重排,要么走 ES:
geo_distance过滤+function_score(销量/评分加权)或直接sort:[{sales:desc}];纯 GeoHash 前缀方案则要在 DB 侧配ORDER BY sales DESC的复合索引。一句话:「附近 + 按销量」=先地理过滤、再属性排序,两步不能合成一步; - 优化:按城市分片(门店归属城市,查询先定位城市)、网格缓存(热门区域的门店列表缓存,TTL 短)、范围查询限制(如 10km 内);
- 动态数据:营业状态、实时排队数——查缓存/状态服务;
- 扩展:多边形区域(配送范围判断:点在多边形内用射线法+空间索引)、热力图;
- 容量估算:假设全国门店 50 万、单城 1~5 万;查询高峰全国 2 万 QPS,单城约 200~2000。GEO 点均约 60B,50 万点≈30MB,可按城市 key 全量进内存;网格缓存按命中 30% 估,可挡约 6000 QPS;
- 失败与降级: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 分钟;框架 B,骨架共性见文首第二节,本题只写特定内容)
| 时间 | 任务 | 内容 |
|---|---|---|
| 0:00–0:30 | 定性 | “LBS 是空间索引+分层缓存问题” |
| 0:30–1:30 | 三方案 | GEO / GeoHash+MySQL / ES |
| 1:30–2:30 | GeoHash 原理 | 空间→字符串前缀;9 宫格 |
| 2:30–3:30 | 分片与缓存 | 城市分片;网格缓存 |
| 3:30–4:30 | 动态与排序 | 状态实时查;候选集取回后按销量/评分重排(两步不能合成一步) |
| 4:30–5:00 | 收尾 | “一句话:静态进索引,动态进缓存,按城分片” |
【关键数字】
| 参数 | 经验值 | 说明 |
|---|---|---|
| 门店量级 | 几十万~百万 | GEO 够用 |
| 查询半径 | ≤ 10km | 产品约束 |
| GeoHash 前缀位数(业务粗筛用) | 6~7 位 | 约 1km~百米;Redis 存的是 52 bit(≈11 位等效),两套位数别混成一个数 |
| 城市分片 | 按 city | 避免大 Key |
| 网格缓存 TTL | 10s~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 原理、网格缓存、动态状态分离、多边形配送范围 |
【关联题】
【自测】
- 判断对错:全国门店放一个 Redis GEO key 最简单。 参考答案: 大 Key 且查询无局部性;应按城市分片。
- GeoHash 前缀相同表示什么? 参考答案: 空间邻近;可用前缀匹配做范围检索。
- 营业状态为什么单独存? 参考答案: 动态数据频繁变,不宜进空间索引;走缓存实时查。
144. 视频上传要转码、播放要流畅、分发要快(短视频系统)
【考察内容】短视频是内容平台最高频设计题
【题目】短视频产品:用户上传视频(几百 MB)要转码成多清晰度、播放要秒开流畅、还要支撑千万级并发播放。上传链路(分片/断点)、转码(异步任务/转码集群)、播放(CDN/预加载/缓存)怎么设计?
【参考答案】
- 上传链路:客户端分片上传 → 对象存储(OSS/S3)→ 消息触发转码流水线(多清晰度:720p/1080p、封面截取、水印)→ 审核(机审+人审)→ 上线;转码集群是独立 Worker 池按优先级队列消费(新上传>冷转码>批量重转),按视频时长估算任务耗时做并发配额,堆积时先只出 720p 保可用;预加载在播放侧按 HLS 分段提前拉下 1~2 个分片,配合首片小 GOP 才谈得上秒开;
- 存储:原始文件+转码产物存对象存储(冷热分层:热视频 CDN 边缘缓存,冷数据低频访问回源);元数据(视频信息、作者、标签)入 DB/缓存;
- 播放:播放请求 → CDN(边缘节点缓存视频分片,就近分发)→ 未命中回源;首屏优化(首帧快速加载:低清晰度先出+渐进式播放);HLS/DASH 切片(分段拉流,自适应码率);
- 推荐分发:见推荐系统——上传后先小流量测试(分发到小池子测数据)→ 数据好则加大分发(冷启动策略);
- 计数与互动:播放量、点赞、评论(见计数/点赞题,播放量异步累加+定期落库);
- 扩展:内容审核(色情/违规——图片/视频 AI 审核+人工复核)、版权识别(指纹)、删除/下架(CDN 失效);
- 容量估算:日上传假设 100 万条×均 300MB(题干「几百 MB」取下沿)→ 原始约 300TB/天;若按 100MB 算是压缩后/短视频口径,须写明,转码 3 档后存储约 2~3 倍;播放峰值假设千万级 QPS,CDN 命中 >95% 时回源约 50 万 QPS,仍需中心带宽与对象存储扛;
- 失败与降级:转码失败自动重试 3 次后进死信人工;CDN 节点故障靠调度切换边缘;播放失败降级更低清晰度或返回封面;审核服务超时默认「先不可见」,避免违规内容漏出。
【原理溯源】
- 为什么必须对象存储+CDN 而不是应用服务器? 视频文件大(几百 MB)、播放并发高(千万级)。应用服务器磁盘与带宽都是瓶颈。对象存储海量容量、CDN 边缘节点就近分发,把带宽压力从中心分散到边缘。这是内容分发的物理最优解。
- 为什么要转码成多清晰度? 不同网络环境(4G/WiFi)需要不同码率。多清晰度+自适应码率(HLS/DASH)让播放器按带宽切换,保证流畅。同时降低存储与分发成本(冷门视频只保留低清晰度)。
- 为什么要分片/切片播放? 整文件下载失败要重头再来。HLS 切成几秒的小分片,可独立请求、可切换码率、可预加载下一秒。首屏只需第一个分片,秒开。
- 为什么要异步转码? 转码 CPU 密集(几分钟到几十分钟),同步等待不可接受。上传成功即返回,转码流水线异步跑,完成后通知。这是用异步换用户体验。
- 为什么要先小流量测试再全量分发? 新视频质量未知,直接全量推荐会浪费流量且可能推垃圾内容。小池子测 CTR/完播率,数据好再放大。这是推荐系统的冷启动。
【选型判断树】
存储:OSS/S3 + CDN
上传:分片+断点续传(见 148 题)
转码:异步流水线,多清晰度
播放:HLS/DASH 切片,CDN 边缘,低清晰度先出
审核:机审+人审判断口诀: 对象存储管容量,CDN 管分发,切片管流畅,异步管转码。
【口述骨架】(5 分钟;框架 B,骨架共性见文首第二节,本题只写特定内容)
| 时间 | 任务 | 内容 |
|---|---|---|
| 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」) | 转码多清晰度后再翻倍 |
| 回源 QPS | CDN 命中 95% 时约峰值 5% | 中心仍要容量 |
【追问链】(三层)
L1|“视频直接存应用服务器行不行?” → 不行。带宽与磁盘瓶颈;必须对象存储+CDN。
L2|“播放量怎么统计防刷?” → 客户端上报+服务端抽样+异步累加;同用户/设备/时间窗去重,异常增速离线清洗;见第 162 题计数系统。
L3|“视频下架后 CDN 还有缓存怎么办?” → CDN 主动失效(invalidate)+版本号/URL 变更;读时校验状态兜底。
【评分标准】
| 档位 | 答案特征 |
|---|---|
| 60 分 | 知道要转码和 CDN |
| 80 分 | 完整链路、异步转码、HLS |
| 95 分 | 冷启动分发、审核、CDN 失效、冷热分层存储、与推荐联动 |
【关联题】
【自测】
- 判断对错:视频可以同步转码完成后才返回上传成功。 参考答案: 转码耗时长,必须异步;上传成功即返回。
- 为什么用 HLS 切片? 参考答案: 小分片可独立请求、切换码率、预加载,首屏秒开。
- 为什么要多清晰度? 参考答案: 适配不同带宽,自适应切换保证流畅。
145. 多人同时编辑一个文档,不冲突、实时同步(协作文档)
【考察内容】协作文档是高级设计题(阿里/字节/飞书系高频)
【题目】要做在线协作文档:10 个人同时编辑同一文档,每个人都能看到别人的修改、不能互相覆盖。OT(操作转换)和 CRDT 两种并发控制方案是什么?服务端怎么合并、怎么存储版本?
【参考答案】
- 架构:客户端(编辑器+本地状态)→ WebSocket 长连接 → 文档服务(版本管理+广播);
- 实时同步:OT(操作转换)或 CRDT(无冲突复制数据类型):
- OT:每个编辑操作(插入/删除+位置)带操作 ID,服务端对并发操作做转换(transform),保证所有客户端最终一致;Google Docs 用 OT;
- CRDT:每个字符带唯一 ID,合并时按 ID 排序天然收敛,无需中心转换;适合离线优先;
- 协作协议:客户端发送 op → 服务端应用 op(校验)→ 广播给其他在线客户端 → 应用;版本号(doc version)递增,客户端 op 带 base version,版本不匹配先同步最新;
- 存储:文档内容按版本存储(增量 op 日志 + 定期快照),可回溯历史版本;
- 在线状态:用户光标位置(广播)、在线成员列表(Presence);
- 一致性保证:服务端是权威(op 序号)、冲突解决策略统一(OT/CRDT 保证收敛)、离线编辑(本地暂存,重连后同步——CRDT 更优);
- 扩展:权限(查看/编辑/评论)、评论批注(锚定文本)、历史回滚;
- 容量估算:单文档 10 人协作、op 峰值按 50/s 设计;公司级假设 10 万文档、日均约 1000 万 op,增量日志可承受,快照按 100~1000 op 一次摊薄恢复成本;
- 失败与降级:断线本地暂存 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 分钟;框架 A,骨架共性见文首第二节,本题只写特定内容)
| 时间 | 任务 | 内容 |
|---|---|---|
| 0:00–0:30 | 定性 | “协作文档核心是并发操作收敛” |
| 0:30–1:30 | OT | 操作转换;transform 例子 |
| 1:30–2:30 | CRDT | 唯一 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 万文档×日均 100 op;其中活跃文档约 2 万。日均 100 op/文档与「单文档 op 峰值 ~50/s」是两个口径——峰值只在有人正编辑的那几十秒出现,别用它乘全量文档 |
| 单文档 op 峰值 | ~50/s | 10 人协作上限量级 |
【追问链】(三层)
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)——同为「写扩散 vs 读扩散」的取舍;实时推送那一跳的对照题是第 129 题
- 存储: 日志+快照模式(同 Redis 持久化)
【自测】
- 判断对错:协作文档可以给整个文档加锁保证一致。 参考答案: 错。串行编辑体验差;应 OT/CRDT 允许并发收敛。
- OT 的 transform 解决什么? 参考答案: 并发操作的位置修正,保证多端最终一致。
- CRDT 为什么适合离线? 参考答案: 唯一 ID+确定性合并,离线编辑重连后可自动收敛。
146. 老板要看实时 GMV/订单量/转化率看板(BI 看板)
【考察内容】数仓与实时计算架构
【题目】老板要求实时看板:GMV、订单量、转化率、各省份分布,秒级刷新。数据从订单库实时流出,怎么聚合(实时计算)、怎么存储(OLAP/预聚合)、看板接口怎么支撑高频查询?
【参考答案】
- 数据链路:业务埋点/订单事件 → MQ(Kafka)→ 实时计算(Flink 窗口聚合:秒级滚动窗口或每条事件累加——题干要秒级,窗口宽度决定数值新鲜度,缓存 TTL 只决定前端拉取频率,两件事别混)→ 结果写 Redis/ClickHouse;离线计算(Hive/Spark 定时跑日/周报)→ 数仓;
- 存储:明细数据入数仓(Hive/OSS);聚合结果入 ClickHouse(列式、聚合查询快)或 Redis(实时 TopN/计数);
- 查询层:看板接口查 ClickHouse/Redis → 前端 ECharts 渲染;预聚合(按维度组合物化视图)避免大查询;
- 关键设计:
- 维度建模:事实表(订单事实:金额、数量、时间)+ 维度(渠道、商品、地区),支持任意维度组合下钻;
- 转化率(题干四个指标里唯一有分母陷阱的,必须单独答)=成交订单数 ÷ 曝光(或 UV):分母不在订单库里,要补前端埋点流(曝光/UV 事件),与订单流在同一窗口、同一维度对齐后再算;分子分母各自按窗口预聚合(Flink 每 5s/1min 累加写 Redis),看板只读比值,不在查询时跨表现算;口径要写明分母是 UV、PV 还是曝光次数——否则老板看到的「转化率」和你算的不是同一个数;
- 实时 vs 离线一致性:实时看板与离线报表口径对齐(同源数据,允许差异并在 T+1 校准);
- 查询性能:大数据量用 ClickHouse(亿级秒级返回)、按日期分区、物化视图;
- 缓存:高频看板缓存(秒级 TTL);
- 扩展:指标口径管理(指标字典)、告警(指标异常波动)、自助报表;
- 容量估算:订单事件假设日 500 万单、每单约 6 条状态事件 → 3000 万事件/日;高峰 4 小时占 70% 时段的均值为 3000万×0.7÷14400 ≈ 1460/s,再乘瞬时系数 5~10 → 流式写入峰值约 0.7~1.5 万 QPS(取 1 万计),大促再 ×5~10;ClickHouse 明细按日分区、亿级聚合秒级返回;看板本身读 QPS 通常百级,用 Redis/结果缓存扛,不直接打明细;
- 失败与降级: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 分钟;框架 B,骨架共性见文首第二节,本题只写特定内容)
| 时间 | 任务 | 内容 |
|---|---|---|
| 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 查询 | 亿级秒出 | 列存优势 |
| 缓存 TTL(BI 看板结果) | 1~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 题热榜呼应 |
【关联题】
【自测】
- 判断对错:看板直接查订单 MySQL 明细即可。 参考答案: 亿级聚合慢;应 ClickHouse 预聚合。
- 为什么要实时+离线双链路? 参考答案: 实时要快、离线要准;T+1 校准口径。
- 什么是维度建模? 参考答案: 事实表(度量)+维度表(渠道/地区/时间),支持任意下钻。
147. 刷单/薅羊毛/盗号,怎么识别和拦截(风控系统)
【考察内容】风控是金融/电商高级设计题
【题目】电商平台被薅羊毛团伙盯上了:批量注册小号领券、刷单骗补贴、盗号下单。风控系统怎么设计?规则引擎与机器学习怎么配合?实时拦截(毫秒级决策)怎么实现?误杀与漏杀怎么权衡?
【参考答案】
- 数据采集:行为埋点(登录、下单、支付、领券)、设备指纹(设备 ID、IP、浏览器)、账户信息;
- 规则引擎(第一层,实时):黑白名单(IP/设备/账号)、频控规则(同设备多账号、短时间高频)、阈值规则(金额、频率)——可配置、热更新(Drools/自研规则平台);
- 模型层(第二层):机器学习模型(风险评分:XGBoost/GBDT 打分 0~100,欺诈/盗号/薅羊毛分类模型)——离线训练、在线推理(实时特征:行为序列特征);
- 决策引擎:规则+模型分组合决策(评分>80 直接拒绝,60~80 人工审核/二次验证码,<60 放行);人工审核平台(可疑订单人工复核);
- 特征:实时特征(近 5 分钟下单次数)、离线特征(历史行为画像)、图特征(社交关系/设备关联图——团伙检测);
- 闭环:处置(拦截/验证码/限额)→ 反馈(处置结果回流标注)→ 模型迭代(样本更新重训);
- 延迟要求:毫秒级决策(不影响正常下单)——规则引擎内存化+模型 ONNX/自研推理+特征缓存;
- 误杀控制:分级处置(先验证码/限额,再拒绝),白名单通道;
- 容量估算:下单峰值假设 5 万 QPS,风控决策同量级;规则内存 O(1),特征缓存命中 >95%,整段决策 P99 < 50ms;
- 失败与降级:按业务风险选 fail-open(放行+补扫)或 fail-closed(拒绝)——支付倾向 fail-closed,营销领券可短时 fail-open;特征超时用默认画像,避免死等。
【原理溯源】
为什么规则和模型要配合而不是二选一? 规则快、可解释、拦截确定风险(黑名单、频控),但对抗性差(黑产改手法即失效)。模型能发现未知模式,但慢、黑盒、可能误杀。组合:规则先挡确定风险,模型处理复杂/未知,输出融合进决策引擎。这是可解释性与泛化能力的互补。
为什么要毫秒级决策? 风控在下单关键路径上,超时会拖垮用户体验甚至超时失败。规则内存化、模型轻量推理(ONNX/TensorRT)、特征预计算缓存,保证 P99 < 50ms。
为什么要分级处置(验证码/限额/拒绝)? 一刀切拒绝误杀严重(正常用户被挡)。分级:可疑先验证码/二次验证,再限额,最后拒绝。白名单通道保护 VIP。这是误杀与漏杀的权衡——宁可多验证,不可错杀。
为什么要图特征? 团伙作案设备/IP/社交关联紧密。单点特征难发现,图上“同设备多账号”“资金环”等模式一目了然。图计算离线做,在线查子图特征。
为什么要闭环反馈? 处置结果(真实欺诈/误杀申诉)是最好标注。回流训练集,模型迭代。没有闭环的风控会逐渐失效。
【选型判断树】
第一层:规则引擎(快、可解释)
黑名单、频控、阈值 → 直接拦截
第二层:模型评分(泛化)
>80 拒绝 / 60~80 验证码或人审 / <60 放行
特征:实时+离线+图
闭环:处置结果回流训练判断口诀: 规则挡确定,模型挡未知,分级降误杀,闭环保迭代。
【口述骨架】(5 分钟;框架 B,骨架共性见文首第二节,本题只写特定内容)
| 时间 | 任务 | 内容 |
|---|---|---|
| 0:00–0:30 | 定性 | “风控是规则+模型双层、毫秒级、分级处置” |
| 0:30–1:30 | 数据与特征 | 埋点、设备指纹、实时/离线/图特征 |
| 1:30–2:30 | 双层引擎 | 规则快拦;模型评分 |
| 2:30–3:30 | 决策与处置 | 分级:拒绝/验证码/人审/放行 |
| 3:30–4:30 | 闭环与性能 | 反馈训练;毫秒级推理 |
| 4:30–5:00 | 收尾 | “一句话:规则可解释、模型可泛化、分级可控误杀” |
【关键数字】
| 参数 | 经验值 | 说明 |
|---|---|---|
| 决策延迟 | < 50ms P99 | 关键路径 |
| 模型评分 | 0~100 | 阈值分级 |
| 高危 | >80 | 拒绝 |
| 中危 | 60~80 | 验证码/人审 |
| 实时特征窗口 | 近 1/5/30 分钟 | 行为序列 |
| 闭环 | 日级重训 | 样本回流 |
| 决策 QPS | 跟随下单峰值(如 5 万) | 与交易同级 |
| 特征缓存命中 | > 95% | 保 P99 |
【追问链】(三层)
L1|“规则和模型怎么配合?” → 规则快速拦截确定风险,模型处理复杂/未知风险,融合进决策引擎。
L2|“误杀怎么控制?” → 分级处置(验证码→限额→拒绝)、白名单、申诉通道;监控误杀率与人工翻案率,超阈值回滚规则/降阈值;新规则先影子流量再全量。
L3|“黑产不断变招怎么办?” → 闭环反馈快速迭代;图特征发现团伙;对抗训练;蜜罐/探针。
【评分标准】
| 档位 | 答案特征 |
|---|---|
| 60 分 | 知道要做规则拦截 |
| 80 分 | 规则+模型双层、实时特征 |
| 95 分 | 分级处置、图特征、闭环迭代、毫秒级性能、与 134/150/157 呼应 |
【关联题】
【自测】
- 判断对错:风控只用规则引擎就够了。 参考答案: 规则对抗性差;需模型泛化+闭环迭代。
- 为什么要分级处置? 参考答案: 降低误杀;可疑先验证再拒绝。
- 决策为什么要毫秒级? 参考答案: 在下单关键路径,超时影响体验与成功率。