面试知识

Redis(远程字典服务)延迟队列、限流与项目案例

12-Redis从基础到精通 面试知识整理。

Redis(远程字典服务)延迟队列、限流与项目案例

你学完本章后,能把 ZSet(有序集合)延迟队列、Lua(脚本语言)原子领取、限流算法和数据库/MQ(消息队列)一致性讲成一条可落地的链路,而不是只会背命令。

目录

  1. 面试定位与总览
  2. 延迟队列:从到期索引到可靠执行
    • kb:knowledge-01:score(分值)是到期时间而非优先级
    • kb:knowledge-02:轮询、批量与时钟积压
    • kb:knowledge-03:并发领取与 Lua(脚本语言)原子搬移

1. 面试定位与总览

本章适用于 WMS(仓储管理系统)出库超时、跨境物流轨迹补偿、IoT(物联网)报警风暴、Runner(执行器)调度和支付回调补偿。核心原则是:Redis(远程字典服务)负责低延迟调度与削峰,MySQL(关系型数据库)负责业务事实,MQ(消息队列)负责可靠异步传播;任何一层都不能单独承担最终一致性的承诺。

flowchart LR
  A[业务写入 MySQL(关系型数据库)] --> B[Outbox(发件箱)事件]
  B --> C[MQ(消息队列)]
  C --> D[调度服务]
  D --> E[Redis(远程字典服务)ZSet(有序集合)]
  E --> F[Worker(工作线程)领取]
  F --> G[业务执行与幂等落库]
  G --> H[成功确认或重试/死信]
组件责任不能承担的责任必要兜底
Redis(远程字典服务)到期索引、短期状态、限流计数永久业务事实可重建、持久化审计
MySQL(关系型数据库)订单/任务状态、幂等键、Outbox(发件箱)高吞吐轮询调度事务、唯一键、补偿扫描
MQ(消息队列)异步投递、削峰、消费重试跨系统业务事务消费幂等、死信、对账

2. 延迟队列:从到期索引到可靠执行

kb:knowledge-01:score(分值)是到期时间而非优先级

把任务标识作为 member(成员),把 executeAtEpochMs 作为 score(分值)。消费者用 ZRANGEBYSCORE key -inf now LIMIT 0 N 读取到期任务。score(分值)是排序索引,不是可靠消费状态;任务的当前状态必须在 Hash(哈希表)或 MySQL(关系型数据库)中可查询。

flowchart TD
  A[创建任务] --> B[计算 executeAt(执行时刻)]
  B --> C[ZADD 延迟集合 score(分值)=毫秒时间戳]
  C --> D{now >= score(分值)?}
  D -- 否 --> E[继续等待]
  D -- 是 --> F[原子搬到 processing(处理中)]
  F --> G[执行业务]
字段示例设计原因
taskId(任务标识)payment:9821:retry:3全局唯一并承载幂等语义
score(分值)1720000000123用毫秒时间戳直接比较到期
payloadRef(载荷引用)task:9821避免 ZSet(有序集合)存大对象
attempt(尝试次数)3控制重试上限与退避

数据演绎 1: 10:00:00.000 创建支付查单任务,延后 300 秒,score(分值)为 10:05:00.000。10:04:59.900 的轮询不可领取;10:05:00.120 的轮询可领取。若服务时钟慢 800 毫秒,实际将迟 800 毫秒,因此支付类任务须以数据库时间或受控 NTP(网络时间协议)时钟为基准,并保留主动查单的宽限窗口。

热门面试题

问题 1:为什么用 ZSet(有序集合)的 score(分值)实现延迟队列?

  • 考点: 有序索引、时间复杂度、适用边界。
  • 回答思路: 先说明按到期时间排序,再说明批量取最早到期任务。
  • 详细答案: ZSet(有序集合)按 score(分值)排序,写入是 O(logN),按分值范围取到期任务可限制批量大小。它适合秒到分钟级、大量短任务的低延迟调度;它不自带确认、消费组和可靠重投,因此不能把 ZRANGEBYSCORE 当成可靠消费。
  • 进阶追问: score(分值)用秒还是毫秒?
  • 进阶回答: 默认使用毫秒,避免同一秒内任务无序;若同毫秒很多,成员中加入序列号,业务顺序仍由幂等状态机决定。

问题 2:延迟任务为什么不直接把完整 JSON(对象表示法)放进 ZSet(有序集合)?

  • 考点: 内存放大、更新成本、数据治理。
  • 回答思路: ZSet(有序集合)只作索引,载荷独立存放。
  • 详细答案: 大载荷会放大内存、复制和网络带宽,修改一次可能要删加成员;更危险的是业务字段无法独立校验。实践中成员只保存 taskId(任务标识),载荷存 Hash(哈希表)或 MySQL(关系型数据库),并设置过期回收策略。
  • 进阶追问: 载荷丢失怎么处理?
  • 进阶回答: 领取后查 MySQL(关系型数据库)任务表;查不到则记录脏任务指标并删除索引,不能盲目执行。

问题 3:WMS(仓储管理系统)如何使用延迟队列释放预占库存?

  • 考点: 库存状态机、幂等、时间边界。
  • 回答思路: 预占成功后投放到期释放任务,执行时二次校验。
  • 详细答案: 预占事务写库存冻结记录和 Outbox(发件箱)事件;投放失败由扫描任务补齐。到期消费者以 reservationId 查询仍为冻结且未支付的记录,条件更新后释放库存。支付与释放竞争时,由 MySQL(关系型数据库)条件更新裁决,Redis(远程字典服务)只负责唤醒。
  • 进阶追问: 怎么避免延迟任务早到?
  • 进阶回答: 业务执行时重新比较数据库过期时间;未到期则按剩余时间重新入队。

kb:knowledge-02:轮询、批量与时钟积压

轮询间隔不是越短越好。间隔过短会造成空轮询与 Redis(远程字典服务)QPS(每秒查询率)(每秒查询数)浪费;间隔过长会拉高任务延迟。推荐使用固定短周期加自适应退避:队列为空时逐步退避,到期量高时按批次连续领取。每次领取应设置数量与执行时间预算,避免一个分片拖住整个扫描器。

sequenceDiagram
  participant P as Poller(轮询器)
  participant R as Redis(远程字典服务)
  participant W as Worker(工作线程)
  P->>R: 读取到期数与最小 score(分值)
  alt 有到期任务
    P->>R: Lua(脚本语言)搬移一批任务
    R-->>P: taskId(任务标识)列表
    P->>W: 投递受限并发执行
  else 无到期任务
    P->>P: 退避到下一轮
  end
指标正常参考告警含义优先动作
dueLagMs(到期滞后毫秒)P99(99 分位响应时间)(第 99 百分位)小于 2 秒调度跟不上扩分片、减批量阻塞
readyCount(就绪数量)平稳到期堆积查消费者与下游
emptyPollRate(空轮询率)可控轮询过密增加退避
clockSkewMs(时钟偏差毫秒)小于 100 毫秒早触发/晚触发校时并扩大宽限

