Ir para o conteúdo principal

Buscar no CooLWJ

选择搜索范围,输入关键词后按回车。

Idioma

Consumidores do rabbitmq “desaparecem sem motivo”

Contexto do problema:

Por volta das 17:12 do dia 1 do mês 9, recebemos uma reclamação da equipe de implantação: alguns dispositivos não conseguiam atualizar normalmente, não recebiam os comandos enviados pelo console, entre outros problemas. Um colega examinou os logs do projeto control (doravante chamado de control) e descobriu que o control não havia recebido as informações de sombra reportadas pelos dispositivos e, por isso, não havia enviado comandos. O projeto control se conecta diretamente aos dispositivos e, com base nas informações reportadas por eles, envia comandos e configurações, incluindo atualização, envio de logs etc. Antes da entrada do IoT em operação, o control dependia dos relatos de heartbeat para obter as informações atuais dos dispositivos; depois da entrada do IoT em operação, passou a depender das informações de sombra dos dispositivos

para obter as informações atuais do dispositivo, o control assina as informações da sombra do dispositivo, mas essas informações são encaminhadas pelo serviço de sombras (abreviado como IoT); ele não recebe diretamente os relatos da sombra enviados pelo dispositivo. Para ver os detalhes do fluxo, consulte os dois diagramas abaixo:

A aplicação (control) obtém o estado do dispositivo

A aplicação(control)envia comandos ao dispositivo

 

Quando soube que o control não estava recebendo mensagens de sombra, fui imediatamente ao console do rabbitmq verificar se havia mensagens e confirmar duas coisas: 1. se o dispositivo havia enviado mensagens 2. se o rabbitmq estava funcionando normalmente. As figuras 1 e 2 abaixo são duas capturas do console do rabbitmq feitas naquele momento, a partir de Figura 1 É possível confirmar claramente que o dispositivo está enviando mensagens, mas muitas delas estão unacked(indica que já foi entregue ao consumidor, mas ele ainda não enviou o ack; em teoria, basta esperar um pouco para voltar ao normal); para saber exatamente em qual fila estão se acumulando as mensagens unacked, consulte Figura 2,“spacebridgeiot-shadow” é justamente o que usamos para receber as informações de sombra enviadas pelos dispositivos. Como todas as mensagens estavam se acumulando na fila, fazia sentido que não fossem encaminhadas ao control. Depois de observar por algum tempo, percebemos que a quantidade de unacked havia caído para 0, mas o total praticamente não havia mudado. A impressão era de que as mensagens unacked tinham voltado para a fila, aguardando entrega. De fato, alguns minutos depois, voltamos a encontrar uma grande quantidade de mensagens unacked. Passados mais alguns minutos, essas mensagens unacked voltaram para a fila, e o control continuava sem receber mensagens. Ao consultar os logs do IoT, descobrimos, para nossa surpresa, que nenhuma mensagem de sombra estava chegando. No console do rabbitmq, consultamos “spacebridgeiot-shadow” Para nossa surpresa, essa fila ficou sem consumidores, como Figura 3 como mostrado. Nesse momento, ao consultar os logs do rabbitmq, de fato havia mensagens de erro, como Figura 4 como mostrado, o rabbitmq encerrou a conexão por iniciativa própria.

Figura 1:visão geral do rabbitmq

 

 

Figura 2:gráfico de estatísticas das filas do rabbitmq

 

 

Figura 3:Visão geral do spacebridgeiot-shadow

 

 

 Figura 4: mensagem de erro do rabbitmq

 

Solução temporária:

Como já haviam chegado muitas reclamações naquele momento, adotamos uma solução bastante drástica: “excluir as mensagens acumuladas”. Depois da exclusão, tudo realmente voltou ao normal(Observação:problemas em produção precisam ser resolvidos o mais rápido possível, não temos tempo para analisar os logs e depois resolvê-los de maneira ordenada, é preciso agir rápido)。

 

Identificador técnico do RabbitMQ

Reproduzindo o problema em um ambiente local:

1.Acumular uma grande quantidade de mensagens (pelo menos dezenas de milhares) na fila “mirrorTestQueue” do rabbitmq no ambiente 10.200.41.166

2.Pare o consumidor de mirrorTestQueue e reinicie-o depois que as mensagens se acumularem

3.Após concluir o acúmulo, reinicie o consumidor

 

Como imaginávamos, milhares de mensagens foram enviadas ao consumidor em poucos segundos. Após alguns minutos, o RabbitMQ encerrou por iniciativa própria a conexão com o consumidor; nesse momento, o consumidor da fila já não aparecia no console. Como configuramos a recuperação automática do consumidor, depois de um tempo ele se conecta novamente de forma automática,

