手游匹配服务器设计总结

设计实现了一个手游匹配服务器,在这里记录一下思路与优化点,举一反三:D

设计的总体思路参照目前主流游戏做法(比如DOTA2),点击匹配->等待->结果弹出->确认或者取消->进入游戏或者取消进入。因为手游的特殊性,为了减少对客户端的依赖变成:点击匹配->等待->进入游戏或者匹配超时(匹配超时在客户端网络不好的情况下发生)。

总体的设计原则有以下几点:

一、尽量减少对客户端的逻辑依赖。

二、整体服务支持平行扩容,防单点,防雪崩。

三、匹配的合理性与公平性。

手游匹配简易架构图:

wp-match

匹配逻辑转发 – MatchProxy

核心处理逻辑是对于不同类型的匹配按规则进行转发,可用的参数例如用户UID,Match Making Rating (MMR)之类。支持多线程。

转发目标服务器基本规则根据一致性哈希(Consistent Hashing),实现可以参考这里,不足之处是需要自行定时维护MatchPvxSvr服务器的状态。

小技巧:对于Pve匹配的规则可以按照玩家UID+盐值,即哈希Key带盐进行,主要是保证合理的随机,因为玩家UID固定,每次会哈希到相同服务器,可能会造成A、B两个玩家永远不会匹配到的情形。Pvp匹配我们想保证相近MMR的玩家在同一处理线程中,对哈希Key(MMR)提前加工。

    key = key / n % m;

其中m是目标服务器的数量,n是每个服务器桶的容量。

举个例子,玩家原来的MMR=100,我们现在需要将0-199MMR的玩家发送至同一Pvp匹配服务器中,现在有A、B两台服务器,即m=2,n=200,根据公式key=100/200%2得到key=0,按数组索引会匹配到A服务器,0-199MMR的玩家都会去A服务器匹配。

可能有人会问:在某一个MMR段人数较少情况下会跨MMR匹配,这种匹配模式怎么满足?后文会介绍这个问题的解决方案:D

逻辑处理 – MatchPveSvr

支持多线程收,塞入线程安全队列(入口),启用一个核心线程处理队列出口,即匹配核心逻辑。

这里没什么特别技巧,需要注意的是对于匹配房间状态需要做超时处理,对于已经存活1h(根据实际一局游戏时间来定)的房间做直接清除处理。因为可能是某种异常导致了这个房间状态异常。

对于取消匹配的逻辑需要同时检查队列与房间两个地方。

逻辑处理 – MatchPvpSvr

支持多线程收,塞入线程安全队列(入口),启用一个核心线程处理队列出口,即匹配核心逻辑。

很据业务需求在本段MMR匹配不到足够玩家,解决方案是:修改他的匹配MMR重新放入MatchProxy重新进行转发,战斗结果再根据他原始的MMR进行计算。

多人匹配的原则是,已经匹配好的玩家不做变动,变动的只是单人匹配的玩家(就是过来凑数的),其实这里有优化点:多人的变动与组队玩家变动的实现。想法是队伍玩家进行深度遍历优先匹配,需要一个全局的房间空缺管理器。

匹配的心跳,需要客户端每隔一段时间发送匹配心跳包来告诉服务器“我还在匹配”,否则会产生玩家网络已经非正常断开,服务端无法知晓。这里有二种方式处理心跳:一、续期。比如设置20秒的超时计时,每收到1个心跳续期5秒(心跳每隔5秒发一次,理论上每次收到心跳永远不会超时,漏掉4条心跳则会超时);二、更新。设置20秒的超时,每收到一个心跳则将超时重新置为20秒重新开始倒计时。

对于取消匹配的逻辑需要同时检查队列与房间两个地方。

对于Pvp战斗,理论上开始后即可把房间释放。

 

整理的比较乱,写了一些大概的核心点,细节没有说的很全,尽量避免了很业务紧密相关的逻辑(比如房间超时时间,匹配多少秒后没人会跨MMR段),很多都是为了用户体验更好而设定的“经验值”。

(全文结束)

 


