Mikhail Salosin. Golang Meetup. Usando Go no back-end do aplicativo Watch +

Mikhail Salosin (daqui em diante - MS): - Olá pessoal! Meu nome é Michael. Trabalho como desenvolvedor de back-end na MC2 Software e falarei sobre o uso do Go no back-end do aplicativo móvel Watch +.



Alguém presente gosta de hóquei?



Então este aplicativo é para você. É para Android e iOS, usado para assistir a transmissões de vários eventos esportivos on-line e em gravações. Também no aplicativo, existem várias estatísticas, transmissões de texto, tabelas para conferências, torneios e outras informações úteis para os fãs.



Também no aplicativo existem momentos em vídeo, ou seja, é possível ver os momentos agudos das partidas (gols, lutas, tiroteios, etc.). Se você não quiser assistir a transmissão inteira, poderá assistir apenas os mais interessantes.

O que foi usado no desenvolvimento?


O corpo principal foi escrito em Go. A API com a qual os clientes móveis se comunicaram foi escrita em Go. Além disso, um serviço foi gravado no Go para enviar notificações push para dispositivos móveis. Também tivemos que escrever nosso próprio ORM, sobre o qual algum dia falaremos. Bem, alguns pequenos serviços estão escritos no Go: redimensionamento e upload de imagens para os editores ...

Usamos o Postgres (PostgreSQL) como um banco de dados. A interface para editores foi escrita em Ruby on Rails usando a gema ActiveAdmin. A importação de estatísticas do provedor de estatísticas também é gravada no Ruby.

Para testes de API do sistema, usamos o Python unittest (Python). O Memcached é usado para controlar solicitações de pagamento da API, Chef para controlar a configuração, Zabbix para coletar e monitorar estatísticas internas do sistema. Graylog2 - para coletar logs, o Slate é uma documentação da API para clientes.



Seleção de protocolo


O primeiro problema que encontramos: tivemos que escolher um protocolo para a interação do back-end com clientes móveis, com base nos seguintes pontos ...

  • O requisito mais importante: os dados dos clientes devem ser atualizados em tempo real. Ou seja, todos os que estão assistindo à transmissão no momento devem receber atualizações quase que instantaneamente.
  • Por simplicidade, aceitamos que os dados sincronizados com os clientes não sejam excluídos, mas ocultos usando sinalizadores especiais.
  • Todos os tipos de solicitações raras (como estatísticas, escalações, estatísticas da equipe) são recebidas por solicitações GET comuns.
  • Além disso, o sistema deveria suportar calmamente 100 mil usuários ao mesmo tempo.

Com base nisso, tivemos duas opções de protocolo:
  1. Websockets. Mas não precisamos de canais do cliente para o servidor. Nós só precisamos enviar atualizações do servidor para o cliente, portanto, um soquete da Web é uma opção redundante.
  2. Eventos enviados pelo servidor (SSE) surgiram da maneira certa! É bastante simples e basicamente satisfaz tudo o que precisamos.

Eventos Enviados pelo Servidor


Algumas palavras sobre como isso

funciona ... Ele funciona em cima da conexão http. O cliente envia uma solicitação, o servidor responde com Tipo de conteúdo: texto / fluxo de eventos e não fecha a conexão com o cliente, mas continua a gravar dados na conexão: Os



dados podem ser enviados em um formato acordado com os clientes. No nosso caso, enviamos da seguinte forma: no campo de evento, o nome da estrutura alterada (pessoa, jogador) foi enviado e no campo de dados - JSON com novos campos alterados para o jogador.

Agora, sobre como a própria interação funciona.
  • Primeiro, o cliente determina quando a última sincronização foi feita com o serviço: ele analisa seu banco de dados local e determina a data da última alteração registrada a partir dele.
  • Ele envia uma solicitação com esta data.
  • Em resposta, enviamos a ele todas as atualizações que ocorreram nesta data.
  • Depois disso, ele faz uma conexão com o canal ao vivo e não fecha até precisar das atualizações:



Enviamos a ele uma lista de mudanças: se alguém marcou um gol - mudamos o placar da partida, se machucou - também foi enviado em tempo real. Assim, no fluxo de eventos da partida, os clientes recebem instantaneamente dados relevantes. Periodicamente, para que o cliente entenda que o servidor não morreu, que nada aconteceu, enviamos um carimbo de data / hora a cada 15 segundos - para que ele saiba que tudo está em ordem e que não há necessidade de se reconectar.

