当前位置: 首页 > news >正文

redis跨服分布式全局锁

问题描述,在玩家切换场景之前,我存了一个坐标。在玩家再次切换场景的时候,我取出上次存的坐标,居然不是我存的坐标 然后看log save_last_scene 10007500009521 101 43 57 ------remsg--------- { ["scene_id"] = 101, ["pos"] = { ["x"] = 37, ["y"] = 66, }, ["reconnect"] = false, } last_x 和 last_y不是保存的值 看起来是redis保存失败了? 进程 logic function Oper.User.SetGameInfo(userid, game_info) if(not game_info)then return end -- 更新redis local user_cache = redis_wraper.wrap_table('user_info_cache','userid') local userinfo = user_cache:select(userid) userinfo.game_info = game_info userinfo.room_id = game_info.room_id user_cache:save(userid, userinfo) player_mgr.set(userid, "game_info",game_info) end 进程 scene function CMD.save_last_scene(userid, scene_id, x, y) local t = redis_wraper.wrap_table("user_info_cache","userid") local userinfo = t:select(userid) userinfo.last_scene_id = scene_id userinfo.last_x = x userinfo.last_y = y t:save(userid, userinfo) print("save_last_scene",userid,scene_id,x,y) end local function build_enter_scene_remsg(mbr) local userid = mbr.userid local user_info_cache = proxy.call_game(nil, nil, 'get_user_info_cache', userid) or {} if user_info_cache.last_scene_id and user_info_cache.last_scene_id == g_data.scene_id then mbr.cur_pos.x = user_info_cache.last_x mbr.cur_pos.y = user_info_cache.last_y end local remsg = { scene_id = g_data.scene_id, pos = mbr.cur_pos, reconnect = mbr.reconnect, state = g_data.state } mbr.reconnect = nil --重连标记是一次性的,用完清空 -- 战斗ID相关逻辑 if mbr.battleId then local battleRecord = Handler.Battle.GetBattle(mbr.battleId) if battleRecord then for _, battleUserId in pairs(battleRecord.userList or {}) do if battleUserId == userid then remsg.battleId = mbr.battleId break end end end end print("------remsg---------",tostring(remsg)) return remsg end 玩家切换场景回先通知 logic 调用函数 SetGameInfo 然后通知 scene调用 save_last_scene。 理论上如果是一个地方存就没问题,但是我们先不讨论设计的问题,先讨论保存2次出现bug. 其实就是要加锁,redis一样存在不同进程同时操作同一个数据的问题,修改会失败。 下面是修改后的 function Oper.User.SetGameInfo(userid, game_info) if(not game_info)then return end -- 更新redis(整条读写包进跨服全局锁,和 save_last_scene 串行化,避免互相覆盖 last_x/last_y) local lock_ok = redis_wraper.redis_global_lock(userid, function() local user_cache = redis_wraper.wrap_table('user_info_cache','userid') local userinfo = user_cache:select(userid) logger.fmt.info("[set_game_info_lock] GOT_LOCK userid:%s read_last_x:%s read_last_y:%s -> set game_info.scene_id:%s", tostring(userid), tostring(userinfo and userinfo.last_x), tostring(userinfo and userinfo.last_y), tostring(game_info and game_info.scene_id)) userinfo.game_info = game_info userinfo.room_id = game_info.room_id user_cache:save(userid, userinfo) logger.fmt.info("[set_game_info_lock] SAVED userid:%s keep_last_x:%s keep_last_y:%s", tostring(userid), tostring(userinfo.last_x), tostring(userinfo.last_y)) end) logger.fmt.info("[set_game_info_lock] DONE userid:%s lock_ok:%s", tostring(userid), tostring(lock_ok)) player_mgr.set(userid, "game_info",game_info) end function CMD.save_last_scene(userid, scene_id, x, y) logger.fmt.info("[save_last_scene_lock] REQ userid:%s scene_id:%s x:%s y:%s", tostring(userid), tostring(scene_id), tostring(x), tostring(y)) local lock_ok = redis_wraper.redis_global_lock(userid, function() local t = redis_wraper.wrap_table("user_info_cache","userid") local userinfo = t:select(userid) logger.fmt.info("[save_last_scene_lock] GOT_LOCK userid:%s read_last_x:%s read_last_y:%s -> write x:%s y:%s", tostring(userid), tostring(userinfo and userinfo.last_x), tostring(userinfo and userinfo.last_y), tostring(x), tostring(y)) userinfo.last_scene_id = scene_id userinfo.last_x = x userinfo.last_y = y t:save(userid, userinfo) end) logger.fmt.info("[save_last_scene_lock] DONE userid:%s lock_ok:%s", tostring(userid), tostring(lock_ok)) print("save_last_scene",userid,scene_id,x,y) end 加锁函数 -- ====================== 【跨服分布式全局锁】逻辑服/游戏服共用,防止并发脏写 user_info_cache ====================== -- 作用:以 userid 为维度,跨服/多机 原子串行化 Redis 整条 read-modify-write -- 用法:redis_wraper.redis_global_lock(userid, function() ... end) local REDIS_LOCK_PREFIX = "redis:global:lock:" local LOCK_EXPIRE = 300 -- 0.3秒自动释放:临界区是 Redis 整条读写(亚毫秒),0.3s足够;缩短崩溃恢复窗口 local LOCK_RETRY = 5 -- 瞬时竞态下(另一服正持有)最多重试次数,避免输家直接跳过写入 local LOCK_RETRY_TICK = 1 -- 每次重试间隔(skynet tick, 1 tick≈10ms),5次≈50ms 远小于 300ms TTL function redis_wraper.redis_global_lock(userid, func) local key = REDIS_LOCK_PREFIX .. tostring(userid) local uuid = skynet.self() .. ":" .. skynet.now() local conn = redis_wraper.get_raw_conn(userid) -- 原子加锁 (SET key value NX PX 毫秒),失败时短暂退避重试 local acquired = false for _ = 1, LOCK_RETRY do local ok = conn:set(key, uuid, "NX", "PX", LOCK_EXPIRE) if ok then acquired = true break end skynet.sleep(LOCK_RETRY_TICK) end if not acquired then logger.error("[RedisLock] 获取Redis全局锁失败 userid=" .. tostring(userid)) return nil end -- 执行业务逻辑(整条读改写都在临界区内) local suc, err = pcall(func) if not suc then logger.error("[RedisLock] Redis操作失败 userid=" .. tostring(userid), err) end -- 原子解锁 (Lua脚本,只有持有者能释放,防误删) local script = [[ if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) end return 0 ]] conn:eval(script, 1, key, uuid) return true end -- ====================== 【跨服锁结束】======================
http://www.jsqmd.com/news/1325201/

