Introdutório
Olá, Habr! Meu nome é Boris e neste trabalho compartilharei com vocês minha experiência na concepção e implementação de um serviço de mala direta como parte de um sistema abrangente de alerta de alunos para professores (doravante também referido como Ada), que também implemento.
Inferno
É então necessário negar o número de interrupções no processo educacional pelos seguintes motivos:
- Os professores não querem compartilhar detalhes de contato pessoais;
- Os alunos também são - eles simplesmente não têm muita escolha;
- Devido às especificidades da minha alma mater, muitos professores são forçados ou preferem usar dispositivos móveis sem acesso à Internet;
- Se você enviar mensagens através dos líderes dos grupos, então o efeito de um "telefone danificado" entra em ação, assim como o fator "ah, esqueci :(".
Ele está sendo executado sobre modo :
- O professor através de um dos canais de comunicação à sua disposição: SMS, Telegrama, SPA-aplicativo - envia para Ada o texto da mensagem e a lista de destinatários;
- A Ada transmite a mensagem recebida a todos os alunos interessados * por meio de diversos canais de comunicação.
* O acesso ao serviço é fornecido mediante inscrição voluntária.
É assumido que
- O número total de usuários não excederá dez mil;
- A proporção aluno - professor / membro da Corregedoria (reitores, posto de saúde, balcão de registo militar, etc.) será mantida ao nível de 10: 1;
- : « », « ))0» ..
- ;
- , , ;
- ;
- : - - , , .
Este trabalho consiste em cinco partes: introdutória, preparatória, conceitual, temática e final.
Você pode pular com segurança a parte preparatória se estiver familiarizado com a interpretação do Redis do padrão Pub / Sub, bem como com os mecanismos de eventos, script LUA e manipulação de chaves obsoletas, além disso, é altamente desejável ter pelo menos alguma ideia da arquitetura de microsserviço do software.
Na parte do assunto, o código em Python é revisado, mas acredito que haja informações suficientes para você escrever algo assim em qualquer coisa.
Preparatório
Muito bruto e muito abstrato ~ 5 minutos
Redis — [BSD 3-clause] , «-» ().
, .
, -, .
( ).
, , , LUA 5.1.
, .
, -, .
( ).
, , , LUA 5.1.
Detalhes e em primeira mão ~ 15 minutos
- Pub/Sub — Redis. , fire&forget , ,
PUBLISH,SUBSCRIBE-; - Redis Keyspace Notifications. ;
- EXPIRE — Redis. «How Redis expires keys»;
- Redis 6.0 Default Configuration File. . 939:948 (The default effort of the expire cycle…);
- EVAL — Redis.
EVALEVALSHA, «Atomicity of scripts», «Global variables protection» «Available libraries»,cjson; - Redis Lua Scripts Debugger. , . — ;
- . , .
Conceptual
Abordagem ingênua
A solução mais óbvia que você pode imaginar: vários métodos de entrega (
send_vk, send_telegrametc.) e um manipulador que os chamará com os argumentos necessários.
Problema de extensibilidade
Se quisermos adicionar um novo método de entrega, teremos que modificar o código existente, e esta é uma limitação da plataforma de software.
Problema de estabilidade
Um dos métodos quebrou = todo o serviço quebrou.
Problema aplicado
As APIs de diferentes canais de comunicação diferem significativamente umas das outras em termos de interação. Por exemplo, o VKontakte oferece suporte a envios em massa, mas não mais do que cem usuários por chamada. O Telegram não existe, mas permite mais ligações por segundo.
A API VK funciona apenas via HTTP; O Telegram tem um gateway HTTP, mas é menos estável que o MTProto e é menos documentado.
Existem muitas dessas diferenças: comprimento máximo da mensagem
random_id, interpretação e tratamento de erros, etc. etc.
Como lidar com isso?
Decidiu-se separar o processo de enfileiramento de mensagens e os processos de envio (doravante denominados correios) em nível organizacional, de modo que o primeiro nem mesmo suspeitasse da existência do último e vice-versa, e o Redis atuaria como um elo de conexão entre eles.
Não está claro? Pedir uma refeição!
Nesse ínterim, você está esperando - deixe-me apresentar a minha interpretação dessa nobre ação, começando com o design e terminando com a porta fechada atrás do correio.
- Você clica no grande botão amarelo "Encomendar";
- Yandex.Food encontra um mensageiro, informa o restaurante sobre os itens selecionados e retorna o número do pedido para você, a fim de diluir a incerteza das expectativas;
- Ao terminar o preparo, o restaurante atualiza o status do pedido e entrega a comida ao mensageiro;
- O mensageiro, por sua vez, dá a comida para você e, em seguida, marca o pedido como concluído.
Bom apetite!
Voltar ao design
É possível que o modelo dado no parágrafo anterior não corresponda totalmente à realidade, mas foi ela quem formou a base da solução desenvolvida.
Os dados associados ao número do pedido serão chamados de histórico , permitindo que você responda às seguintes perguntas a qualquer momento :
- Quem mandou;
- O que ele enviou;
- De onde;
- A quem;
- Quem tem e como.
O histórico é criado junto com o pedido como duas chaves Redis separadas, vinculadas por um sufixo:
suffix={ }:{UNIX- }
=history:{suffix}
=delivery:{suffix}
O pedido determina quando os transportadores verão o histórico uma vez, para alterar a resposta à pergunta “Quem o recebeu e como” após o envio.
A "visão" dos mensageiros funciona por meio de uma assinatura das
DELchaves do evento no formulário delivery:*.
Quando chega o momento da entrega, o Redis apaga a chave do pedido, após o que os entregadores começam a processá-la.
Como existem vários mensageiros, há uma grande probabilidade de competição no estágio de mudança da história.
Você pode evitá-lo definindo a operação correspondente atomicamente - no Redis, isso é feito por meio do script LUA.
Os detalhes da implementação serão discutidos em detalhes no próximo capítulo. Agora é importante ter uma ideia clara da solução como um todo, o que pode ser ajudado pela figura abaixo.
Status de rastreamento
O cliente pode rastrear o status de entrega por meio da chave de histórico, que é gerada por um método API separado do serviço que está sendo desenvolvido antes que a mensagem seja enfileirada (assim como o número do pedido é gerado por Yandex.Eda no início).
Depois que a chave é gerada, um rastreador com um tempo limite é pendurado (opcionalmente e também por um método separado), que monitorará o número de mudanças de histórico por mensageiros (
SETeventos). Só agora a mensagem está na fila.
Se o mensageiro não encontrar os contatos do destinatário em seu domínio - o canal de comunicação, ele aciona um evento artificial
SETpor meio do comando PUBLISH, mostrando que está “bem” e que não há mais necessidade de esperar.
Por que bagunçar eventos no Redis quando você tem RabbitMQ e Celery
Existem pelo menos cinco razões objetivas para isso:
O sistema de notificação (abrangente) é implementado na forma de um conjunto de microsserviços. Por conveniência, interfaces, métodos para inicializar camadas de dados, texto de erro, bem como alguns blocos de lógica repetitiva foram movidos para a biblioteca
core, que, por sua vez, depende de: gino(envoltório de asyncio SQLAlchemy) aioredise aiohttp.
Você pode ver diferentes entidades no código, por exemplo
User, Contactou Allegiance. As conexões entre eles são apresentadas no diagrama abaixo, uma breve descrição está sob o spoiler.
Sobre entidades ~ 3 minutos
— .
: , , . ., .
, : , Telegram, . .
[allegiance].
[supergroup].
[ownership] .
: , , . ., .
, : , Telegram, . .
[allegiance].
[supergroup].
[ownership] .
Gerando uma chave de histórico
delivery / handlers / history_key / get - GitHub
Fila
entrega / manipuladores / fila / colocação - GitHub
Observação:
- Comentário 171: 174;
- Que todas as manipulações com Redis [164: 179] são agrupadas em uma transação.
Visão dos mensageiros [94: 117]
núcleo / entrega - GitHub
Atualizando a história por mensageiros
core / redis_lua - GitHub As
instruções [48:60] não convertem listas vazias em dicionários (
[] -> {}), uma vez que a maioria das linguagens de programação, incluindo CPython, as interpretam de forma diferente de LUA.
ISS: Permitir a diferenciação de matrizes e objetos para serialização adequada de objetos vazios - GitHub
Tracker
entrega / manipuladores / faixa / pós - GitHub - implementação.
conectar / telegrama / manipuladores / selecionar - GitHub [101: 134] - exemplo de uso na interface do usuário.
Mensageiros
Qualquer entrega de
task_stream(@Sight Couriers) é tratada em uma corrotina assíncio separada.
A estratégia geral para lidar com as restrições de tempo das APIs é a seguinte: não contamos RPS (solicitações por segundo), mas corretamente / reagimos / respondemos às respostas por tipo
http.TooManyRequests.
Se a interface implementar, além de global (para o aplicativo), limites de tempo personalizados, eles serão processados na ordem da fila, ou seja, primeiro enviamos a todos que podemos e só então começamos a esperar, se não por muito tempo.
Telegrama
courier / telegram - GitHub
Conforme observado anteriormente, a interface MTProto do Telegram supera sua contraparte HTTP em termos de estabilidade e tamanho de documentação. Para interagir com ele, usaremos uma solução pronta, chamada LonamiWebs / Telethon .
Em contato com
courier / vk - GitHub
VKontakte API oferece suporte a e-mails em massa, passando uma lista de identificadores para o método messages.send (não mais que cem), e também permite que você "cole" até vinte e cinco
messages.sendem uma execução , o que nos dá 2.500 mensagens por chamada.
Fato curioso
API,
execute , .
O final
No presente trabalho, um método para organizar um sistema de alerta de massa multicanal é proposto. A solução resultante satisfaz a solicitação (@ requisitos-chave para o serviço de mala direta) da maioria das partes interessadas, e também pressupõe a possibilidade de expansão.
A principal desvantagem é o efeito do Pub / Sub dispare e esqueça, ou seja, se o apagamento da chave do pedido for necessário no momento da doença de um dos mensageiros, ninguém receberá nada no domínio correspondente, o que, no entanto, ficará refletido na história.