在线联机原型全集:第 24 章 动态赛事系统(Dynamic Tournament System)

第 24 章:动态赛事系统(Dynamic Tournament System),介绍了一个简单的动态赛事系统游戏原型,包括实时事件驱动、自动调度、排名积分系统、多赛制支持、跨时区排程与积分结算一致性等功能。

动态赛事系统(Dynamic Tournament System)

  • 类别:实时事件驱动 + 自动调度 + 排名积分系统
  • 目标:验证 Job Queue 调度、FSM 状态流转、跨时区排程与积分结算一致性
  • 原型代号proto-024-dynamic-tournament
  • 依赖模块proto-020-lobby-portal
  • 推荐语言栈:Go / Java / Rust
  • 协议栈:HTTP + WebSocket + JobQueue / EventBus + Redis / Kafka
  • 关键主题:赛程(Schedule)、积分(Scoring)、自动调度(Auto-Scheduling)、最终一致性(Eventual Consistency)

24.1 系统目标与约束

目标定义

  1. 多赛制支持

    • Round Robin(循环赛)
    • Swiss System(瑞士制)
    • Single/Double Elimination(单败 / 双败淘汰)
    • League(积分联赛)
    • Ladder(天梯排名制)
  2. 自动化赛程调度(Auto Scheduling)

    • 动态时区匹配
    • 跨区房间分配
    • 异常重排与补赛(Reschedule / Replay)
    • 延迟处理与弃权判定(No-show / Timeout)
  3. 积分与排名系统(Scoring & Ranking)

    • Elo / Glicko-2 动态积分计算
    • 积分制 + 净胜分制(Points / Goal Diff)
    • 破同规则(Tiebreakers)
    • 排名快照(Leaderboard Snapshot)
  4. 一致性保障与幂等处理

    • Job Queue + 事务外发(Transactional Outbox)
    • 幂等键(Idempotency Key)防重复执行
    • 异常回滚与状态修复(Rollback FSM)
  5. 可观测性与运维

    • Job 执行监控与延迟指标
    • 赛程执行 SLA
    • 自动预警与重试机制

24.2 领域建模(Domain Modeling)

核心实体(Entities)

实体说明
Tournament赛事主档(基础信息、赛制类型、状态、配置)
Stage阶段/轮次(分组结构、时间区间、依赖前置阶段)
Bracket淘汰树(单败/双败结构节点)
Match单场对局信息(参赛方、时间、状态、结果)
Participant参赛者信息(玩家/战队、积分、历史记录)
Standing积分榜(积分、净胜分、胜负比、破同规则)
Job异步任务(类型、执行状态、重试策略、日志)
ScheduleSlot可用资源窗口(时间、房间、服务器)
RuleSet规则定义(积分、弃权、结算、处罚等)

关系结构

  • Tournament 1 - N Stage
  • Stage 1 - N Match
  • Match 关联 2 个 Participant
  • StandingMatchResult 事件流中增量更新(Event-Sourced)

关键约束

  • 赛事元数据(Meta)要求强一致性(ACID)
  • 赛程与积分结果支持最终一致性(Eventual Consistency)
  • 任何跨分区或跨时间段任务由 Job Queue 调度触发

24.3 赛制与积分体系(Formats & Scoring System)

24.3.1 主要赛制规则

赛制特性适用场景
Round Robin全循环对阵;总场次 = N×(N−1)/2小规模正式赛事
Swiss System胜者组对阵胜者组;轮数 ≈ log₂N中大型赛事
Single Elimination一败淘汰;最短时间完成淘汰赛、杯赛
Double Elimination双败容错;上下分区高水平锦标赛
League / Ladder持续积分制;可长期维护天梯、排名赛

24.3.2 积分与破同(Scoring & Tiebreakers)

积分规则示例

结果得分
+3
+1
0
弃权-1