相关文章:

  • 基于MediaPipe与Unity3D的实时手部追踪与交互开发指南
  • RabbitMQ常见知识点总结
  • Omron C200PC-ISA03-1 印刷电路板
  • 二叉搜索树中第k小的元素
  • Unity网络通信中Curl error 60的根源分析与安全解决方案
  • Python实战进阶:从环境配置到量化交易开发
  • B站视频下载终极指南:三步解锁大会员4K高清视频
  • STM32 U盘模式IAP实现:基于HAL库的固件升级方案详解
  • 战略撤退决策框架:识别时机与执行路径
  • 技术成长:从执行到思考的认知跃迁与工程实践
  • Wi-Fi天线原理与实战调优:从增益、极化到MIMO,彻底改善信号质量
  • RocketMQ 的“全局画面
  • 3步解锁网易云音乐:让加密NCM文件重获播放自由
  • 软件公司生存策略:火箭模式与印钞机模式解析
  • Vue3+Vite项目集成Unity WebGL:解决路径与构建配置的完整指南
  • 智能客服Agent的“知识焦虑”与RAG破局之道
  • 静态时序分析实战:从建立/保持时间到时钟偏斜的完整计算与优化
  • 如何把开题报告设计成可复核的研究工作流
  • Java枚举类深度解析:原理、应用与性能优化
  • AI编程时代:智能体框架和基础模型,到底谁更重要?
  • OpenClaw智能体框架下提示词注入的纵深防御体系构建
  • 从零构建纯净Win10 PE:定制化系统维护环境的完整指南
  • 软件测试能力构建:自动化、安全与性能测试的实战融合指南
  • 从流量监控到样本仿真:构建主动防御的应急响应闭环
  • 中国历史上古到新中国成立历史大事表
  • 毫米波技术解析:从物理特性到5G、雷达与工业应用实战
  • NHSE终极指南:3步掌握动物森友会存档编辑器,轻松实现岛屿改造与村民管理
  • 内容重发布实验:提升数字营销效果的系统方法
  • Windows Server 2008 R2打印服务器搭建与客户端部署全指南
  • 第五届智能机械与人机交互技术国际学术会议(IHCIT 2026)