Skip to content

About

distributed matchmaking system Go + Redis matchmaking demo with multi-instance shard coordination,ELO rating updates, isolated test environments, load simulation,and a real-time monitoring dashboard (Tailwind + Chart.js). Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

Matchmaking Demo

基于 Go + Redis 的分布式游戏匹配系统演示项目,涵盖多实例协调、分片租约、ELO 评分、动态策略引擎与实时监控仪表盘。


目录


特性概览

特性 说明
多实例水平扩展 多个 Server 实例共享同一 Redis,通过分片租约协调工作
分片 + 租约机制 Shard lease(TTL=10s,Lua CAS 续约),防止重复处理
防重复匹配 Lua 原子脚本校验所有玩家均为 queuing 状态后批量设为 matched
动态 MMR 容忍度 BaseMMR(200) + tickCount × expansion(50),等待越久范围越宽
ELO 评分系统 对局结束后异步更新(模拟 10–30s 对局时长,K=32)
策略引擎 支持 max_diff / exact / has_tag 三种规则,可混合硬过滤与软惩罚
玩家属性扩展 数值型属性(PlayerAttributes)+ 标签型属性(Tags)双通道
测试环境隔离 独立 Redis 前缀的沙箱环境,API 创建/销毁,支持持久化重启恢复
异步日志 Channel-based 非阻塞写入,支持 stdout + 文件双路输出
实时监控 SSE 推送 + Chart.js 仪表盘,展示队列深度、匹配速率、等待时间、MMR 分布

架构设计

┌─────────────────────────────────────────────────────────┐
│                      HTTP Clients                        │
└──────────┬──────────────────────────┬───────────────────┘
           │                          │
    ┌──────▼──────┐            ┌──────▼──────┐
    │  Server :8080│            │  Server :8081│   多实例
    │  (inst-1)   │            │  (inst-2)   │
    └──────┬──────┘            └──────┬──────┘
           │                          │
           └──────────┬───────────────┘
                      │ Redis (共享状态)
              ┌───────▼────────┐
              │  Redis :6379   │
              │                │
              │  solo:pool:{N} │  ← 分片匹配池(ZSet,score=MMR)
              │  team:pool:{N} │
              │  queue:{pid}   │  ← 玩家排队信息
              │  player:{pid}  │  ← 玩家档案
              │  match:{mid}   │  ← 比赛记录
              │  shard:{mode}:{N}:lease │  ← 分片租约
              │  metrics:{iid} │  ← 实例指标
              └────────────────┘

核心流程

JoinQueue → SetPlayerState(queuing) → SaveQueueEntry
         → AddToSoloPool / AddToTeamPool (ZSet, score=MMR)

tickLoop (1s) → 遍历分片 → 滑动窗口选候选
             → MMR 范围检查(动态容忍度)
             → 策略硬过滤(GroupCompatible)
             → AtomicSetMatchedState (Lua 原子操作)
             → SaveMatch → asyncELOUpdate (goroutine)

分片租约与负载均衡

  • 每个分片持有 shard:{mode}:{N}:lease(TTL 10s),SETNX 竞争获取
  • 每 3s 执行一次 Rebalance:若持有分片数 > fairShare = (NumShards×2) / numInstances,则主动释放多余分片

项目结构

match_making_demo/
├── cmd/
│   ├── server/
│   │   ├── main.go              # HTTP 服务入口(多实例),embed 嵌入静态资源
│   │   └── web/
│   │       ├── index.html       # 仪表盘 HTML shell(侧边栏布局)
│   │       ├── css/
│   │       │   └── style.css    # 全局样式
│   │       └── js/
│   │           ├── utils.js     # 公共工具函数(apiGet/Post/Delete, spotlight)
│   │           ├── overview.js  # Overview 标签(指标卡片、图表、实例表、Redis)
│   │           ├── envs.js      # Test Envs 标签 + 仿真引擎
│   │           ├── strategies.js# Strategy 标签(模板库、规则构建器、CRUD)
│   │           ├── help.js      # Help 标签(API 文档、参数说明)
│   │           └── app.js       # 侧边栏路由、SSE 连接、全局初始化
│   └── simulator/
│       └── main.go              # 负载测试模拟器
├── internal/
│   ├── model/
│   │   └── model.go             # 数据模型(Player, Match, QueueEntry, PlayerContext…)
│   ├── elo/
│   │   └── elo.go               # ELO 算法(K=32)
│   ├── store/
│   │   └── store.go             # Redis 操作层(前缀隔离、Lua 脚本、指标存储)
│   ├── matchpool/
│   │   └── matchmaker.go        # 核心匹配引擎(分片管理、Tick 循环、队列 API)
│   ├── metrics/
│   │   └── metrics.go           # MetricsPayload 定义与 Redis 推送
│   ├── strategy/
│   │   └── strategy.go          # 规则引擎(max_diff/exact/has_tag,内置模板)
│   ├── testenv/
│   │   └── manager.go           # 隔离测试环境管理(Create/Stop/List/Restore)
│   ├── admin/
│   │   └── handler.go           # 管理 HTTP 路由 + SSE 推流 + 静态文件服务
│   ├── api/
│   │   └── handler.go           # 玩家 API 路由(注册、加入、取消、状态)
│   └── logger/
│       └── logger.go            # Channel-based 异步日志 Writer
├── docker-compose.yml
├── Dockerfile
├── Makefile
└── go.mod

