Informações secas | Baixo custo e pequenos erros, prática da Ctrip de fila de atraso sem servidor baseada em Kafka

Sobre o autor

Fixe, preste atenção às tecnologias nativas da nuvem, como RPC, Service Mesh e Serverless.

1. Fundo

À medida que o projeto de nuvem avança, um grande número de aplicativos precisa ser implantado na AWS, muitos dos quais dependem da função de fila de atraso. No aws, optamos por usar o Kafka como fila de mensagens, mas o próprio Kafka não oferece suporte a filas de atraso, então precisamos pensar em como implementar filas de atraso com base no Kafka.

2. Demanda

Após contabilizar todos os cenários que requerem a utilização de filas de atraso, temos as seguintes características:

  • O tempo de atraso não é fixo. Alguns tópicos precisam suportar um atraso de 5 minutos, enquanto outros precisam suportar um atraso de 7 dias.

  • O número de mensagens atrasadas é pequeno. O número de mensagens atrasadas envolvidas em todos os cenários por dia não excede 100 milhões e o tamanho de cada mensagem não excede 1 MB.

  • As mensagens atrasadas não podem ser perdidas e a ordem não é garantida.

  • O erro de atraso é pequeno. O erro de latência refere-se à diferença de tempo entre o momento em que a mensagem é realmente consumida e o momento em que se espera que a mensagem seja consumida. De acordo com cenários estatísticos de negócios, o erro de atraso deve estar dentro de 2 segundos.

  • O valor máximo das mensagens atrasadas na produção é relativamente alto. Em muitos casos, a empresa criará 10 milhões de mensagens atrasadas de uma só vez, e a duração do atraso dessas mensagens atrasadas é consistente.

3. Objetivo

Como existem muitas maneiras de implementar filas de atraso, definimos vários objetivos com base na premissa de atender às necessidades: baixo custo de nuvem, baixo custo de operação e manutenção, baixo custo de desenvolvimento, alta estabilidade e pequeno erro de atraso.

4. Seleção de produtos

Os produtos que oferecem suporte a filas de mensagens na AWS incluem RabbitMQ, Apache ActiveMQ e SQS. Entre eles, os aws RabbitMQ e Apache ActiveMQ hospedam principalmente sua instalação e implantação, em vez de fornecer serviços externos sem servidor. Além disso, atualmente optamos por usar Kafka como fila de mensagens. Se substituirmos a fila de mensagens apenas para atender à função da fila de atraso, o custo será obviamente enorme.

Além disso, a AWS também fornece SQS para suportar filas de atraso. Embora o SQS não tenha servidor, o SQS tem suas próprias limitações: o SQS suporta um atraso de até 15 minutos, o que obviamente não é capaz de atender às nossas necessidades.

Percebe-se que os produtos existentes baseados na nuvem não atendem mais às nossas necessidades, por isso começamos a investigar a implementação de mensagens atrasadas para ver se conseguimos atender às nossas necessidades com um pequeno desenvolvimento.

5. Planeje a pesquisa

Existem muitas soluções para implementar a função de fila de atraso na indústria. Realizamos uma análise simples sobre elas, como segue:

5.1 CoelhoMQ

RabbitMQ é implementado com base na fila de mensagens mortas TTL +. Especificamente, ao definir o TTL da mensagem, quando o TTL for atingido, a mensagem não será consumida e será entregue na fila de devoluções. Existem dois tipos de TTL:

  • TTL de nível de fila: TTL unificado para todas as mensagens

  • TTL em nível de mensagem: cada mensagem pode ter um TTL diferente, mas há um problema de bloqueio de cabeçalho

A vantagem desta solução é que ela é simples de implementar, mas o erro de atraso é incerto.

5.2Apache ActiveMQ

Apache ActiveMQ é implementado com base em agendamento agendado. Especificamente, o tempo de atraso ou expressão cron é configurado para representar a estratégia de entrega de mensagens, e a implementação do Timer baseada em Java armazena a mensagem hierarquicamente em arquivos e memória.

