匹配系统实现
概述
"快速开始"背后是匹配系统:把段位相近、人数凑齐、模式一致的玩家撮合进同一个房间。匹配做得好不好,直接决定玩家开局等待时长与对局体验。本文设计完整的匹配系统:ELO/Rating 评分算法、匹配池设计(段位/人数/模式维度)、定时匹配与快速开始、匹配超时处理。
一、匹配的核心目标
三个目标,互相制约:
等待时间:越快越好(10s 内理想)
实力均衡:段位接近(体验公平)
模式正确:人数/玩法匹配(规则约束)
匹配器本质:
在"等待时间"与"实力差距"之间做权衡
等待越久 → 放宽实力范围(渐进匹配)衡量指标:
平均等待时长
匹配成功率
对局实力方差(均衡度)
取消率(玩家等太久退出)二、ELO / Rating 评分
2.1 ELO 核心思想
每个玩家一个评分(Rating)
初始分:如 1200
对局后按结果调整:
胜者加分、败者减分
爆冷(低分胜高分)加分更多
预期胜率由分差决定ELO 公式:
预期胜率 E = 1 / (1 + 10^((R_b - R_a)/400))
更新:R' = R + K × (S - E)
K:调整系数(新玩家大、老玩家小)
S:实际结果(胜 1 / 平 0.5 / 负 0)2.2 休闲游戏的简化
休闲小游戏不一定需要完整 ELO:
需求更简单:分差在允许范围内即可
用分段(段位)而非精确分:
段位:青铜/白银/黄金...
匹配范围:同段位优先,相邻段位可放宽
实现选择:
段位 + 隐藏分(段位展示、隐藏分匹配)
段位决定匹配池,隐藏分做池内排序2.3 评分存储与更新
存储:玩家表字段 rating / 段位
更新时机:每局结算后
并发:结算更新(含分布式锁防重复)
新玩家处理:
初始段位 + 快速校准期(K 值大,几局内定位)
防刷:校准期内多打几局才稳定三、匹配池设计
3.1 匹配池结构
匹配池 = 按维度拆分的等待队列集合
维度:
模式(斗地主 / 德州 / 桌游)
人数(2 人 / 4 人 / 10 人)
段位(青铜 / 白银 / ...)
池键:mode + playerCount + rank
如 match:dou-dizhu:3:diamond为什么分池:
玩家只会与同模式同人数的对手同局
分池减少无效比较
每池独立调度,互不干扰3.2 池内数据结构
用 Redis 或内存队列?
单节点:内存结构(List + 索引)
多节点:Redis ZSet(按 rating 排序)
Redis ZSet 匹配:
入池:ZADD match:dou:3:gold rating playerId
撮合:按 rating 范围取前 N 人
出池:ZREM
优点:跨节点共享、天然按分排序内存方案(单节点游戏服):
ConcurrentHashMap<PoolKey, ConcurrentSkipListMap<Rating, Player>>
按 rating 有序,二分找最近分差对手
优点:零网络开销、实时
缺点:不跨节点
休闲游戏初期单节点内存方案足够3.3 入池与出池
入池:
玩家点击"快速开始" → 校验(不在房间)→ 入池
出池:
匹配成功 → 移出池 → 进房间
玩家取消 → 移出池
匹配超时 → 处理(见下文)
掉线 → 移出池四、匹配算法与撮合
4.1 撮合流程
1. 玩家入池(带 rating)
2. 匹配器定期扫描(如每 500ms 一拍)
3. 对每个池:
收集等待玩家,按 rating 排序
从高分到低分贪心配对:
找分差最小的凑够人数
4. 凑够 → 创建房间 → 通知全体进入
5. 未凑够 → 继续等待java
public class MatchService {
private static final int TICK_MS = 500;
@Scheduled(fixedDelay = TICK_MS)
public void tick() {
for (PoolKey key : matchPools.keySet()) {
MatchPool pool = matchPools.get(key);
while (pool.size() >= key.needPlayers()) {
// 取评分最接近的一批玩家
List<WaitingPlayer> group = pool.pickNearest(key.needPlayers());
// 创建房间,全体入房
Room room = roomService.createMatchRoom(key, group);
for (WaitingPlayer p : group) {
pool.remove(p);
notifyJoined(p.getPlayerId(), room);
}
}
}
}
}4.2 渐进放宽
等待越久范围越宽:
T < 5s:分差 ±50
T < 15s:分差 ±150
T < 30s:分差 ±300
T >= 30s:不限(跨段位)
实现:
入池时记录入池时间
撮合时按等待时长计算允许分差
保证大多数玩家 15s 内开上局4.3 人数不足的特殊处理
池内人数凑不齐:
方案一:继续等(默认)
方案二:人机补位(低峰期、新手局)
方案三:放宽模式(不适用人数硬规则场景)
方案四:提示玩家人数不足
休闲游戏建议:等 + 人机兜底
人机补位要打"机器人"标识,合规五、定时匹配与快速开始
5.1 快速开始流程
1. 玩家点"快速开始"
2. 校验:不在房间、无未结算对局
3. 计算池键(模式 + 人数 + 段位)
4. 入池,返回"匹配中"
5. 匹配成功 → 推送房间信息 → 进入
6. 匹配取消/超时 → 通知客户端展示:
匹配中动画 + 已等待时长
可取消(回到大厅)
超时提示(人少时可看"当前匹配人数")5.2 好友房 vs 快速开始
好友房:不走匹配器,直接凭邀请码加入
快速开始:走匹配器
两者最终都落到 Room
设计上匹配器只负责"撮合",建房逻辑复用六、匹配超时处理
6.1 超时策略
| 超时阈值 | 处理 |
|---|---|
| 15s | 扩大分差范围(渐进匹配) |
| 30s | 跨段位匹配 / 提示人少 |
| 60s | 询问玩家:继续等 or 退出 or 人机局 |
| 90s | 自动退出并提示(避免无限等) |
兜底原则:
不让玩家无限等
每个阈值都有动作
超时信息上报监控(反映匹配池健康度)6.2 超时定时器实现
入池即注册定时任务:
用 Netty HashedWheelTimer 或 ScheduledExecutorService
到点触发:判断仍在池中 → 执行超时策略
匹配成功/取消 → 取消定时任务
注意:
定时任务要与出池互斥(房间锁)
防止"已出池还触发超时"七、匹配系统监控
监控指标:
各池人数(池健康度)
平均等待时长
成功率 / 取消率
超时分布
人机补位比例
告警:
平均等待 > 阈值 → 池可能失衡(段位断层)
某池空置 → 低峰期提示
匹配成功率骤降 → 算法/网络故障八、常见问题
| 问题 | 处理 |
|---|---|
| 段位断层排不上 | 渐进放宽 + 跨段位兜底 |
| 反复匹配到熟人 | 匹配池随机扰动 |
| 刷匹配刷分 | 结算校验 + 行为检测 |
| 掉线滞留池中 | 心跳/断线检测出池 |
| 匹配瞬间大量同时开房 | 撮合拍频控 + 池锁 |
九、小结
匹配系统解决"把谁和谁放进同一间房":ELO/Rating 给玩家实力量化,休闲游戏简化为"段位定池 + 隐藏分排序";匹配池按模式、人数、段位拆分,内存有序结构或 Redis ZSet 支撑按分撮合;撮合算法贪心取评分最近的一批玩家并渐进放宽分差,平衡等待时间与实力均衡;超时处理逐级放宽并最终兜底退出。快速开始、人机补位与好友房共用房间系统,匹配器只做撮合。匹配质量直接决定留存——"等得短、打得公平"是设计的一体两面。