以下观点局限于个人认知,不一定 100%正确,欢迎留言探讨交流。

目前 RO 服务器存在的一些问题与想到解决方案。

分包

目前单个包最大 65535 字节(2 字节标识包大小),网络层读写接口不支持消息的分包接受与发送,逻辑层得自己判断单个数据包是否超过最大包大小。

解决方案:

  1. 现有的消息协议 flags 字段(1 byte)目前只使用了 2 bit,占用第 3 个 bit 表示当前包是否是完整的包。
  2. 直接增大包大小,从 2 字节改成 4 字节(单个包最大 4 GB)。

Protobuf 的 encode/decode

主线程做 Protobuf 的 encode/decode,耗主线程 CPU 算力。

解决方案:引入 Protobuf 反射机制,在 IO 线程里做 Protobuf 的消息的 encode/decode,需要做个工具自动做消息 <cmd, param> 与消息 name 的映射(消息分发代码生成器的解析 PB 代码逻辑可以实现这个操作)。

配置

  • 配置需要手写读取代码。
  • 服务器启动的时候加载配置速度很慢。

解决方案:

  1. 服务器启动的时候用多个线程开各自的 lua 虚拟机读 lua,这样做能提高一点读取 lua 变量的速度。
  2. Excel 生成给服务器读的数据存储格式改用 Flatbuffer(这里需要做一个打表工具,不想改变策划的工作流,可以把这个工作流放服务器这边),C++ 的读取配置代码做一个代码生成器自动生成,需要兼容 Excel 现有的字段类型(包括 table 字段,这个是难点,这个可以继续在 C++ 中用 Lua 虚拟机来读,也可以实现一个 C++ Lua Table Parser),Flatbuffer 没有 Decode 成本,理论上 FlatBuffer 读取速度可以比 Lua 快 100 倍以上。

git 工作流

  • 同时维护太多长存分支,且合并逻辑混乱没有章法,严重影响开发效率。
  • 每次都基于落后 studio 很久的 release 分支建立功能分支做开发,这样提交入 studio 分支的代码要过很久才能使用,很多时候同时开发的很多功能都代码可能有交集,这样只能用同一个 feature 分支开发多个功能。
  • 整搬操作很骚(studio 合并 tf 以及 tf 合并 release 竟然会用手动 copy 然后提交的方式)。

解决方案:暂时没有心力解决,工作流程涉及到多个工种的工作方式变动,后续会开一篇文章详细分析现有的 git 工作流的缺点,以及提出我认为较为优秀的工作流方案。

消息分发处理逻辑

  • switch-case 式的代码。
  • 整坨消息处理的代码堆砌在 SceneUser 下,影响编译速度,可读性很差且不优雅。
  • switch-case 合并代码时 break 还容易被冲掉。

解决方案:引入消息分发器的设计。

数据库操作

大部分 mysql/redis 在主线程里做堵塞的 IO 操作。

解决方案:改进 mysql/redis 接口,支持异步接口。

日志库

  • 日志库没有打印所在的文件与行数以及调用处的函数名,定位问题带来不便。
  • 日志打印用 stringstream 方式拼接日志串,效率最低的一种(与 printf 和 fmt 比)。
  • 日志每打印一行都 flush,这样会影响日志写入的效率。

解决方案:引入 fmt 至现有的日志库中改进格式化字符串性能,日志打印时加上所在文件与行数以及调用处的函数名的信息,flush 频繁的问题可以借鉴 muduo 使用的哨兵 + 定时 flush 的方案。

命名规范

  • C++ 的代码还算有代码风格和命名规范,但是部分业务层代码也没有完全践行。
  • 配置的命名完全没有命名规范。
  • 日志打印没有规范,日志级别的使用,变量名的大消息,分隔符等。

解决方案:

  • 现有的 sonar 检查已经能做到把 C++ 代码部分不符合规范的命名都检查出来了,但这个只能做到事后诸葛亮,可以引入 clang-tidy 进工作流,在编码时间就检查命名风格。
  • 配置的命名的问题,得出一个规范出来,excel 的命名检查还是很好做工具来检查命名风格的,lua 的变量的命名,工具类检查暂时没有思路。
  • 日志这块也是得先出一个规范出来,工具类检查暂时也没有思路。

后续

和同事交流后得到的一些反馈(12 月 14 日),记录一下:

  • 不是所有消息都需要 decode,可能收到这个消息后,直接就转发了。

    不是很赞同,作为一个承载 ProtoBuffer 的消息,最终一定会 decode,这样才能消费承载的数据,可能我用了放在 IO 线程的字眼,才会让同事有这样的想法,可以单独放其他线程来做 decode,Gateway 这种偏消息转发的 Server 可以不用使用写 decode 的逻辑,而 SceneServer 这类就可以用这个方法来降低主线程做 decode ProtoBuffer 的消耗。

    这块后续会写的 demo 做 benchmark,看单独起线程对主线程分担压力的收益大不大。

  • lua 配置的读取可能只占用服务器启动时间的一部分。

    做了 benchmark 验证(所有 lua 配置加载的时间也要 30s,整个服务器集群启动起来要 190 秒左右),确实如此,后续会沿用现在 lua 载体的方案,在这之上做一个 C++ Binding 的代码生成器。

  • 消息分发器用 unordered_map 作为消息 id 与消息处理函数的载体是否会有性能问题。

    从理论来说这个质疑其实很没有必要,unordered_map 查找时间复杂度为 O(1) 的。现改用固定 256 长度的 array 作为消息处理函数的载体,array 的下标直接是消息的 id。

  • mysql/redis 异步的需求不大,部分场景就是需要阻塞等操作结果的,比如玩家切线的时候。且异步接口对使用者要求比较高,容易写错代码导致宕机。

    保留观点,待后续理解切线逻辑后再考虑。