惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Know Your Adversary
Know Your Adversary
阮一峰的网络日志
阮一峰的网络日志
V
Visual Studio Blog
H
Help Net Security
博客园 - Franky
博客园_首页
博客园 - 【当耐特】
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
腾讯CDC
人人都是产品经理
人人都是产品经理
T
Tailwind CSS Blog
博客园 - 三生石上(FineUI控件)
爱范儿
爱范儿
博客园 - 聂微东
小众软件
小众软件
宝玉的分享
宝玉的分享
美团技术团队
WordPress大学
WordPress大学
L
LINUX DO - 热门话题
S
Secure Thoughts
IT之家
IT之家
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
有赞技术团队
有赞技术团队
V
V2EX
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
量子位
G
GRAHAM CLULEY
Attack and Defense Labs
Attack and Defense Labs
Jina AI
Jina AI
罗磊的独立博客
Security Archives - TechRepublic
Security Archives - TechRepublic
W
WeLiveSecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Tenable Blog
L
Lohrmann on Cybersecurity
S
SegmentFault 最新的问题
J
Java Code Geeks
Last Week in AI
Last Week in AI
Cyberwarzone
Cyberwarzone
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
大猫的无限游戏
大猫的无限游戏
T
Tor Project blog
酷 壳 – CoolShell
酷 壳 – CoolShell
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
H
Heimdal Security Blog

重归混沌的BLOG

给silly实现了一个ernro模块 | 重归混沌的BLOG 给silly实现了一个ernro模块 | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 如何基于LanguageServerProtocol来编写lint工具 谈谈游戏服务器中RPC模块的设计 谈谈游戏服务器代码抽象 最近碰到的一个分布式一致性问题 谈谈游戏服务器的自动化测试 对Raft协议的一点理解 使用mmap来学习/proc/pid/smaps 2023(完) 再次实现了一个Lua性能分析器 终于给Silly的定时器增加了取消功能 一次虚拟内存排查经历 为silly增加了互斥锁 2022(完) Go语言之闭包篇 一例误用unsafe包引起的内存问题 Go语言之内存篇 初识Go语言 重新抽象图形API 给Lua实现了一个数学库 谈谈跨平台图形API的抽象 寻路和Flocking算法的结合 行为树的一种高效实现 内测过程中Shader出现的问题 彻底解决多国语言 谈谈数据库的选型 再谈Lua热更新(终) 初窥Rust 关于游戏服务器的服务拆分 ECS的初步实现 ECS初探 屏幕空间(SreenSpace)的想象力 一些对辐射度量学的理解 深度缓冲和半透明渲染 Mysql的间隙锁 更新一些GPU相关知识 2020 地形渲染之爬过的坑 Lua5.3 GC源码阅读(5) 实现一个数据库存储队列 再学计算机图形学入门 再谈分布式服务架构 游戏上线一个月后的反思 一次并发Bug 双向链表的三种实现 谈谈随机数的使用 再谈性能优化 2019 Lua中的函数式编程 重构登录逻辑 Unity资源管理(续) 谈谈Unity的资源管理 一次关于Cache的性能分析 历史之2018 DC3算法 移动平台native代码遭遇的坑 从CPU层面谈谈优化 开卷有益(UNIX编程艺术篇) GC竞争问题 通过Mesh投影来实现贴花系统 谈谈我对数据同步的理解 又一个类型提升引起的Bug Lua5.3 GC源码阅读(4) Lua5.3 GC源码阅读(3) Lua5.3 GC源码阅读(2) Lua5.3 GC源码阅读(1) 三角形光栅化时遇到的坑 一次git事故 再见2017 又一个lua调试器 客户端缓存落地方案 Paxos算法 HTTP服务器的特点 一次性能优化经历 关于CPU分支预测 C程序中让两个不同版本的库共存 实现了一个AOI模块 一个高可伸缩的游戏服务器架构 关于网络协议封装的一些新想法
游戏服务器分布式数据的一种同步的思路
重归混沌 · 2023-07-01 · via 重归混沌的BLOG

