字节后端一面
日期:2026-06-27
类型:后端一面复盘
面试问题整理
1. 自我介绍
- 问题:自我介绍。
2. 秒杀平台项目介绍
- 问题:介绍秒杀平台项目。
3. MQ 削峰与故障处理
有没有想过 MQ 如果挂了怎么办?
如果 MQ 挂了,我会先看它在系统里承担的角色。秒杀场景里 MQ 主要是用来削峰和异步处理,所以 MQ 不可用时,不能直接绕过 MQ 去写数据库,否则高峰流量会把库存、订单库打崩。
我的处理会分几层:
第一层是 MQ 集群自身的高可用,比如 Broker 集群部署、主从/副本机制、多可用区部署、故障转移,避免单个 Broker 宕机导致整个消息链路不可用。
第二层是入口降级。如果生产者发现 MQ 写入失败,接口可以快速失败或返回“系统繁忙,请稍后重试”,同时配合限流、熔断、活动开关,保护后端核心服务。
第三层是重要消息兜底。如果业务必须保证请求不丢,可以把请求先写入本地可靠存储或数据库 outbox 表,再由后台任务在 MQ 恢复后补偿投递。但秒杀入口流量很大,这种方案要控制写入量,否则会把压力转移到数据库。
第四层是恢复后的补偿。MQ 恢复后,要根据订单表、库存流水、消息状态做对账,重新投递未处理消息,并且消费者侧要保证幂等,防止重复消费导致重复扣库存或重复下单。
4. 接口限流
一般情况下,除了用户和 IP 这两个维度之外,还可以增加哪些维度?
-
接口维度 例如 /seckill/order、/coupon/receive。 用来保护高成本或核心接口。
-
资源维度 例如商品 ID、优惠券 ID、活动 ID、店铺 ID。 用来防止热点资源被打爆。
-
设备维度 例如 deviceId、客户端指纹。 用来补充 IP 和用户维度,防止多账号或换 IP 刷。
5. 限流策略
常见限流策略主要有固定窗口、滑动窗口、漏桶和令牌桶。
固定窗口
固定窗口是把时间切成固定长度的窗口,比如每 1 秒一个窗口。每个窗口内维护一个计数器,请求进来时计数加 1,只要计数没有超过阈值就放行;超过阈值就拒绝。窗口结束后,计数器清零,进入下一个窗口。
- 优点:实现简单,计数成本低,适合对精度要求不高的场景。
- 缺点:存在临界突刺问题。比如限制每秒 100 次请求,用户可以在上一秒最后 100 毫秒打满 100 次,又在下一秒最开始 100 毫秒再打 100 次,短时间内实际通过 200 次请求。
滑动窗口
滑动窗口是在固定窗口基础上做细粒度拆分。比如把 1 秒拆成 10 个 100 毫秒的小窗口,每次统计最近 1 秒内多个小窗口的请求总数。随着时间推进,窗口会不断向前滑动。
- 优点:比固定窗口更平滑,可以缓解临界突刺问题,限流结果更接近真实的最近一段时间请求量。
- 缺点:实现和存储成本更高,需要维护多个小窗口的计数。窗口拆得越细,精度越高,维护成本也越高。
漏桶
漏桶可以理解为一个固定容量的桶,请求先进入桶里排队,系统按照固定速率从桶里取出请求并处理。如果桶满了,后续请求会被拒绝或丢弃。
- 优点:输出速率稳定,可以把突发流量整形成平稳流量,适合保护下游服务。
- 缺点:对突发流量不够友好。即使系统短时间内还有处理能力,漏桶也会按照固定速率放行,请求可能在桶里等待或被拒绝。
令牌桶
令牌桶是系统按照固定速率往桶里放令牌,桶有最大容量。请求到来时需要先拿到令牌,拿到令牌才能通过;没有令牌就被拒绝或等待。桶里如果积累了一些令牌,短时间内可以允许一批请求同时通过。
- 优点:既能限制平均速率,也能允许一定程度的突发流量,适合秒杀、网关、接口限流等场景。
- 缺点:参数需要结合业务容量设置,比如令牌生成速率和桶容量。桶容量设置过大时,瞬时突发流量仍然可能对下游造成压力。
6. Redis 与 MySQL 数据一致性
Redis 一般作为缓存,MySQL 作为最终数据源。系统通常保证最终一致性,常见方案是 Cache Aside。
Cache Aside 的基本流程
读请求:
先读 Redis-> Redis 命中,直接返回-> Redis 未命中,读 MySQL-> 把 MySQL 查询结果写回 Redis-> 返回结果写请求:
先更新 MySQL-> 再删除 Redis 缓存写请求里通常选择删除缓存,让后续读请求重新从 MySQL 加载最新数据。这样可以避免复杂缓存内容在并发更新时被错误覆盖。
主从延迟带来的旧数据问题
如果 MySQL 做了读写分离,写请求更新的是主库,读请求可能走从库。主库同步到从库存在延迟,所以缓存删除之后,如果有读请求回源到从库,就可能读到旧数据,并把旧数据重新写回 Redis。
这个问题可以根据一致性要求分层处理:
-
普通最终一致场景
可以使用延迟双删。更新主库后先删除缓存,等待一段时间后再删除一次缓存,清掉可能被旧数据回填的缓存。延迟时间需要参考主从同步延迟,一般取大于主从延迟 P99 的时间。 -
删除动作可靠性
可以结合消息队列、binlog 监听或重试任务,保证缓存删除动作最终执行成功。比如监听 MySQL binlog,发现数据变更后异步删除对应 Redis Key。 -
强一致场景
对订单状态、支付状态、库存扣减结果这类强一致读,可以在写后一段时间内强制读主库,或者直接绕过缓存和从库,以 MySQL 主库结果为准。
秒杀热点场景的补充
秒杀库存属于热点高并发写场景,如果每次购买都同步更新 MySQL 再删除 Redis,后续大量读请求可能同时回源 MySQL,导致数据库压力过高。
这类场景通常把库存提前预热到 Redis:
活动开始前:MySQL 库存 -> 预热到 Redis
用户抢购时:Redis 原子扣减库存-> 扣减成功后发送 MQ-> 消费者异步创建订单、落 MySQL、扣数据库库存Redis 承接高并发读写,MySQL 作为最终数据源。后续通过 MQ 消费、库存流水、订单状态和对账任务保证最终一致性。
面试时可以总结为:
普通读多写少业务可以用 Cache Aside,写 MySQL 后删除 Redis,保证最终一致性。如果存在主从延迟,缓存删除后可能从从库读到旧数据并回填 Redis,普通场景可以用延迟双删或 binlog 监听做二次删除;强一致场景可以在写后一段时间内强制读主库,或者直接绕过缓存和从库。秒杀库存这种热点场景会用 Redis 预扣库存加 MQ 异步落库,再通过流水和对账保证最终一致。
7. 缓存过期与热点 Key
如果缓存过期时流量很高,需要先区分两类问题:
-
大量 Key 同时过期
这类问题通常叫缓存雪崩。大量请求同时发现 Redis 没有数据,然后一起回源 MySQL,数据库会承受瞬时高压。 -
单个热点 Key 过期
这类问题通常叫缓存击穿。比如秒杀商品库存、热门商品详情、热门活动页配置这类 Key 访问量特别大,一旦过期,短时间内会有大量请求同时打到 MySQL。
缓存雪崩:大量 Key 同时过期
常见处理方式包括过期时间随机化、分批预热、限流降级。
过期时间随机化
缓存写入 Redis 时,不给所有 Key 设置完全相同的过期时间,而是在基础过期时间上增加一个随机偏移。
基础过期时间:30 分钟随机偏移:0-5 分钟实际过期时间:30-35 分钟这样可以让 Key 分散过期,降低同一时刻大量回源 MySQL 的概率。
分批预热
分批预热是指在高峰流量到来之前,提前把热点数据从 MySQL 加载到 Redis,并且控制加载节奏。
比如秒杀活动开始前,可以先筛选活动商品、库存、活动配置、商品详情等热点数据,然后按批次写入 Redis:
第 1 批:活动配置第 2 批:热门商品基础信息第 3 批:秒杀库存第 4 批:商品详情和展示数据分批预热要注意几点:
-
控制预热速率
预热本身也会查 MySQL,所以不能一次性把所有数据打到数据库上。可以分页读取、分批写入 Redis,并控制每批之间的间隔。 -
优先预热热点数据
先预热访问量最高、最影响核心链路的数据,比如秒杀商品库存、活动配置、热门商品详情。低频数据可以等首次访问时再加载。 -
错开过期时间
预热写入 Redis 后,仍然要给不同 Key 设置不同过期时间,避免下一轮集中失效。 -
做预热校验
预热完成后,可以校验 Redis 中的 Key 数量、库存值、活动状态,避免活动开始后才发现缓存缺失。
面试里可以这样表达:
对活动类热点数据,我会在活动开始前做缓存预热,把商品信息、活动配置、库存等数据提前加载到 Redis。预热时不会一次性全量加载,而是分页查 MySQL、分批写 Redis,并控制预热速率,避免预热过程本身把数据库打满。预热完成后还会校验关键 Key 是否存在,并给不同 Key 设置随机过期时间,减少后续集中失效。
限流降级
限流降级是在缓存异常、回源 MySQL 增多或数据库压力升高时,主动减少进入核心链路的请求量。
常见做法包括:
-
入口限流
在网关或接口层限制整体 QPS,也可以按用户、IP、接口、商品、活动等维度限流。比如某个秒杀商品的请求量超过阈值后,后续请求直接返回“系统繁忙,请稍后重试”。 -
回源限流
对“查 MySQL 重建缓存”这条路径单独限流。即使 Redis 失效,也不能让所有请求都去查 MySQL,只允许少量请求回源,其余请求等待、重试或返回兜底结果。 -
降级返回
对商品详情、活动展示这类读请求,可以短时间返回旧缓存、默认值或简化信息;对下单、扣库存这类核心写请求,可以返回排队中、系统繁忙,或者通过活动开关暂停入口。 -
熔断保护
如果发现 MySQL 响应时间升高、错误率升高或连接池耗尽,可以暂时熔断回源请求,让系统进入降级状态,优先保护数据库和核心服务。
面试里可以这样表达:
如果缓存失效后回源流量明显升高,我会在入口和回源路径都做限流。入口层限制用户、IP、商品和接口维度的请求量;回源层只允许少量线程查 MySQL 重建缓存,其他请求等待、重试或返回兜底结果。如果 MySQL 压力已经很高,可以触发熔断和降级,比如返回系统繁忙、返回旧缓存,或者临时关闭活动入口,优先保护数据库。
缓存击穿:单个热点 Key 过期
热点 Key 过期时,重点是避免所有请求同时回源 MySQL。常见方案是互斥锁和逻辑过期。
互斥锁
缓存未命中后,只有一个线程能拿到锁去查 MySQL 并重建缓存。其他线程等待、重试,或者直接返回旧值。
请求发现 Redis 未命中-> 尝试获取分布式锁-> 获取成功:查 MySQL,重建缓存,释放锁-> 获取失败:等待后重试,或返回兜底结果互斥锁可以减少数据库回源次数,但要注意锁超时时间、死锁、重试间隔和线程堆积问题。
逻辑过期
逻辑过期是缓存 Key 本身不设置物理过期时间,而是在 value 里保存一个过期时间字段。请求发现逻辑时间过期后,先返回旧数据,同时只让一个后台线程异步重建缓存。
Redis value = 数据内容 + 逻辑过期时间
请求读取 Redis-> 数据没过期:直接返回-> 数据逻辑过期:先返回旧数据-> 后台线程异步查 MySQL 并刷新缓存逻辑过期可以提高可用性,适合商品详情、活动配置这类允许短时间旧数据的场景。缺点是短时间内可能返回旧值,所以强一致业务要谨慎使用。
结合秒杀项目的回答
秒杀库存这类热点数据一般会提前预热到 Redis,抢购时通过 Redis 原子扣减库存,扣减成功后写入 MQ,由消费者异步落 MySQL。这样高并发入口主要打 Redis,MySQL 通过 MQ 被削峰。
面试时可以总结为:
如果缓存过期时流量很高,我会先区分缓存雪崩和缓存击穿。大量 Key 同时过期可以通过随机过期时间、分批预热、限流降级来缓解;分批预热会提前把热点数据分页加载到 Redis,并控制速率,避免预热过程压垮 MySQL。单个热点 Key 过期可以用互斥锁或逻辑过期,只让一个线程重建缓存,其他请求等待、重试或返回旧数据。秒杀库存这种热点场景会提前预热到 Redis,用 Redis 原子扣减加 MQ 异步落库,避免请求集中回源 MySQL。
8. RAG 检索准确性
RAG 检索准确性重点不是只讲“用户问题 -> 检索 -> 拼 Prompt -> 生成答案”的流程,而是要说明怎么让检索结果更相关、更可靠。
可以从数据质量、文档切分、元数据过滤、查询改写、混合检索、重排、阈值控制和离线评估几个方面回答。
数据质量
知识库入库前要先做清洗、去重和去噪。比如去掉重复文档、无效 HTML、导航栏、广告、乱码内容,保留真正有业务价值的信息。
如果知识库内容本身质量很差,后面的向量检索和大模型生成都很难稳定。
文档切分
文档切分要尽量按标题、段落、语义边界切分,避免把一个完整语义拆散。
Chunk 太大时,检索结果可能包含很多无关内容;Chunk 太小时,又容易丢失上下文。实际项目里一般会结合 chunk size、overlap 和文档结构来调整。
比如商品知识库可以按商品、规格、售后规则、活动规则切分;技术文档可以按标题、章节、段落切分。
Metadata 过滤
入库时要保留标题、来源、业务类型、商品 ID、时间、权限、租户等 metadata。检索前可以先根据 metadata 做过滤,再做向量召回。
比如用户问某个商品的售后政策,可以先过滤到对应商品、售后文档、有效时间范围内的内容,再进行相似度检索。
这样可以减少无关文档进入候选集。
Query 改写
用户问题可能比较口语化,也可能缺少关键词。可以对 Query 做关键词提取、同义词扩展、多路查询或结合历史上下文改写。
比如用户问“这个能不能退”,可以结合上下文改写成“某商品是否支持退货、退款、售后政策”。
Query 改写的目标是提高召回率,让检索系统更容易找到相关资料。
混合检索
只用向量检索时,语义相似效果比较好,但可能漏掉精确关键词、编号、商品名、错误码、专业术语。只用关键词检索时,精确匹配强,但语义泛化能力弱。
所以可以使用混合检索:
向量检索:负责语义相似召回关键词检索:负责精确关键词召回混合排序:合并两路候选结果常见组合是向量检索加 BM25。这样既能召回语义相近的内容,也能保留精确匹配能力。
Rerank 重排
第一阶段检索通常会召回 TopK 候选片段,比如 Top20 或 Top50。召回后可以用 rerank 模型对候选片段重新排序,把和问题最相关的内容排到前面。
Rerank 的作用是提高前几条结果的相关性,因为最终塞进 Prompt 的内容数量有限,前几条质量会直接影响回答质量。
阈值控制
检索结果需要设置相似度阈值或相关性阈值。如果最高分结果都很低,说明知识库里可能没有可靠答案。
这种情况下不应该强行把低相关内容塞给模型,否则模型容易根据弱相关材料编答案。更合理的做法是返回“知识库中没有找到明确依据”,或者让模型基于无资料状态拒答。
离线评估
RAG 不能只靠感觉调参,需要准备一批测试问题和标准答案,评估检索效果。
常见指标包括:
Recall@K:正确资料有没有出现在前 K 个召回结果里MRR:正确资料排在越前面分数越高Hit Rate:问题是否命中了有效资料人工评估:判断检索片段是否真的能支撑答案通过这些指标可以持续调整 chunk 大小、overlap、TopK、阈值、metadata 过滤规则、混合检索权重和 rerank 策略。
面试时可以总结为:
我会从知识库质量、文档切分、召回策略、重排和评估几个方面保证 RAG 检索准确性。数据入库前先清洗、去重,并保留标题、来源、业务标签等 metadata;切分时按语义边界切 chunk,避免上下文丢失;检索时可以用向量检索结合关键词检索,提高语义召回和精确匹配能力;召回后再用 rerank 模型重排,把最相关的片段放到前面;最后设置相似度阈值,低于阈值就认为没有可靠资料,并通过离线问题集持续评估 Recall@K、MRR 等指标。
9. RAG 无答案时的幻觉控制
如果知识库里面没有准确答案,核心目标是让模型识别“无可靠依据”,并且明确拒答或说明资料不足,避免根据常识和猜测补全答案。
检索阈值
检索阶段要设置相似度阈值或 rerank 分数阈值。如果召回结果低于阈值,说明知识库里没有足够相关的资料,就不把这些低相关片段强行塞给模型。
召回结果分数 >= 阈值:进入生成阶段召回结果分数 < 阈值:判断为无可靠资料这样可以减少模型基于弱相关内容进行发挥。
TopK 结果校验
不能只看有没有召回结果,还要判断 TopK 结果是否真的覆盖用户问题。
比如用户问“某商品是否支持 7 天无理由退货”,检索结果只命中了商品介绍,没有命中售后规则,这种情况下也应该认为资料不足。
可以让系统在生成前先判断:
检索结果是否与问题相关检索结果是否包含回答所需的关键信息检索结果之间是否互相矛盾如果结果不相关、信息不完整或互相冲突,就不要输出确定性结论。
Prompt 约束
生成阶段要在 Prompt 里明确约束模型只能基于检索资料回答。
可以加入类似规则:
只能使用给定资料回答问题。如果资料中没有答案,回复“知识库中没有找到相关信息”。不要根据常识、经验或猜测补充资料中没有出现的内容。这类约束可以降低模型自由发挥的概率。
引用约束
答案最好绑定引用来源,比如文档标题、文档 ID、段落编号或来源链接。模型输出结论时,需要能对应到具体检索片段。
如果一个结论没有来源支撑,就不能作为确定答案输出。
这种方式可以让回答从“模型自己觉得对”变成“能在知识库里找到依据”。
结构化输出
可以让模型先输出一个结构化判断,再决定是否回答。
例如:
has_answer: true / falseevidence: 命中的资料片段answer: 最终回答如果 has_answer=false,就直接返回资料不足的说明,不进入正常回答。
这种方式可以把“是否有依据”和“生成答案”拆开,减少无依据生成。
评估监控
线上需要记录无答案问题、低置信度问题、用户负反馈和人工标注结果。后续可以把这些问题用来补充知识库、优化切分策略、调整阈值和改进 Query 改写。
RAG 的幻觉控制不是只靠 Prompt,一般需要检索阈值、证据约束和评估反馈一起做。
面试时可以总结为:
如果知识库里没有准确答案,我不会让模型强行回答。检索阶段会设置相似度阈值和 rerank 阈值,低于阈值就判断为无可靠资料;生成前会检查 TopK 结果是否真的覆盖问题;Prompt 中会约束模型只能基于检索资料回答,没有依据就说明知识库中没有找到相关信息;答案需要带引用来源,没有证据支撑的结论不能输出。还可以让模型先输出 has_answer 判断,再决定是否生成答案,并记录无答案问题用于后续补充知识库和优化检索。