转载文章请注明出处:漫漫路 - lanindex.com

6 Comments

 Add your comment
  1. 您好,我在做一个pvp匹配功能,请问逻辑处理这个地方,大体怎么实现。。 全局的房间空缺管理器是做什么的。如果方便告诉,不胜感激:)

    • 不好意思,回复比较晚。

      不知道你们pvp需求是什么(比如几v几,是否客户端需要显示房间),我就以最简单的1v1和最方便的数据结构来说:
      整个匹配数据结构是map > matchRoom
      int32_t是匹配区间的key,int64_t是房间uid,PveMatchRoom就是房间具体信息。

      玩家A匹配过来,加入一个队列,入列可以多线程加锁,出列可以用单线程处理。出列寻找matchRoom匹配,
      看是否已经存在key,若有就直接去PvpRoom里面找对手(这里是匹配实力接近);若没有就新插入一个key。
      这里会有一个超时时间,每过一段时间允许相邻key的PveRoom进行合并匹配(这里的匹配实力没那么接近)。

  2. Viclan 您好,看了您的方案深受启发,还是有点问题想请教您。项目采用KBengine(https://github.com/kbengine/kbengine)引擎,类似于bigworld,基于(base、cell、client)实体架构,底层是c++,脚本是python。在基于这个引擎设计1v1匹配时,我的方案是这样:分为三级。低中高,按玩家的战斗力值划分。匹配玩家只匹配对应等级的玩家。具体来说是这样的:玩家进入大厅实体。玩家匹配对战。
    在大厅中新建一个请求对战的玩家字典:
    waitBattlePlayers={}
    然后,玩家点击匹配对战之后,将玩家放入到这个waitBattlePlayers字典中。
    接着,寻找战斗值相近的玩家进入匹配。匹配成功之后,创建Room,然匹配成功的玩家进入这个Room。
    接下来就开始战斗。现在有个问题,玩家A匹配,玩家B也匹配,请求对战的玩家放入waitBattlePlayer匹配池,匹配实体根据战力匹配,但是问题是waitBattlePlayer匹配池每时每刻都有新加入的以及匹配成功的实体,因此匹配实体读取waitBattlePlayer匹配池感觉有很大的问题。看了您的博文,感觉根据一致性哈希算法,假设有三个匹配进程A、B、C(KBengine底层架构被设计为多进程分布式动态负载均衡方案, 理论上只需要不断扩展硬件就能够不断增加承载上限),那就是0-199范围战力的进A匹配,200-400进B匹配,400-600的加入C匹配,此时每个进程的匹配实体是不是设计一个定时器timer,比如每隔0.02s就发动匹配,将战力接近的玩家设为一对(每隔线程的匹配池waitBattlePlayers是不是还有同时读写的问题?),匹配成功之后创建房间实体,消除各个线程匹配成功的玩家,这样每秒能匹配50次,100个玩家,似乎并发有点小。听一下您的意见,谢谢。

    • 不是使用定时器timer,应该是有一个单独的线程来处理匹配核心。

      若没有新入匹配就wait一会,有新入匹配就竭力处理新入匹配,不需要wait。

      至于同时读写的问题,我没使用过KBengine所以不好针对它说出意见,推荐是使用消息队列的形式,多线程往队列里写,单线程(匹配核心)从队列取出进入处理。

  3. 你好 请问 游戏开测的时候 玩家段位都集中在MMR 0-199 这个时候A进程是不是扛不住 因为按照MMR划分进程 很难保证负载均衡

    • 看你们的游戏设计,强度最大的设计就是新号就可以进入匹配,并且初始的MMR所有玩家一样(与等级、训练赛、定级赛无关),那么在开服/开测的阶段压力最大;

      遇见这种情况可以开始使用用户的uid划分进程(会造成一些用户“永远”匹配不到一些用户),等开服一段时间之后再切换到MMR划分,均在MatchProxy进行修改即可。

      一般来说如果进入MMR相关内容的有一定门槛,不会存在流量暴增的问题。

Leave a Comment

Your email address will not be published.