在游戏服务器的分布式设计中,我们通常避免将密切交互或存在数据强耦合的两个模块分别实现在不同的进程中。

然而,在某些情况下,我们不得不通过RPC在不同进程之间进行通信。例如,在处理玩家请求时,A进程可能需要与B进程进行资源扣除,并继续执行逻辑。

由于网络的不稳定性,A进程向B进程发起的RPC可能会有三种可能的结果:成功、失败或超时。

在分布式设计中,超时问题是最具挑战性的,因为超时可能是由于B进程未收到请求,也可能是B进程执行后的响应未被A进程接收到。

因此,在重试超时的RPC时必须非常小心,因为有可能这个RPC已经执行成功了,只是响应包丢失了而已,这样就会多次扣除玩家资源。

幸运的是,同一个游戏服务器的一组进程通常位于同一个子网下,并且在停服维护期间也是同生共死的。因此,RPC超时的概率通常不会很大,除非服务器超载。

在这种前提下,针对RPC的超时情况,一般的做法是在调用方和被调用方打上详细的日志。如果真的发生了超时,可以根据日志给玩家进行相应的补偿。


最近我遇到了一个有点棘手的需求,经过简化后的内容是这样的:

我们需要在"跨服"给玩家定期(大概几十秒到几百秒不等)产出一定的资源到"本地服",这个产出的资源需要玩家手动领取。在玩家没有领取之前,只能暂存N个周期的资源产出,到达暂存上限后就停止产出,直到玩家手动进行领取之后,再继续产出。

这个需求的棘手之处在于,在维护时,"跨服"的生命周期很大程度上和"本地服"不一样,也就是说数据包丢失必然会发生,被丢包的玩家必然会丢失这一周期产出的资源。

这种情况下,就不能只打印日志了,因为这是一种必然会发生的事件,而且影响范围是所有玩家,每次维护后手动处理是不能接受的。

经过仔细思考后,我认为要将"跨服产出到本地服"这个行为拆成两部分来实现。

第一步是先将资源产出到"跨服",这一步在进程内完成,如果跨服进程被关闭,就直接不会产出,下次启动后根据时间戳可以自动恢复出相应需要产出的资源。

第二步是将"跨服"进程中暂存的所有资源同步到"本地服"。同步数据包可能会丢失,但是由于每次总是同步当前玩家暂存的所有资源,所以重试这个同步操作是幂等的。

如果仅仅是产出重试这一问题,到这里问题基本上已经解决了。但是,现实往往是复杂的。

玩家领取资源时,还需要将产出的资源扣除以便"跨服"可以继续产出,而只要行为是"扣除",无论怎么设计这个RPC请求都不可能被重试。

所以需要进一步改进"跨服"产出逻辑。

"跨服"记录的不再是暂存的资源数量,而是从玩家参与这个玩法时到现在一共产出的"总"产出周期(TotalCycle)和"总"产出资源(TotalResource),这两个值都是只增不减的。

"本地服"同样记录收从"跨服"收到的最新的TotalCycle和TotalResource。

"本地服"每次收到"同步"包之后,根据同步包中的TotalCycle和TotalResource减去"本地服"中的TotalCycle和TotalResource,就能得出本次同步一共产出了多少周期和多少资源。再将本次同步产出的周期和资源合并到"本地服"的暂存周期数(StashCycle)和暂存产出资源数(StashResource)。

当玩家领取奖励时,"本地服"只需要将存储的TotalCycle和StashCycle同步到"跨服",跨服就可以知道当前还有多少周期的产出资源未被领取。具体公式为:(StashCycle + 跨服.TotalCycle – 本地服.TotalCycle)。

这样在玩家领取奖励时,从扣除暂存资源到加入玩家背包又是在进程内完成的,通知跨服可以继续产出的行为又变成了幂等的。


在整个问题的解决思路中,其实无非就是一点。将一些分布式行为拆分成本地行为+ "同步"行为(同步是指幂等的行为),在拆分过程中,"单调"会是一个很有用的武器。