数据演绎 2: 8 个分片,每片每秒领取 200 个、平均处理 30 毫秒、并发 20,则理论吞吐约 5,333 个/秒;若支付网关变慢至 300 毫秒而并发不变,吞吐降至约 533 个/秒。此时盲目提升轮询频率只会扩大 processing(处理中)集合,正确动作是对网关限流、降低领取量并启动退避。

热门面试题

问题 4:如何设置延迟队列的轮询间隔?

  • 考点: 延迟、空转、反馈控制。
  • 回答思路: 用目标延迟反推基础周期,再按队列状态自适应。
  • 详细答案: 例如目标 P99(99 分位响应时间)(第 99 百分位)调度延迟 1 秒,可用 100 至 300 毫秒基础周期;无任务时指数退避并设置上限,有任务时连续批领。还要记录到期滞后而非只看轮询次数,因为下游慢时增加轮询无效。
  • 进阶追问: 为什么不使用阻塞等待?
  • 进阶回答: ZSet(有序集合)没有原生按最早 score(分值)阻塞弹出语义;可结合通知优化,但仍须保留扫描补偿。

问题 5:时钟回拨会造成什么问题?

  • 考点: 时间源、过期语义、容错。
  • 回答思路: 分析早晚执行与重复执行两个方向。
  • 详细答案: 本机回拨会让消费者误认为任务未到期,导致积压;快进会提前执行。关键业务不以 Redis(远程字典服务)服务器时间作为唯一真相,而是在 MySQL(关系型数据库)状态校验中再次判断 execute_at,并对时间偏差告警。
  • 进阶追问: Redis(远程字典服务)TIME(服务器时间命令)能否解决?
  • 进阶回答: 它减少应用节点差异,但不能消除 Redis(远程字典服务)节点时钟异常,也无法替代业务状态校验。

问题 6:出现百万级到期任务积压如何处理?

  • 考点: 分片、限流、可恢复性。
  • 回答思路: 先止血再扩容,最后补偿核对。
  • 详细答案:hash(哈希)(taskId)%N 分片,消费者只领取所属分片;动态增加分片前要有双读迁移策略。按下游容量限速,优先处理支付、库存等高价值任务;低价值通知降级。处理完成后按 MySQL(关系型数据库)状态与延迟索引做对账。
  • 进阶追问: 能直接把任务全部转到 MQ(消息队列)吗?
  • 进阶回答: 不能无边界转移,否则只是把积压移动;必须按下游配额发送并保留可重放记录。

kb:knowledge-03:并发领取与 Lua(脚本语言)原子搬移

ZRANGEBYSCORE 后再 ZREM 有竞态:多个消费者可能同时读到同一 taskId(任务标识)。正确做法是在一个 Lua(脚本语言)脚本内读取、移除并写入 processing(处理中)集合;脚本返回实际领取的任务列表。Lua(脚本语言)只做 Redis(远程字典服务)内的短事务,不调用外部服务,也不执行长循环。

flowchart LR
  A[ready ZSet(有序集合)] -->|Lua(脚本语言)读取到期任务| B[删除 ready]
  B --> C[写 processing ZSet(有序集合)]
  C --> D[返回 taskId(任务标识)]
  D --> E[业务 Worker(工作线程)]
  E -->|成功| F[删除 processing]
  E -->|失败| G[重新计算 score(分值)]
  G --> A
-- KEYS(键)[1] 为 ready(就绪)集合,KEYS(键)[2] 为 processing(处理中)集合。
-- 先搬移再返回,避免多个消费者对同一任务并发执行。
local ids = redis.call('ZRANGEBYSCORE', KEYS[1], '-inf', ARGV[1], 'LIMIT', 0, ARGV[2])
for _, id in ipairs(ids) do
  if redis.call('ZREM', KEYS[1], id) == 1 then
    redis.call('ZADD', KEYS[2], ARGV[3], id)
  end
end
return ids
错误做法竞态后果修正
查询后逐条删除双消费者都执行Lua(脚本语言)原子搬移
用分布式锁包整批执行锁时间长、吞吐低只锁领取,不锁业务
脚本内调用业务阻塞 Redis(远程字典服务)主线程返回标识后异步执行

数据演绎 3: 消费者 A、B 同时读到 task-7。非原子方案中 A、B 都在删除前拿到载荷,可能各自调用一次退款接口。原子脚本中 A 的 ZREM 成功后写 processing(处理中),B 再读取时任务已经不在 ready(就绪)集合,因此只有 A 返回任务。即使 A 随后宕机,回收逻辑也只会重试一次语义上的同一任务,业务幂等键仍必须存在。

热门面试题

问题 7:Lua(脚本语言)如何保证原子性?

  • 考点: Redis(远程字典服务)事件循环、脚本执行边界。
  • 回答思路: 说明脚本运行期间命令不交错,再说明限制。
  • 详细答案: Redis(远程字典服务)把一个 Lua(脚本语言)脚本作为连续命令执行,其他客户端命令不会插入,因此读、删、写是原子的。原子性只覆盖该实例内的数据操作,不覆盖网络调用、MySQL(关系型数据库)提交或脚本执行后的宕机。
  • 进阶追问: 集群模式脚本有什么限制?
  • 进阶回答: 所有 KEYS(键)必须落在同一 slot(槽),可使用 hash(哈希) tag(哈希标签)如 {delay:0}:ready{delay:0}:processing

问题 8:为什么脚本返回的任务也可能重复执行?

  • 考点: 至少一次、宕机窗口、幂等。
  • 回答思路: 指出领取与完成确认之间不是原子事务。
  • 详细答案: 任务已经搬到 processing(处理中)后,消费者可能在业务成功但确认前宕机;可见性超时回收会再次投递。因此系统提供的是至少一次,不是恰好一次。必须以任务号、支付流水号或库存预占号在 MySQL(关系型数据库)做唯一约束或条件更新。
  • 进阶追问: 能用 SETNX(不存在则设置)(不存在时设置)解决吗?
  • 进阶回答: 它可做短期去重,但不能替代持久幂等,因为过期、故障切换和长事务都可能穿透。

问题 9:如何控制 Lua(脚本语言)脚本的风险?

  • 考点: 单线程阻塞、可观测性、灰度。
  • 回答思路: 控制批量、复杂度和超时。
  • 详细答案: 限制每批数百以内,脚本只进行固定次数的集合命令;预加载 SHA(安全散列算法)并处理 NOSCRIPT 回退;监控脚本耗时、慢日志和命令延迟。脚本版本升级使用新键或可回滚开关,不能在线替换不兼容的数据结构。
  • 进阶追问: 脚本超时能随意杀掉吗?
  • 进阶回答: 不能;若脚本已写数据,强行中断会破坏调用方对结果的判断,应按 Redis(远程字典服务)运维流程处理并依赖幂等修复。

kb:knowledge-04:可见性超时、确认与重试死信

领取时将 taskId(任务标识)写入 processing(处理中)集合,score(分值)设为 now + visibilityTimeout。成功后 ACK(确认)删除;失败按指数退避重新入 ready(就绪)集合;超过最大次数或不可重试错误写入 DLQ(死信队列)并落 MySQL(关系型数据库)审计。可见性超时应明显大于正常 P99(99 分位响应时间)(第 99 百分位)执行时间,并允许心跳续期。