Como a conexão ao vivo é servida?


  • Primeiro de tudo, criamos um canal no qual as atualizações com um buffer virão.
  • Depois disso, assinamos este canal para receber atualizações.
  • Defina o cabeçalho correto para que o cliente saiba que está tudo bem.
  • ping. timestamp .
  • , . timestamp, , .



O primeiro problema que encontramos foi o seguinte: para cada conexão aberta com o cliente, criamos um timer que funcionava uma vez a cada 15 segundos - acontece que se tivéssemos 6 mil conexões com uma máquina (com um servidor API), 6 mil temporizadores foram criados. Isso levou ao fato de a máquina não conter a carga necessária. O problema não era tão óbvio para nós, mas eles nos ajudaram um pouco e nós o eliminamos.

Como resultado, agora temos ping vindo do mesmo canal do qual a atualização vem.

Por conseguinte, existe apenas um temporizador que marca uma vez a cada 15 segundos.

Aqui estão algumas funções auxiliares - enviando o cabeçalho, ping e a própria estrutura. Ou seja, o nome da tabela (pessoa, partida, temporada) e as informações sobre esse registro são transmitidas aqui:



Mecanismo para envio de atualizações


Agora, um pouco sobre de onde vêm as mudanças. Temos várias pessoas, editores, que assistem à transmissão em tempo real. Eles criam todos os eventos: alguém foi removido, alguém foi ferido, algum tipo de substituição ...

Com a ajuda do CMS, os dados entram no banco de dados. Depois disso, o banco de dados usando o mecanismo Listen / Notify notifica os servidores da API sobre isso. Os servidores de API já estão enviando essas informações aos clientes. Portanto, de fato, temos apenas alguns servidores conectados ao banco de dados e não há carga especial no banco de dados, porque o cliente não interage diretamente com o banco de dados de nenhuma maneira:



PostgreSQL: Ouvir / Notificar


O mecanismo Listen / Notify no Postgres permite notificar os assinantes de eventos que algum evento foi alterado - algum tipo de registro foi criado no banco de dados. Para fazer isso, escrevemos um gatilho e uma função simples:



ao inserir ou alterar um registro, chamamos a função de notificação no canal data_updates, transferimos o nome da tabela e o identificador do registro que foi alterado ou inserido lá.

Para todas as tabelas que devem ser sincronizadas com o cliente, definimos um gatilho que, após alterar / atualizar o registro, chama a função indicada no slide abaixo.
Como a API se inscreve nessas mudanças?

O mecanismo Fanout é criado - ele envia mensagens para o cliente. Ele coleta todos os canais do cliente e envia as atualizações que recebeu através desses canais:



Aqui, a biblioteca pq padrão, que se conecta ao banco de dados e diz que deseja ouvir o canal (data_updates), verifica se a conexão está aberta e está tudo bem. Eu omito a verificação de erros para economizar espaço (não marque sobrecarregado).

Em seguida, configuramos o Ticker de forma assíncrona, que enviará o ping a cada 15 segundos e começaremos a ouvir o canal no qual assinamos. Se obtivemos um ping, publicamos esse ping. Se recebemos um registro, publicamos esse registro para todos os assinantes deste Fanout.

Como funciona o fan-out?


Em russo, isso se traduz como um "divisor". Temos um objeto que registra assinantes que desejam receber atualizações. E assim que chega uma atualização para esse objeto, ela se espalha a todos os assinantes que possui. Simples:



Como é implementado no Go:



Existe uma estrutura, é sincronizada usando Mutexes. Possui um campo que salva o status da conexão Fanout no banco de dados, ou seja, no momento em que escuta e receberá atualizações, além de uma lista de todos os canais disponíveis - mapa, cuja chave é o canal e a estrutura na forma de valores (na verdade, ele não utilizado de forma alguma).

Dois métodos - Conectado e Desconectado - permitem que você diga ao Fanout que temos uma conexão com a base, ela apareceu e que a conexão com a base está desconectada. No segundo caso, você precisa desconectar todos os clientes e dizer a eles que eles não podem mais ouvir nada e que se reconectam, pois a conexão com eles foi encerrada.

Há também um método de inscrição que adiciona um canal aos ouvintes:



existe um método de cancelamento de inscrição que remove um canal dos ouvintes se o cliente for desconectado, bem como um método de publicação que permite enviar uma mensagem a todos os assinantes.

Pergunta: - O que é transmitido através deste canal?

MS: - É transmitido um modelo que foi alterado ou efetuado ping (essencialmente apenas um número, número inteiro).

EM:- Você pode enviar qualquer coisa, publicar qualquer estrutura, ela se transforma em JSON e é isso.

MS: - Recebemos uma notificação do Postgres - ele contém o nome e o identificador da tabela. Pelo nome da tabela que obtemos e pelo identificador, obtemos o registro que precisamos, e essa estrutura já é enviada para publicação.

A infraestrutura


Como é a aparência em termos de infraestrutura? Temos 7 servidores de ferro: um deles é completamente dedicado à base, os computadores virtuais estão girando nos seis restantes. Existem 6 cópias da API: cada máquina virtual com a API é executada em um servidor iron separado - isso é confiável.



Temos dois frontends nos quais o Keepalived está instalado para melhorar a acessibilidade, para que, no caso, um frontend possa substituir o outro. Mais duas cópias do CMS.

Há também um importador de estatísticas. Existe um DB Slave a partir do qual os backups são feitos periodicamente. Existe o Pigeon Pusher - o aplicativo que envia pushies aos clientes, além de itens de infraestrutura: Zabbix, Graylog2 e Chef.

De fato, essa infraestrutura é redundante, porque 100 mil podem ser atendidos com menos servidores. Mas havia ferro - nós o usamos (nos disseram que é possível - por que não).

Profissionais de Go


Depois de trabalharmos nesse aplicativo, essas vantagens óbvias do Go foram reveladas.
  • Biblioteca http legal. Usando-o, você pode criar bastante já pronto para uso.
  • Além disso, os canais que nos permitiram implementar com facilidade o mecanismo de envio de notificações aos clientes.
  • O maravilhoso detector de corrida nos permitiu eliminar vários bugs críticos (infraestrutura de teste). Tudo o que funciona na preparação está em execução, compilado com a tecla Race; e, consequentemente, podemos ver quais problemas potenciais temos na infraestrutura de preparação.
  • Minimalismo e simplicidade da linguagem.




Estamos à procura de desenvolvedores! Se alguém quiser - por favor.

Questões


Pergunta da platéia (doravante - B): - Parece-me que você perdeu um ponto importante em relação ao Fan-out. Entendo corretamente que, quando você envia uma resposta a um cliente, você é bloqueado se o cliente não quiser ler?

MS: - Não, não estamos bloqueando. Primeiro, temos tudo por trás do nginx, ou seja, não há problemas com clientes lentos. Em segundo lugar, o cliente possui um canal com um buffer - na verdade, podemos colocar até cem atualizações lá ... Se não podemos gravar no canal, ele o exclui. Se percebermos que o canal está bloqueado, fechamos o canal e é isso: o cliente se reconectará se houver algum problema. Portanto, em princípio, o bloqueio não ocorre aqui.

P: - Você poderia enviar imediatamente para ouvir / notificar um registro, não uma tabela de identificadores?

EM:- Ouvir / Notificar tem um limite de 8 mil bytes por pré-carregamento, que ele envia. Em princípio, seria possível enviar se estivéssemos lidando com uma pequena quantidade de dados, mas me parece que a maneira [como fazemos] é simplesmente mais confiável. As limitações estão no próprio Postgres.

P: - Os clientes recebem atualizações nas correspondências nas quais não estão interessados?

EM:- em geral sim. Por via de regra, há 2-3 partidas em paralelo e, em seguida, muito raramente. Se o cliente está assistindo alguma coisa, geralmente ele está assistindo a partida que está acontecendo. Em seguida, no cliente, há um banco de dados local no qual todas essas atualizações são adicionadas e, mesmo sem uma conexão com a Internet, o cliente pode ver todas as correspondências anteriores para as quais ele possui atualizações. De fato, sincronizamos nosso banco de dados no servidor com o banco de dados local do cliente para que ele possa trabalhar offline.

P: - Por que você fez seu ORM?

Alexey (um dos desenvolvedores do "Watch +"):- Naquela época (foi há um ano), o ORM era menor do que agora, quando existem muitos deles. Da maioria dos ORMs existentes, o que eu mais não gosto é que a maioria deles trabalha em interfaces vazias. Ou seja, os métodos que esses ORMs estão prontos para enfrentar qualquer coisa: estrutura, ponteiro de estrutura, número, algo irrelevante ...