A vantagem desta solução é que ela é simples de implementar e o erro de atraso é controlável, mas pode ocupar muita memória.

5.3 FogueteMQ

RocketMQ é implementado com base no agendamento de tempo + nível de atraso. Especificamente, a mensagem atrasada é enviada para a fila de nível de atraso especificada (há 18 níveis no total) e, em seguida, um temporizador é usado para pesquisar esses ConsumeQueue para obter o efeito de atraso. A implementação específica é a seguinte:

  • Modifique o nome do tópico da mensagem e as informações da fila e entregue-os ao ConsumeQueue do nível correspondente de mensagens atrasadas.

  • ScheduleMessageService consome as mensagens em ConsumeQueue e as entrega novamente ao CommitLog.

  • Entregue as mensagens no CommitLog ao tópico de destino e o consumidor consumirá as mensagens no tópico de destino.

A vantagem desta solução é que o erro de atraso é controlável, mas a implementação é complexa.

5.4 Redis

Há muitas maneiras de implementar filas de atraso baseadas no Redis, duas das quais são brevemente descritas aqui:

1) Pesquisa regular

As etapas gerais do programa são as seguintes:

  • Use o carimbo de data/hora atrasado da mensagem como a chave de zset e o ID da mensagem como o valor de zset.

  • O ID da mensagem é usado como chave e o corpo da mensagem é serializado em String e armazenado no Redis como valor.

  • Pesquise zset regularmente. Se o tempo for maior que o horário atual, ele será entregue à Lista Redis para consumo do consumidor.

2) Ouvinte de expiração de chave

Defina um prazo de expiração para cada mensagem, monitore o evento de expiração e depois entregue a mensagem ao tópico de destino.

A vantagem de implementar uma fila de atraso baseada em Redis é que ela é simples de implementar, mas as mensagens podem ser perdidas e o custo de armazenamento é alto.

6. Plano de implementação

Como o uso de um único produto em nuvem não pode atender às nossas necessidades, só podemos considerar a implementação da função de fila de atraso baseada em Kafka por meio de um pequeno desenvolvimento e combinação dos recursos dos produtos em nuvem. As soluções de implementação específicas incluem o seguinte:

6.1 RabbitMQ ou Apache ActiveMQ

RabbitMQ ou Apache ActiveMQ são produtos suportados pela AWS e podem atender às necessidades de uma perspectiva funcional. A fila de mensagens atual é implementada com base no Kafka. Se combinada com RabbitMQ ou Apache ActiveMQ para implementar a função de fila de atraso, o principal problema é: a falta de reservas técnicas relacionadas ao RabbitMQ ou Apache ActiveMQ, porque AWS suporta apenas RabbitMQ ou Apache ActiveMQ. Ele é gerenciado apenas no nível de implantação. Quando surgem problemas, os desenvolvedores precisam resolvê-los eles mesmos. Portanto, esta opção não foi considerada.

6.2 Fila multinível baseada em SQS

Como o SQS já oferece suporte a filas de atraso de 15 minutos, se você quiser implementar uma fila de atraso mais longa, pode considerar o uso de uma fila de atraso de vários níveis? O plano de implementação específico é o seguinte:

  1. Adicione um campo times à mensagem de atraso para indicar em qual rodada ela está atualmente (com base na ideia do algoritmo da roda do tempo).

  2. Se o tempo de atraso da mensagem atrasada for inferior a 15 minutos, defina os tempos da mensagem atrasada como 0 e entregue-a diretamente ao SQS.

  3. Se o tempo de atraso da mensagem atrasada for superior a 15 minutos, calcule o valor dos tempos (tempo de atraso/15 minutos) e entregue-o diretamente ao SQS.

  4. Se o Consumidor consumir uma mensagem atrasada do SQS e o tempo for maior que 0, o valor do tempo será subtraído por 1 e entregue ao SQS novamente. Repita isso até que o tempo seja 0.

  5. Se o Consumidor consumir uma mensagem atrasada do SQS e o tempo for 0, significa que a mensagem atingiu o tempo de atraso e o Consumidor entregará a mensagem diretamente ao tópico de destino correspondente.