stateDiagram-v2
  [*] --> READY: 投放延迟任务
  READY --> PROCESSING: Lua(脚本语言)领取
  PROCESSING --> SUCCESS: ACK(确认)
  PROCESSING --> READY: 可重试失败/超时回收
  PROCESSING --> DLQ: 达到上限/不可重试
  DLQ --> READY: 人工修复后重放
  SUCCESS --> [*]
结果类型动作例子重试策略
成功ACK(确认)并审计轨迹已写入不重试
短暂失败延迟重试网络超时指数退避+jitter(抖动)
限流按 Retry-After(重试等待时间)延后供应商 429尊重配额
业务拒绝写 DLQ(死信队列)单据已关闭人工或补偿

数据演绎 4: 正常支付查单 P99(99 分位响应时间)(第 99 百分位)为 2 秒,设置可见性超时 15 秒。第一次网络超时在 3 秒返回,按 30 秒、2 分钟、10 分钟退避;第 5 次仍失败进入 DLQ(死信队列)。若某次实际执行 20 秒却没有续期,15 秒时会重复领取,所以长任务要按阶段拆分或每 5 秒心跳续期。

热门面试题

问题 10:什么是可见性超时?

  • 考点: 至少一次投递、宕机恢复。
  • 回答思路: 说明领取后的临时独占与自动回收。
  • 详细答案: 可见性超时把已领取但尚未确认的任务暂时隐藏;消费者宕机或卡死后,到期回收器重新投放。它避免任务永久丢失,但天然允许重复执行,所以必须配合业务幂等。
  • 进阶追问: 超时时间如何选?
  • 进阶回答: 根据业务 P99(99 分位响应时间)(第 99 百分位)耗时、网络抖动和下游超时相加,再留安全裕量;不要简单取很大值,否则故障恢复慢。

问题 11:重试为什么要加入 jitter(抖动)?

  • 考点: 惊群、退避、下游保护。
  • 回答思路: 同时失败的任务不能同时重试。
  • 详细答案: 固定退避会让同批失败任务在同一时刻再次冲击供应商,形成重试风暴。指数退避叠加随机 jitter(抖动)把重试分散到时间窗口,并须设置最大重试次数与总时限。
  • 进阶追问: 哪些错误不应该重试?
  • 进阶回答: 参数校验失败、状态机非法转换、签名错误等确定性业务错误应直接 DLQ(死信队列),避免无效消耗。

问题 12:死信任务如何处理才不丢单?

  • 考点: 审计、人工闭环、重放。
  • 回答思路: 死信不是终点,要可查询、可归因、可重放。
  • 详细答案: 写 DLQ(死信队列)时记录任务号、载荷引用、错误分类、尝试链路和最后错误,并同步更新 MySQL(关系型数据库)任务表。运营页面只允许带权限的人工修复后重放;重放仍走幂等状态机,不能直接重复调用外部接口。
  • 进阶追问: DLQ(死信队列)本身故障怎么办?
  • 进阶回答: MySQL(关系型数据库)审计表是最终回溯来源,Redis(远程字典服务)DLQ(死信队列)只作快速处理索引。

3. 限流:算法、Lua(脚本语言)与热点治理

kb:knowledge-05:固定窗口、滑动窗口、令牌桶与漏桶

固定窗口实现简单,但窗口边界会突刺;滑动窗口更精确但内存和计算更高;令牌桶允许可控突发,适合接口准入;漏桶以恒定速度出流,适合保护稳定但慢的下游。不要脱离业务谈“最优算法”:支付通道看供应商配额,IoT(物联网)报警看聚合出流,WMS(仓储管理系统)库存接口看瞬时保护与公平性。

flowchart LR
  A[请求] --> B{固定/滑动窗口}
  B -- 超限 --> C[429 或业务降级]
  B -- 通过 --> D{令牌桶}
  D -- 无令牌 --> C
  D -- 有令牌 --> E[业务服务]
  E --> F{漏桶/并发舱壁}
  F --> G[稳定调用下游]
算法是否允许突发精度典型用途主要缺点
固定窗口边界处会突发简单接口配额双窗口可翻倍
滑动窗口受窗口约束登录、风控存储/计算较高
令牌桶允许桶容量内突发API(应用程序接口)准入需防令牌竞争
漏桶基本不允许突发供应商保护排队会增延迟

数据演绎 5: 限制为每分钟 600 次。固定窗口在 10:00:59 接受 600 次、10:01:00 再接受 600 次,2 秒内实际放过 1,200 次;滑动窗口会拒绝第二批大部分请求。令牌桶设置速率 10 个/秒、容量 50,空闲后可瞬时通过 50 个,随后每秒补 10 个,适合用户点击造成的短暂峰值。

热门面试题

问题 13:固定窗口为什么会有边界突刺?

  • 考点: 计数窗口与真实时间窗口差异。
  • 回答思路: 用相邻窗口的请求举例。
  • 详细答案: 固定窗口只在窗口内计数,窗口切换时计数归零;攻击者把流量放在两个窗口边界两侧,就能在极短时间通过两倍配额。风险敏感场景使用滑动窗口或令牌桶,固定窗口只适合粗粒度保护。
  • 进阶追问: 双窗口加权能改善吗?
  • 进阶回答: 可以按当前窗口与上一个窗口的时间占比加权,近似滑动窗口,成本低于逐请求记录但仍是近似值。

问题 14:令牌桶与漏桶如何选择?

  • 考点: 突发容忍、下游稳定性。
  • 回答思路: 令牌桶管进入,漏桶管离开。
  • 详细答案: 令牌桶允许短时突发,改善正常用户体验,适合网关入口;漏桶把出流整形成恒定速率,适合对方接口有严格并发或速率限制。工程上常串联使用:入口令牌桶、下游漏桶或并发信号量。
  • 进阶追问: 令牌桶可否只在单机实现?
  • 进阶回答: 单机只能限制单实例,集群总量会放大;全局配额应放 Redis(远程字典服务)或网关,单机限流作为第二层保护。

问题 15:限流返回 429 后是否应该自动重试?

  • 考点: 反压、重试风暴、用户体验。
  • 回答思路: 区分同步请求与异步任务。
  • 详细答案: 同步接口应快速返回 429 并给出 Retry-After(重试等待时间),客户端采用退避;异步任务可重排到延迟队列。绝不能立即无限重试,否则会把过载放大。对支付、库存这类关键流程还应提供查询结果或排队受理。
  • 进阶追问: 限流计数故障怎么办?
  • 进阶回答: 按风险分级:防刷宁可保守拒绝,内部非关键查询可本地兜底;关键写操作要进入受控队列而非静默放行。

kb:knowledge-06:Lua(脚本语言)限流的原子实现

分布式限流必须把“计算、判断、更新、设置过期”放进一个 Lua(脚本语言)脚本,否则多个应用实例会在读取剩余配额后同时放行。固定窗口可用 INCR 与首次 EXPIRE;滑动窗口可使用 ZSet(有序集合)删除过期成员、计数并新增当前请求;令牌桶要保存剩余令牌和上次补充时间。