快速开始

前置依赖

  • Go 1.22+
  • Redis 7(本地或 Docker)

本地运行

# 1. 启动 Redis
docker-compose up -d redis

# 2. 编译
go build ./...

# 3. 启动两个服务实例(分开终端)
SOLO_MATCH_SIZE=4 PORT=8080 INSTANCE_ID=inst-1 go run ./cmd/server
SOLO_MATCH_SIZE=4 PORT=8081 INSTANCE_ID=inst-2 go run ./cmd/server

# 4. 启动模拟器(注入 100 个玩家)
go run ./cmd/simulator -players 100 -servers "localhost:8080,localhost:8081"

# 5. 打开仪表盘
open http://localhost:8080/dashboard

Makefile 快捷命令

make infra-up        # 启动 Redis 容器
make run-server1     # 启动实例 1(:8080)
make run-server2     # 启动实例 2(:8081)
make simulate        # 运行 100 玩家模拟
make simulate-small  # 运行 24 玩家小规模模拟
make flush-redis     # 清空 Redis 所有数据
make clean           # 清理编译产物

Docker Compose 一键启动

# 启动 Redis + 两个 Server 实例
docker-compose up --build

# 仪表盘
open http://localhost:8080/dashboard
open http://localhost:8081/dashboard

compose 文件中已配置:SOLO_MATCH_SIZE=4、TEAM_MATCH_COUNT=2、NUM_SHARDS=4,适合本地演示。


环境变量

变量 默认值 说明
REDIS_ADDR localhost:6379 Redis 地址
REDIS_PASSWORD "" Redis 密码(无密码时留空)
PORT 8080 HTTP 监听端口
INSTANCE_ID 自动生成 实例唯一标识符
NUM_SHARDS 8 分片总数(建议生产 16+,演示用 4)
SOLO_MATCH_SIZE 12 Solo 模式每局人数(演示用 4)
TEAM_MATCH_COUNT 4 组队模式参赛队伍数(演示用 2)
TEAM_SIZE 3 每支队伍人数
LOG_FILE "" 日志文件路径(空 = 仅 stdout)

API 参考

玩家 API

方法 路径 说明
POST /match/register 注册玩家档案
POST /match/join 加入匹配队列
DELETE /match/cancel/:pid 取消匹配
GET /match/status/:pid 查询匹配状态

注册玩家

curl -X POST http://localhost:8080/match/register \
  -H "Content-Type: application/json" \
  -d '{"id":"p1","name":"Alice","mmr":1500,"kd":1.2,"level":30,"idc":"us-east"}'

加入队列(支持属性与标签)

curl -X POST http://localhost:8080/match/join \
  -H "Content-Type: application/json" \
  -d '{
    "player_id": "p1",
    "mode": "solo",
    "attributes": {"ping": 30, "device": 1, "recent_wr": 0.62},
    "tags": ["premium", "mobile"]
  }'

查询状态

curl http://localhost:8080/match/status/p1
# → {"state":"queuing"} 或 {"state":"matched","match":{...}}

管理 API

方法 路径 说明
GET /admin/instances 所有实例指标快照
GET /admin/redis Redis 内存/连接/OPS 信息
GET /admin/stream SSE 实时推流(metrics + redis 事件)
GET /admin/strategies 列出所有策略
POST /admin/strategies 创建策略
PUT /admin/strategies/:id 更新策略
DELETE /admin/strategies/:id 删除策略
GET /admin/strategies/templates 获取内置策略模板
GET /admin/envs 列出测试环境
POST /admin/envs 创建隔离测试环境
GET /admin/envs/:id 查询环境状态与指标
DELETE /admin/envs/:id 停止并删除测试环境
POST /admin/envs/:id/match/join 在测试环境中加入队列
GET /admin/envs/:id/match/status/:pid 查询测试环境中玩家状态
GET /dashboard 监控仪表盘页面

策略引擎

策略由一组**规则(Rule)**组成,在每次候选窗口中对玩家两两进行兼容性检查。

规则字段

{
  "attribute": "ping",       // 数值型规则使用(max_diff / exact)
  "tag_value": "premium",    // has_tag 规则使用
  "op": "max_diff",          // 运算符:max_diff | exact | has_tag
  "threshold": 60,           // max_diff 的容差上限
  "weight": 1.0,             // 软规则的惩罚权重
  "hard_filter": true        // true=硬过滤(不满足则不可组队),false=软惩罚
}

运算符说明

