社交游戏之可行双机热备方案 预防单点故障

【导读】

某一天深夜,单盘配置的服务器出现硬盘损坏,导致该服务器上所提供的服务停止,于是有了开发双机热备服务的想法,经过长时间(半年)的多人的努力,这个东西慢慢就出来了。基于各种原因,这里不能提供相关源代码,仅仅提供设计思想,基本实现思路和实现过程遇到的问题和挑战,顺带记录下这半年努力的成果,若有描述不够详细或清楚的地方,敬请见谅!

1. 稳定性思考

废话不多说,本文所说的服务器特指使用C /C++ 实现的中间件角色的应用服务器,比如DB Proxy(Cache) Server,常规MMORPG架构中的中心服务器,在其整体应用中都是以单点的形式存在,而且所起的作用又及其重要,如何应对各种程序问题或者不可控因素引起的Crash,而把其Crash所造成的损失减少到最低,这里介绍一个已实现并且可行的双机热备(hot-standby)解决方案。

名词解释:

HS: 当前双机热备技术架构简称

Master: 主服务器,即当前online服务器

Slave: 从服务器,即备份服务器,offline服务器

服务器: Master或者Slave

2. 双机互备工作方式

HS工作在【主-从】模式下,即同一时间只有一台服务器提供服务,默认情况下Slave服务器不处理外部请求命令,只处理由Master端发来的同步请求。

这种情况下,服务器提供统一的接口给LVS,如果LVS检测到Master无响应或停止工作,则发命令使Slave变成服务器(online)模式,同时将客户端请求都转到Slave,此时Slave主机也就升级成为Master,工作方式如Figure.1所示:

Figure.1

3. 软件架构

HS是被设计成通用性模块,理论上可以供所有应用服务器接入使用,如Figure.2所示:

Figure.2

由于HS模块自身并没有网络传输功能,在集成中还需要将应用服务器的网络I/O接口接入到HS模块(常规情况下,应用服务器的网络I/O模块都会提供单独接口?)。

整体架构如Figure.3,HS自己运行一个线程,对所有异步传递到HS模块的数据,先使用queue进行排队缓存,再在本线程内一次性将其传输到配置的Slave端,同时这部分数据也会写入模块的第三方存储(HS目前只实现了Memory存储)。

Slave在接收到Master传输的data后,更新应用服务器相关内存(根据Master发送的KEY可以确定具体的内存类型),同时也会将接收到的数据写入自身的数据缓冲队列和第三方存储。

Master和Slave只要编写很少代码(发送时HOOK,接收时更新)就可以实现一个完备的hot-standby server。

Figure.3

这里就有个疑问了,为什么Slave也需要维护一个queue和第三方存储呢?这是因为Slave可能会在未来的某一天会变成Master,需要同步数据给未来的一个Slave。Figure.4流程图说明了服务器启动的过程。

Figure.4

4. 模块化

HS可以单独编译成动态库形式(.so)使用。

具体实现中使用boost的一些库,主要有io_service,thread,bind,shared_ptr等。

不过在编译时可以选择将boost库静态编译,实际使用上就不用单独进行依赖了。

需要说明的是,为了更好的支持HS的运行,远程过程调用模块被开发出来,这里就不详述了。

5. 网络传输如何处理突如其来的大数据量

在某种极端情况下,当Master启动了很久之后,Slave才姗姗来迟,Master可能积累了大量的数据(超过4G),这个时候如果Master对Slave进行无限制的传输数据,会占用大量的网卡资源(不过一般双网卡,不影响外网服务),最重要的会占用大量网络线程资源(HS和应用服务器公用网络线程)。

实际测试中发现,由于4G的小块数据大小不一,传输耗时超过1分钟。为了避免对应用服务器的网络线程拥塞,加入了传输数据的流量控制,在每个间隔时间内只传输指定配置的数据量,测试证明,这个方法解决了所有相关问题。

第三方存储有什么用?

Slave端断开后或者在Master启动一段时间后再连上来时,用于存储历史备份数据。就是说Slave 在断开很长时间之后再连上来,期间所有的历史数据都是在第三方存储中。连上后,首先需要同步的第三方存储的数据,完成后再对queue中的数据进行实时同步。

6. Socket的幽灵属性keepalive

为了检测网线断开或者不可知的网络异常,HS需要一套能够检测网络真断开,还是伪断开(比如网络短时间内的不通),首先想到了利用TCP/IP协议自身的属性,即KEEPALIVE,经使用测试发现,这个不是很好的检查机制,至少不是我们需要的。因为KEEPALIVE依赖系统的三个属性,如下所示:

系统设置

# cat /proc/sys/net/ipv4/tcp_keepalive_time

7200

# cat /proc/sys/net/ipv4/tcp_keepalive_intvl

75

# cat /proc/sys/net/ipv4/tcp_keepalive_probes

9

简单说明下,这个三个属性表示TCP/IP协议层会在7200秒没有收到客户端消息的情况下,发送10(9+1)个报文段,每两个间隔75秒,若客户端对这些信息都没有响应,则终止该连接。

不使用这个属性的两个理由,一是这些信息属于系统属性级别的改动,二是实际测试中发现很不稳定。

以下为设置keepalive的代码:

int optval = 1;

socketlen_t optlen = sizeof(optval);

setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen);

更有效的心跳方案:

就是自己实现,方案比较土,简单描述下:

服务器维护两个变量A和B,一个定时器,客户端每来一个请求包,A变量自增1,同时在定时器到点时,检测A和B是否相同,如果不同表明客户端是活动的,同时将A同步到B,开始下一轮定时器。若一个连接有N次的定时器到点是都未活动,则判定客户端断开,关闭该连接。

7. OVER

【编者加注】

专注于社交游戏的研发与运营,逐渐对社交游戏产品的业务越来越熟悉,不断地总结与分析,加快社交游戏产品的研发速度,我们技术团队做很多研究与尝试,为此我们开发出来产品:

1) 数据中间件:解决大用户并发与大数据量的问题,不需要游戏研发工程师关心数据的存取等;

2) 任务服务器:解决游戏产品众多活动或奖励活动的举办,以及游戏自身任务的配置与管理;

3) 统计服务器:我们玩家的操作日志数据量太大,无法全部存储到服务器上,为此有选择地存储相关玩家的行为日志数据,并且完成数据分析的工作;

4) 好友列表服务器:社交游戏产品主要是接入众多的社交网站平台,为此要实现一套通用的好友列表服务器;

5) …..等其他游戏引擎组建

随着推出上述相关通用性游戏引擎的组建,我们也逐渐赢得更多时间与精力可以做更有意义的事情,比如我们可以静下心研究解决这些服务器作为单点运行的问题,为此研发出解决各个游戏引擎组建服务的主备提供服务的通用性组建,也即其他游戏引擎组建通过引入hot-standby组建,可以解决单点故障的问题。这个通用性组建,也可以为非社交游戏行业的服务器后台程序的热备模式,提供一定的技术参考价值。感谢@ZEROV17的投稿支持,也欢迎各位技术朋友站内留言或者新浪微博直接交流,同时也非常感谢零度视角F为此项目所作的贡献!