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 -- 【跨服锁结束】

相关新闻

用一篇文章,帮你了解交互设计方法论「渐进式披露」

用一篇文章,帮你了解交互设计方法论「渐进式披露」

大家好。今天为大家分享的是「渐进式披露」。在设计中并不是给用户的选择越多越好,每一个额外的选择都会导致做决定所需的时间变长。 大家好,我是 Clippp。今天为大家分享的是「渐进式披露」。在设计中并不是给用户的选择越多越好,每一个额外…

2026/8/6 0:24:48 阅读更多 →
事件驱动架构(EDA)核心原理与实战优化

事件驱动架构(EDA)核心原理与实战优化

1. 事件驱动架构的本质与核心价值第一次接触事件驱动架构(EDA)是在2016年一个电商促销系统改造项目中。当时我们的单体应用在流量高峰时频繁崩溃,而引入基于事件的解耦方案后,系统吞吐量提升了8倍。这种架构范式与传统请求/响应模…

2026/8/5 23:50:52 阅读更多 →
如何免费解锁网易云音乐NCM格式:3分钟快速转换指南

如何免费解锁网易云音乐NCM格式:3分钟快速转换指南

如何免费解锁网易云音乐NCM格式:3分钟快速转换指南 【免费下载链接】ncmdump ncmdump - 网易云音乐NCM转换 项目地址: https://gitcode.com/gh_mirrors/ncmdu/ncmdump ncmdump是一款专业的网易云音乐NCM格式转换工具,能够快速解密网易云音乐的加密…

2026/8/5 19:06:00 阅读更多 →

最新新闻

计算机毕业设计之基于Spring Boot的同城宠物服务预约系统的设计与实现

计算机毕业设计之基于Spring Boot的同城宠物服务预约系统的设计与实现

随着新世纪无纸化办公方式的普及,自动化信息处理和基于网络的信息交互方式已被广泛应用。现在很多行业基本上都是交由计算机进行管理和测试,网络与计算机已成为整个线上管理体系中的重要组成部分。虽然信息技术广泛应用和数据存取更加方便,但…

2026/8/6 11:14:24 阅读更多 →
计算机毕业设计之基于spring boot的外卖平台小程序

计算机毕业设计之基于spring boot的外卖平台小程序

当前,由于人们生活水平的提高和思想观念的改变,然后随着经济全球化的背景之下,互联网技术将进一步提高社会综合发展的效率和速度,互联网技术也会涉及到各个领域,于是传统的管理方式对时间、地点的限制太多,…

2026/8/6 11:14:24 阅读更多 →
终极网盘直链下载解决方案:九大平台一键获取,告别限速烦恼

终极网盘直链下载解决方案:九大平台一键获取,告别限速烦恼

终极网盘直链下载解决方案:九大平台一键获取,告别限速烦恼 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国…

2026/8/6 11:14:24 阅读更多 →
2026Java八股文(通用版)1000+题附答案,一篇就够!

2026Java八股文(通用版)1000+题附答案,一篇就够!

的面试即将到来,大家准备的怎么样了呢?你有没有正在为此而发愁呢?那么一起来看看小编整理的这富含的 15 个互联网大厂 Java 高级工程师核心面试问题整理吧!内容包括:Java 集合 22 题及答案解析JVM 与调优 21 题及答案解…

2026/8/6 11:14:24 阅读更多 →
Unity到Godot 4.0迁移实战:Vulkan渲染管线与独立开发者的轻量化转型

Unity到Godot 4.0迁移实战:Vulkan渲染管线与独立开发者的轻量化转型

1. 项目概述:为什么从Unity转向Godot 4.0? 作为一名在Unity生态里泡了快十年的独立开发者,我最近做了一个挺大的决定:把正在开发的一个3D项目,从Unity引擎整体迁移到了Godot 4.0。这个决定不是一时冲动,也不…

2026/8/6 11:14:24 阅读更多 →
基于AI大语言模型的Unity游戏实时翻译方案:XUnity.AutoTranslator深度优化指南

基于AI大语言模型的Unity游戏实时翻译方案:XUnity.AutoTranslator深度优化指南

1. 项目概述:为什么你需要一个“游戏翻译神器”? 如果你是一名Unity游戏开发者,或者是一位热衷于体验全球独立游戏的玩家,那么“语言不通”这个问题,大概率是你绕不开的痛点。开发者希望自己的作品能被更多地区的玩家理…

2026/8/6 11:13:23 阅读更多 →

日新闻

深入解析LimboAI C++内核:架构设计与性能优化实战

深入解析LimboAI C++内核:架构设计与性能优化实战

1. 项目概述:为什么我们需要深入LimboAI的C内核?如果你是一名使用Godot引擎的游戏开发者,尤其是对AI行为逻辑有较高要求的项目,那么LimboAI这个名字你大概率不会陌生。它作为Godot 4生态中一个备受瞩目的行为树与状态机插件&#…

2026/8/6 0:00:06 阅读更多 →
Unity 2D游戏敌人AI系统:基于PlayMaker状态机与2D Toolkit的实战开发

Unity 2D游戏敌人AI系统:基于PlayMaker状态机与2D Toolkit的实战开发

1. 项目概述与核心思路大家好,我是老张,一个在游戏开发一线摸爬滚打了十多年的老码农。今天咱们接着聊《空洞骑士》风格2D动作游戏的Demo制作。上一期我们搭好了基础框架,处理了角色移动和碰撞,这一期,我们要让游戏世界…

2026/8/6 0:00:06 阅读更多 →
被动防火门市场前景发展趋势

被动防火门市场前景发展趋势

被动防火门依靠材质结构、密闭构造阻隔烟火蔓延,无需电控启动,是建筑被动消防系统核心构件,行业依托新规管控、城市更新、工业安全升级迎来稳定扩容,整体朝着合规化、专项化、低碳化、智能化方向发展。现阶段 GB12955‑2024 新版国…

2026/8/6 0:00:06 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/5 15:00:43 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/5 13:13:56 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/5 10:20:36 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/5 23:28:39 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/5 21:00:14 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/5 23:46:51 阅读更多 →