日期:2026-09-27
标签:#面试 #场景设计 #Redis
难度:中等
来源:牛面场景题
答案说明:独立整理(站内题目标记为 VIP,未读取会员答案)
一句话答案
租约应覆盖可预期的执行、暂停与网络抖动,同时限制故障后的等待;没有可靠耗时上界时用有身份校验的受控续期,并在业务资源侧防止旧执行者写入。
面试口语版(约 60 秒)
我不会套固定秒数。先采集从获锁到最后一次受保护写入的耗时分布,拆出数据库、外部调用、重试、进程暂停和网络抖动,再结合故障恢复能等待多久设初始 TTL。TTL 太短,A 还未完成就过期,B 会进入;太长,A 宕机后 B 等待较久。长任务若需续期,必须在 Redis 中原子比对本次令牌并延长过期时间,且设置业务总超时和停止条件。即使有续期,长时间暂停、网络分区或切主仍可能让旧执行者失去锁;严格顺序的关键写入还要让权威资源校验单调栅栏令牌,普通版本检查不一定足够。
如何确定与验证
- 测量:统计完整持锁时长,不只测函数本体;包含依赖调用、重试及异常清理。演练尾部延迟和暂停。
- 取舍:在业务可接受的故障等待上限内覆盖正常耗时及合理余量。若两者无法兼顾,拆小工作段、缩短依赖超时,或受控续期。不存在通用的最佳 TTL。
- 续期:任务仍在运行且令牌匹配时,用原子脚本执行
PEXPIRE;失败、超时或结果不确定时停止新增工作,重新确认所有权。不能盲目或无限续期。 - 验证:模拟进程崩溃、Redis 不可达、外部调用卡住与进程暂停,检查重复执行及下游拒绝旧写入的效果。
on_acquired(key, token, lease, business_deadline, business_task):
start business_task and renewal_watchdog concurrently
renewal_watchdog:
while business_task is running and before_business_deadline:
wait_until(next_renewal_time) # 定时等待,不忙循环
if business_task is finished: break
result = atomic_if_token_matches_then_pexpire(key, token, lease)
if result != success:
mark_lock_lost()
cancel_remaining_work()
break
try:
wait for business_task to finish or be cancelled
finally:
stop and join renewal_watchdog
atomic_if_token_matches_then_delete(key, token)
伪代码中的提前量、总时长及续期次数需按实际延迟决定。条件删除只避免误删新锁,不能撤销已经写出的副作用。
失败分支与取舍
A 获锁后调用第三方接口,进程暂停超过租约;B 获锁并完成更新;A 恢复继续写库。即使 A 此时续期失败,旧写仍可能落地。严格按获锁顺序保护的关键状态应由权威资源比较单调栅栏令牌;普通行版本条件可以防丢失更新,却不保证在 B 尚未写入时拒绝 A。栅栏生成源本身也必须保证单调。
- 短租约恢复快,但更易误过期;长租约相反。
- 续期减少正常长任务误过期,却增加定时器、网络与停止逻辑的复杂度,不能证明无限期持有锁。
- 观察持锁时长分布、剩余 TTL、续期失败、任务超租约、获取等待和业务版本冲突。
面试官递进追问
- 为什么不能把 TTL 直接设为接口平均耗时?
- 续期请求超时但 Redis 实际执行成功,如何处理结果不确定?
- 旧执行者丢锁后仍写数据库,数据库如何识别并拒绝?
自测
- 不看笔记,60 秒讲清短租约、长租约、续期的代价。
- 为含外部 RPC 的任务列出要采集的时间数据和要演练的故障。
参考资料
资料核对日期:2026-09-27。