sequenceDiagram
  participant C as Client(客户端)
  participant A as API(应用程序接口)
  participant R as Redis(远程字典服务)
  C->>A: 请求 userId(用户标识)
  A->>R: Lua(脚本语言)计算配额
  alt 令牌充足
    R-->>A: allow(允许)+remaining(剩余)
    A-->>C: 执行业务
  else 配额耗尽
    R-->>A: deny(拒绝)+retryAfter(重试等待)
    A-->>C: 429
  end
-- 原子完成补令牌、扣令牌与过期设置,防止多个实例超卖同一配额。
local now = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local capacity = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
local state = redis.call('HMGET', KEYS[1], 'tokens', 'last')
local tokens = tonumber(state[1]) or capacity
local last = tonumber(state[2]) or now
tokens = math.min(capacity, tokens + (now - last) * rate / 1000)
if tokens < requested then return {0, tokens} end
tokens = tokens - requested
redis.call('HMSET', KEYS[1], 'tokens', tokens, 'last', now)
redis.call('PEXPIRE', KEYS[1], math.ceil(capacity / rate * 2000))
return {1, tokens}
方案原子边界内存成本适合的键
INCR(递增)固定窗口计数与过期很低tenantId(租户标识)分钟配额
ZSet(有序集合)滑动窗口清理、计数、写入较高userId(用户标识)精细防刷
Hash(哈希表)令牌桶补发与扣减API(应用程序接口)突发准入

数据演绎 6: 令牌桶容量 100、速率 20 个/秒。10:00:00 余额 10,10:00:02 到来时补充 40 后变 50;请求 60 个被拒绝且不扣减,请求 30 个通过并剩 20。若补令牌和扣令牌拆为两次命令,两个实例都可能看到 50 并各放 30,实际通过 60,违反配额。

热门面试题

问题 16:为什么限流脚本必须设置过期时间?

  • 考点: 键生命周期、内存泄漏。
  • 回答思路: 无访问的主体不应永久占内存。
  • 详细答案: 用户、IP(互联网协议地址)(网络地址)或租户的限流键数量可能无限增长;设置略大于一个窗口或桶回满时间的 TTL(存活时间)(生存时间)后,闲置主体自然回收。TTL(存活时间)(生存时间)要在脚本内原子设置,避免只加计数未设过期的异常键。
  • 进阶追问: 每次请求都刷新 TTL(存活时间)(生存时间)吗?
  • 进阶回答: 固定窗口只在首次创建设过期,避免窗口被不断延长;令牌桶可按回满时间刷新,确保状态仍有意义。

问题 17:滑动窗口如何控制 ZSet(有序集合)膨胀?

  • 考点: 清理、粒度、近似算法。
  • 回答思路: 每次操作先删旧成员,并限制高基数场景。
  • 详细答案: 脚本先按窗口起点 ZREMRANGEBYSCORE 清理,再判断 ZCARD,通过后写入唯一请求标识并设置 TTL(存活时间)(生存时间)。对于每秒数万请求的主体,改为时间桶聚合或令牌桶,不能逐请求写 ZSet(有序集合)。
  • 进阶追问: 成员为何必须唯一?
  • 进阶回答: 相同成员重复 ZADD 会覆盖而非计数,成员应包含请求序号或唯一标识。

问题 18:Redis(远程字典服务)故障时限流如何降级?

  • 考点: 可用性与安全性权衡。
  • 回答思路: 按业务风险配置不同失效策略。
  • 详细答案: 登录、防刷、资金写入应默认拒绝或切换到严格本地配额;商品浏览、内部查询可使用小于全局配额的本地令牌桶并打降级标记。无论哪种策略,都要报警并禁止把“限流失效”悄悄变成“无限放行”。
  • 进阶追问: 本地限流如何避免集群总量超标?
  • 进阶回答: 把全局额度按实例数再乘安全系数切分;实例数变化通过配置中心同步,剩余误差由下游限流兜住。

kb:knowledge-07:热点分片、隔离与业务降级

热点往往来自一个大商家、爆款 SKU(库存单位)或单一 IoT(物联网)设备组。单键无法通过增加消费者横向扩展,且会让同一 Redis(远程字典服务)分片热点化。把业务键拆为逻辑分片,例如 limit:{merchantId}:0..15;但分片不是随意随机,必须能稳定路由并把总配额按分片收敛。对真正单对象的热点,还要从合并请求、本地短缓存、请求排队和功能降级处理。

flowchart TD
  A[热点请求] --> B{识别维度}
  B --> C[merchantId(商家标识)分片]
  B --> D[SKU(库存单位)分片]
  B --> E[deviceGroup(设备组)分片]
  C --> F[Redis(远程字典服务)局部令牌桶]
  D --> F
  E --> F
  F --> G{全局保护}
  G -- 超载 --> H[降级:聚合/排队/拒绝]
  G -- 正常 --> I[业务执行]
热点类型分片策略不能做的事降级方式
大商家查询merchantId(商家标识)哈希分片随机键导致额度失真缓存最近结果
爆款库存SKU(库存单位)分片+库存分段只靠 Redis(远程字典服务)扣减排队与售罄页
报警风暴deviceGroup(设备组)聚合每条都同步通知去重、摘要、静默
供应商回调supplierId(供应商标识)隔离共用全局并发池舱壁与延迟重试

数据演绎 7: 一个商家在大促每秒 8,000 次请求,单键限流脚本耗时 0.4 毫秒,理论上该键附近已成为热点。拆为 16 个稳定分片后,每片约 500 次/秒;总额度 4,800 次/秒则每片基础额度 300,余量可由中心桶借用。若 16 片都独立给 4,800 次/秒,就会把总额度错误放大为 76,800 次/秒。

热门面试题

问题 19:热点分片会不会破坏全局限流精度?

  • 考点: 配额拆分、全局约束、热点均衡。
  • 回答思路: 承认局部误差,并用两级限制收敛。
  • 详细答案: 静态分片将全局额度拆给局部桶,确实可能因流量不均产生闲置或局部拒绝。可采用本地分片桶加中心总桶:局部先快速拦截,接近阈值时再原子消耗总桶;高价值路径宁可保守拒绝,也不能让分片把总额度放大。
  • 进阶追问: 为什么不只用中心总桶?
  • 进阶回答: 中心单键会再次成为热点;两级设计把绝大多数请求留在分片内,仅边界请求访问中心桶。

问题 20:热点库存为什么不能只用 Redis(远程字典服务)原子扣减?

  • 考点: 缓存与数据库一致性、超卖、故障恢复。
  • 回答思路: Redis(远程字典服务)可削峰,不是最终账本。
  • 详细答案: 原子扣减能避免同一主节点内的并发负数,却无法自动保证 MySQL(关系型数据库)订单、库存冻结和支付结果一致。主从切换、过期、补偿失败都可能造成偏差。正确做法是 Redis(远程字典服务)预检或排队,MySQL(关系型数据库)条件更新作为最终扣减,并定期按预占记录对账。
  • 进阶追问: Redis(远程字典服务)库存与数据库不一致怎么办?
  • 进阶回答: 暂停放量、以数据库冻结与可售库存重算缓存、记录差异单;不得直接覆盖数据库事实。

