5868 字
29 分钟
字节跳动后端一面复盘

字节后端一面#

日期:2026-06-27
类型:后端一面复盘

面试问题整理#

1. 自我介绍#

  • 问题:自我介绍。

2. 秒杀平台项目介绍#

  • 问题:介绍秒杀平台项目。

3. MQ 削峰与故障处理#

有没有想过 MQ 如果挂了怎么办?

如果 MQ 挂了,我会先看它在系统里承担的角色。秒杀场景里 MQ 主要是用来削峰和异步处理,所以 MQ 不可用时,不能直接绕过 MQ 去写数据库,否则高峰流量会把库存、订单库打崩。

我的处理会分几层:

第一层是 MQ 集群自身的高可用,比如 Broker 集群部署、主从/副本机制、多可用区部署、故障转移,避免单个 Broker 宕机导致整个消息链路不可用。

第二层是入口降级。如果生产者发现 MQ 写入失败,接口可以快速失败或返回“系统繁忙,请稍后重试”,同时配合限流、熔断、活动开关,保护后端核心服务。

第三层是重要消息兜底。如果业务必须保证请求不丢,可以把请求先写入本地可靠存储或数据库 outbox 表,再由后台任务在 MQ 恢复后补偿投递。但秒杀入口流量很大,这种方案要控制写入量,否则会把压力转移到数据库。

第四层是恢复后的补偿。MQ 恢复后,要根据订单表、库存流水、消息状态做对账,重新投递未处理消息,并且消费者侧要保证幂等,防止重复消费导致重复扣库存或重复下单。

4. 接口限流#

一般情况下,除了用户和 IP 这两个维度之外,还可以增加哪些维度?

  1. 接口维度 例如 /seckill/order、/coupon/receive。 用来保护高成本或核心接口。

  2. 资源维度 例如商品 ID、优惠券 ID、活动 ID、店铺 ID。 用来防止热点资源被打爆。

  3. 设备维度 例如 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。

这个问题可以根据一致性要求分层处理:

  1. 普通最终一致场景
    可以使用延迟双删。更新主库后先删除缓存,等待一段时间后再删除一次缓存,清掉可能被旧数据回填的缓存。延迟时间需要参考主从同步延迟,一般取大于主从延迟 P99 的时间。

  2. 删除动作可靠性
    可以结合消息队列、binlog 监听或重试任务,保证缓存删除动作最终执行成功。比如监听 MySQL binlog,发现数据变更后异步删除对应 Redis Key。

  3. 强一致场景
    对订单状态、支付状态、库存扣减结果这类强一致读,可以在写后一段时间内强制读主库,或者直接绕过缓存和从库,以 MySQL 主库结果为准。

秒杀热点场景的补充#

秒杀库存属于热点高并发写场景,如果每次购买都同步更新 MySQL 再删除 Redis,后续大量读请求可能同时回源 MySQL,导致数据库压力过高。

这类场景通常把库存提前预热到 Redis:

活动开始前:
MySQL 库存 -> 预热到 Redis
用户抢购时:
Redis 原子扣减库存
-> 扣减成功后发送 MQ
-> 消费者异步创建订单、落 MySQL、扣数据库库存

Redis 承接高并发读写,MySQL 作为最终数据源。后续通过 MQ 消费、库存流水、订单状态和对账任务保证最终一致性。

面试时可以总结为:

普通读多写少业务可以用 Cache Aside,写 MySQL 后删除 Redis,保证最终一致性。如果存在主从延迟,缓存删除后可能从从库读到旧数据并回填 Redis,普通场景可以用延迟双删或 binlog 监听做二次删除;强一致场景可以在写后一段时间内强制读主库,或者直接绕过缓存和从库。秒杀库存这种热点场景会用 Redis 预扣库存加 MQ 异步落库,再通过流水和对账保证最终一致。

7. 缓存过期与热点 Key#

如果缓存过期时流量很高,需要先区分两类问题:

  1. 大量 Key 同时过期
    这类问题通常叫缓存雪崩。大量请求同时发现 Redis 没有数据,然后一起回源 MySQL,数据库会承受瞬时高压。

  2. 单个热点 Key 过期
    这类问题通常叫缓存击穿。比如秒杀商品库存、热门商品详情、热门活动页配置这类 Key 访问量特别大,一旦过期,短时间内会有大量请求同时打到 MySQL。

