前言
近期接触了到了游戏后台的匹配逻辑,写此文作记录,也给后来者一些参考。
本文主要是对逻辑的一些整理,真正使用的时候还需根据实际场景来。
场景与需求
- 玩家去匹配战斗力与自己类似的其他玩家。如果在一定区间找不到对手,那就扩展区间。
- 连续匹配两次,不能匹配到相同的玩家。
- 如果已经战斗过的玩家,在一定时间内不能再被匹配到。(此处假设5分钟内不能再被匹配到,5分钟之后需要又可以被匹配到)
数据结构
根据上面几个需求,得出以下初步结论:
- 至少要有两个List,一个是可以匹配的玩家的List,另一个是已经战斗过的玩家的List。
- 由于需要根据不同战斗力来匹配不同的玩家,所以要根据不同战斗力,分出多个List。
- 需要随时可以获取到每个用户上次战斗的时间,这样才能让用户过了5分钟后又可以匹配到。
根据这三点,目前的结构是这样的:
//上次战斗的时间
private final HashMap<String,Long> battleTimeMap=new HashMap<>();
//不能战斗的队列
private final LinkedList<String> unableBattleList=new LinkedList<>();
//根据战力分区
private final HashMap<Integer,LinkedList<String>> enableBattleMap=new HashMap<>();
匹配逻辑
已经可以确定是从一个List中选出被对战的玩家,选择的方式其实是两种:顺序读和随机读。
由于随机匹配两次,不能匹配到相同的玩家,所以随机读是很不方便的,于是就确定使用顺序读。
匹配的时候有一个场景的问题是必须解决的:
用户匹配到的对手,不一定会选择战斗,也可能直接退出匹配界面,或者换一个匹配的对手。
这时候,被匹配到,但却没有战斗过的用户仍应该在可被匹配的List中。
于是结构就变成了这样:
//上次战斗的时间
private final HashMap<String,Long> battleTimeMap=new HashMap<>();
//不能战斗的队列
private final LinkedList<String> unableBattleList=new LinkedList<>();
//根据战力分区
private final HashMap<Integer,LinkedList<String>> enableBattleMap=new HashMap<>();
//是否可以被匹配
private final HashSet<String> enableBattleSet=new HashSet<>();
添加了一个enableBattleSet,如果战斗过了,那么就不会在这个Set中。
于是逻辑就变成了这样:
- 匹配的时候,如果一个玩家被匹配到,首先看下是否enableBattleSet中,如果在,那么就返回,如果不在,那就把这个玩家移出匹配List,并且匹配下一个。
- 返回之后不移出可匹配的List,而是放到队列末尾。(因为只有战斗了才会被认为不能匹配)
- 在战斗开始后,将这个用户从enableBattleSet中移出。(这样在匹配的时候就不会被匹配到)
战斗冷却结束逻辑
简单来说就是将战斗冷却结束的用户放到可匹配的List中。
这个大概有两种实现方案:
- 由玩家每次匹配,触发战斗冷却结束的逻辑。
- 后来放一个线程,定时去触发战斗冷却技术的逻辑。
由于随着玩家越来越多,战斗匹配的逻辑触发会越发频繁。但是战斗冷却结束的逻辑没有必要那么频繁触发,所以方案一是不合适的。
笔者最终选择的是方案二。
上锁的逻辑
如果两个玩家战斗力类似,他们同时触发匹配的话,可能会匹配到同样的对手,这样就会导致被匹配到的玩家在同一时间与两个玩家战斗。这样显然是不合理的,所以需要对逻辑上锁。
上锁也是有两种方案:
- 对可匹配玩家的List上锁
- 对匹配函数上锁
两种方案笔者评估下都是可行的,都各有利弊:
- 对List上锁。
优势: 只有战斗力类似的玩家同时触发匹配逻辑才会阻塞,减小了由于“同时匹配”被阻塞的概率。
劣势: 如果在匹配的时候触发了“战斗冷却结束逻辑”,那么也会被阻塞,存在请求超时风险。 - 对匹配函数上锁
优势: 如果在匹配的时候触发了“战斗冷却结束逻辑”,不会被阻塞。而且结束战斗冷却的玩家是被放到List尾部,所以一般情况也不会影响匹配逻辑。
劣势: 所有玩家中,只要有有两个人同时触发匹配逻辑,就会有玩家被阻塞,存在请求超时风险。
一致性问题
实际操作过程中,一个服务一般都会部署在多台服务器上,这样才能通过负载均衡提高并发,并且在有一台服务器出故障的时候,能保证服务正常运行。
在这种情况下,匹配逻辑的中间过程就不能仅仅放在缓存中,而应该使用redis放在redis服务器上,这样才能保证匹配数据只有一份,而不是每台服务器都有一份。