问题 21:什么情况下应该做功能降级?

  • 考点: 业务分级、保护核心链路。
  • 回答思路: 明确保什么、舍什么、如何恢复。
  • 详细答案: 当下游容量被关键流程占满、延迟队列持续增长或限流拒绝显著上升时,先保障支付确认、库存释放和安全报警;将轨迹详情改为缓存结果、将 IoT(物联网)同类报警聚合、暂停低优先级报表任务。降级必须可观测、可开关、可恢复,不能把数据错误伪装成成功。
  • 进阶追问: 降级后用户如何感知?
  • 进阶回答: 返回明确的“处理中/稍后可查”状态与查询入口,异步完成后通知;不要返回伪造的最终成功。

kb:knowledge-08:ZSet(有序集合)与 Stream(流)、MQ(消息队列)、定时任务的选型

ZSet(有序集合)擅长“按时间找到到期项”,但可靠消费能力需要自行建设;Stream(流)有 Consumer Group(消费者组)、确认和 Pending(待确认)恢复,适合已经就绪的事件流;MQ(消息队列)适合跨服务可靠传播、堆积隔离和成熟死信;定时任务适合低频全量扫描与兜底。生产系统常组合,而不是强行只选一个。

flowchart LR
  A[业务事务] --> B[Outbox(发件箱)]
  B --> C[MQ(消息队列)可靠投递]
  C --> D[ZSet(有序集合)按时调度]
  D --> E[Stream(流)就绪任务流]
  E --> F[Consumer Group(消费者组)执行]
  G[定时任务] --> H[扫描漏投/超时/对账]
  H --> B
维度ZSet(有序集合)Stream(流)MQ(消息队列)定时任务
延迟到期查询需自行延迟层取决于产品粗粒度
消费确认自建原生 Pending(待确认)原生或产品能力
跨服务可靠性自建中等
运维复杂度中到高
最佳定位调度索引就绪工作流事件传播补偿扫描

数据演绎 8: 跨境轨迹有 50 万票等待供应商查询:若每分钟定时任务全表扫描,数据库每分钟读取 50 万行且延迟最坏 60 秒;用 ZSet(有序集合)只取到期任务可把读取缩到实际到期的 2,000 行。执行后写 Stream(流)可以让多个 Consumer Group(消费者组)分别更新轨迹、发送通知和做指标,但这些消费者都要幂等。

热门面试题

问题 22:什么时候选择 Stream(流)而非 ZSet(有序集合)?

  • 考点: 消费组、确认、任务就绪状态。
  • 回答思路: 延迟与消费可靠性拆开。
  • 详细答案: 当任务已经就绪且需要多个消费者组、确认、待确认消息认领时,Stream(流)更合适;当核心需求是按未来时间排序并取最早到期任务时,ZSet(有序集合)更直接。可由 ZSet(有序集合)负责时间轮,领取后写 Stream(流)负责可靠执行。
  • 进阶追问: 为什么不直接把未来消息都写 Stream(流)?
  • 进阶回答: Stream(流)按追加顺序而非到期时间索引,未来大量任务会让消费者反复跳过或需要额外排序层。

问题 23:为什么仍保留定时任务?

  • 考点: 补偿、对账、故障自愈。
  • 回答思路: 实时链路并不等于没有漏投。
  • 详细答案: Redis(远程字典服务)故障、发布中断、Outbox(发件箱)投递失败和人工修复都可能让索引缺失。低频定时任务以 MySQL(关系型数据库)事实表扫描“应执行未执行”的记录并补投,是可靠性的最后防线;它不承担高频实时调度。
  • 进阶追问: 扫描会压垮数据库吗?
  • 进阶回答: 使用状态+时间复合索引、游标分页、限速和按分片扫描,并避开业务高峰。

问题 24:MQ(消息队列)延迟消息能替代 ZSet(有序集合)吗?

  • 考点: 产品能力与控制权。
  • 回答思路: 从精度、可观测性、迁移和重试比较。
  • 详细答案: 支持任意延迟级别和可靠存储的 MQ(消息队列)可以替代部分 ZSet(有序集合)场景,但要验证延迟精度、消息上限、重试死信和运维成本。需要频繁取消、查询最近到期、动态调整任务时,ZSet(有序集合)通常更灵活;关键业务仍需数据库状态机。
  • 进阶追问: 两者并用如何避免重复?
  • 进阶回答: 所有投递使用同一 taskId(任务标识)和 MySQL(关系型数据库)幂等记录,任一通道到达均先做条件领取。

kb:knowledge-09:数据库、MQ(消息队列)与 Redis(远程字典服务)的一致性边界

不要把“先写数据库再写 Redis(远程字典服务)”称作事务。跨三个系统没有天然原子提交,正确模型是:MySQL(关系型数据库)事务中写业务状态与 Outbox(发件箱),投递器至少一次发送 MQ(消息队列),消费者按 taskId(任务标识)幂等建立 Redis(远程字典服务)索引;扫描任务补偿漏投。业务执行时再次以 MySQL(关系型数据库)状态机裁决,而不是相信队列消息本身。

sequenceDiagram
  participant S as Service(服务)
  participant D as MySQL(关系型数据库)
  participant O as Outbox(发件箱)
  participant M as MQ(消息队列)
  participant R as Redis(远程字典服务)
  S->>D: 业务状态+Outbox(发件箱)同一事务
  D-->>O: 已提交事件
  O->>M: 至少一次投递
  M->>R: 建立延迟索引
  R->>S: 到期执行
  S->>D: 条件更新/幂等记录
故障窗口现象正确恢复方式
数据库提交前宕机无业务事实不投递
提交后 MQ(消息队列)前宕机漏消息Outbox(发件箱)重扫投递
MQ(消息队列)重复投递多次建索引taskId(任务标识)幂等写入
Redis(远程字典服务)丢索引到期不执行MySQL(关系型数据库)扫描补投
执行成功未 ACK(确认)重复执行条件更新/唯一键挡住

数据演绎 9: 支付回调把订单从“待支付”更新为“已支付”,并写入“取消超时任务”事件在同一 MySQL(关系型数据库)事务内。事务提交后服务崩溃,MQ(消息队列)尚未收到事件;Outbox(发件箱)扫描到未投递记录后补发。即使取消任务已在 ZSet(有序集合)里,到期执行会用 update order set status=cancelled where id=? and status=unpaid,因订单已支付而更新 0 行,安全结束。

热门面试题

问题 25:如何保证数据库与 MQ(消息队列)最终一致?

  • 考点: Outbox(发件箱)、至少一次、幂等。
  • 回答思路: 先保证本地原子,再接受外部至少一次。
  • 详细答案: 业务表与 Outbox(发件箱)表在同一个 MySQL(关系型数据库)事务提交;投递器可靠重试发送 MQ(消息队列),消费者用事件 ID(标识)或业务唯一键去重。这样避免双写窗口丢消息,但仍是最终一致,不承诺跨系统同步提交。
  • 进阶追问: 发送成功但标记已投递失败怎么办?
  • 进阶回答: 投递器会重复发送,消费者幂等吸收;不能因为害怕重复而放弃重试。