缓存雪崩:大量 Key 同时过期#

常见处理方式包括过期时间随机化、分批预热、限流降级。

过期时间随机化#

缓存写入 Redis 时,不给所有 Key 设置完全相同的过期时间,而是在基础过期时间上增加一个随机偏移。

基础过期时间:30 分钟
随机偏移:0-5 分钟
实际过期时间:30-35 分钟

这样可以让 Key 分散过期,降低同一时刻大量回源 MySQL 的概率。

分批预热#

分批预热是指在高峰流量到来之前,提前把热点数据从 MySQL 加载到 Redis,并且控制加载节奏。

比如秒杀活动开始前,可以先筛选活动商品、库存、活动配置、商品详情等热点数据,然后按批次写入 Redis:

第 1 批:活动配置
第 2 批:热门商品基础信息
第 3 批:秒杀库存
第 4 批:商品详情和展示数据

分批预热要注意几点:

  1. 控制预热速率
    预热本身也会查 MySQL,所以不能一次性把所有数据打到数据库上。可以分页读取、分批写入 Redis,并控制每批之间的间隔。

  2. 优先预热热点数据
    先预热访问量最高、最影响核心链路的数据,比如秒杀商品库存、活动配置、热门商品详情。低频数据可以等首次访问时再加载。

  3. 错开过期时间
    预热写入 Redis 后,仍然要给不同 Key 设置不同过期时间,避免下一轮集中失效。

  4. 做预热校验
    预热完成后,可以校验 Redis 中的 Key 数量、库存值、活动状态,避免活动开始后才发现缓存缺失。

面试里可以这样表达:

对活动类热点数据,我会在活动开始前做缓存预热,把商品信息、活动配置、库存等数据提前加载到 Redis。预热时不会一次性全量加载,而是分页查 MySQL、分批写 Redis,并控制预热速率,避免预热过程本身把数据库打满。预热完成后还会校验关键 Key 是否存在,并给不同 Key 设置随机过期时间,减少后续集中失效。

限流降级#

限流降级是在缓存异常、回源 MySQL 增多或数据库压力升高时,主动减少进入核心链路的请求量。

常见做法包括:

  1. 入口限流
    在网关或接口层限制整体 QPS,也可以按用户、IP、接口、商品、活动等维度限流。比如某个秒杀商品的请求量超过阈值后,后续请求直接返回“系统繁忙,请稍后重试”。

  2. 回源限流
    对“查 MySQL 重建缓存”这条路径单独限流。即使 Redis 失效,也不能让所有请求都去查 MySQL,只允许少量请求回源,其余请求等待、重试或返回兜底结果。

  3. 降级返回
    对商品详情、活动展示这类读请求,可以短时间返回旧缓存、默认值或简化信息;对下单、扣库存这类核心写请求,可以返回排队中、系统繁忙,或者通过活动开关暂停入口。

  4. 熔断保护
    如果发现 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 / false
evidence: 命中的资料片段
answer: 最终回答

如果 has_answer=false,就直接返回资料不足的说明,不进入正常回答。

这种方式可以把“是否有依据”和“生成答案”拆开,减少无依据生成。

评估监控#

线上需要记录无答案问题、低置信度问题、用户负反馈和人工标注结果。后续可以把这些问题用来补充知识库、优化切分策略、调整阈值和改进 Query 改写。

RAG 的幻觉控制不是只靠 Prompt,一般需要检索阈值、证据约束和评估反馈一起做。

面试时可以总结为:

如果知识库里没有准确答案,我不会让模型强行回答。检索阶段会设置相似度阈值和 rerank 阈值,低于阈值就判断为无可靠资料;生成前会检查 TopK 结果是否真的覆盖问题;Prompt 中会约束模型只能基于检索资料回答,没有依据就说明知识库中没有找到相关信息;答案需要带引用来源,没有证据支撑的结论不能输出。还可以让模型先输出 has_answer 判断,再决定是否生成答案,并记录无答案问题用于后续补充知识库和优化检索。

字节跳动后端一面复盘
https://blog.huangnv.online/posts/26627/1/1/
作者
huangnv
发布于
2026-06-27
许可协议
CC BY-NC-SA 4.0