Embora esta solução possa realizar a função de fila de atraso e o próprio SQS não tenha servidor, o custo de manutenção também é relativamente baixo.

No entanto, investigamos os padrões de cobrança do SQS e descobrimos que o SQS cobra principalmente com base no número de mensagens. Desta forma, se o tempo de atraso for maior, o número de mensagens será amplificado de forma mais grave. Em nosso negócio real, o tempo de atraso não é de 15 minutos, geralmente de 1 hora a 7 dias, portanto esta solução não é viável.

6.3 Baseado em SQS e estratégia de agendamento de tempo

O maior problema com o uso de filas multiníveis baseadas em SQS é o problema de custo na nuvem e, mais especificamente, o problema de custo de armazenamento na nuvem. Como esta solução armazena todas as mensagens atrasadas no SQS, este é o principal motivo do aumento do custo. Neste caso, podemos considerar escrever mensagens com atraso superior a 15 minutos para um armazenamento de baixo custo, e então consultá-las e entregá-las ao SQS quando o atraso for inferior a 15 minutos. Desta forma, o tempo de atraso não afetará o custo do SQS, basta considerar como escolher um produto Serverless com baixo custo de armazenamento e fácil leitura e gravação como armazenamento de mensagens atrasadas.

Com base nesta ideia, foi desenhado um plano de implementação baseado em SQS e estratégia de escalonamento de tempo:

485d0820de6543ae17e6f10c8ad3516b.png

O processo específico é o seguinte:

  1. Mensagens normais produzidas por Produtores são entregues diretamente ao tópico de destino do Kafka. Se as mensagens forem atrasadas, elas serão entregues a um Tópico de Mensagem Atrasada de uma mensagem atrasada no Kafka.

  2. O consumidor consome mensagens no Delay Message Topic. Se o tempo de atraso da mensagem for inferior a 15 minutos, ela é entregue diretamente ao SQS (Delay Queue). Se o tempo de atraso da mensagem for superior a 15 minutos, grave a mensagem diretamente no Armazenamento de Mensagens.

  3. O Agendador verificará regularmente as mensagens no Armazenamento de Mensagens. Se o tempo de atraso for inferior a 15 minutos, ele será entregue diretamente ao SQS (Delay Queue). O Scheculer é acionado através do Event Bridge.

  4. O emissor consumirá a mensagem em SQS (Delay Queue) e entregará a mensagem ao tópico de destino.

Todo o processo não é complicado e os serviços AWS envolvidos são todos sem servidor, mas se houver muitos serviços envolvidos, a solução de problemas se tornará mais complicada.

Com base nas questões acima, melhoramos e simplificamos a implementação da solução, conforme segue:

fa7e6bb25e55260663e73542fa14df4a.png

O processo específico é o seguinte:

  1. Mensagens normais produzidas por Produtores são entregues diretamente ao tópico de destino do Kafka. Se as mensagens forem atrasadas, elas serão entregues a um Tópico de Mensagem Atrasada de uma mensagem atrasada no Kafka.

  2. O serviço consome mensagens no Delay Message Topic. Se o tempo de atraso da mensagem for inferior a 15 minutos, ela é entregue diretamente ao SQS (Delay Queue). Se o tempo de atraso da mensagem for superior a 15 minutos, grave a mensagem diretamente no Armazenamento de Mensagens.

  3. O Serviço verificará regularmente as mensagens no Armazenamento de Mensagens. Se o tempo de atraso for inferior a 15 minutos, ele será entregue diretamente ao SQS (Delay Queue).

  4. O serviço consumirá a mensagem em SQS (Delay Queue) e entregará a mensagem ao tópico de destino.

