乐趣区

关于erlang:Erlang-游戏开发经验总结

早早就想写这篇文章,但这段时间忙于工作的事件,就不盲目地给了本人各种懈怠的理由。当初回头看下这个问题,总结下 erlang 游戏开发教训。就当是,为我过来一段时间的 erlang 开发经验,画上一个小句号。

1. 架构设计

很多人都说 Erlang 天生分布式,归纳起因是 erlang 分布式这块实现比较完善。节点间的通信是通明的,无论是节点内的过程,还是不同节点的过程,都能够应用 erlang:send/2 发送音讯,而且数据不须要做任何转换。
所以,很多我的项目会选用多节点架构,能够通过横向拓展(减少机器数量)来撑持更多负载,把性能压力大的子系统调配到不同的节点,这样,就能够 反对更多的在线玩家。
这样做的出发点是好的,但会引起一系列的问题:
1、业务逻辑的问题:
跨节点逻辑变得复杂,玩家数据同步、一致性问题,节点断开及重连的解决等
2、语言局限性问题:
节点间音讯通信要序列化和反序列化,原子 传输要转成字符串,二进制复制 等

那架构抉择单节点,还是多节点?
没有相对,这要看游戏而定。如果你的游戏像页游那样,分服而且各个服之间交互较少,单节点会比拟适宜。但如果像 lol 那种对战玩法的游戏,多节点会比拟适合,登录和玩法在不同的节点,玩家对战时抉择集群内压力较小的节点做战斗计算。

单节点,益处是玩家数据容易保障统一,运维不便。以常见的 MMORPG 游戏,单节点在 16G 内存,8 外围 CPU,能够撑持 2k 人稳固在线,思考非沉闷玩家,达到 5000 也是可能的。当然,游戏的框架要正当设计,做一些斗争,比方玩家寻路不能在服务端做,地图 九宫格的设计,排行榜不实时刷新,管制同屏玩家数量等 等。当初,单节点做游戏服,还有一个重要起因是,当初的机器性能相较以前高很多了,加上 erlang 无锁的 Actor 设计在并发场合下解放了 cpu,从某种程度上讲进步了 cpu 的运算能力。

单节点的设计:

多节点的设计:

!

erlang 节点留神一个问题,默认 erlang 节点是全联通的,也就是当一个节点退出集群时,集群其余所有节点会和新退出的节点建立联系。全联通带来的问题,集群节点间两两连贯,随着节点减少,连贯数量呈 N *(N-1)/ 2 增长,越发恐怖,连贯自身占用了端口资源。更坏的是,为了检测节点的存活,erlang 会定期发心跳包查看,即便一分钟一个 tick,节点多的话也会造成大量的网络风暴。
解决办法就是在集群中暗藏节点,就能够防止全联通,只有 erlang 启动加个参数即可 -hidden

  1. 数据库
    数据库 io 向来都是游戏的次要性能瓶颈,解决不好容易导致 游戏卡顿,甚至解体。而通用的长久化策略就是,内存读写 + 定时长久化。玩家上线时,加载玩家频繁用到的数据到内存,玩家大多时候就是在读写这些数据。而后定时把这些数据从内存刷到磁盘,在玩家下线时也做一次长久化。

说下 erlang 自带数据库 – mnesia
实际上,erlang 已实现了这样一套长久化机制,也就是 mnesia 数据库。
mnesia 数据存储是基于 ets 和 dets 实现,对于 ram_copies 表应用 ets;disc_copies 表同时应用 ets 和 dets,数据读写应用 ets,dets 做长久化;而 disc_only_copies 表应用的是 dets
这里先探讨 disc_copies 的状况,mnesia 启动时,会将 disc_copies 表所有数据加载到 ets 表中,每次读数据都是读 ets,写数据则会先写到 ets,而后再写一份到日志文件中,期待定时(或定量)长久化刷到 dets。
通常 disc_copies 表能够满足咱们的业务需要,但应用 mnesia 要留神一个问题。后面也提到了,mnesia 启动会将表中的数据加载到 ets,如果你的表过大,就会导致内存被急剧消耗掉。(特色就是,ets 所占的内存比率过大)
所以,应用 mnesia 表时,常常都是要 disc_copies 表和 disc_only_copies 表配合应用。那问题来了,什么时候应用 disc_copies 表,什么时候 disc_only_copies 表。