破同顺序示例(Tiebreak Order)

  1. 总积分(Total Points)
  2. 直接对战胜者(Head-to-Head)
  3. 净胜分(Point Differential)
  4. Buchholz 系数(Opponent Strength)
  5. 随机抽签或加赛(Toss/Extra Round)

动态 Elo 更新公式

$$
E_A = \frac{1}{1 + 10^{(R_B - R_A)/400}}
$$

$$
R’_A = R_A + K \times (S_A - E_A)
$$

其中

  • (R_A) = 当前评分
  • (S_A) = 实际结果(胜=1,平=0.5,负=0)
  • (E_A) = 预期结果
  • (K) = 调整系数

24.4 状态机(FSM)设计

24.4.1 赛事主状态机

stateDiagram-v2
    [*] --> Draft : create_tournament
    Draft --> Registration : publish
    Registration --> Seeding : lock_registration
    Seeding --> Scheduling : build_brackets
    Scheduling --> Ongoing : round_ready
    Ongoing --> Paused : manual_pause / outage_detected
    Paused --> Ongoing : resume
    Ongoing --> Completed : all_matches_resolved
    Ongoing --> Cancelled : admin_cancel
    Completed --> Archived : archive

24.4.2 单场对局状态机

stateDiagram-v2
    [*] --> Pending
    Pending --> Scheduled : assign_slot
    Scheduled --> RoomAlloc : allocate_server
    RoomAlloc --> Live : start_signal
    Live --> Resolving : game_over
    Resolving --> Settled : commit_result
    Resolving --> Disputed : dispute_open
    Disputed --> Settled : arbiter_commit
    Settled --> [*]

24.5 Job 调度体系(Job Scheduling System)

24.5.1 Job 类型

类型说明
BUILD_BRACKETS构建分组、种子、对阵表
ALLOCATE_SLOTS分配时间与服务器资源
MATERIALIZE_ROUND实体化某轮比赛(生成房间)
LOCK_MATCH锁定比赛、阻止重复启动
SETTLE_MATCH结算比赛、记录积分
ROLLBACK_MATCH回滚异常状态
UPDATE_STANDINGS更新排行榜
PUBLISH_LEADERBOARD推送积分榜
RESCHEDULE_MATCH延期/补赛任务
CLOSURE赛事冻结与归档

24.5.2 优先级与队列模型

  • critical:结算类任务(SETTLE、LOCK)
  • sched:赛程调度类任务(ALLOCATE、BUILD)
  • low:榜单更新类(STANDINGS、LEADERBOARD)

采用 per-key FIFO ordering:同一 Tournament 内强制串行,跨赛事并行执行。
Worker 线程数可动态扩展(例如 Go 协程池 32×N 核)。

24.5.3 幂等与事务外发

type Job struct {
    ID               string
    Type             string
    Payload          []byte
    IdempotencyKey   string
    State            string // pending, running, success, failed
    RetryCount       int
    CreatedAt        time.Time
}
  • 幂等键规则:tournament_id:job_type:round:match_id
  • 执行完成后写入 Outbox 表
  • Outbox 消费器(Outbox Consumer)异步推送事件到 Kafka 或 Redis Stream
  • 保证「不丢失、不重放、不并发

24.6 数据库结构(Schema)

CREATE TABLE tournaments (
  id BIGSERIAL PRIMARY KEY,
  name VARCHAR(128),
  format VARCHAR(32),
  timezone VARCHAR(64),
  state VARCHAR(16),
  rule_set JSONB,
  created_at TIMESTAMP,
  updated_at TIMESTAMP
);

CREATE TABLE matches (
  id BIGSERIAL PRIMARY KEY,
  tournament_id BIGINT,
  stage_id BIGINT,
  round INT,
  participant_a BIGINT,
  participant_b BIGINT,
  scheduled_at TIMESTAMP,
  state VARCHAR(16),
  result JSONB,
  UNIQUE(tournament_id, round, participant_a, participant_b)
);

