docs: add routing.md — balancing modes and routing rules

This commit is contained in:
Maksim Totmin 2026-06-25 22:54:37 +07:00
parent f0262dde3a
commit 0784e8fcea
4 changed files with 111 additions and 1 deletions

View File

@ -69,6 +69,7 @@ curl -X POST http://localhost:8080/api/auth/login \
| [docs/architecture.md](docs/architecture.md) | Архитектура, диаграмма потоков данных, стек, структура проекта |
| [docs/api.md](docs/api.md) | Полная справка REST API + WebSocket: auth, route, nodes, trunks, users, monitoring, примеры |
| [docs/scoring.md](docs/scoring.md) | Scoring engine: lethal-условия, weighted score, пример расчёта, кэширование |
| [docs/routing.md](docs/routing.md) | Маршрутизация: режимы балансировки (best / weighted_random), routing rules (node / pool / auto) |
| [docs/agent.md](docs/agent.md) | pulse-lets-go-agent: collectors, конфигурация, деплой, troubleshooting |
| [docs/deployment.md](docs/deployment.md) | Production деплой (8 шагов), systemd unit, security, rollback |
| [docs/freeswitch.md](docs/freeswitch.md) | Интеграция с FreeSWITCH: route.lua, dialplan, ESL, предварительные проверки |

View File

@ -16,6 +16,7 @@ Roles: `admin` (full access), `viewer` (read-only nodes/metrics).
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `GET` | `/api/route` | | Route a call. Query: `caller_id`, `dest`, `ingress_trunk` |
| `PUT` | `/api/route/mode` | admin | Set balancing mode: `{"mode":"best\|weighted_random"}` — см. [Routing](routing.md#balancing-modes) |
| `GET` | `/api/health` | | Stats: total nodes, healthy, requests, uptime |
| `GET` | `/api/health/live` | | Liveness probe (always 200) |
| `GET` | `/api/health/ready` | | Readiness probe (NATS + metrics) |
@ -23,9 +24,11 @@ Roles: `admin` (full access), `viewer` (read-only nodes/metrics).
### Route Response (success)
```json
{ "node_id": "pbx-03", "score": 88, "sip_gateway": "sip:pbx03.lan:5060" }
{ "node_id": "pbx-03", "score": 88, "sip_gateway": "sip:pbx03.lan:5060", "matched_rule": "none" }
```
`matched_rule` — имя сработавшего routing rule, либо `"none"` если применился глобальный scoring.
### Route Response (fallback — all nodes unhealthy)
```json

104
docs/routing.md Normal file
View File

@ -0,0 +1,104 @@
# Routing
Маршрутизация звонков состоит из двух уровней:
1. **Routing Rules** — priority-based правила с regex-матчингом (first-match-wins)
2. **Balancing Mode** — стратегия выбора ноды внутри правила или глобально
## Routing Rules
Правила хранятся в `data/routes.json` и проверяются в порядке `Priority` (меньше = раньше). Первое совпавшее правило определяет маршрутизацию. Если правило не сработало (нода мертва / пул пуст) — переход к следующему. Если ни одно правило не совпало — глобальный `PickNode()`.
### Match Conditions (AND-логика)
Все непустые поля должны совпасть одновременно. Пустые поля — не проверяются.
| Поле | Тип | Описание |
|------|-----|----------|
| `match_caller_id` | regex | Caller ID звонящего |
| `match_destination` | regex | Номер назначения |
| `match_ingress_trunk` | regex | ID входящего транка |
| `match_days` | string | Дни недели: `"1-5"` (пн-пт), `"1,3,5"` (пн,ср,пт), `"6"` (сб). Воскресенье = 0 или 7 |
| `match_time_start` | `"HH:MM"` | Начало временного диапазона |
| `match_time_end` | `"HH:MM"` | Конец временного диапазона |
### Action Types
| Тип | Поле | Описание |
|-----|------|----------|
| `node` | `action_node_id` | Маршрутизация на конкретную ноду. Если нода dead/stale/lethal — fallthrough к следующему правилу |
| `pool` | `action_pool_ids` | Выбор лучшей ноды из заданного списка (по текущему режиму балансировки). Пустой/нездоровый пул — fallthrough |
| `auto` | — | Fallthrough к глобальному `PickNode()` с учётом режима балансировки |
### Пример правила
```json
{
"id": "rule-001",
"name": "VIP caller to dedicated pool",
"enabled": true,
"priority": 10,
"match_caller_id": "^7495",
"match_destination": "",
"match_ingress_trunk": "",
"match_days": "1-5",
"match_time_start": "09:00",
"match_time_end": "18:00",
"action_type": "pool",
"action_pool_ids": ["pbx-01", "pbx-02"],
"description": "VIP номера 7495 в бизнес-часы на выделенные ноды"
}
```
## Balancing Modes
Режим балансировки определяет, **как** выбирается нода из множества healthy-нод (после отсева lethal). Применяется как в глобальном `PickNode()`, так и внутри `pool`-правил.
### best
Выбирает ноду с **максимальным score** из кэша. O(1) — `bestScore` предрассчитывается при каждой метрике.
- Всегда одна и та же нода при одинаковых метриках — максимально нагружает лучшую ноду
- Хорошо для тестирования и сценариев где нужна предсказуемость
### weighted_random (по умолчанию)
Выбирает ноду **пропорционально score**: чем выше score, тем выше вероятность быть выбранной. O(n) — сканирует все ноды.
Алгоритм:
1. Отсеивает disabled, stale, lethal (score < 0)
2. Суммирует score всех кандидатов (`totalScore`)
3. `pick = rand(0, totalScore)`, идёт по кандидатам, вычитая score
4. Если `totalScore <= 0` — равновероятный выбор
- Распределяет нагрузку пропорционально готовности
- Предсказуемость ниже, но утилизация выше — менее загруженные ноды получают пропорционально меньше звонков
### Переключение режима
**Через API:**
```
PUT /api/route/mode
Authorization: Bearer <admin-jwt>
{"mode": "best"}
```
**Через конфиг:**
```json
{ "balance_mode": "weighted_random" }
```
Допустимые значения: `"best"`, `"weighted_random"`. API-эндпоинт сразу применяет режим и сохраняет в `config.json`.
## Ответ /api/route
В ответе маршрутизации появилось поле `matched_rule` — имя сработавшего правила (или `"none"` если правило не применилось):
```json
{
"node_id": "pbx-03",
"score": 88,
"sip_gateway": "sip:pbx03.lan:5060",
"matched_rule": "VIP to dedicated"
}
```

View File

@ -50,6 +50,8 @@ score = 83.2×0.40 + 57.5×0.30 + 35.0×0.20 + 99.5×0.10 = 70.1
`best_node_id`, `best_score`, and `fallback_active` are recalculated on every metric update.
`/api/route` reads them under RLock — O(1) constant time.
Выбор ноды из кэша зависит от [режима балансировки](routing.md#balancing-modes).
## Configuration
```json