最简略的就是,对玩家数据动刀。玩家数据默认用 disc_copies 表,如果长时间没登录后将这个玩家的数据移到 disc_only_copies 表,等到他下次登录时再将数据移到 disc_copies 表。
之所以能够这么做,理由有两个:

  1. 游戏中玩家数据所占的比例较大,调整玩家数据能够取得显著收益。
  2. 玩家散失后回到游戏的可能性很小,就算有的话比例也不大。

这么做的弊病就是玩家散失后从新登录的工夫较长,但通过这种形式缩小的内存很可观。

mnesia 的应用,还要留神 3 个问题:

  1. mnesia 单个表 2G 文件大小限度,所以要本人分表,或者应用表分片
  2. mnesia 集群性能,过多的人说有坑,但我没有这方面的教训,就不做探讨
  3. mnesia 事务并发性太差,尽可能不必 mnesia 事务,多脏写;事务可利用过程实现,保障数据安全
  4. 过程
    每个玩家一个过程的设计曾经成为了 erlang 游戏开发的潜规则了。这个没什么好讲,玩家过程批改本人的数据,过程音讯同步解决机制保证数据一致性。可能有些游戏还会将玩家过程和 scoket 过程独立开,负责连贯的建设和保护,协定封包解包,甚至做攻打的防备等。但如果玩家过程和 socket 过程同在一个节点内,显然整合在一个过程较好,erlang 音讯基于复制,两头多了一个过程,一次前后端交互要多了 2 次内存复制。

那么,除玩家外,其余过程怎么确定?

  1. 地图过程
    每个玩家都是独立的过程,玩家 pk 要替换两个过程的公有数据,就要发消息给另一个过程解决。如果是强 pk 的游戏,同时有 N 个玩家一起打斗,音讯就会繁多。因为数据一致性问题,过程间的并发机制就会弱化成同步机制,减少了战斗时延。
    所以,这里会引入地图过程,通常以一个地图一个过程。玩家进入地图时,会同步战斗相干数据到地图过程,玩家来到地图时,再将战斗数据同步回玩家过程。而在玩家进入地图到来到前的这段时间,所有的战斗计算都由地图过程实现。
    或者有人会有纳闷,就算有了地图过程,还是有同步问题,地图过程还是要同步解决 pk 申请,无奈并发解决,玩家过程还是要期待地图过程操作实现。
    其实,对于玩家的 pk 申请,解决至多有两个过程,第一个过程是验证攻打的合法性,如是否有这个技能,技能 cd,等等。第二个过程才是战斗计算,玩家过程查看合法性,再由地图过程做外围的战斗计算。另外一个,玩家 过程除了战斗申请外,还有其余业务逻辑上的音讯,容易呈现过程挂起的状况,这时候,玩家过程不可能解决到战斗计算,就会导致战斗卡顿。

2. 公共过程
公共过程指的是那些提供公共服务的过程,比方:

  1. 社交类,有好友、帮派、组队等,这些服务治理着少数玩家的数据,都须要一个过程来治理。
  2. 计算类,这类有肯定的计算量,比如说排行榜,要有一个过程来承当计算。
  3. 播送类,有聊天室、世界聊天、帮派聊天、地图播送等
  4. 开关类,有 流动零碎,较量零碎等等,管制游戏流动的开启和敞开