问题 26:Redis(远程字典服务)索引写入失败会不会丢任务?

  • 考点: 可重建索引、事实与派生数据。
  • 回答思路: 明确 Redis(远程字典服务)是派生层。
  • 详细答案: 不会前提是 MySQL(关系型数据库)任务表保存了应执行状态和执行时间。消费者或投递器写索引失败后重试,定时扫描还会根据任务表补齐。若只把任务放在 Redis(远程字典服务)而没有持久事实,故障就可能造成不可追溯的丢单。
  • 进阶追问: Redis(远程字典服务)重启后怎么快速恢复?
  • 进阶回答: 先按近期时间窗重建,再后台全量分批补齐;重建期间执行端仍先查 MySQL(关系型数据库)状态。

问题 27:如何设计业务幂等而不是只做消息幂等?

  • 考点: 状态机、唯一约束、外部副作用。
  • 回答思路: 幂等判断贴近业务结果。
  • 详细答案: 消息 ID(标识)去重只能处理同一消息重复,无法阻止不同消息触发同一退款或释放库存。应以支付流水、预占单号等业务键建唯一约束或条件更新,并把对外调用的请求号持久化。重复到达时返回已有结果,不再次产生资金或库存副作用。
  • 进阶追问: 外部支付方没有幂等接口怎么办?
  • 进阶回答: 先持久化调用意图和唯一请求号,超时后主动查单;在结果不明时不盲重试扣款。

kb:knowledge-10:线上排查、容量与演练

线上排查先看结果指标:到期滞后、processing(处理中)数量、重试率、DLQ(死信队列)增长、限流拒绝率和下游耗时;再定位在“未投递、未领取、执行慢、确认丢失、下游拒绝”哪一段。不要直接清空队列或扩容消费者,先保留证据并根据状态机决定是否重放。

flowchart TD
  A[发现延迟/拒绝告警] --> B{dueLagMs(到期滞后毫秒)升高?}
  B -- 是 --> C{ready(就绪)还是 processing(处理中)增长?}
  C -- ready(就绪) --> D[查轮询、分片、Lua(脚本语言)耗时]
  C -- processing(处理中) --> E[查下游耗时、可见性超时、Worker(工作线程)]
  B -- 否 --> F{429 增长?}
  F -- 是 --> G[查热点键、配额、降级开关]
  D --> H[按任务表对账补偿]
  E --> H
  G --> H
症状首看指标常见根因安全动作
到期滞后增长dueLagMs(到期滞后毫秒)消费不足、时钟异常限流领取、扩分片
processing(处理中)暴涨执行耗时、超时回收下游慢、卡死隔离下游、续期/回收
DLQ(死信队列)增长错误分类参数/签名/状态错误停止盲重试、修复后重放
429 增长热点键、令牌余量流量突发、额度过低聚合、排队、降级

数据演绎 10: 监控显示 ready(就绪)集合从 2 万升至 30 万、processing(处理中)稳定在 2,000、下游 P99(99 分位响应时间)(第 99 百分位)正常,说明问题在领取端而非执行端。继续扩大 Worker(工作线程)无效,应检查 Lua(脚本语言)失败率、分片消费者存活和时钟偏差。若 processing(处理中)从 2,000 升至 20 万且供应商超时增加,则应先下调领取并发、启动漏桶和降级,防止可见性超时造成重复风暴。

热门面试题

问题 28:延迟任务积压时为什么不能先清空 Redis(远程字典服务)?

  • 考点: 数据保全、状态机、补偿。
  • 回答思路: 队列是索引但仍是执行线索。
  • 详细答案: 清空会丢失尚未执行的调度线索,并让人工无法区分已成功、待执行与失败任务。应先冻结新投递或限速领取,导出任务标识和错误指标,再以 MySQL(关系型数据库)任务表为准重建或逐步重放。
  • 进阶追问: 索引已损坏怎么办?
  • 进阶回答: 保留损坏键做证据,创建新版本键并从事实表分批重建,完成对账后再按变更流程清理旧键。

问题 29:如何给延迟队列做容量规划?

  • 考点: 吞吐、积压时间、内存、恢复目标。
  • 回答思路: 从峰值到期率和下游容量反推。
  • 详细答案: 计算峰值到期速率、单任务内存、可接受滞后和故障恢复时间;消费者有效吞吐必须大于峰值并留冗余。还要单测/压测 Lua(脚本语言)批量耗时、评估复制与持久化开销,并为 DLQ(死信队列)预留审计存储。
  • 进阶追问: 只看 Redis(远程字典服务)内存够不够可以吗?
  • 进阶回答: 不够;真正瓶颈常在下游连接池、支付配额或数据库条件更新,需要端到端压测。

问题 30:故障演练至少覆盖哪些场景?

  • 考点: 自愈、重复、丢失、降级。
  • 回答思路: 按各故障窗口逐项验证。
  • 详细答案: 演练消费者宕机、Redis(远程字典服务)主从切换、MQ(消息队列)重复投递、MySQL(关系型数据库)提交后投递失败、供应商超时、时钟偏差和热点流量。每项都要验收到期任务不永久丢失、重复不产生业务副作用、DLQ(死信队列)可重放、指标和告警可定位。
  • 进阶追问: 演练如何避免影响真实资金?
  • 进阶回答: 使用影子任务、沙箱通道、只读查单和可回滚测试订单;生产演练应有开关、审批和明确停止条件。

题库使用方法

下面的项目题不是新的知识型小节,而是把前文机制转成面试表达。每题按六字段组织;你应先用“背景—设计—风险—结果”在两分钟内复述,再接受追问时展开一致性、可观测性与兜底。

10. 项目长答案题库

项目题 01:WMS(仓储管理系统)如何用延迟队列释放超时预占库存?

  • 问题: 请完整说明 WMS(仓储管理系统)下单预占后 15 分钟未支付自动释放库存的设计。
  • 考点: 延迟调度、库存状态机、MySQL(关系型数据库)条件更新、最终一致性。
  • 回答思路: 先区分 Redis(远程字典服务)调度与数据库事实,再讲支付和超时释放竞争。
  • 详细答案: 在WMS(仓储管理系统)的超时预占库存释放中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果支付回调在 15 分钟边界与释放任务同时到达,如何保证不超卖也不误释放?
  • 进阶回答: 两条链路都不能先读后写,而要用 MySQL(关系型数据库)事务中的条件更新或版本号比较。库存可售数变化与冻结记录状态变化必须同事务提交;支付方超时结果不明时先主动查单,不能因回调晚到就重新扣减。
  • 项目结果: 该设计将超时释放从单机定时扫描改为按到期任务领取,平峰延迟控制在秒级,同时保留数据库补偿与审计,面对重复消息和消费者宕机仍不产生库存重复释放。

项目题 02:WMS(仓储管理系统)大促热点 SKU(库存单位)怎么限流和防超卖?

  • 问题: 大促期间一个热点 SKU(库存单位)请求激增,你如何设计限流、排队和最终扣库存?
  • 考点: 热点分片、令牌桶、削峰、数据库一致性。
  • 回答思路: 入口先保护,核心再排队,最终由数据库条件更新确认。
  • 详细答案: 在WMS(仓储管理系统)大促热点 SKU(库存单位)下单中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: Redis(远程字典服务)预扣成功但数据库扣减失败怎么办?
  • 进阶回答: 把预扣视为可回滚的容量凭证,数据库失败后由可靠事件归还凭证;同时使用预占单对账重建缓存。不能因为缓存成功就向用户承诺下单成功。
  • 项目结果: 链路把洪峰限制在可计算的队列深度内,数据库始终是库存真相,既保护热点又避免 Redis(远程字典服务)故障或主从切换导致超卖。