Op 适用字段 语义
max_diff attribute(数值) `
exact attribute(数值/枚举) a == b,适合设备类型、段位等
has_tag tag_value(字符串) 所有玩家必须同时持有或同时不持有该标签

创建策略示例

# 创建一个"延迟硬过滤 + 胜率软均衡"策略
curl -X POST http://localhost:8080/admin/strategies \
  -H "Content-Type: application/json" \
  -d '{
    "name": "竞技模式",
    "rules": [
      {"attribute": "ping",      "op": "max_diff", "threshold": 60,   "hard_filter": true,  "weight": 1},
      {"attribute": "recent_wr", "op": "max_diff", "threshold": 0.15, "hard_filter": false, "weight": 2}
    ]
  }'

内置策略模板

通过 GET /admin/strategies/templates 获取,仪表盘 Strategy 页面会以卡片形式展示,点击「Use Template」即可填入编辑器。

模板 ID 名称 规则
tpl_device 设备类型硬隔离 device exact hard
tpl_ping 延迟范围过滤 ping max_diff ≤60 hard
tpl_winrate 近期胜率均衡 recent_wr max_diff ≤0.15 soft w=2
tpl_premium 会员专属房 tag:premium has_tag hard

负载模拟器

cmd/simulator 直接写入 Redis 并并发调用 HTTP API,模拟真实多玩家场景。

go run ./cmd/simulator [flags]

# 参数
-players   100               # 模拟玩家总数
-servers   "host:8080,..."   # 逗号分隔的服务实例地址
-team-frac 0.33              # 组队玩家比例(会取整为 TeamSize 的倍数)
-team-size 3                 # 每队人数
-redis     localhost:6379    # Redis 地址(用于直接写入玩家数据)

模拟结束后会输出统计报告:

========== SIMULATION RESULTS ==========
Total players:  100
  Solo:         67
  Team:         33 (in 11 teams)
Matched:        98
Failed/Timeout: 2
Total elapsed:  18.3s

--- Wait Time Distribution (seconds) ---
  Min:    1.0
  Median: 4.2
  P95:    11.8
  Max:    15.3

--- MMR Spread Distribution ---
  Min:    12.0
  Median: 87.5
  P95:    198.4
  Max:    241.0
=========================================

监控仪表盘

访问 http://localhost:8080/dashboard,固定侧边栏导航,包含四个页面:

页面 内容
Overview 聚合指标卡片(Solo/Team 匹配数、队列深度、平均等待)+ 4 条实时折线图 + 各测试环境队列彩色曲线 + 实例明细表 + Redis 统计
Test Envs 创建/删除隔离沙箱环境;内置 Poisson 到达 + Gaussian MMR 分布的负载仿真引擎;切换标签时仿真不中断
Strategy 内置模板库(点击 Use Template 直接填入编辑器)、可视化规则构建器、策略 CRUD
Help API 端点速查、策略引擎说明、仿真器参数文档、架构要点

数据通过 GET /admin/stream(SSE)每 2 秒推送一次,无需手动刷新。静态资源通过 //go:embed web 内嵌进二进制,部署无需额外文件。


前端架构

仪表盘采用模块化 Vanilla JS,通过 Go embed.FS 内嵌,访问路径为 /static/*。

/dashboard          → web/index.html(HTML shell)
/static/css/        → web/css/style.css
/static/js/utils.js → 公共 API fetch 封装
/static/js/overview.js  → SSE 驱动的聚合指标 + 每 3s 轮询各 env 队列
/static/js/envs.js      → 智能渲染(不销毁运行中的仿真卡片)
/static/js/strategies.js→ Template 按钮用 addEventListener 而非 onclick
/static/js/help.js      → 静态文档
/static/js/app.js       → 侧边栏路由 + SSE 连接

关键设计决策:

  • 智能渲染:_renderedEnvs Map 跟踪已渲染的环境卡片,loadEnvs() 只插入新卡片、删除消失的卡片,切换标签不重建 DOM,仿真状态得以保留。
  • DOM 按需查询:simLog / updateSimUI 每次调用时通过 document.getElementById 查找元素,不缓存引用,避免卡片重建后引用失效。
  • Template 按钮修复:模板卡片使用 addEventListener('click', () => { ... }) 闭包传递数据,避免 JSON.stringify 在 HTML 属性中破坏引号边界。

技术栈

组件 版本 用途
Go 1.22+ 服务端语言
gin-gonic/gin v1.9.1 HTTP 框架
redis/go-redis v9.4.0 Redis 客户端
google/uuid v1.5.0 UUID 生成
Redis 7 共享状态存储(匹配池、租约、玩家数据、指标)
Chart.js v4.4.0 CDN 仪表盘实时图表
CSS / Vanilla JS — 仪表盘前端(无框架,//go:embed 内嵌)

About

distributed matchmaking system Go + Redis matchmaking demo with multi-instance shard coordination,ELO rating updates, isolated test environments, load simulation,and a real-time monitoring dashboard (Tailwind + Chart.js). Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages