如何处理Redis Key的并发更新?多系统进程完成通知场景求助
嘿,这个场景我之前帮朋友的团队落地过,用Redis确实是非常合适的选择,结合你的多线程架构,我给你整理几个实用的方案和注意点:
方案一:用Hash结构追踪每个子进程状态
这种方式适合你需要明确知道每个子进程状态的场景:
- 以你的标识符作为Redis Hash的Key,每个子进程的唯一标识(比如发送系统ID+进程ID)作为Hash的Field,状态值设为
"completed" - 收到进程完成事件时,执行
HSET {identifier} {unique_process_id} "completed"(重复执行也没关系,幂等性有保障) - 提前把该标识符对应的总进程数存在单独的Key里,比如
{identifier}:total - 核心的判断逻辑要用Lua脚本原子执行,避免多线程竞态:
local hash_key = KEYS[1] local total_key = KEYS[2] local notified_key = KEYS[3] -- 统计已完成的进程数 local completed_count = redis.call('HLEN', hash_key) local total_count = tonumber(redis.call('GET', total_key)) -- 判断是否全部完成且未发送过通知 if completed_count == total_count and redis.call('GET', notified_key) ~= "1" then redis.call('SET', notified_key, "1", "EX", 86400) -- 标记已通知,过期1天 return 1 -- 返回1触发通知逻辑 else return 0 end
- 你的多线程服务调用这个脚本后,若返回1就执行通知操作即可
方案二:原子计数器+阈值判断
如果不需要追踪单个进程状态,只关心整体完成情况,这个方案更轻量:
- 提前为标识符设置总进程数Key:
SET {identifier}:total {num_of_processes} - 每个进程完成时,调用下面的Lua脚本(原子更新计数+判断):
local completed_key = KEYS[1] local total_key = KEYS[2] local notified_key = KEYS[3] -- 原子递增完成计数 local completed = redis.call('INCR', completed_key) local total = tonumber(redis.call('GET', total_key)) -- 避免重复通知+判断全部完成 if completed == total and redis.call('GET', notified_key) ~= "1" then redis.call('SET', notified_key, "1", "EX", 86400) return 1 else return 0 end
- 这里要注意:如果同一个进程可能重复发送完成事件,需要先加一层判断(比如用
SETNX标记该进程已完成),避免计数错误,优化后的脚本如下:
local process_mark_key = KEYS[1] local completed_key = KEYS[2] local total_key = KEYS[3] local notified_key = KEYS[4] -- 先判断该进程是否已经处理过,避免重复计数 if redis.call('GET', process_mark_key) == "done" then return 0 end -- 标记进程已处理 redis.call('SET', process_mark_key, "done", "EX", 86400) local completed = redis.call('INCR', completed_key) local total = tonumber(redis.call('GET', total_key)) if completed == total and redis.call('GET', notified_key) ~= "1" then redis.call('SET', notified_key, "1", "EX", 86400) return 1 else return 0 end
必加的几个注意点
- 避免重复通知:一定要加
{identifier}:notified这类标记Key,一旦触发通知就设置它,后续所有事件过来先检查这个Key,存在就直接跳过 - 内存清理:给所有相关的Redis Key设置合理的过期时间(比如任务完成后保留1天),避免Redis内存被无效数据占满
- 幂等性保障:不管用哪个方案,都要确保同一个进程的重复完成事件不会影响最终判断,比如Hash的HSET是幂等的,计数器方案要加进程标记
- 多线程安全:所有涉及“判断+更新”的逻辑必须用Lua脚本,因为Redis会原子执行整个脚本,完全避免多线程并发带来的竞态问题
内容的提问来源于stack exchange,提问作者kane.zorfy
相关产品推荐
相关产品推荐