项目题 03:跨境物流轨迹补偿为什么选择 ZSet(有序集合)而不是全表定时扫描?

  • 问题: 请讲一个跨境物流轨迹延迟查询和补偿的实现方案。
  • 考点: 延迟索引、轮询、外部接口配额、补偿扫描。
  • 回答思路: 用 ZSet(有序集合)缩小到期范围,用数据库保存轨迹事实,用定时任务防漏。
  • 详细答案: 在跨境物流运单的延迟轨迹查询中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如何避免同一运单被多个任务同时查询?
  • 进阶回答: Lua(脚本语言)只保证一次领取,业务层仍在 MySQL(关系型数据库)用状态版本或下一次查询时间的条件更新领取权;查询超时后通过可见性超时回收,而不是创建无界的新任务。
  • 项目结果: 数据库从周期性全表扫描转为处理实际到期项,供应商被按配额保护,轨迹数据可通过任务表和去重表完整回溯。

项目题 04:供应商 429 限流导致轨迹积压时如何止血?

  • 问题: 跨境物流供应商突然开始返回大量 429,你会怎么处置?
  • 考点: 限流反馈、退避、优先级、可观测性。
  • 回答思路: 立即尊重对方配额,降低领取,再按业务价值恢复。
  • 详细答案: 在跨境物流供应商持续返回 429 的恢复处置中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 为什么不简单扩大消费者线程数?
  • 进阶回答: 429 表明瓶颈在外部配额,扩线程只增加无效请求、连接占用和重试量。正确的并发上限应由下游能力而非本机 CPU(中央处理器)决定。
  • 项目结果: 处置重点是隔离与有序恢复:关键轨迹优先、普通任务降频、积压可解释,避免单一供应商问题扩散到所有物流链路。

项目题 05:IoT(物联网)报警风暴如何用限流和延迟聚合治理?

  • 问题: 一个设备组故障造成每秒数万条报警时,怎样保证告警不丢失又不淹没人员?
  • 考点: 去重聚合、滑动窗口、延迟任务、分级降级。
  • 回答思路: 原始事件可靠入库,通知按设备和规则聚合,升级由延迟队列控制。
  • 详细答案: 在IoT(物联网)设备组发生报警风暴中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 限流会不会把真正高危报警压掉?
  • 进阶回答: 风险分级必须先于限流:高危事件有独立配额和最小保底通道,低危事件才能聚合或静默;所有被抑制事件仍保留明细和审计计数。
  • 项目结果: 系统把“报警数”转成“可行动的告警会话”,在风暴中保护值班人员和通知通道,同时保持原始事件可追溯。

项目题 06:IoT(物联网)报警恢复与升级任务如何避免重复通知?

  • 问题: 告警持续、恢复、再次触发交错时,如何设计延迟升级和恢复通知?
  • 考点: 状态机、可见性超时、幂等、撤销语义。
  • 回答思路: 每个告警会话只有一个当前版本,延迟任务执行时必须校验版本与状态。
  • 详细答案: 在IoT(物联网)告警升级、确认与恢复交错中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 为什么不在恢复时直接删除 ZSet(有序集合)里的升级任务?
  • 进阶回答: 删除可以优化但不能当正确性前提:删除与领取存在竞态,且索引可能已丢失。以版本化状态机校验,才能安全处理已领取、重复和漏删的所有窗口。
  • 项目结果: 延迟任务变成“触发检查”而不是“无条件发通知”,恢复、确认和重试不会造成错误升级或重复骚扰。

