游戏开发论坛

 找回密码
 立即注册
搜索
查看: 5816|回复: 0

网络游戏中的分布式事务

[复制链接]

1万

主题

1万

帖子

3万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
32026
发表于 2016-9-2 14:07:58 | 显示全部楼层 |阅读模式
QQ截图20160902135949.jpg

  文/达达

  因为我们新开发的游戏是全球通服,所以最近一直在跟分布式事务的数据一致性作斗争,来回琢磨了很多种方案,比如引入消息队列,或者强化现有RPC框架,或者打散数据用map-reduce。

  来回折腾琢磨的原因是,始终找不到彻底解决问题的方案。我所希望的彻底解决是开发人员在进行业务逻辑开发的时候不需要心里挂着分布式事务可能会有不一致或者消息重传要有幂等性这样的事情,就像开发单机功能一样好像数据就在手边,事务也在手边。

  这里我分享一下我对几种方案的思考,每个项目的情况不太一样,说不定在我们项目里是个问题的事情,在别的项目里不是个问题,有的方案就可以派上用场。

  前提条件

  开始之前,我得先说一下网络游戏为什么会有分布式事务,并且会是个问题。

  假设我们做游戏服务端的时候一开始用的就是一个可以横向扩容并支持分布式事务的数据库,比如TiDB,很可能是不需要纠结这个问题的。

  但是奈何游戏实时性要求高,所以游戏通常会有一层缓存,而我们为了让实时性更好,并且也为了让缓存的事务控制更简单,选择了将缓存做在业务进程内的方式。

  这样导致的结果就是各业务进程间是互相不共享数据的,当一个进程需要操作到其它进程管辖范围内的数据的时候,就需要走RPC。

  举个简单的例子,比如说游戏里面工会管理员通过一条加入工会的申请,这时候会有两步操作,一步是在工会服务器上删除申请记录并添加一条工会成员记录,接着会发起RPC到玩家所属服务器上更新玩家所属工会。

  如果上述过程中RPC因为网络故障而没有执行成功,或者玩家所属服务器上更新数据因为程序BUG而失败了,就会出现数据不一致的情况,而数据不一致又会进一步的引发像一个玩家加入了两个工会这样的BUG,所以数据不一致会是个问题。

  换个角度看,如果缓存是独立的,并且是业务进程间共享的,就可以在缓存面实现分布式事务,而不需要把分布式事务的存在感传递到业务逻辑这边。

  支持分布式事务的缓存系统是个美好的愿望,我自己也还没想清楚,所以今天要讲的是怎么躲债而不是怎么还债。

  消息队列

  我最先想到的是引入消息队列,通过消息队列的重传机制来实现事务的一致性。

  但是消息队列的重传机制是很傻的,它能做的就是没收到回应就一直重发。这个实现方式会导致消息的消费端需要实现幂等性,幂等性用人话说就是允许重复执行。

  作为一个以给开发人员减轻心智负担为己任的CTO,显然是不符合品味的,这相当于是给手下的程序员们挖坑嘛,从此以后每天就得挂着这个事情,哪天精神状态不好可能就产生了严重的BUG。

  于是我就想到统一生成ID,统一记录下消息是否已处理,这样程序员不用知道有这样一个事情,功能开发的难度应该跟目前用RPC实现是同样的复杂度。

  但是运维的兄弟就得填坑了,得维护一套消息队列群集。(说得好像真有运维一样,其实我们这种小作坊早就是DevOp了,我们还CtoDevOp呢)

  我想来想去想到个偷工减料的法子,增强现有的RPC。

  增强RPC

  增强RPC这个事情我是这样看的。

  之所以要引入消息队列来做重传是因为现在项目里用的RPC框架太简陋了,网络故障就放弃调用了,没有重传机制。

  如果我在现有RPC框架中加入重传,不就不需要消息队列了嘛。并且对已开发的功能修改量极小,甚至可能都不需要修改。

  进一步我想到消息持久化的问题,如果是把消息放内存里排队,万一进程挂了那不还是丢消息了。正好我们自己有实现一套缓存事务机制,在事务提交时顺便把需要保证送达的RPC消息落地了,不就让整个系统更稳固了。

  这时候进一步给自己挖坑了,数据二进制格式落地不方便查问题啊,弄个JSON落地吧。噢,还有个问题,万一在系统更新的时候把RPC参数给改了呢?这个得记下了,系统更新时候得等RPC队列都空了才开始。

  开发人员轻松了,我可不轻松了,边界情况老多,我也不喜欢做有心智负担的事情啊。正准备开始改RPC代码的时候,又想到一个事情。

  RPC可以处理网络故障,但是处理不了业务故障,如果一个消息送到对方那,对方业务逻辑BUG导致执行失败了,一直重传也没用啊。

  发警报人工处理是可以,但是实时性不高,这期间不还是数据不一致嘛。

  然后又开始想怎么偷工减料了。

  Map Reduce

  跟组里兄弟讨论这个事情的时候我开了个大脑洞,如果每个人只拿到局部的数据,只操作自己的局部数据,不就没有分布式事务了嘛。

  比如工会成员每个人都记录着自己是哪个工会的,当我需要查询工会成员列表的时候,就发起一个Map Reduce,到所有节点上查询并汇总,就能得出一份工会成员列表。

  工会管理员获取工会加入申请列表的时候也是一个Map Reduce,汇总回来一个申请列表。当管理员通过其中一条申请的时候,发起一个RPC到这条申请所在服务器上删除申请并修改玩家数据,事务只发生在一个节点上。

  多么美好啊!但是Map Reduce过程中如果有个节点挂了呢?我是等它恢复还是不等它恢复呢?不等它恢复业务数据就不完整了,等它恢复我的下场就跟曹操打赤壁之战一样了。。。

  数据集中

  讨论的过程中一开始就有兄弟提到要不把数据集中起来,业务相关数据存到对应服务器上,而不是玩家进程有一部分,业务进程有一部分。

  我一开始直接否掉了这个想法。

  当时的判断是,如果要知道玩家属于哪个工会,一样需要记录在玩家所属服务器上,那必然还是有一个分布式事务,工会服务器和玩家所属服务器都要参与。

  但后来结合Map Reduce这个脑洞之后,想到一个可能性,就是不在玩家服务器上记录玩家属于哪个工会,而是通过Map Reduce查询玩家属于哪个工会。

  这样Map Reduce只用在局部,比较容易针对性的解决边界情况。而业务这边就可以做到数据集中,这样可以让一些本来要RPC才能完成的业务变成本进程内业务。

  单点设计

  上面说了好几种思路,但是终归还是避免不了分布式事务,因为根源我前面说了,架构不改是不可能完全避免分布式事务的,就算集中业务的设计,也还是会有需要RPC的业务,这是债得认,出来混就迟早要还。

  但是既然现在已经是靠躲债过日子了,有没有办法让这样的日子过得潇洒一点呢?

  办法还是有的,神经大条一点把业务设计成单点,边界问题最少,心智负担最轻,RPC量最少。

  比如帮派设计成一个单点的帮派进程,好友设计成单点的好友进程,现在服务器配置这么高,内存大大地有,这种业务又不是CPU密集型的,单点唯一的问题就是挂了大家都不能玩了,但是这跟前面说的火烧连营不一样,只影响单一业务,并且因为是单点,故障了反而最容易处理。

  搞游戏又不是搞核电站,单点说不定真的挺好,没必要自己过不去:)

  最后

  上面说了几种方案,总之都不是彻底根治的方案,我目前在项目里只能对症下药,比如一些RPC的消息是不需要可靠性的,像跨服聊天之类的;有些RPC是需要可靠性的,就走可靠RPC和重传机制;有些业务是比较容易做成数据集中的;有些业务可以接受单点故障;

  具体怎么做见招拆招...

  P.S:此外还有一点,我在开发过程中发现,架构也没办法完全磨平互动功能的天生复杂性。

  比如工会管理员通过申请这个事情,就有可能存在并发的情况,两个管理员同时点了同一条申请,如果开发者没有考虑到会有这种情况就有可能出问题。

相关阅读:MMO与弱交互游戏的服务端技术区别

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2024-5-20 07:58

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表