Mas a conexão logo volta a cair, praticamente como no problema que encontramos em produção. Afinal, o que causou isso?Para ser sincero, na época eu não tinha muita ideia;procurei pela internet e também não encontrei uma resposta especialmente satisfatória(Na época, não identifiquei a essência do problema, e as palavras-chave que pesquisei eram amplas demais), posteriormente

Suspeitamos de algum problema na camada TCP e decidimos capturar pacotes para ver se encontrávamos alguma pista. E, de fato, tivemos sorte. Sem mais delongas, vamos direto à imagem.

 

 

 Antes das 13:06:25.643428, o rabbitmq continuava enviando mensagens ao consumidor. Foi às 13:06:25.643428 que a janela TCP do consumidor começou a ficar cheia. Isso durou cerca de 30 segundos, até que o rabbitmq encerrou a conexão por iniciativa própria (enviando um pacote RST). Depois, o consumidor se reconectou, a janela voltou a ficar cheia e, após cerca de 30 segundos, a conexão foi encerrada novamente.

 

Parece bem regular, dura 30s a cada vez, o que parece ser um parâmetro configurável. Resumindo, seria algo como “tcp full window levou o servidor a encerrar ativamente a conexão com RST, e isso ainda acontece de forma regular”

Desta vez, mudei as palavras-chave da pesquisa e encontrei Resposta,o RabbitMQ tem um parâmetro chamadotcp_listen_options.send_timeout é um parâmetro que controla o tempo limite de escrita; quando esse limite é excedido, ele aciona o RST do TCP(https://github.com/rabbitmq/rabbitmq-java-client/issues/341),faça uma alteração e veja como fica:

1. Alterar o tempo limite de escrita para 10s

tcp_listen_options.send_timeout = 10000

 2.Capturar os pacotes para verificar se funcionou

 

 Da saturação da janela até o fechamento da conexão, passam-se cerca de 10s.

 

Retrospectiva do ocorrido:

Como o consumidor do rabbitmq não tinha configurado o prefetch, o rabbitmq entregou mensagens demais de uma só vez ao consumidor, preenchendo sua janela TCP e acionando tcp_listen_options.send_timeout do rabbitmq. Quando esse tempo limite de escrita atinge determinado limiar, o rabbitmq desconecta a conexão TCP do consumidor.

 

Solução definitiva:

        Excluir mensagens anteriormente foi apenas uma solução de último recurso. Embora tenha resolvido o problema, foi uma medida drástica demais; precisamos encontrar uma solução elegante. Já que o excesso de mensagens enviadas ao consumidor fez a janela TCP ficar cheia, devemos trabalhar na taxa de recebimento: basta dizer ao rabbitmq, ao estabelecer a conexão, que não envie mensagens demais.(O trecho a seguir foi extraído de https://blog.csdn.net/james_searcher/article/details/70308565)O RabbitMQ tem uma propriedade chamada prefetch, que indica o número máximo de mensagens sem confirmação que um único consumidor pode consumir. Como entender isso? O MQ configura um buffer para cada consumidor, cujo tamanho é o valor de prefetch. Sempre que recebe uma mensagem, o MQ a envia ao buffer e depois ao cliente. Ao receber uma mensagem de ack (quando o consumidor emite a instrução baseack), o MQ libera uma posição no buffer e adiciona uma nova mensagem. Porém, se o buffer estiver cheio nesse momento, o MQ entrará em estado de bloqueio. Mais concretamente, suponha que prefetch esteja definido como 10 e haja dois consumidores. Isso significa que cada consumidor pré-buscará 10 mensagens da fila por vez, armazenando-as no cache local à espera de consumo. Ao mesmo tempo, o número de mensagens sem confirmação desse canal passa a ser 20. A ordem de entrega do Rabbit é a seguinte: primeiro entrega 10 mensagens ao consumer1, preenchendo sua capacidade, e depois entrega 10 mensagens ao consumer2. Se houver uma nova mensagem para entregar nesse momento, primeiro verifica se o número de mensagens sem confirmação do canal é igual a 20. Se for, a mensagem não será entregue a um consumidor e continuará na fila. Depois, quando um dos consumidores confirmar uma mensagem com ack, o número de mensagens sem confirmação passará a ser 19. O Rabbit então verifica qual consumidor tem menos de 10 mensagens sem confirmação e entrega a mensagem a ele.

 

No código, isso se traduz em

 

 

Como avaliar esse valor? Há um artigo oficial do RabbitMQ que explica isso muito bem, então não vou entrar em detalhes; atualmente, em nosso sistema, ele está definido como 20.

https://www.rabbitmq.com/blog/2012/05/11/some-queuing-theory-throughput-latency-and-bandwidth/

 

 

Considerações finais:

Ainda somos iniciantes no uso do rabbitmq. Antes de utilizá-lo, é essencial consultar bastante sua API, familiarizar-se com os usos das APIs mais comuns e fazer muitos experimentos em ambiente local.


Comentários

Enviando

Escolha seu tema

O tema é aplicado a todo o site.