如何妥善处理重试Worker引发的竞态条件问题
缓存重试与删除的竞态问题解决方案
针对你遇到的缓存重试写入覆盖删除操作的竞态问题,给你几个实用的解决思路:
1. 给缓存操作加版本号/墓碑标记
给每个写入缓存的请求绑定一个时间戳或递增版本号,写入时把值和版本号一同存在缓存中(比如用Hash结构存储value: A, version: 123456):
- 用户发起删除请求时,无论缓存是否存在,都写入一个墓碑标记:
tombstone: true, tombstone_time: 789012(时间戳需晚于写入请求的创建时间)。 - Worker执行重试写入前,先读取缓存当前状态:若发现墓碑标记的时间戳晚于重试请求的创建时间,直接放弃写入;若无墓碑或墓碑时间更早,再执行写入。
这种方式能确保旧的重试请求不会覆盖新的删除操作。
2. 用元数据记录删除状态
单独维护一个元数据存储(比如Redis的Hash表、业务数据库的小表),专门记录缓存键的删除事件:
- 用户执行删除操作时,不管缓存有没有值,都在元数据里给该缓存键打上删除标记,并设置一个过期时间(时长等于重试队列的最大超时时间即可)。
- Worker重试写入前,先查询元数据:若该缓存键有未过期的删除标记,直接终止重试;无标记再执行写入。
这个方案无需修改缓存的存储结构,元数据可独立管理,过期后自动清理,不会占用过多资源。
3. 优化重试触发逻辑,增加前置检查
调整之前“所有写入失败直接入队重试”的逻辑,在入队或重试执行前加一步前置校验:
- 当写入缓存超时失败后,先快速发起1-2次GET请求检查缓存状态(若GET成功,就能明确缓存当前是否存在)。
- 若GET发现缓存键不存在,说明已被删除,直接放弃重试;若GET能拿到值,或GET也超时(避免误判),再进入重试队列。
注意:GET请求也可能超时,最多尝试2次即可,避免因GET超时导致不必要的重试。
4. 给删除操作加延迟生效机制
用户发起删除请求时,不直接删除缓存,而是给缓存设置一个短时间的过期时间(比如30秒),同时记录该删除事件:
- Worker重试写入时,先检查缓存的剩余过期时间:若发现缓存处于即将过期状态(或有删除事件记录),直接放弃写入。
- 延迟时间过后,再让缓存自动过期或执行真正的删除操作。延迟时长要设置得比重试队列的平均处理时间长,确保所有旧的重试请求都能在延迟期内完成状态检查。
为什么不建议删除无记录时抛错误?
删除操作本身是幂等的,“无记录可删”属于正常结果,强行抛错误会导致删除请求被重试队列反复处理,既增加系统负载,还可能引发连锁问题(比如用户端收到多次错误提示),完全没必要。
内容的提问来源于stack exchange,提问作者Farhad
相关产品推荐
相关产品推荐

