/projects/dietbox-realtime
← Todos os projetosDietbox Realtime
Chat entre a nutricionista e o paciente, e qualquer outra coisa que precise chegar agora.
Engenheiro de Software Sênior · 2022
Visão geral
Isto existe para que duas pessoas possam conversar dentro do produto. A nutricionista e o paciente mantêm cada um uma conexão aberta, e uma mensagem enviada por um aparece na tela do outro sem que nenhum dos dois recarregue nada. O mesmo canal carrega as notificações da própria plataforma — qualquer coisa que precise alcançar alguém agora, e não no próximo carregamento de página. Trinta e quatro commits em dois meses de 2022, para um serviço que sobreviveu a ambos.
O que eu fiz
Construí este praticamente sozinho: trinta e três dos trinta e quatro commits, do relay de chat ao harness de carga que comprovou que ele aguentava.
- O relay de chat: uma mensagem emitida por um cliente é empurrada direto para a sala do destinatário, chegando a uma tela aberta em vez de esperar um recarregamento.
- O servidor de sockets embaixo dos dois: o handshake com segredo compartilhado, a atribuição de sala por id de usuário, o endpoint de notificação para onde a plataforma posta, e os endpoints de info e de saúde usados para observá-lo.
- A convenção de carregamento de handlers: um handler de evento é um arquivo, carregado automaticamente a partir de um diretório.
- O harness de teste de carga, construído para manter deliberadamente uma parte dos clientes em long-polling em vez de deixar todos fazerem upgrade.
O problema
A nutricionista e o paciente não tinham como conversar dentro do produto, e qualquer coisa que a plataforma precisasse avisar ficava esperando a pessoa recarregar a página. Colocar as conexões abertas dentro do monolito não era opção: ele subia uma vez por madrugada, então qualquer coisa que dividisse esse pipeline só podia ser mudada nesse horário. Conexões abertas também escalam com quantas pessoas estão online, enquanto requisições escalam com quantas chegam.
Arquitetura
Duas coisas chegam pelo mesmo caminho: uma mensagem de chat emitida por uma das duas pessoas conversando, ou um push de outro serviço da plataforma. Qualquer uma resolve para uma sala, e quem estiver naquela sala agora recebe.
- OriginOu uma das duas pessoas conversando emite uma mensagem de chat, ou outro serviço da plataforma posta uma sala, um nome de evento e um payload para o endpoint de notificação.
- Room resolvedO servidor verifica quais conexões estão de fato naquela sala agora.
- Fan-outO evento é empurrado para cada cliente atualmente na sala.
- BrowserO cliente recebe o evento e atualiza sem um refresh.
O que faz
- Chat entre a nutricionista e o paciente, carregado pela conexão que os dois já mantêm aberta.
- Notificações que a plataforma empurra para uma pessoa, chegando na tela que ela já tem aberta.
- Um endpoint de info reportando a contagem de conexões ao vivo, e um endpoint de saúde reportando a própria latência.
- Um handshake com segredo compartilhado que desconecta um cliente imediatamente se ele acabar sem entrar em nenhuma sala.
Decisões de engenharia
Tempo real como implantação própria
Dois motivos, ambos reais: conexões abertas e tráfego de requisição escalam em eixos diferentes, e o produto subia uma vez por madrugada, o que ditava o ritmo de tudo que estava dentro dele. Separá-lo em um serviço próprio deixou cada eixo escalar nos seus próprios termos, e deixou este subir no próprio ritmo.
Uma sala por id de usuário
O endereçamento é por identidade, não por conexão, então a plataforma consegue empurrar para uma pessoa sem saber quantas abas, dispositivos ou reconexões essa pessoa tem abertos no momento.
Handlers carregados automaticamente de um diretório
Adicionar um evento é adicionar um arquivo — não existe registro para lembrar de atualizar, nem handler que existe no código mas nunca foi ligado.
Um teste de carga que mantém clientes em long-polling
Nem todo cliente faz upgrade para um websocket. Um teste de carga em que todos fazem mede uma população que não existe, então o harness mantém deliberadamente uma parte dos clientes em long-polling via HTTP.