implement alternative match-making approach with Lua scripts in Redis

This commit is contained in:
2026-10-25 15:57:24 +03:00
parent 582ecb7c74
commit acdaebd491
19 changed files with 796 additions and 139 deletions
+38 -28
View File
@@ -207,31 +207,37 @@ curl -X DELETE http://localhost:3000/api/logout \
---
### Поиск случайного соперника (Матчмейкинг)
### Поиск соперника (Матчмейкинг)
Начинает поиск соперника. Работает в режиме **long-polling**: запрос зависает до 30 секунд, ожидая второго игрока.
Поиск соперника происходит через **WebSocket** (`Action Cable`), а не через long-polling: HTTP-поток не блокируется, а результат доставляется сервером push-уведомлением, как только найден соперник.
**URL:** `POST /api/matchmaking`
**URL подключения:** `ws://localhost:3000/cable?token=<JWT>`
**Auth:** требуется
**Auth:** токен передаётся параметром `token` в query string (тот же JWT, что и для HTTP API).
**Параметры:** нет
**Канал:** `MatchmakingChannel`
**Пример запроса:**
#### Поток взаимодействия
```bash
curl -X POST http://localhost:3000/api/matchmaking \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..." \
--max-time 35
1. Подключиться к `/cable?token=...` и подписаться на канал `MatchmakingChannel`.
2. Отправить команду начала поиска:
```json
{ "action": "start", "rating": 1000 }
```
**Успешный ответ (200 OK)** — когда найден соперник:
Поле `rating` опционально (по умолчанию — `MATCHMAKING_DEFAULT_RATING`). Игроки подбираются по близости рейтинга; допуск расширяется с ростом времени ожидания.
3. Дождаться одного из сообщений:
**Матч найден:**
```json
{
"type": "matched",
"match_id": "145575d6-3a4b-4c5d-9e6f-7a8b9c0d1e2f",
"game_token": "eyJhbGciOiJIUzI1NiJ9.eyJtYXRjaF9pZCI6IjE...",
"game_server": "ws://game-server.example.com:8080",
"game_server": "ws://localhost:8080",
"players": [1, 2]
}
```
@@ -243,23 +249,19 @@ curl -X POST http://localhost:3000/api/matchmaking \
| `game_server` | WebSocket-адрес игрового сервера |
| `players` | ID обоих игроков `[player1_id, player2_id]` |
**Таймаут (408 Request Timeout)** — если соперник не найден за 30 секунд:
**Таймаут поиска** (соперник не найден за `MATCHMAKING_QUEUE_TTL` секунд):
```json
{
"error": "No opponent found. Try again."
}
{ "type": "timeout" }
```
**Ошибка (401 Unauthorized):**
4. Поиск отменяется автоматически при закрытии WebSocket-подписки. Также можно явно отправить:
```json
{
"error": "Unauthorized"
}
{ "action": "stop" }
```
**Примечание:** `game_token` содержит `match_id`, `player1_id`, `player2_id`, `game_server` и действителен **1 час**.
**Примечание:** `game_token` содержит `match_id`, `player1_id`, `player2_id`, `game_server` и действителен `MATCHMAKING_TOKEN_TTL` секунд.
---
@@ -311,7 +313,6 @@ curl -X DELETE http://localhost:3000/api/matchmaking \
| `200` | Успешный запрос |
| `201` | Ресурс создан (регистрация) |
| `401` | Неавторизован (отсутствует/невалиден токен) |
| `408` | Таймаут матчмейкинга (соперник не найден) |
| `422` | Ошибка валидации |
| `500` | Внутренняя ошибка сервера |
@@ -321,8 +322,17 @@ curl -X DELETE http://localhost:3000/api/matchmaking \
| Переменная | По умолчанию | Описание |
|------------|--------------|----------|
| `REDIS_URL` | `redis://localhost:6379/0` | Подключение к Redis |
| `GAME_SERVER_URL` | `ws://game-server.example.com:8080` | Адрес игрового сервера для матчмейкинга |
| `REDIS_URL` | `redis://localhost:6380/0` | Подключение к Redis (матчмейкинг и pub/sub Action Cable) |
| `GAME_SERVER_URL` | `ws://localhost:8080` | Адрес игрового сервера для матчмейкинга |
| `MATCHMAKING_DEFAULT_RATING` | `1000` | Рейтинг по умолчанию для новых игроков |
| `MATCHMAKING_QUEUE_TTL` | `60` | Время ожидания соперника до таймаута, сек |
| `MATCHMAKING_PENDING_TTL` | `15` | Время ожидания финализации пары, сек |
| `MATCHMAKING_MATCH_TTL` | `120` | Время жизни данных матча, сек |
| `MATCHMAKING_TOKEN_TTL` | `120` | Время жизни `game_token`, сек |
| `MATCHMAKING_BASE_RANGE` | `100` | Начальный допуск по рейтингу |
| `MATCHMAKING_RANGE_PER_SECOND` | `50` | Рост допуска за секунду ожидания |
| `MATCHMAKING_MAX_RANGE` | `1000` | Максимальный допуск по рейтингу |
| `MATCHMAKING_SWEEP_INTERVAL` | `2` | Периодичность фоновой сверки очереди, сек |
| `CORS_ORIGINS` | `*` | Разрешённые origins для CORS (через запятую) |
| `SECRET_KEY_BASE` | из `credentials.yml.enc` | Секретный ключ для JWT |
@@ -341,8 +351,8 @@ TOKEN=$(curl -s -X POST http://localhost:3000/api/register \
curl -s http://localhost:3000/api/me \
-H "Authorization: Bearer $TOKEN"
# 3. Поиск матча (long-polling)
curl -s -X POST http://localhost:3000/api/matchmaking \
-H "Authorization: Bearer $TOKEN" \
--max-time 35 | jq .
# 3. Поиск матча — WebSocket (например, через wscat):
# wscat -c "ws://localhost:3000/cable?token=$TOKEN"
# > {"command":"subscribe","identifier":"{\"channel\":\"MatchmakingChannel\"}"}
# > {"command":"message","identifier":"{\"channel\":\"MatchmakingChannel\"}","data":"{\"action\":\"start\",\"rating\":1000}"}
```