A solução simplificada concentra a lógica de Consumidor, Emissor e Agendador no serviço Service. O serviço Service é implantado em um cluster. Toda a lógica desta solução está no serviço Service, que é relativamente mais conveniente na hora de solucionar problemas. Depois de determinada a orientação geral do plano de implementação global, as seguintes questões precisam de ser refinadas:

1) Como as mensagens são armazenadas

Podemos observar que a principal função do Message Store é armazenar mensagens atrasadas com atraso superior a 15 minutos e disponibilizá-las para consulta pelo Agendador.A consulta é baseada no tempo. Existem muitos serviços que suportam armazenamento sem servidor.Depois de pesquisar, finalmente escolhi o DynamoDB.

A chave de partição no DynamoDB é o tempo de atraso, e a chave classificada seleciona o ID da mensagem.Isso garante que uma mensagem possa ser localizada exclusivamente por meio da chave de partição e da chave classificada sem conflito. Ao mesmo tempo, ao consultar, você só precisa consultar todas as mensagens no segmento de tempo com base na chave de partição, e não haverá pontos de acesso ou problemas de partição irregular.

Suponha que a chave de partição seja 1677400776 (que é o carimbo de data/hora de 2023-02-26 16:39:35, com precisão de segundos), então todas as mensagens correspondentes à chave de partição serão atrasadas de 2023-02-26 16:39:35 Mensagens entre 26/02/2023 16:39:36. Como cada mensagem possui um ID de mensagem exclusivo, definir a chave classificada como o ID da mensagem não causará conflitos de mensagens. O Agendador só precisa passar o carimbo de data / hora a ser consultado durante a consulta, podendo extrair todas as mensagens do período de tempo.Se nenhuma mensagem for consultada, significa que não há mensagens atrasadas no período de tempo.

Ao mesmo tempo, o TTL também é definido para mensagens no DynamoDB para excluir dados automaticamente. O tempo TTL definido é 24 horas a mais que o tempo de atraso, o que serve principalmente para facilitar a solução de problemas. Quando a mensagem atrasada no DynamoDB é entregue ao SQS, a API é chamada para excluir a mensagem. A estrutura de dados das mensagens no DynamoDB também inclui tópico, corpo da mensagem e outras informações.

2) Problema de ponto único

O problema de ponto único ocorre principalmente porque ao verificar mensagens atrasadas armazenadas no DynomaDB por mais de 15 minutos, há um problema com o Agendador que recebe a notificação de verificação, e as mensagens neste período de tempo não são entregues ao SQS, resultando em perda de mensagens . Agora as funções do Agendador estão integradas ao serviço de Serviço, e o serviço de Serviço é implantado em um cluster, portanto, não há nenhum problema pontual para o Agendador.

Mas outro problema precisa ser resolvido: como garantir que apenas um Agendador no cluster verifique os dados no DynamoDB e, quando ocorrer um problema com o Agendador, outros Agendadores no cluster possam continuar a executar?

Para resolver este problema: usamos a fila FIFO do SQS. SQS oferece suporte a dois tipos de filas, uma é a fila padrão e a outra é a fila FIFO. A fila FIFO pode garantir estritamente a ordem das mensagens e apoiar a visibilidade das mensagens, ou seja, a mensagem só pode ser visível para um consumidor dentro de um período de tempo, e outros consumidores não podem acessá-la. Ao mesmo tempo, a fila FIFO do SQS também suporta a função de desduplicação. Com base nesses recursos da fila FIFI do SQS, é mais fácil resolver problemas de ponto único. O plano de implementação específico é o seguinte:

  1. Inicie um cronômetro no serviço de serviço para entregar regularmente mensagens de notificação à fila SQS FIFO uma vez por minuto. O corpo da mensagem de notificação é o carimbo de data/hora da hora atual, com precisão de minuto. Dessa forma, mesmo que n Timers entreguem n mensagens para a fila SQS FIFO no mesmo minuto, apenas uma mensagem será entregue com sucesso à fila SQS FIFO e n-1 mensagens serão filtradas pela função de desduplicação do SQS FIFO fila. Perdido.

  2. A visibilidade das postagens na fila FIFO do SQS é definida para 5 minutos (configurável). É garantido que apenas um Agendador pode consumir mensagens de notificação em 5 minutos. Se o Agendador falhar, outros Agendadores poderão continuar a consumir mensagens de notificação. Quando o Agendador consome uma mensagem de notificação, ele irá convertê-la em um carimbo de data/hora com base no conteúdo da mensagem, consultar todas as mensagens dentro deste intervalo de carimbo de data/hora no DynamoDB, modificar o tempo de atraso da mensagem, entregá-la à fila padrão do SQS e, finalmente, exclua a mensagem de notificação SQS This na fila FIFO.

