现在的位置: 首页 > 综合 > 正文

网游服务器系列之三:定制Linux内核实现基于QoS高效数据广播

2013年10月12日 ⁄ 综合 ⁄ 共 2665字 ⁄ 字号 评论关闭

高延迟产生的原因

前文提到了网游服务器有大量的广播数据需要及时分发出去,否则将导致延迟增长,这也是限制网游服务器负载能力的一个重要原因。目前主流的网游服务器的实现机制是采用TCP的前置连接服务器分担广播压力。好处是可以通过增加前置连接服务器的数量增加负载能力,但是其发送延迟却不是最优的。

首先,TCP的可靠性保证,流量控制机制在保证可靠传输的同时也增加了通讯的延迟。

第二,前置连接服务器的存在实际上额外增加了转发处理的延迟。

假如游戏逻辑服务器直接与客户端通讯,那么数据从服务器发到客户端的通道如下图,逻辑服务器应用层将数据通过Socket API提交到网卡驱动(一般会采用异步IO),然后由操作系统驱动层发送给客户端。客户和服务器直接建立一个TCP连接。当然实际上不会让逻辑服务器直接连客户端,因为逻辑服务器会是多台服务器的集群,如果直接暴露给客户端客户端就要维护多个连接,而且每个服务器都要处理大量并发连接,不利于服务器专注处理游戏逻辑。


使用前置连接服务器之后的数据通路如下图:


逻辑服务器通过TCP将数据发送给连接服务器,再由连接服务器发送给客户端。这个过程涉及两个串行的TCP连接,三次Socket API调用,两次发送,一次接收,每一次Socket 操作都涉及到相关数据在应用层和内核之间的数据复制,这些都是导致延迟增加的原因。

区分数据传输的服务质量要求(QoS)

网游服务器下行数据需要的可靠性其实并不完全相同。有些要求非常可靠,如战斗时技能的释放,血量的变化,道具的使用等等。而玩家高速跑动时的位置,远处玩家的基本动作(走跑跳等)和一些非关键的状态变化要求的可靠性则可以低一些。因为这些状态会服务器会定期发送给客户端,即使丢失了一次,最新的状态也会在其后发送过来。因此对于这部分的数据广播可以采用UDP的方式。因为不保证可靠传输,这样下行数据的广播效率会大大提高。

即使使用UDP也可通过增加一些简单的重发机制来提高数据传输的可靠性。简单的重发机制没有TCP这么复杂,一般是重发几次后如果仍没有收到确认就丢弃。本质上仍然是非可靠的传输,但是数据的到达率应高于纯粹无确认的UDP,发送效率介于TCP和无确认的UDP之间。对于游戏中不要求完全可靠,但比较重要的数据可以采用这种方式,同时可以自定义重发次数来设定不同等级的可靠性保证。

基于LVS设计UDP广播机制

LVS介绍

LVSLinux Virtual Server)是基于IP层和基于内容请求分发的负载平衡调度解决方法,并在Linux内核中实现了这些方法,将一组服务器构成一个实现可伸缩的、高可用网络服务的虚拟服务器。早期是作为Linux内核的一个补丁包的形式发布,后来被吸纳进Linux内核,作为一个内核模块,模块名称IPVS。现在所有的linux发行版中都带有这个模块。详情请参阅LVS官方网站。

设计方案如上图所示。这里有几个要点:

 

 

 

 

 

 

 

 

 

 

 

  • 在连接服务器前面部署一台LVS服务器,实际上就是一台Linux服务器,大多数Linux发行版都可以,不需要安装其他软件。
  • 只有LVS需要暴露在公网,其他服务器部署在独立的内网。连接服务器不再直接面向公网,LVS服务会充当连接服务器的负载均衡器,将TCP连接均衡的分配到各个连接服务器。
  • LVS配置成直接路由模式。所有上行报文都会通过Lvs服务器发送到其他服务器,但是其他服务器的下行报文不需要通过LVS,可以直接发送给路由器。
  • 每一个客户端到服务器都有两条数据通道,一条是TCP通道,用来收发需要完全可靠的数据报文。一条UDP通道,用来接收服务器的广播报文。
  • 每一台应用服务器(物理机器)都启动一个广播代理进程,它通过名字管道(或其他高效的IPC机制)接收应用服务器的广播请求,直接将数据通过UDP发送给客户端(不经过LVS)
  • UDP通道只用于服务器向客户端发送广播数据,客户端通过UDP通道发送的数据只有确认报文。所有客户端的数据请求仍然是通过TCP通道到达服务器。
  • 需要修改Linux内核的IPVS模块,增加一种负载调度算法来支持上行UDP确认报文到达正确的物理服务器。

LVS的引入的作用主要有三点:

  • 充当连接服务器的负载均衡器,可以将并发连接平均分配到连接服务器。
  • 将所有的服务器集群对客户端虚拟为一台服务器
  • LVS的直接路由模式允许后端服务器可以在不暴露在公网的环境下直接将广播报文发送到客户端,有利于降低延迟。

下图是对此方案的广播延迟的分析:


 

 

 

 

 

 

 

 

 

下行广播数据从应用服务器到广播代理服务走的是本机名字管道(也可以采用其他高效的IPC机制,Unix 系统还可以使用doman socket),由于是本机内通信,延迟非常低。广播代理服务直接将数据发送到客户端。与单纯使用连接服务器的广播方案相比,中间环节减少很多,UDP通道的重发机制很很简单,整体的延迟会大大降低。

这中方案下,原有的连接服务器逻辑全部保留,但是只处理可靠数据的收发及广播。由于数据量最大的部分分流到UDP通道,连接服务器的广播压力大大减小,负载能力也能提高很多。这里应用服务器也分担了部分广播任务,由于应用服务器是计算密集的应用,在原来的方案下其I/O能力基本上是闲置的。此方案可以使用者部分闲置的I/O能力,充分挖掘整个集群系统整体的潜力,提高负载能力。

额外的复杂性

此方案带来好处的同时,也增加了一些额外的复杂性。首先客户端需要维护两条通信通道,只有两条通道都正常客户端同服务器的连接才是正常的。特别是UDP通道,需要定期发送连接保持报文,让沿途所有路由器保持路由信息,才能保证下行通道的畅通。

对于服务器而言,也需要专门的服务处理客户端的连接保持报文,一方面检测客户端的连接状态,另一方面也要记录每一个客户端的源地址信息,这是能够向客户端发送UDP报文的关键。这些信息要在所有的广播代理服务之间共享,根据这些信息才能将UDP广播数据发送给客户端。

为了支持UDP的重发机制,需要定制一下Linux内核,修改IPVS模块的部分代码。虽然代码不多(不到20行),但如何修改和调试还是要费点功夫的。

整个系统实现起来还是有一定的复杂度,设计、实现、测试、优化都不容易。如果每个游戏都把这个工作做一遍将是很大的浪费。我设计了一个分布式中间件系统,其通讯层及一些公共的服务解决了全部上述问题。游戏开发者只需要关注游戏逻辑的实现, QoS的要求可以直接在IDL文件中指定,特定的编译器会根据IDL定义生成实际的通讯层的代码。

   
 

后续文章将逐步介绍此分布式中间件的实现,敬请期待。

抱歉!评论已关闭.