CREATE TABLE standings (
  tournament_id BIGINT,
  participant_id BIGINT,
  wins INT,
  losses INT,
  points INT,
  elo FLOAT,
  PRIMARY KEY (tournament_id, participant_id)
);

24.7 自动调度逻辑(Auto-Scheduling Algorithm)

Heuristic 策略

  1. 时区优先匹配:选手 UTC 偏移相近优先分组
  2. 服务器资源约束:优先分配低延迟、带宽稳定机房
  3. No-Show 防御:连续弃权自动标记降级(Penalty)
  4. 并行窗口控制:同轮最多并发 N 场,避免资源爆满
  5. 边缘调度补偿:调度失败重试间隔指数递增(Exponential Backoff)

调度伪代码示例

func ScheduleRound(tournamentID int64, round int) error {
    matches := repo.FindPendingMatches(tournamentID, round)
    for _, m := range matches {
        slot := slotFinder.FindAvailableSlot(m)
        if slot == nil {
            queue.DelayRetry(m.ID, 10*time.Minute)
            continue
        }
        repo.AssignSlot(m.ID, slot)
        queue.Enqueue(Job{
            Type: "LOCK_MATCH",
            IdempotencyKey: fmt.Sprintf("%d:LOCK_MATCH:%d", tournamentID, m.ID),
        })
    }
    return nil
}

24.8 积分结算与排行榜(Scoring & Leaderboard)

结算流程

  1. game_overResultEvent → Job: SETTLE_MATCH
  2. 写入结果表(ResultLog)
  3. 更新 Standing
  4. 推送 LeaderboardUpdated 事件
  5. WebSocket 通知前端刷新榜单

结算事务一致性

采用 分布式事务 + Outbox 模式

  • 本地事务:更新 match → standings → 写入 outbox
  • 异步消费:消息总线(Kafka / Redis Stream)处理榜单推送
  • 重试策略:失败重放 ≤ 3 次,幂等验证 via IdempotencyKey

24.9 监控与告警(Observability & Alerting)

指标说明
job_latency_avgJob 平均执行延迟
job_retry_totalJob 重试次数统计
schedule_backlog调度积压任务数
settle_fail_rate结算失败率
leaderboard_delay榜单更新时间延迟

Prometheus + Grafana 可实现 Job 实时监控,延迟超阈自动触发告警。

24.10 异常恢复与回滚(Rollback & Recovery)

  1. 状态不一致恢复

    • 通过事件日志(Event Log)与状态快照(Snapshot)重建 FSM。
    • 比较 match.state 与 leaderboard.last_update 时间差,必要时补偿。
  2. 重放机制(Replay Mechanism)

    • Job 失败 → DelayQueue 重放
    • Outbox 消息重复消费 → 幂等验证阻断重复写入
  3. 人工干预接口

    • 管理员可触发 rollback_matchforce_settle
    • 所有操作记录入审计日志(Audit Log)。

24.11 系统部署与扩展性(Deployment & Scalability)

  • 水平扩展:Job Worker 可横向扩容;同一赛事强制分区串行。
  • 跨区部署:分赛事集群(Region Cluster) + 中央排行榜同步。
  • Serverless 模式:每场比赛可动态创建短生命周期 RoomPod。
  • 灾备策略:主赛事服 + 冷备节点;使用 CDC + WAL 增量恢复。

24.12 总结(Summary)

动态赛事系统是整个游戏生态中最复杂的异步调度核心之一。
通过 Job + FSM + Outbox 模式 的结合,可以实现:

  • 精确的状态流转与容错调度;
  • 跨时区公平的赛程自动分配;
  • 并发安全的积分与榜单一致性;
  • 可观测的任务执行链与 SLA 保障;
  • 可扩展的分布式赛事平台架构。

核心理念
「让复杂的赛事调度像流水线一样自动运转。」

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页