Com base na solução acima, problemas pontuais podem ser resolvidos muito bem.

3) Problema de perda de mensagens

Como o Timer e o Scheduler estão no serviço Service, ambos são problemas de cluster e não há um único ponto de problema. Além disso, a fila FIFO do SQS pode garantir que as mensagens sejam estritamente ordenadas, para que não haja problema de perda de mensagens. O único problema possível é o atraso excessivo das mensagens devido ao grande acúmulo de mensagens.

4) Como consultar mensagens atrasadas

A mensagem consultada pelo Agendador deve satisfazer o tempo de atraso da mensagem inferior a 15 minutos, portanto após receber a mensagem de notificação e convertê-la no carimbo de data / hora correspondente, ele pode consultar a mensagem com o carimbo de data / hora atual + 14 minutos (mensagens atrasadas não podem exceder 15 minutos).

5) Como implantar o serviço de serviço

Para serviços de serviço, usamos ECS+Fargate para implantá-los. Toda a implantação do código é implementada por meio de scripts Terraform para criar recursos como Code Pipeline, DynamoDB, SQS e ECS. Todos os recursos são implementados por meio de código. O design de toda a solução de implantação é baseado na ideia do gitOps.

Após avaliação abrangente de múltiplas soluções, finalmente escolhemos uma solução baseada em SQS e estratégias de agendamento de tempo para implementar mensagens atrasadas.

6.4 Otimização de desempenho

Durante a prática do esquema acima, muitas otimizações foram feitas, que podem ser resumidas aproximadamente nos seguintes pontos:

1) Atraso de mensagens

Mensagens atrasadas que precisam ser processadas levarão a um acúmulo de mensagens devido à capacidade de consumo insuficiente. A otimização deste problema parte principalmente dos seguintes aspectos:

  • A partição do Delay Message Topic está definida como 64. A melhoria da capacidade de consumo do Kafka pode ser alcançada adicionando consumidores, mas a premissa é garantir que o número de partições seja maior ou igual ao número de consumidores.

  • Reduza a configuração do serviço e aumente o número de cópias do serviço Service. O cluster de serviço consome mensagens no tópico de mensagem de atraso. Quanto mais cópias, mais forte será a capacidade de consumo.

2) WCU e RCU no DynamoDB

Grande parte dos custos do DynamoDB são calculados por meio de WCU e RCU. WCU refere-se ao número de mensagens escritas por unidade de tempo e RCU refere-se ao número de mensagens lidas por unidade de tempo. Se o número de mensagens escritas por unidade de tempo exceder o limite do WCU, a escrita da mensagem falhará e a leitura da mensagem também falhará.

Se tanto a WCU quanto a RCU forem definidas para valores de pico, isso certamente não causará falhas de leitura e gravação, mas resultará em um enorme desperdício de custos. Para este fim, definimos a WCU e a RCU para expandir e contrair dinamicamente a capacidade. Se ocorrer uma falha durante a expansão, uma nova tentativa será realizada. Depois de otimizar os parâmetros relevantes, um status quo ideal pode agora ser alcançado.

3) Configurações de expansão e contração do ECS

