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/9/28 1:09:07 阅读更多 →
事件驱动架构(EDA)核心原理与实战优化

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

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

2026/9/29 16:32:29 阅读更多 →
如何免费解锁网易云音乐NCM格式:3分钟快速转换指南

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

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

2026/10/7 7:18:27 阅读更多 →

最新新闻

VFS核心对象inode深度剖析:元数据、生命周期与inode耗尽排查

VFS核心对象inode深度剖析:元数据、生命周期与inode耗尽排查

在Linux内核里,如果只允许挑一个结构体来代表“文件系统”,那一定非inode莫属。它既不像dentry那样只负责路径和名字映射,也不像file那样随每次open而来、close而去;inode是整个VFS层的中枢对象,一头连着磁盘上的元数据…

2026/10/10 21:03:48 阅读更多 →
三年后还能一键重跑:OpenResearch 的可复现性是怎样炼成的

三年后还能一键重跑:OpenResearch 的可复现性是怎样炼成的

三年后还能一键重跑:OpenResearch 的可复现性是怎样炼成的 【免费下载链接】OpenResearch Turn your coding agents into research agents 项目地址: https://gitcode.com/GitHub_Trending/op/OpenResearch 科研可复现性向来是领域内最沉重的话术之一&#x…

2026/10/10 21:03:48 阅读更多 →
C# DirectX导入三维动画:Assimp解析、骨骼蒙皮与渲染管线实战

C# DirectX导入三维动画:Assimp解析、骨骼蒙皮与渲染管线实战

简介:面向C#开发者的DirectX三维动画导入与渲染学习资源,聚焦在Visual Studio 2017环境下借助C/CLI中间层调用DirectX原生接口,解决托管代码与底层图形库协作的核心难题,适合希望系统掌握三维图形编程、动画处理与渲染管线的中高级…

2026/10/10 21:03:48 阅读更多 →
螺旋线代码怎么写:Python+matplotlib绘制阿基米德螺旋线

螺旋线代码怎么写:Python+matplotlib绘制阿基米德螺旋线

今天想聊一个很多初学者都会卡住的题目:螺旋线代码怎么写。准确地说,是一道典型的数学绘图例题:用代码画出一条漂亮的阿基米德螺旋线。有一次我看到有人把自己写的螺旋线代码贴出来,说照着例题敲完运行出来的却是一堆散点&#xf…

2026/10/10 21:03:48 阅读更多 →
传奇M2客户端C++源码解析:从封包到渲染的模块拆解

传奇M2客户端C++源码解析:从封包到渲染的模块拆解

简介:这套Mir_m2客户端C源码压缩包面向传奇类游戏引擎研究者、客户端程序员与具备C基础的进阶学习者,可用于对照理解经典网游客户端的组织方式:从服务器-客户端交互、界面逻辑到游戏循环都有对应实现可查。包体约613KB,共187个文件…

2026/10/10 21:03:48 阅读更多 →
PCA9422+STM32F446ZE构建高可靠嵌入式电源管理子系统

PCA9422+STM32F446ZE构建高可靠嵌入式电源管理子系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 21:02:47 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →