账号服务器开发总结

游戏开发 Apr 2, 2023

从六月份开始开发账号服务器到今天,账号服务器的大体功能也算完善了,账号服务器的开发暂时告一段落,预计下周又回去开发游戏服务端了。 一直想写一篇文章总结一下账号服务器开发过程的经验和教训,迟迟没有时间动笔,今天正好借这个机会写一下。虽然不是什么高大上的东西,但是我们的项目还在运营,另外也有商业秘密的原因,设计到具体实现细节的这里不会描述,也不用来问我,我也没法告诉你。

起因


公司本来已经有了一套比较成熟的账号服务器了,不过一直是端游在用的。而我们是手机游戏,许多需求和端游不同:

  1. 手游常常需要接入各个第三方渠道
  2. 登录验证方式不同,手游的第三方渠道账号登录以后需要通过HTTP协议到第三方平台进行账号验证
  3. 手游有混服登录的需求(即多个第三方渠道的账号登录同一台游戏服务器)
  4. 原有账号服务器掌握在别的部门手上,每次功能修改和开发都需要耗费大量时间沟通协调,自己开发维护响应会比较迅速

虽然有这么多需求,早期我们也没打算自己开发账号服务器,而是在游服上直接做,然后用一个数据库对账号进行重新编号排序。这种做法,在内测阶段只有一两台服务器当然没有太大问题,但是开放内测是说一开始就会开放5台游服,后续人满就开新服,预计会有20多万人。我掰着手指一算,不对呀,这不是会有一两百台服务器嘛?这要是同时进行插入和查询,那还不得卡成狗了,玩家估计会把我们项目组上下所有人全家祖宗N代都问候一遍吧。于是和老大合计了一下,决定自己开发一个账号服务器来解决这个问题。 老大说:开发计划是——开发人员:你,时间:一个月。 我:~!@#$%^...呵呵...

架构设计


其实最早我的想法很简单,就只打算做一个最土的账号服务器,大致就是一个大区一台账号服务器,账号服务器下面挂N台游服,满了就开新区。一台账服预计同时能处理1万左右的并发就够了。 如果是这样的话该是多么轻松加愉快啊,随便搞个程序也能满足要求。于是就大致把想法和老大说了一下。老大一听,果断不答应啊,逼格不够高,做了太掉价,要做就做高大上的,BlaBlaBla(嗯,原话大概是重复造轮子的事,做了以后评审的时候会被喷,既然做了就要有点创新)。最后讨论的结果是账服要做到10万人并发,全区全服账号数据互通(本来还说要游服也能互通,就是没有大区的概念,被我否决了,一个人月啊,我就是变成大神也搞不定)。 既然方案定了,那么架构基本上也就定了。还是分大区,每个大区一台账服,账服下面挂服务器,账号数据所有大区共享。

概要设计


基本架构确定了,首先要做的就是明确需求。

  1. 游服列表下发[1]
  2. 通过第三方账号重新生成内部统一账号[1]
  3. 账号登录的验证[1]
  4. 通过激活码激活账号[1]
  5. 通过激活码领取礼包[1]
  6. 登录排队[2]
  7. 封号解封踢人[3]
  8. 设备绑定和安全监测[4]

PS:这个列表是迄今为止完整的账服功能,上面的标号是表示该功能是哪个版本增加的。看到这个需求列表就好像看到一部血泪史啊,这是后话,暂时按下不表。 接着就是设计账服软件的框架了。好在我本来就有打算自己写一套服务器框架,所以业余时间也有在收集一些觉得可能用的上的开源免费库,所以很快就找了几个。加上公司原有的一些代码积累,差不多就够用了。

  • 网络IO:我们公司的服务器大部分都是用Windows(请不要问我为什么,我只能告诉你……呵呵)
    • 异步SELECT
      • 公司现有的SocketLib:10万并发啊,看到异步SELECT我直接就呵呵了。果断枪毙。
    • libevent:同上,Windows下用的也是异步SELECT。
    • IOCP
    • libuv:Node.js的底层高性能网络库,Windows下支持IOCP,C语言实现。
    • libev:对Windows的支持出了名的烂,直接放弃。
    • ace和boost::ASIO:这两个库实在太大太复杂,想想还是放弃了。

