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

推荐订阅源

T
Tenable Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
V
Vulnerabilities – Threatpost
G
GRAHAM CLULEY
Simon Willison's Weblog
Simon Willison's Weblog
C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Privacy International News Feed
H
Heimdal Security Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
MyScale Blog
MyScale Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
V
Visual Studio Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
L
LINUX DO - 最新话题
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Cloudflare Blog
美团技术团队
Recorded Future
Recorded Future
T
Tailwind CSS Blog
Latest news
Latest news
Security Archives - TechRepublic
Security Archives - TechRepublic
Security Latest
Security Latest
Know Your Adversary
Know Your Adversary
Cloudbric
Cloudbric
Schneier on Security
Schneier on Security
I
Intezer
L
LINUX DO - 热门话题
P
Palo Alto Networks Blog
云风的 BLOG
云风的 BLOG
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Vercel News
Vercel News
Attack and Defense Labs
Attack and Defense Labs
人人都是产品经理
人人都是产品经理
L
LangChain Blog
爱范儿
爱范儿
博客园 - 三生石上(FineUI控件)
博客园 - 叶小钗
L
Lohrmann on Cybersecurity
S
SegmentFault 最新的问题
W
WeLiveSecurity
C
Cybersecurity and Infrastructure Security Agency CISA
S
Securelist
SecWiki News
SecWiki News
V2EX - 技术
V2EX - 技术
IT之家
IT之家
Cyberwarzone
Cyberwarzone
F
Full Disclosure
Spread Privacy
Spread Privacy
阮一峰的网络日志
阮一峰的网络日志

重归混沌的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模块 一个高可伸缩的游戏服务器架构 关于网络协议封装的一些新想法
最近碰到的一个分布式一致性问题
重归混沌 · 2024-10-04 · via 重归混沌的BLOG

在之前的游戏设计中,好友功能只在同一服务器内生效,涉及到以下几个操作:

  • A玩家想加B玩家为好友,首先需要向B玩家发出好友申请
  • B玩家可以选择删除申请,也可以同意。一旦同意,AB的好友列表中都会同时显示对方。
  • 成为好友后,A每天可以向B送花,AB的关系会因此增加友情值。当B收到A的花时,双方的友情值也会增加。每日送花的次数会在0点重置,但累计的友情值不会清空。
  • 根据友情值的累积,AB可以领取不同档次的奖励,每个档次的奖励只能领取一次。

最近策划提出了一个新需求,要求去掉服务器之间的限制,使所有服务器的玩家都可以互加好友,且在操作上与同服好友无异。

面对这个需求,我有两个选择:

  1. 单独做一个服务,将所有好友关系集中管理。这样可以避免数据一致性问题。
  2. 扩展现有的本服好友系统,将跨服好友纳入其中,但会涉及到同步问题。

虽然单独做一个服务能够较轻松地解决数据一致性问题,但也带来了新的挑战:

  • 其他模块可能依赖好友关系,如果把好友关系放在一个单独的服务中,依赖此关系的业务需要异步查询,增加了不确定性。
  • 当玩家数量和在线人数达到一定规模时,如果需要进行水平扩展,数据一致性问题仍然会重新浮现。
  • 由于游戏已上线,如果跨服好友和本服好友分开存储,两个系统的融合也会带来不小的复杂度。

因此,我最终选择了扩展本服好友系统,让它能够接管所有好友关系,而不仅仅是本服好友。毕竟,“复杂度留给自己,为他人提供简洁的接口”才是最佳选择。


好友关系是强双向的,因此将好友关系存储在各自玩家的服务器中必然涉及数据一致性问题。

在处理好友申请同意申请时问题不大,这些操作可以很容易地设计为幂等操作。比如,B同意A的好友申请时,可以先向A所在服务器添加好友关系,成功后再从B的申请列表中删除关于A的申请。即便在中途协议超时,也可以通过重试机制来解决。

然而,送花收花友情值的处理让我陷入了困境。

因为送花只能每天进行一次,而收花必须在送花之后才能进行。这两个操作都和每日的0点相关联,意味着送花操作无法保证幂等。

不过这也不是很重要,因为其最终的导向是友情值,而友情值才和奖励挂勾。

我最初的想法是,送花收花完成时,先在本地计算出最新的友情值,然后将这个值同步给好友的服务器。

但我很快发现这个策略存在问题:AB送的花会增加AB之间的友情值,而BA的花也会增加同样的友情值。如果双方同时进行送花收花操作,友情值可能会被相互覆盖。


这就像是数据库的双主机制,问题开始变得棘手。

经过与同事讨论。他建议不再进行数据同步,当需要使用友情值时,直接从对方服务器获取对方操作产生的友情值,然后与本地的友情值相加。

我很快否定了这个方案。

因为玩家登录时,客户端需要拉取好友列表进行红点提示或其他展示。

如果按照此方案,我必须查询所有好友所在的服务器。

最坏的情况是,有50个好友分别在50个不同的服务器,这会导致协议请求数量放大50倍。

尤其是停服维护再开服时,这个瞬间的消息量是不可接受的。

这些年我刻意对抗性能强迫症时养成了一个习惯,就算一个方案在性能上不可行,我也会忽略性能来思考他的结果究竟是不是对的,有没有可能达成更简洁的抽象。

经过仔细的思考之后发现,虽然性能问题让这个方案行不通,但它揭示了友情值机制的底层逻辑。

通过简化,我们可以将操作归纳为以下公式:假设玩家AB友情值F

  • A一次送花产生的友情值为a1,一次收花产生的友情值为a2
  • B一次送花产生的友情值为b1,一次收花产生的友情值为b2

那么,F的最终值就是a1 + a2 + b1 + b2

从公式中不难看出,F是由AB各自操作产生的友情值之和。

因此,我们无需在用到友情值时查询对方服务器,而是将友情值拆分为F_AF_B,在需要时将F视为F_A + F_B

这样,A送花时可以本地更新F_A,并尝试将F_A同步到B所在的服务器。B的操作也是如此。

这解决了双主问题,两人分别同步自己所有权的数据,即使同时操作也不会产生冲突。


ps. 这篇博客之所以值得一写,是因为在扩展已有代码时,很容易陷入旧有的思维框架,难以跳脱出来。当发现数据所有权冲突时,如果我们仔细思考一下,这块数据中,到底哪部分该由管理,也许就可以解决双主问题。

pps. 解决完所有权问题,之后的解决方案又回到了我之前提到的本地事务 + 重试的分布式编程范式。

ppps. 在游戏分布式编程中,强一致性几乎是不可能的,最终一致性才是我们追求的目标。