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

推荐订阅源

S
Secure Thoughts
P
Privacy International News Feed
T
Tenable Blog
L
Lohrmann on Cybersecurity
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threat Research - Cisco Blogs
S
Securelist
C
CXSECURITY Database RSS Feed - CXSecurity.com
Cisco Talos Blog
Cisco Talos Blog
T
The Exploit Database - CXSecurity.com
S
Schneier on Security
P
Privacy & Cybersecurity Law Blog
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
月光博客
月光博客
T
The Blog of Author Tim Ferriss
Scott Helme
Scott Helme
爱范儿
爱范儿
Stack Overflow Blog
Stack Overflow Blog
C
Cisco Blogs
aimingoo的专栏
aimingoo的专栏
博客园 - 司徒正美
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Proofpoint News Feed
A
Arctic Wolf
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
L
LangChain Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
阮一峰的网络日志
阮一峰的网络日志
Simon Willison's Weblog
Simon Willison's Weblog
T
Tor Project blog
Security Latest
Security Latest
Blog — PlanetScale
Blog — PlanetScale
G
GRAHAM CLULEY
V
Vulnerabilities – Threatpost
博客园 - 三生石上(FineUI控件)
I
InfoQ
Spread Privacy
Spread Privacy
B
Blog RSS Feed
Microsoft Azure Blog
Microsoft Azure Blog
S
SegmentFault 最新的问题
云风的 BLOG
云风的 BLOG
Last Week in AI
Last Week in AI
MongoDB | Blog
MongoDB | Blog
C
CERT Recently Published Vulnerability Notes
A
About on SuperTechFans
博客园_首页
Engineering at Meta
Engineering at Meta
Project Zero
Project Zero
Latest news
Latest news

重归混沌的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

在我刚开始写代码时,许多书籍不断教导我遵循正交性DRY原则(Don’t Repeat Yourself)和SPOT原则(Single Point of Truth)等编程准则。

这些原则确实能够显著提高代码质量和可维护性,我也乐此不疲地在日常开发中应用这些原则。

然而,当我转向编写游戏服务器代码时,却发现自己常常难以严格执行这些原则。

在游戏服务器的开发中,各个模块通常独立存储它们所需的数据。

用关系型数据库的术语来说,每个模块可能会使用独立的table。不过,游戏服务器的存储方式通常并不是按字段构建table,而是将结构体(struct)序列化为blob格式后进行存储。

模块之间很少通过外键进行约束,数据一致性和容错性完全依赖于代码的逻辑处理。

通常,模块内部负责将数据存储到数据库。如果多个模块共享一个table,由于各模块落地时机问题,如果不妥善处理,会大大增加数据不一致的风险。

此外,加上强类型语言的约束以及各模块之间相似却微妙的逻辑差异,迫使开发者反复编写相似但不完全相同的代码。

这些年,我尝试了不同的方法来增加代码的复用。

  1. 抽象中间结构:当我们需要使用某段代码时,可以将DB结构转换成抽象的内存结构,处理完业务逻辑后再将内存结构转换回DB结构进行存储。
  2. 复用设计好的DB结构:在设计模块数据结构时,复用一些通用的DB结构,并直接调用相同的算法代码。

然而,这两种方式都有很大的局限性。

例如,方式1可能会转换1000个数据,但最终只修改其中的1个数据。不进行转换是不行的,因为修改一个元素时可能会读取其他数据。一个典型的例子就是抽卡。

方式2的复用性更差,举例说明:

struct common {
    // 一些公共字段
}
struct entry_a {
  struct common c;
  // 其他字段
}
struct entry_b {
  struct common c;
  // 其他字段
}
struct module_a {
  struct entry_a[] list;
}
struct module_b {
  struct entry_b[] list;
}

由于强类型语言的限制,几乎不可能直接用相同的代码同时处理module_a.listmodule_b.list

现实中的情况更复杂,比如entry_?.other_fields往往会以某种方式影响struct common[] list的逻辑。


在我以前的游戏服务器开发中,我一直被数据库落地给禁锢了。

我一直认为数据库落地的不一致是无法解决的,因此也就不可能抽象出公共模块来存储并处理不同模块中相同的逻辑。

这就导致了,在进行代码设计时,无法自由的进行抽象。从而在方式1方式2之间徘徊。

直到最近我终于找到了部分答案。

解决方案其实也很简单:模块不再负责数据的落地,而是通知落地框架需要落地的数据。

落地框架将在合适的时机(如每秒, 或每条协议之后)统一处理所有脏数据的落地。

如果在落地之前服务器崩溃,丢失的也仅仅是最后一秒的修改,相当于游戏回档到1秒前。

这对玩家几乎没有影响(充值问题可以通过掉单处理,充值服务器只需重新补发1秒前的订单即可)。

为强类型语言的游戏服务器设计这个落地框架会比较麻烦,但收益是巨大的。

从此在进行数据库抽象时,我们不必再有数据库的心智负担。可以像普通应用程序那样进行自由的抽象。