erlang 过程尽管便宜,然而不要太过随便创立过程,比方创立一个长期过程异步传输数据等等。尽管这在某种程度上进步了并发性,但过程的创立和销毁须要肯定的零碎耗费,而且会导致我的项目中过程数量不可控,可能零碎莫名其妙多了很多过程,这些保护起来也麻烦。再说,erlang 同时存在的过程有最大 数量限度

  1. 过程字典与 ets
    过程字典是 erlang 游戏开发中最为罕用的数据记录形式,理由很简略,因为它够快,差不多比 ets 快了一个数量级。然而,过程字典的数据为所在过程公有,无奈跨过程间接 get 到过程字典的数据,而且,在过程被销毁时,过程字典的数据也会被回收。
    再说下 ets,比照过程字典,ets 的实用场景是跨过程读写数据。遇到一个数据频繁被多个过程读到,就要思考应用 ets 了。另外,ets 有归属过程,但归属过程销毁时,ets 的数据就会被零碎回收。

比照过程字典和 ets 的实现,区别如下:

  1. 锁方面,过程字典为无锁操作,ets 是读写锁
  2. 数据方面,过程字典数据在过程中,查问没有多余的复制操作;而 ets,因为数据不在过程中,查问时会复制一份到过程。

所以,过程字典通常是最优先的抉择。如果玩家的数据是存储在 mnesia,特地是玩家的外围数据,就有必要从 mnesia 读到放在过程字典中。后面讲到了 mnesia 是利用 ets 和 dets 实现的,玩家上线时将外围数据读到过程字典,这样,读写都在过程字典,就能够利用过程字典带来的性能晋升。这个晋升是很可观,毕竟快了一个数量级。

后面提到了 ets 归属过程销毁时,ets 数据也会被回收?
那么如果预防数据失落的问题,ets 也提供了办法,通过设置继承者过程就能够了。
ets:new(person, [named_table, {heir, HeirPid, HeirData}])
当归属过程解体时,继承者过程就会收到信息 {‘ETS-TRANSFER’, Tid, FromPid, HeirData},并且,ets 的归属权就会转交到继承者过程。

  1. 音讯播送
    音讯播送是游戏中的性能耗费大头,次要包含地图的行走、战斗播送,世界聊天播送。世界聊天播送能够通过管制玩家发送工夫距离来限度,地图中的播送包,比方行走和战斗包的播送实时性高,只需发给视线内的玩家就能够,不必全地图播送。所以常见的解决形式是,玩家的视线通过九宫格划分,玩家的地图播送只发送给九宫格内的玩家。
    如何在服务器压力大时,进一步优化播送音讯:

针对时效性高的数据包,不重要的播送包,能够选择性抛弃:
1、挂机玩家不发送战斗包,特地是战斗 buff。
2、每 N 个包抛弃一个
挂机玩家的断定,前端以玩家长时间没动作来断定,或者将游戏切到后盾运行时断定。等玩家在游戏内点击鼠标,或键盘时解除挂机状态。

这里再谈下,音讯播送还可能进一步优化。通常,后端发数据给前端都会对协定数据进行序列化(封包),而后再发给前端反序列化(解包)。而播送数据,对于每个玩家可能都是一样的,没必要每个玩家发给前端时都各自序列化一次,就只有序列化一次,而后每个玩家拿到数据后间接发给前端。

erlang 音讯播送要留神什么问题?
1、reduction 计数
通常会启动一个音讯治理过程,这个过程就负责把播送音讯转发给对应的所有玩家过程。启用治理过程的一个益处是,过程发消息会扣除 reduction,而且这个 reduction 扣除大小还受到接收者过程影响。如果间接在地图过程做音讯播送,就会导致地图过程受到的调度极度缩小,影响战斗计算。
2、音讯复制
erlang 音讯发送基于复制,但对于比拟大的二进制数据,则会优化成二进制援用,缩小二进制复制带来的开销。所以,当一个音讯要发给多个过程,特地是协定数据(发给前端也要先转成二进制),能够先转成二进制再发送。

结束语
文章到这里就完结了,这是我做 Erlang 游戏开发经验总结的一些教训,后续,我还会找工夫总结更多的开发教训。

图片起源:http://www.coubai.com/ 页游

退出移动版