集群环境下Redis读写多线程竞态条件无锁解决方案问询
解决方案
核心思路是利用Redis单线程执行命令的原子性,将「判断列表是否为空」和「写入初始值」两个操作合并为单个原子操作,完全规避竞态条件,无需加锁也不会产生线程阻塞。
方案1:原生LPUSH命令实现(无额外依赖)
Redis的LPUSH命令执行后会原子返回操作完成后列表的总长度,基于这个特性可以直接实现逻辑:
// 所有线程统一先执行写入操作 int currentListLen = lpush(key, value); if (currentListLen > 1) { // 列表此前已有内容,当前线程写入的是冗余值,直接删除即可 lpop(key); // 执行原if分支的业务逻辑 } else { // 当前线程是第一个写入的线程,执行原else分支的业务逻辑 }
优势
- 完全基于Redis原生命令,不需要引入额外依赖,兼容性极强
- 所有操作都是非阻塞的,调用后立即得到结果,不需要等待其他线程执行
- 天然适配集群环境,单key操作不需要处理跨节点问题
方案2:Lua脚本实现(逻辑更直观)
如果希望逻辑更清晰,也可以把判断+写入逻辑封装为Lua脚本,Redis会以原子方式执行整个脚本,不会被其他请求打断:
-- 入参:KEYS[1]为列表key,ARGV[1]为要写入的初始值 local listLen = redis.call('LLEN', KEYS[1]) if listLen == 0 then redis.call('LPUSH', KEYS[1], ARGV[1]) return 0 -- 标记:执行了写入操作,走原else分支 else return 1 -- 标记:列表已有内容,走原if分支 end
业务代码直接调用该Lua脚本,根据返回值执行对应分支逻辑即可。
优势
- 逻辑直观易懂,后续维护成本低
- 不需要额外处理冗余数据,写入操作只会执行一次
两种方案都不需要使用任何锁机制,也不依赖本地同步逻辑,完全满足集群环境的使用要求,也不会出现线程等待的问题。
内容的提问来源于stack exchange,提问作者Mega Mbo
相关产品推荐
相关产品推荐