项目题 07:Runner(执行器)如何实现租户公平与全局保护?

  • 问题: 多租户 Runner(执行器)平台中,一个大租户提交海量任务时如何不影响其他租户?
  • 考点: 分层限流、队列隔离、权重公平、调度一致性。
  • 回答思路: 先按租户隔离,再做全局容量保护,调度状态以数据库为准。
  • 详细答案: 在多租户 Runner(执行器)的任务公平调度中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 任务执行被公平分配,单租户高峰不会拖慢其他租户。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 08:Runner(执行器)任务超时和重复执行怎么处理?

  • 问题: 任务超过可见性超时后被重新领取,如何不重复产生外部副作用?
  • 考点: 租约、心跳、检查点、业务幂等。
  • 回答思路: 运行租约只用于故障恢复,真正防重依赖外部操作幂等和任务状态机。
  • 详细答案: 在Runner(执行器)任务超时并被重新领取中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 实例宕机可恢复,重复领取不会重复扣费或重复发布。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 09:支付回调为什么必须同步响应、异步处理和主动查单?

  • 问题: 请设计支付回调的可靠处理链路,并说明延迟队列的作用。
  • 考点: 回调幂等、快速响应、状态机、结果不明补偿。
  • 回答思路: 先确认接收,再异步落账,最后通过延迟查单处理不确定性。
  • 详细答案: 在支付回调、异步入账与主动查单中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 支付事件可追溯,任何未决状态都能被查单与对账收敛。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 10:支付撤销、退款和超时关闭如何避免资金状态打架?

  • 问题: 订单自动关闭、支付成功回调、退款申请并发发生时如何防止状态错乱?
  • 考点: 有限状态机、条件更新、补偿、对账。
  • 回答思路: 每次迁移带前置状态,外部动作先记录意图再查单确认。
  • 详细答案: 在支付关闭、支付成功与退款请求并发中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 每笔资金都有状态迁移、外部凭证与日终对账闭环。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 11:延迟队列任务取消如何设计才没有竞态?

  • 问题: 用户取消订单或任务被人工撤销后,如何取消已经在延迟队列里的任务?
  • 考点: 撤销与领取竞态、状态校验、版本化。
  • 回答思路: 删除索引只是优化,数据库取消状态才是最终判定。
  • 详细答案: 在订单或人工操作取消延迟任务中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 取消、领取和完成有确定语义,残留索引不会产生副作用。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 12:如何给延迟队列实现灰度发布与无损迁移?

  • 问题: 从旧键结构迁到新分片结构,怎样避免重复执行和漏执行?
  • 考点: 双写、双读、版本、对账、回滚。
  • 回答思路: 共享业务幂等键,按双写、影子消费、分片切流和回收推进。
  • 详细答案: 在延迟队列从旧键迁到新分片结构中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 迁移可量化对账并可分片回滚,不影响在途任务。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 13:WMS(仓储管理系统)拣货超时如何自动回收波次?

  • 问题: 仓库拣货员离线或未完成波次时,如何释放资源并防止重复分配?
  • 考点: 延迟释放、人员状态、库存冻结、版本控制。
  • 回答思路: 延迟任务只触发检查,波次表和库存冻结表决定是否回收。
  • 详细答案: 在WMS(仓储管理系统)拣货波次超时回收中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 超时资源可自动回收,人工恢复不会造成重复拣货。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 14:WMS(仓储管理系统)出库任务如何分片并限制下游面单服务?

  • 问题: 高峰出库时面单服务配额有限,调度如何避免任务堆积失控?
  • 考点: 供应商隔离、漏桶、重试死信、优先级。
  • 回答思路: 按仓库和面单供应商分片,领取速率由供应商实际配额决定。
  • 详细答案: 在WMS(仓储管理系统)出库面单任务高峰中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 高价值加急单优先,渠道故障被隔离且可审计重放。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 15:WMS(仓储管理系统)库存差异复核如何利用延迟任务?

  • 问题: 盘点差异出现后为什么不立即改库存,如何安排复核和补偿?
  • 考点: 延迟确认、人工复核、状态机、对账。
  • 回答思路: 先冻结差异,延迟触发复核;最终库存只由审核事务调整。
  • 详细答案: 在WMS(仓储管理系统)盘点差异的延迟复核中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 差异处理有时间窗口和完整审计,库存账不被异步任务直接篡改。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 16:跨境物流面单生成失败如何重试而不重复收费?

  • 问题: 外部面单接口超时后,如何判断重试、查单和死信?
  • 考点: 外部幂等、查单、退避、DLQ(死信队列)。
  • 回答思路: 先持久化请求号,超时先查单,确定失败才退避重试。
  • 详细答案: 在跨境物流面单接口超时与结果不明中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 面单创建至多一次语义由请求号和查单流程共同逼近。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 17:跨境物流状态通知如何限流并保证最终送达?

  • 问题: 大量轨迹变更同时产生时,如何保护通知渠道并保证用户最终看到状态?
  • 考点: 事件合并、通知幂等、渠道限流、补偿。
  • 回答思路: 轨迹事实先入库,通知是可重放的派生副作用。
  • 详细答案: 在跨境物流轨迹状态的大批量通知中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 用户不会被重复骚扰,最终状态可从轨迹表补发。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 18:IoT(物联网)告警静默窗口如何设计?

  • 问题: 维护期间如何静默低风险报警,同时不漏掉高风险异常?
  • 考点: 风险分级、时间窗、审计、动态配置。
  • 回答思路: 静默只影响通知,不影响原始事件采集与高危保底通道。
  • 详细答案: 在IoT(物联网)维护期的报警静默中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 维护期噪声可控,静默行为可追溯且不会损失安全底线。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 19:IoT(物联网)同一报警如何同时驱动多个下游?

  • 问题: 报警既要入库、通知、建工单又要进分析,怎样避免相互阻塞?
  • 考点: 事件扇出、MQ(消息队列)消费组、幂等、隔离。
  • 回答思路: 原始报警一次可靠发布,多消费组独立消费并按各自能力限流。
  • 详细答案: 在IoT(物联网)报警同时驱动通知、工单和分析中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 每个下游独立扩缩容,任何消费失败都有死信和补偿路径。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 20:Runner(执行器)周期任务遇到错过触发怎么补偿?

  • 问题: 服务停机两小时后,周期任务是补跑还是跳过,如何设计?
  • 考点: 错过策略、幂等、积压控制、业务分级。
  • 回答思路: 把错过策略作为任务元数据,按业务决定补跑、合并或跳过。
  • 详细答案: 在Runner(执行器)停机后的周期任务补偿中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 恢复过程有速率上限和审计,业务语义清晰可解释。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 21:Runner(执行器)依赖任务如何避免循环重试?

  • 问题: 上游未完成导致下游无法执行时,延迟队列如何安排重试?
  • 考点: 依赖状态、退避、死信、图校验。
  • 回答思路: 入队前校验依赖图,执行前再检查依赖状态,等待要有上限。
  • 详细答案: 在Runner(执行器)依赖任务长期未满足中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 依赖失败能定位根因,平台不被无效重试耗尽。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 22:支付日终对账如何与实时延迟补偿协同?

  • 问题: 实时回调和查单已经存在,为什么仍需要日终对账?
  • 考点: 最终一致性、差异单、补偿闭环、审计。
  • 回答思路: 实时链路追求低延迟,对账负责发现所有遗漏和异常终态。
  • 详细答案: 在支付实时链路与日终对账差异中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 资金准确性由实时收敛加批量核验双重保证。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 23:支付结算任务如何限流并防止重复出款?

  • 问题: 向商家批量结算时,怎样保护银行通道并确保每笔只出款一次?
  • 考点: 批量分片、幂等请求号、渠道限流、状态机。
  • 回答思路: 结算单是事实,出款请求是可重试动作,结果不明必须查单。
  • 详细答案: 在向商家批量支付结算中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 渠道容量被保护,出款结果可对账且不会因重试重复付款。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。

项目题 24:如何演练 Redis(远程字典服务)延迟队列和限流系统的灾难恢复?

  • 问题: 请给出一套不影响真实业务的演练设计。
  • 考点: 故障注入、可观测性、重建、回滚。
  • 回答思路: 用影子任务和沙箱下游覆盖故障窗口,验证不丢失、不失控、可恢复。
  • 详细答案: 在Redis(远程字典服务)调度与限流的灾难恢复演练中,Redis(远程字典服务)只做时间索引和限流,MySQL(关系型数据库)任务表、状态机和唯一键才是最终事实。创建业务时,我在同一个 MySQL(关系型数据库)事务写业务记录、任务记录与 Outbox(发件箱)事件;投递器至少一次发送 MQ(消息队列),消费者按业务标识幂等建立 ZSet(有序集合)索引。到期后 Lua(脚本语言)把任务从 ready(就绪)原子搬到 processing(处理中);工作线程先以状态和版本条件更新取得执行权,过期、取消或已完成的任务直接确认。成功则 ACK(确认);网络超时和限流按指数退避与 jitter(抖动)重排,参数错误和非法状态进入 DLQ(死信队列)。如果业务成功后消费者宕机,任务会被重领,但数据库唯一约束、幂等请求号和条件更新会吸收重复。Redis(远程字典服务)索引丢失或 MQ(消息队列)漏投时,扫描 MySQL(关系型数据库)待执行任务补建。运行时监控到期滞后、重试率、DLQ(死信队列)增长、限流拒绝率和下游 P99(99 分位响应时间)(第 99 百分位)耗时;异常先限速、隔离和降级,再按任务表对账重放,不通过清空队列掩盖问题。所有重放保留任务标识、错误分类和尝试次数,人工修复后仍走同一状态机,保证审计可追溯。并把关键指标接入告警,保障故障可快速定位和人工闭环。
  • 进阶追问: 如果业务成功后 Redis(远程字典服务)确认失败,系统怎样避免重复副作用?
  • 进阶回答: 这正是至少一次投递的正常窗口。不能依赖 ACK(确认)保证恰好一次,而要由业务唯一键、条件状态迁移和外部请求幂等号保障;重领任务时查询已有结果并补确认即可。
  • 项目结果: 团队拥有可重复的恢复剧本,知道每个指标对应的安全动作。同时,数据库/MQ(消息队列)一致性通过 Outbox(发件箱)、至少一次投递、消费者幂等和扫描补偿实现;Redis(远程字典服务)只保存可重建的派生索引。