- O Lichess é uma plataforma de xadrez gratuita e de código aberto com milhões de jogadores no mundo todo
- Usando a aba Network do Chrome DevTools para monitorar a comunicação entre cliente e servidor
Conexão WebSocket
- O primeiro comportamento de rede digno de nota é uma conexão WebSocket para uma URL semelhante a esta:
wss://socket2.lichess.org/play/H5uHz0egyvIA/v6?sri=bt6QzcyOiZg5&v=0
- O protocolo
wssindica uma conexão WebSocket criptografada usando TLS - WebSocket permite comunicação full-duplex, possibilitando atualizações em tempo real entre cliente e servidor sem requisições HTTP repetidas
Vez do jogador local
- Quando uma ação é executada, pacotes de dados são trocados:
// Enviado às 22:51:35.280
{
"t": "move",
"d": {
"u": "d2d4",
"l": 32,
"a": 1
}
}
- Mensagem recebida do servidor:
// Recebido às 22:51:35.312
{
"t": "ack",
"d": 1
}
- Isso informa que o servidor recebeu nossa ação
// Recebido às 22:51:35.312
{
"t": "move",
"v": 1,
"d": {
"uci": "d2d4",
"san": "d4",
"fen": "rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR",
"ply": 1,
"clock": {
"white": 300,
"black": 300
}
}
}
- Essa mensagem fornece informações detalhadas sobre a ação que realizamos e o estado atualizado da partida
Vez do oponente
- Quando o oponente joga, um pacote semelhante é recebido do servidor:
// Recebido às 22:51:43.489
{
"t": "move",
"v": 2,
"d": {
"uci": "d7d5",
"san": "d5",
"fen": "rnbqkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBQKBNR",
"ply": 2,
"dests": {
"c2": "c3c4",
"g2": "g3g4"
// Movimentos adicionais possíveis
},
"clock": {
"white": 300,
"black": 300
}
}
}
- O parâmetro
destslista todos os movimentos disponíveis a partir da posição atual
Arquitetura do Lichess
- O sistema de jogo em tempo real do Lichess é composto principalmente por dois serviços centrais (ambos escritos em Scala):
lila: serviço principal que gerencia lógica do jogo, estado, interações do usuário e outras funções centraislila-ws: serviço especializado no tratamento de WebSocket, atuando como ponte entre o cliente e olila
Visão geral da arquitetura
lila <-> redis <-> lila-ws <-> websocket <-> client
- O
lilase comunica com olila-wsvia Redis, e este gerencia as conexões WebSocket com os clientes
Comunicação com Redis Pub/Sub
- Eventos de jogada são publicados em canais Redis Pub/Sub, aos quais o
lilase inscreve para processar os movimentos - Redis Pub/Sub oferece entrega at-most-once. Perda de mensagens é possível, mas o uso de memória é reduzido
Persistência final dos dados com MongoDB
- O
lilasalva o estado da partida no MongoDB, mas não persiste cada jogada individual imediatamente - Em vez disso, ele faz buffer das jogadas e salva periodicamente para reduzir a carga no banco de dados
- Quando ocorre um evento importante, o estado da partida é descarregado
Entrando em uma partida em andamento
- Quando um jogador se conecta, ele fornece o parâmetro
vpara informar ao sistema a versão mais recente da partida que conhece - O
lila-wsusaConcurrentHashMappara rastrear e gerenciar todos os eventos das partidas em andamento
Encerramento
O processo de uma jogada no Lichess pode ser resumido assim:
- O cliente estabelece uma conexão WebSocket com o
lila-ws - Quando o jogador faz uma jogada, o cliente envia um evento de movimento para o
lila-ws - O
lila-wsresponde com umackconfirmando o recebimento do movimento - O evento da jogada é publicado em um canal Redis Pub/Sub e processado pelo
lila - O
lilarecebe a jogada, atualiza o estado da partida e finalmente a salva no MongoDB. O estado atualizado da partida é então enviado de volta ao cliente por meio dolila-ws - O cliente recebe o estado atualizado da partida, refletindo a nova jogada e as mudanças no estado do jogo
Opinião do GN⁺
- Este post analisa em detalhes a arquitetura de backend e o processo que tornam possível o gameplay em tempo real no lichess.org, uma popular plataforma de xadrez de código aberto
- Ele apresenta os principais elementos técnicos a considerar ao construir aplicações web em tempo real, como comunicação em tempo real com WebSocket, entrega escalável de mensagens via Redis Pub/Sub e armazenamento final de dados com MongoDB
- A arquitetura do Lichess é muito adequada para jogos multiplayer em tempo real, mas padrões e tecnologias semelhantes também podem ser aplicados a outros tipos de apps web em tempo real, como chats, ferramentas colaborativas e feeds de redes sociais
- Recursos em tempo real podem melhorar a experiência e a interação do usuário, mas também trazem desafios técnicos próprios, como escalabilidade, confiabilidade e consistência de dados. Este post oferece estratégias para lidar com esses desafios
- Projetos open source com stack semelhante incluem Socket.IO (framework para aplicações em tempo real baseado em Node.js) e RethinkDB (banco de dados NoSQL otimizado para apps web em tempo real)
- Esta análise do post não se baseia em uma revisão direta do código-fonte do Lichess, então a implementação real pode ser diferente. Ainda assim, os conceitos básicos e os padrões de arquitetura descritos continuam válidos
- Ao projetar sistemas em tempo real, é preciso considerar cuidadosamente se at-most-once (possibilidade de perda de mensagens) ou at-least-once (possibilidade de duplicação de mensagens) é mais adequado. Isso depende dos requisitos e trade-offs da aplicação
1 comentários
Comentários do Hacker News
Há reclamações sobre a estrutura de tempo do Chess.com. Parece que o servidor rastreia o tempo e ignora o tempo de transmissão e a latência. Isso é especialmente incômodo ao jogar partidas com limite de tempo no cliente móvel
O Lichess escolheu a abordagem do StackOverflow e usa servidores robustos
Calcular os movimentos no lado do servidor garante consistência e otimiza o desempenho para clientes com capacidade de processamento ou energia limitada
Falta explicação sobre como a perda de mensagens é tratada nos canais pub/sub do Redis
O parâmetro "l" pode indicar a latência observada pelo servidor
Surpreende que o servidor enumere e envie todos os próximos movimentos legais
Há perguntas sobre como proteger o servidor de WebSocket
Há dúvida sobre por que o protocolo precisa de ack
FEN codifica apenas o estado do tabuleiro, e não o estado da partida