Redis缓存并发更新致数据状态错误的解决方案咨询
解决Redis游戏房间分数并发更新覆盖问题
你的问题核心是全量替换整个房间对象导致的并发更新覆盖,本质是没有利用Redis的原子操作特性做局部更新。以下是几种直接可行的修复方案:
方案1:改用Redis Hash结构存储玩家分数
放弃将整个房间对象作为单个字符串存储,改用Redis Hash类型拆分存储:
- 以房间ID作为Hash的Key(比如
room:xxxxxx) - 每个玩家ID作为Hash的Field,分数对应Field的Value
更新分数时直接用原子命令HSET修改单个玩家的分数,完全避免全量覆盖:
# 初始化房间分数 HSET room:xxxxxx player1 0 player2 0 # 玩家1更新分数(原子操作) HSET room:xxxxxx player1 1 # 玩家2更新分数(原子操作) HSET room:xxxxxx player2 1
获取房间数据时,用HGETALL room:xxxxxx拿到所有玩家分数,再和房间其他固定信息组装成需要的结构即可。
优点:实现简单,性能最高,天然支持并发安全;缺点:需要调整现有数据存储结构。
方案2:用Redis事务+WATCH实现乐观锁
如果必须保留整个JSON对象的存储方式,可以通过WATCH命令监控房间Key,结合事务实现乐观锁:
# 1. 监控房间Key,开启乐观锁 WATCH room:xxxxxx # 2. 获取当前房间数据 GET room:xxxxxx # 3. 本地解析JSON,修改对应玩家分数 # (业务代码处理,比如把player1的分数改成1) # 4. 开启事务,提交更新 MULTI SET room:xxxxxx '{"roomId":"xxxxxx", "score":[{"player1":1},{"player2":0}]}' EXEC
- 如果在WATCH到EXEC之间,该房间Key被其他客户端修改,
EXEC会返回nil,代表事务执行失败,此时需要重新执行整个流程(重试)。 - 执行成功后,再通过你的pub/sub机制发布更新通知。
优点:无需大幅调整存储结构;缺点:需要处理重试逻辑,高并发下冲突概率高时会影响性能。
方案3:用Redis Lua脚本实现原子更新
利用Redis Lua脚本的原子性,把"读取-修改-写入"整个流程放在Redis端原子执行:
编写Lua脚本(假设Redis已启用cjson模块):
local room_key = KEYS[1] local player_id = ARGV[1] local new_score = ARGV[2] -- 获取当前房间数据 local room_data = redis.call('GET', room_key) if not room_data then return nil end -- 解析JSON并更新分数 local parsed = cjson.decode(room_data) -- 根据你的score数组结构调整,找到对应玩家的对象修改 for _, player in ipairs(parsed.score) do if player[player_id] then player[player_id] = tonumber(new_score) break end end -- 写回Redis redis.call('SET', room_key, cjson.encode(parsed)) return parsed
调用脚本:
EVAL "上面的Lua脚本内容" 1 room:xxxxxx player1 1
Lua脚本在Redis中是单线程原子执行的,不会被其他请求打断,完全避免并发覆盖问题。
优点:灵活支持复杂的更新逻辑,无需客户端处理重试;缺点:需要维护Lua脚本,调试相对复杂。
内容的提问来源于stack exchange,提问作者DRE
相关产品推荐
相关产品推荐