A menor unidade em execução no ECS é uma tarefa. Cada tarefa precisa se expandir rapidamente e diminuir lentamente. O maior problema encontrado na expansão rápida de tarefas é que leva muito tempo para ativar o Serviço. Para o serviço de serviço, usamos golang para implementá-lo. A expansão de uma tarefa pode ser basicamente concluída em 8 segundos. A expansão e a contração são definidas com base no uso máximo da CPU. Cada expansão expandirá 4 tarefas e cada vez a capacidade será reduzida em 1 tarefa.

4) Processamento de suavização de mensagens

Como o valor máximo das mensagens gravadas no Delay Message Topic pode ser relativamente grande, se essas mensagens forem consumidas rapidamente, a pressão subsequente de leitura e gravação no DynamoDB será relativamente alta. Portanto, ao consumir mensagens no Delay Message Topic do Kafka, será controlado o número de mensagens consumidas por cada Serviço. Embora vários serviços sejam consumidos ao mesmo tempo, para um único serviço o número de mensagens escritas é pequeno. Para o DynamoDB, cada gravação é relativamente suave e não grava uma grande quantidade de dados de uma só vez, então a gravação falha A probabilidade será muito menor.

6.5 Resultados práticos

Ele está funcionando de forma estável no ambiente de produção há 6 meses e todos os indicadores estão relativamente saudáveis. Os dados das últimas 4 semanas foram extraídos.

1) Taxa de sucesso de mensagens atrasadas

c8ec56d762b6c74e1931847f1f5e7565.png

Conforme mostrado na figura acima, a taxa de sucesso de mensagens atrasadas com erro de atraso dentro de 2 segundos é basicamente 100%.

2) Número de mensagens atrasadas

1b93370a85dfe0daf3feaac1d057e0af.png

Conforme mostrado na figura acima, o valor máximo de mensagens atrasadas atinge 150.000 em 5 minutos, o que significa que o valor máximo é de 500 mensagens atrasadas por segundo.

3) Indicadores de desempenho do DynamoDB

cf24c60f9eb4412a2f6797bec051ee05.png

Pode ser visto no indicador PutItem ThrottledRequests que não há falhas de gravação ao escrever mensagens por meio do DynamoDB. Pode ser visto no indicador QueryThrottledRequests que não há falha de consulta ao consultar mensagens por meio do DynamoDB. Como pode ser visto no indicador QueryReturnedItemCount, o valor máximo de mensagens atrasadas é de 3.350 mensagens em 5 minutos, o que é inferior a 60 mensagens por segundo. Isso ocorre porque armazenamos mensagens de gravação em buffer no Serviço, reduzindo assim a pressão simultânea de leitura e gravação.

4) Atraso de mensagens Kafka

f4406cd3abba9f069ab3dcdc113c367f.png

Conforme mostrado na figura acima, o pico de backlog de mensagens no Kafka é de 60.000 em 5 minutos, e o backlog de mensagens pode ser consumido rapidamente.

5) Indicadores de desempenho do temporizador

f964e22f41206b5128996e070cdb3913.png

O temporizador entregará uma mensagem na fila FIFO do SQS a cada minuto, e o número de mensagens é igual ao número de cópias do Serviço. Como se pode verificar na figura acima, são entregues no máximo 300 mensagens em 5 minutos (porque o número máximo de cópias do Serviço é 64). Mas a última mensagem recebida foi de apenas 5 mensagens recebidas em 5 minutos, ou seja, 1 mensagem foi recebida em 1 minuto.

7. Resumo

Como esta implementação é inteiramente baseada em Serverless, o custo de manutenção é muito baixo. Embora um tanto complexo de desenvolver, este é um investimento de custo único. A julgar pelos dados dos últimos meses, o custo do uso da nuvem não excede US$ 200 por mês, o erro e o atraso são relativamente pequenos e a operação geral até agora é relativamente estável.

[Leitura recomendada]

3d089904607c83f3a243aad4bde29354.jpeg

 Conta pública “Ctrip Technology”

  Compartilhe, comunique, cresça

Acho que você gosta

Origin blog.csdn.net/ctrip_tech/article/details/131466933
Recomendado
Clasificación