腾讯 IEG 服务器开发二面(场景设计,已挂)
- 轮次
- 二面
- 结果
- 已挂
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 项目与实习深挖:从简历开始逐条追问,全程对话式交流。
- 场景题:设计一个千万级的通知平台,数据只存在一个 KV 数据库,且 value 是二进制流、无法解析(不能从 value 中获取执行时间),怎么实现到期通知?
- 反问与个人情况交流。
《参考解析》
场景题拆解:KV 里 value 不可解析,怎么做到期触发。这道题的题眼是「时间信息只能在 key 上」——既然 value 是二进制黑盒,任何依赖 value 的方案(扫全表解析时间、GSI/二级索引里也没法建时间字段)都不成立,只能把「到期时间」编码进 key。可行方案如下。
按时间分桶 + 前缀扫描(原帖思路,也是这个约束下的标准答案):key 设计成 bucket:{ts}:{biz_id},其中 ts 是到期时间按固定粒度(秒或分钟)归一化后的时间戳,value 只放载荷。写入时直接写这个 key;到点触发由一个或多个扫描 worker 负责:每 tick 取当前时间对应的 bucket,按 key 前缀 scan 出该桶内所有条目,投递通知后删除。这样一次扫描的量是可控的(桶粒度 × 阈值内的条目数),而且天然幂等——删除成功即视为已投递。
为什么它像时间轮:单层桶在「秒级精度 + 跨度几天」时桶数量爆炸,所以用多级时间轮:秒级轮(60 个槽)、分钟级轮(60 个)、小时级轮(24 个)、天级轮(30 个)。新任务按到期时间放进最粗的那一级,粗轮的槽到点时把该槽里的任务降级重新散列到细轮,逐级细化到秒级轮再执行——查找近似 O(1),内存只与任务数相关。对应到 KV 上就是分层的 key 前缀:w:s:{sec}、w:m:{min}、w:h:{hour}。
必须交代的工程细节(能答出这些才算及格):
① 分片:单机扫不动上千万条,用时间轮 + 分片;分片键可以取 hash(biz_id) % N,每片一个 worker 组,桶粒度内再按 shard 前缀分页扫,避免单次前缀扫描返回过多条目。
② 精度与成本权衡:桶越细精度越高但扫描次数多、空扫多;用「秒桶只放最近 5 分钟的任务,更远的放粗桶逐级降级」把空扫成本压下去。
③ 重复与幂等:worker 可能重复投递(重试、分片迁移、扩缩容),投递前要抢锁或用「通知记录 + 唯一键」去重,业务侧接收端也要幂等。
④ 不可解析的 value 怎么办:如果通知内容真的在 value 里,那就不要解析,投递时直接把 value 原样转发给下游/业务消费方,保持原始字节。
⑤ 延迟与时钟:多机时钟不同步要用 NTP 或时间服务,扫描时留一点「早触发」余量再靠业务侧判定是否真到期,避免因时钟漂移漏发;同时处理时钟回拨。
⑥ 水平扩容与热点:同一秒到期的大批量任务(比如整点推送)会造成热点,需要二级打散(在 key 里再加一个随机后缀,扫描时多扫几个前缀)。
⑦ 可靠性:worker 挂了要能续扫,通常按「扫描水位线 + 已处理标记」记录进度,或用至少一次投递 + 去重表。
对照其他方案:延迟队列(基于时间轮的独立服务如 Redis ZSET / RocketMQ 定时消息 / RabbitMQ 延迟插件)在功能上更省事,但本题明确限定「数据只存在一个 KV 数据库」,所以必须给出在 KV 上自建的方案,同时说明它的精度、成本与一致性代价。ZSET 思路可以提一句作为对照:如果 KV 支持有序结构,score = 到期时间戳 + ZRANGEBYSCORE 就是最自然的实现,代价是内存与热 key。
面试复盘:面试官没有要求自我介绍,直接从简历开始看,先评价了本科学校(江苏普通一本),随后以聊天口吻问项目和实习,全程约一小时、无手撕。原帖作者自评思路是按时间分桶(类似时间轮),9.20 晚查询显示已挂。这类「简历 + 一道场景设计」的二面,技术题往往只有一道,胜负更多取决于项目讲述的深度和场景题的工程完整度——场景题答对主干思路只是一半,能把分片、幂等、时钟、热点、扩容这些细节主动说出来,才容易被判定为「真的做过系统」。