Nosso ORM gera estruturas com base no modelo de dados. Própria. E, portanto, todos os métodos são concretos, não usam reflexão etc. Eles aceitam estruturas e esperam usar as estruturas que vêm.

P: - Quantas pessoas participaram?

MS: - Na fase inicial, duas pessoas participaram. Em algum lugar de junho, começamos, em agosto a parte principal estava pronta (primeira versão). Em setembro houve um lançamento.

AT:- Onde você descreve o SSE, você não usa o tempo limite. Por que é que?

MS: - Para ser sincero, o SSE ainda é um protocolo html5: o padrão SSE foi projetado para se comunicar com os navegadores, pelo que entendi. Possui recursos adicionais para que os navegadores possam se reconectar (e assim por diante), mas não precisamos deles, porque tínhamos clientes que poderiam implementar qualquer lógica para conectar e receber informações. Provavelmente não fizemos o SSE, mas algo semelhante ao SSE. Este não é o protocolo em si.
Não havia necessidade. Tanto quanto eu entendo, os clientes implementaram o mecanismo de conexão do zero. Em princípio, eles não se importaram.

P: - Quais utilitários adicionais você usou?

EM:- Os mais ativos que usamos govet e golint, para que o estilo fosse unificado, assim como o gofmt. Eles não usaram mais nada.

Q: - Com o que você depurou?

MS: - Em geral, a depuração foi realizada usando testes. Sem depurador, GOP não usamos.

P: - Você pode retornar o slide em que a função Publicar está implementada? Nomes de variáveis ​​de uma letra não incomodam?

MS: - Não. Eles têm um escopo bastante "estreito". Eles não são usados ​​em nenhum outro lugar (exceto aqui) (exceto no interior desta classe) e é muito compacto - são necessárias apenas 7 linhas.

P: - De alguma forma, ainda não é intuitivo ...

MS:- Não, não, este é um código real! Não é sobre estilo. É apenas uma classe utilitária e muito pequena - existem apenas três campos dentro da classe ...



MS: - Em geral, todos os dados sincronizados com os clientes (partidas sazonais, jogadores) não são alterados. Grosso modo, se formos fazer outro tipo de esporte no qual será necessário mudar a partida, consideraremos tudo na nova versão do cliente e as versões antigas do cliente serão banidas.

P: - Existem pacotes de terceiros para gerenciamento de dependências?

MS: - Nós usamos go dep.

P: - Havia algo sobre o vídeo no assunto do relatório, mas não sobre o vídeo no relatório.

MS: - Não, não tenho nada no tópico sobre o vídeo. É chamado de "Look +" - este é o nome do aplicativo.

AT:- Você disse que está transmitindo para os clientes? ..

MS: - Não realizamos streaming de vídeo. Isso foi completamente feito pelo Megaphone. Sim, eu não disse que o aplicativo é megafone.

MS: - Vá - para enviar todos os dados - por pontuação, por eventos de jogos, estatísticas ... Go - este é o back-end inteiro do aplicativo. O cliente deve descobrir em algum lugar qual link usar para o jogador, para que o usuário possa assistir à partida. Temos links para vídeos e transmissões que são preparados.


Um pouco de publicidade :)


Obrigado por ficar com a gente. Você gosta dos nossos artigos? Deseja ver materiais mais interessantes? Ajude-nos fazendo um pedido ou recomendando aos seus amigos o VPS na nuvem para desenvolvedores a partir de US $ 4,99 , um analógico exclusivo de servidores de nível básico que foi inventado por nós para você: Toda a verdade sobre o VPS (KVM) E5-2697 v3 (6 núcleos) 10 GB DDR4 480 GB SSD 1 Gbps de US $ 19 ou como dividir o servidor? (as opções estão disponíveis com RAID1 e RAID10, até 24 núcleos e até 40GB DDR4).

Dell R730xd 2 vezes mais barato no data center Equinix Tier IV em Amsterdã? Somente nós temos 2 TVs Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV a partir de US $ 199 na Holanda!Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - a partir de $ 99! Leia sobre Como criar um prédio de infraestrutura. classe c usando servidores Dell R730xd E5-2650 v4 que custam 9.000 euros por um centavo?

All Articles