Arquitetura de emissora
O Broadcaster é um servidor WebSocket que permite a comunicação em tempo real entre o backend e os clientes web. Ele alimenta placares ao vivo, atualizações de envio e notificações de esclarecimento.
Visão geral
flowchart LR
subgraph Backend
Grader[Grader]
PHP[PHP API]
end
subgraph Broadcaster
WS[WebSocket Server]
Channels[Channel Manager]
Auth[Auth Handler]
end
subgraph Clients
C1[Browser 1]
C2[Browser 2]
C3[Browser N]
end
Grader -->|HTTPS| WS
PHP -->|HTTPS| WS
WS --> Channels
Channels --> Auth
Auth <-->|WebSocket| C1
Auth <-->|WebSocket| C2
Auth <-->|WebSocket| C3
Eventos em tempo real
Tipos de eventos
| Evento | Descrição | Canal |
|---|---|---|
run_update |
Veredicto de envio alterado | Usuário, Concurso |
scoreboard_update |
Placar alterado | Concurso |
clarification |
Novo esclarecimento | Concurso, Problema |
contest_update |
Configurações do concurso alteradas | Concurso |
Estrutura de carga útil do evento
{
"type": "run_update",
"timestamp": 1704067200,
"data": {
"run_id": 12345,
"verdict": "AC",
"score": 1.0,
"contest_alias": "contest-2024",
"problem_alias": "sum-two"
}
}
Sistema de canais
Tipos de canais
| Padrão de canal | Descrição | Autenticação necessária |
|---|---|---|
/user/{username} |
Eventos específicos do usuário | Sim (proprietário) |
/contest/{alias} |
Eventos de concurso | Acesso ao concurso |
/contest/{alias}/admin |
Eventos administrativos | Administrador do concurso |
/problem/{alias} |
Eventos problemáticos | Acesso problemático |
/scoreboard/{token} |
Placar público | Token válido |
Fluxo de inscrição no canal
sequenceDiagram
participant C as Client
participant B as Broadcaster
participant A as Auth Service
C->>B: WebSocket Connect
B-->>C: Connected
C->>B: Subscribe /contest/abc
B->>A: Verify access
A-->>B: Authorized
B-->>C: Subscribed
Note over B: Event occurs
B->>C: Push event
Protocolo WebSocket
Conexão
const ws = new WebSocket('wss://omegaup.com/events/');
ws.onopen = () => {
// Authenticate
ws.send(JSON.stringify({
type: 'auth',
token: authToken
}));
};
Inscreva-se no canal
ws.send(JSON.stringify({
type: 'subscribe',
channel: '/contest/annual-2024'
}));
Cancelar inscrição
ws.send(JSON.stringify({
type: 'unsubscribe',
channel: '/contest/annual-2024'
}));
Receber eventos
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'run_update':
updateSubmissionStatus(data.data);
break;
case 'scoreboard_update':
refreshScoreboard();
break;
case 'clarification':
showClarificationNotification(data.data);
break;
}
};
Integração de back-end
Graduador para Emissora
Quando um envio é avaliado:
sequenceDiagram
participant R as Runner
participant G as Grader
participant B as Broadcaster
participant C as Client
R-->>G: Verdict result
G->>G: Update database
G->>B: POST /broadcast/
B->>B: Route to channels
B->>C: WebSocket push
PHP para emissora
Para esclarecimentos e atualizações do concurso:
// In Clarification Controller
\OmegaUp\Grader::getInstance()->broadcast(
contestAlias: $contest->alias,
problemAlias: $problem->alias,
message: json_encode([
'type' => 'clarification',
'data' => $clarification
]),
public: false,
username: $identity->username
);
Autenticação
Autenticação baseada em token
As conexões WebSocket são autenticadas usando:
- Token de autenticação: mesmo token da API REST (do cookie
ouat) - Token de placar: para URLs de placar público
Verificação de permissão
Para cada assinatura:
flowchart TD
Sub[Subscribe Request] --> CheckAuth{Authenticated?}
CheckAuth -->|No| Public{Public Channel?}
CheckAuth -->|Yes| CheckAccess{Has Access?}
Public -->|Yes| Allow[Allow]
Public -->|No| Deny[Deny]
CheckAccess -->|Yes| Allow
CheckAccess -->|No| Deny
Escalabilidade
Manipulação de conexão
- Cada instância do Broadcaster lida com milhares de conexões
- As conexões não têm estado (assinaturas armazenadas na memória)
- Pulsação a cada 30 segundos para detectar conexões inoperantes
Escala horizontal
flowchart TB
LB[Load Balancer]
LB --> B1[Broadcaster 1]
LB --> B2[Broadcaster 2]
LB --> B3[Broadcaster N]
Redis[(Redis Pub/Sub)]
B1 <--> Redis
B2 <--> Redis
B3 <--> Redis
Grader[Grader] --> Redis
Com múltiplas instâncias:
- O balanceador de carga distribui conexões WebSocket
- Redis Pub/Sub distribui eventos entre instâncias
- Qualquer instância pode publicar em qualquer canal
Configuração
Configuração da emissora
{
"Broadcaster": {
"Port": 32672,
"TLS": {
"CertFile": "/etc/omegaup/ssl/broadcaster.crt",
"KeyFile": "/etc/omegaup/ssl/broadcaster.key"
},
"EventsPort": 39613,
"PingInterval": 30,
"WriteTimeout": 10
},
"Redis": {
"URL": "redis://redis:6379",
"Channel": "omegaup:events"
}
}
Docker Compor
broadcaster:
image: omegaup/broadcaster
ports:
- "32672:32672" # Internal API
- "39613:39613" # WebSocket
depends_on:
- redis
environment:
- REDIS_URL=redis://redis:6379
Implementação do cliente
Serviço de front-end
O frontend Vue.js usa um serviço WebSocket:
class EventService {
private ws: WebSocket | null = null;
private subscriptions: Map<string, Set<Function>> = new Map();
connect(authToken: string): void {
this.ws = new WebSocket(EVENTS_URL);
this.ws.onopen = () => this.authenticate(authToken);
this.ws.onmessage = (e) => this.handleMessage(e);
}
subscribe(channel: string, callback: Function): void {
if (!this.subscriptions.has(channel)) {
this.subscriptions.set(channel, new Set());
this.ws?.send(JSON.stringify({
type: 'subscribe',
channel
}));
}
this.subscriptions.get(channel)!.add(callback);
}
private handleMessage(event: MessageEvent): void {
const data = JSON.parse(event.data);
const callbacks = this.subscriptions.get(data.channel);
callbacks?.forEach(cb => cb(data));
}
}
Monitoramento
Exame de saúde
curl https://broadcaster:32672/health
Métricas
Disponível em /metrics:
| Métrica | Descrição |
|---|---|
connections_active |
Conexões WebSocket atuais |
subscriptions_total |
Total de assinaturas ativas |
messages_sent_total |
Mensagens enviadas aos clientes |
messages_received_total |
Mensagens de back-ends |
Solução de problemas
Problemas de conexão
| Edição | Causa | Solução |
|---|---|---|
| Conexão recusada | Emissora desligada | Verifique o status do serviço |
| Falha na autenticação | Token inválido | Reautenticar |
| Nenhum evento | Não inscrito | Verifique a assinatura |
| Eventos atrasados | Latência da rede | Verifique a conexão |
Modo de depuração
Ative o registro detalhado:
{
"Logging": {
"Level": "debug",
"IncludeMessages": true
}
}
Código Fonte
O Broadcaster faz parte do repositório quark:
cmd/omegaup-broadcaster/- Ponto de entrada principalbroadcaster/- Lógica principal do WebSocket
Documentação Relacionada
- Recursos em tempo real - Visão geral dos recursos
- Modern Internals - Fonte do evento
- Infraestrutura - Integração Redis