Erlang:游戏服务器

1.宏观流程

客服端通过GateServer(利用gen_tcp握手)与之连接,从而实现所谓的登陆(注册),再初始化DB相关数据,处理一系列的连接消息,得到新的state,当tcp close时(或者相关pid down),加载新的state到DB。

2. C - S设计

3.链接管理器设计

链接器(sys_conn)其实是实际逻辑服务器与客服端之间的桥梁和管道,作用是管理集群中所有的来自client的链接,将从客服端收到的消息转发到对应的逻辑服务器,将逻辑服务器收到的消息转发到相应的client。其几大特征归结于就是路由、广播、安全。需求设计

需求设计:

  • 作为游戏服务器,对于链接器就应要高性能,高并发,高吞吐量
  • 高性能:路由和广播的要尽可能快,保障游戏的流畅性
  • 高并发:因为是游戏服务器,连接比较多
  • 高吞吐量:输出远远大于输入,收到的数量少;但是需要广播的数据较多

理论实现:一个进程对应一个链接(一个进程对应一个客服端TCP链接,一个进程对应一个逻辑服务器链接),所有Client进程由一个监督进程管理,所有Server进程由一个监督进程管理。

逻辑实现:当Client与Server经过三次握手后,后续消息管理就利用sys_conn进行处理,在sys_acceptor后,对每一个链接启一个sys_conn进程,利用Sorcket,Ip,Port对应一个唯一进程,在其初始化的时候调用一下本模块中的read_next方法(用于接受下条消息,并方便进行处理)。其handle_info中对接受和发送的消息进行处理。在接受消息中,在其判断为正常的数据时,进行路由处理,将其放到对应模块方法中实现,异常数据继续调用read_next对其继续进行接受消息。

4. 缓存策略

游戏服务器中避免不了数据的缓存,在大量的C - S交互中,理论上是不希望出现数据的丢失与损坏。对于其最终落地的数据选用MySQL进行存储;在游戏中的玩家数据而言,是希望避免频繁的对其数据库进行操作,所以需要一个进程中缓存方法。

需求设计:避免对数据库MySQL的频繁操作,不但会耗费大量的游戏内存,还会对玩家的体验及其不友好,有可能个人数据较多,在对此功能进行操作时,会出现卡顿出现(不关网络情况问题)。

设计思路:

  • 对于玩家数据:在玩家登陆进入游戏中时,个人重要信息加载到gen中的state进行管理(例如玩家Id,玩家的私有财产等);对于其他功能模块数据会加载到相应的Dict中,后续玩家数据变化都是对其Dict进行处理。如果是玩家比较重要的数据可以选择及时入库之外,其他都是在Dict中管理,最后在TCP close后,对其各个模块的数据加载到MySQL数据库。
  • 对于公关数据:在游戏开始时加载相应的config数据到对应的dets中,对于其它公共数据,都存放到ets表中(例在线玩家列表数据)。

设计考虑:对于Erlang自带的一套持久化机制 —-  数据库mnesia。在这里没有对其频繁使用,只是用来放一些公共数据,在mnesia启动时,都会把数据加载到ets中,每次读写的是ets,等到一定时候或者到达一定数据量才会持久到dets中。但是假如表数据过大,就会导致内存被急剧消耗掉(ets所占的比例过大)对其各个功能模块数据,我在这边选用dict进行处理,理由就是它够快,比ets快了不知多少倍,因为dict是所在进程私有的,所有不适合放公共数据,因为该进程销毁时,进程字典也会被销毁,这里就需要在TCP断开时,,针对其玩家进程数据加载到数据库Mysq中。这样可以避免了对MySQL的频繁操作,还没有过多的mnesia的使用,因为mnesia事务的并发差,一般只能脏读和脏写。

5.进程相关

对于erlang的天生并发,所以就可以对每一玩家启一个进程,玩家在进程中修改自己数据,进程消息的同步处理机制保证的数据一致性。这里还将其玩家进程与Socket进程独立开,虽然说erlang消息基于复制,但是分开后对于攻击有一定的防范。

Erlang中的进程虽说很廉价,但是也不要过多的创建进程(虽说创建一个临时进程可以异步传输数据,提高了并发性,但是进程的创建和销毁也会有一定的系统消耗)。

6.通信相关

Erlang中所有节点间的通信都是透明的,无论是节点内的进程,还是不同节点的进程,都可以用erlang:send发送消息,而且数据不需要任何转换。这样就可以通过扩展来支撑负载。

在网络通信这边,这边使用prim_inet:async_accept来取代gen_tcp:accept,虽然说表面上说前一种使用不是很安全,但是他的优势就在于实现了异步OTP的TCP server在处理大量并发的情况。如果全部使用gen_tcp这种阻塞式accept就会出现大量的并发请求,这种同步请求就会需等待前面处理完,而后面的只能等待。