最后经过反复考虑,我们做出了一个艰难的决定(其实也没别的可选了),用libuv作为底层网络库。

  • IPC
    • 公司原有的MessagePort
    • libuv

公司的MessagePort原来还挺神秘,结果拿来代码一看,各种锁各种memcpy,没法忍啊。果断放弃,我自己用libuv提供的Windows命名管道重写了一套MessagePipe。

  • 数据库
    • redis
    • SQLite
    • memcached
    • MySQL

为了找个适合的数据库我基本上把主流的各种数据库都翻了一遍,本来考虑redis,但是老大说不行啊,公司原来用的是MySQL,你换个新鲜玩意他们搞不定的。于是又只能用MySQL了。不过说起来redis我也没玩过,这样贸贸然应用到项目里面也不是很好,万一出了问题就大条了。

实现过程的失误和妥协


  1. libuv本身是个很优秀的库,但是是用C语言写的,而我们用的是C**。libuv里面大量的回调函数和C++的面向对象机制真是格格不入,于是费了好大劲写了一套Socket层来封装,但是可读性也不是太好。
  2. 环形缓冲池的引入,虽然一定程度上减少了运行时的内存分配释放的开销,但是由于想把消息处理转换成公司惯用的一套消息机制,所以还是多了一次memcpy,然后缓冲池管理的对象也没有使用sk_buff,导致内部通信增加消息头又多了一次memcpy,所以总的来说这个应用并不算是成功的案例。
  3. 分库机制导致某些情况下,查询很不方便,效率也十分低下。代码实现起来也很复杂,可读性差,而且容易出错。其实类似这种应用可能还是redis这类数据库更靠谱些。
  4. 世界服本身其实可以不用操作数据库,全部与账服通信,然后指挥账服干活就可以了,缺点是结果反馈比较麻烦,而且可靠性差些。
  5. 消息机制当时有考虑过引入ProtoBuf,不过这个对游服也有较大的改动量,而且之前没有应用过不是太放心,所以最后还是没用。不过事实证明这是个错误的决定,项目正式上线后,要改个消息需要想尽各种办法兼容,否则就会有灾难性的后果,如果当时换用ProtoBuf或许这方面就没烦恼了。

经验总结


最后开发完成之后,我们自己压测结果账服的性能大概是并发5万人左右。当然,这是在个人PC(酷睿I5),而且是debug版本的情况下,而且这还不是峰值。我自己估计大概Release版本峰值在6~8万人这样吧。 工作了四年多,这大概算是我自己独立完整架构开发的第一个比较大型的程序,事实证明实际情况远比你在设计时想象到的各种恶劣情况还要复杂的多,尤其是各种运营和策划的需求一拥而上,铺面而来时,那种感觉确实让人窒息。实话说,虽然现在的账服上线以后还算是比较稳定可靠,性能也能满足需求,不过我自己还是不大满意。以后有时间我要把整个推翻重写。 墨菲定律告诉我们,如果坏事有可能发生,那么一定会发生。所以开发过程中千万不能抱着侥幸心理:“这个BUG出现的概率只有百万分之一,所以不用管它就好”,这种心态的下场就是在项目上线以后被运营策划追着你修BUG,让你欲仙欲死。特别是账服这种游戏的入口程序,一旦出问题游戏整个瘫痪,造成的损失是无法估量的。 最后,事实证明平时多积累些知识还是有用的,起码关键时刻你能知道该往哪个方向去找资料。书山有路勤为径,学海无涯苦作舟,大家一起共勉吧。

标签