Paho MQTT Client (Javascript), Envoy, VerneMQ: depuração desconecta

Após a instalação do picockpit no meu servidor, foi observado um bug: o frontend da web se desconectava em intervalos muito regulares.

Isto não tinha estado presente no ambiente de desenvolvimento local. Usuários do Picockpit reportaram o mesmo bug.

Hoje fiz a depuração e apliquei uma correcção. Acontece que foi um problema de timing.

TL;DR como resolver isto

criar um keepAliveInterval mais curto para o Paho. 60 segundos é o padrão, mas com latências de rede isto levará a estes timeouts.

Aqui está o meu código para a conexão (dentro do pcp-code.js que é carregado pela página web):

/* https://www.eclipse.org/paho/files/jsdoc/Paho.MQTT.Client.html */
function MQTTconnect_inner(params){
     mqtt = novo Paho.Client(params.uri, params.clientid); /* definição de parâmetros não necessários, pois o JS não permite a passagem de parâmetros posicionais nomeados */
     mqtt.onConnectionLost = mqtt_onConnectionLost;
     mqtt.onMessageDelivered = mqtt_onMessageDelivered;
     mqtt.onMessageArrived = mqtt_onMessageArrived;
     mqtt.onConnected = mqtt_onConnect_proper;

    var opções = {
         tempo esgotado: 3, /* se a conecção não for bem sucedida dentro de 3 segundos, considera-se que falhou */
         onSuccess: mqtt_onConnect_connect,
         onFailure: mqtt_onConnectFailure_connect,
         userName: params.userName,
         senha: params.password,
         keepAliveInterval: 30,
         useSSL: é verdade,
         voltar a ligar: falso,
         mqttVersion: 4
     };
     mqtt.connect(opções);   
}

imagem

(reproduzido também como imagem)

Passos para a depuração

Calendário

Sentei-me com o meu telemóvel para cronometrar os intervalos, e ver se havia lá um padrão.

imagem

Na minha implementação, o Paho é reconectado pelo código JavaScript ao redor a cada 10 segundos no timeout.

imagem

Vemos o cliente Paho javascript MQTT a perder a ligação a cada minuto ou a cada dois minutos. Isto já é uma forte indicação de que isto é sistemático, possivelmente uma desconexão forçada pelo servidor.

console JavaScript

imagem

A mensagem de erro que eu vejo na consola é:

O WebSocket já está em estado FECHADO ou FECHADO.

Em retrospectiva, isto é possivelmente Paho tentando enviar o PINGREQ mas falhando apenas por alguns segundos ou possivelmente microssegundos, já que o servidor desconectou a tomada.

NB: Para uma compatibilidade superior com os firewalls da empresa, etc. Executo o Paho através de websockets.

Verificação com outro navegador

Já vi esta edição com o Chrome (meu navegador principal) e o Firefox. Portanto, não é devido a um problema de navegador.

(Fui levado a acreditar que este site poderia ser para alguns problemas de browser: https://github.com/socketio/socket.io/issues/3259)

Depuração VerneMQ

veja também:

Mostrar client_id completo e ponto de montagem para evitar truncagem:

vmq-admin sessão show -mountpoint -client_id

imagem

Procura um rasto:

vmq-admin trace client client-id=js.1567943187982KSWuLXb -mountpoint=5cc72b97a6b76f00013d35d2

imagem

Como você vê, você receberá um rastro do que é enviado para o cliente e recebido do cliente.

Traço inicial para 1 sessão existente para o cliente "js.1567943187982KSWuLXb" com PIDs

[]

MQTT ENVIAR: MP: "5cc72b97a6b76f00013d35d2" CID: "js.1567943187982KSWuLXb" PUBLISH(d0, q0, r0, m0, "pi/000000007eaefc47/com.picockpit/sensors/$state/pi") com carga útil:

{\i1}"wlan_signal_info".{\i} "Qualidade da ligação=61/70 Nível de sinal=-49 dBm", "cpu_load": 1.4, "net_io_bytes_received": 798582943, "root_partition_use": 2028572672, "net_io_bytes_sent": 1482989984, "soc_temperatu (truncado)

Eu também vi um PINGREQ() e PINGRESP(), mantendo a sessão viva nesse traço:

MQTT RECV: MP: "5cc72b97a6b76f00013d35d2" CID: "js.1567943187982KSWuLXb" PINGREQ()

MQTT ENVIAR: MP: "5cc72b97a6b76f00013d35d2" CID: "js.1567943187982KSWuLXb" PINGRESP()

Aqui está o aspecto da desconexão:

MQTT ENVIAR: MP: "5cc72b97a6b76f00013d35d2" CID: "js.1567943187982KSWuLXb" PUBLISH(d0, q0, r0, m0, "pi/00000000327f9cbf/com.picockpit/sensors/$state/pi") com carga útil:

{\i1}"wlan_signal_info".{\i} "Qualidade da ligação=68/70 Nível de sinal=-42 dBm", "ram_total_bytes": 969392128, "$uuid": “928ca964-d22e-11e9-9e22-dca632007438”, “disk_io_bytes_written”: 619947008, "root_partition_tota (truncado)

Sessão de rastreio para js.1567943187982KSWuLXb parado

Infelizmente há nenhuma informação que isto se deve a um tempo de espera por um pingreq() do cliente.

Enviado para a depuração

Veja também:

Como não havia informações de que isso se devia a um timeout pingreq, a suspeita recaiu sobre a desconexão do enviado devido a timeouts (possivelmente manipulando os websockets incorretamente devido a eu não ter configurado algumas configurações corretamente).

A configuração foi, no entanto, aplicada correctamente:

upgrade_configs:

   - upgrade_type: "websocket"

   habilitado: true

para o caminho correcto (/mqtt)

Não há timeouts além do connect_timeout para os backends (que foram definidos para valores diferentes, certamente não um minuto).

Para permitir a saída de depuração do envoy, você tem que adicionar uma variável de ambiente ao docker-compose.yaml:

ambiente:

  nível de registo: debug

imagem

como as últimas linhas da estrofe para o enviado de serviço

Aqui está o que eu vejo como vestígio:

':status', '101'.

'conexão', 'Upgrade'.

data', 'Dom, 08 Set 2019 12:08:36 GMT'.

sec-websocket-accept', 'mWAEi6p3RBFflHkNbxTMbnhaG4I='.

sec-websocket-extensions', 'permessage-deflate; client_max_window_bits=15'.

protocolo sec-websocket', 'mqtt'.

'servidor', 'enviado'.

'upgrade', 'websocket'.

'conteúdo-comprimento', '0'.


2019-09-08 12:08:38.757][1][debug][main] [fonte/servidor/servidor.cc:170] descarga de estatísticas

2019-09-08 12:08:43.760][1][debug][main] [fonte/servidor/servidor.cc:170] descarga de estatísticas

2019-09-08 12:08:48.755][1][debug][main] [fonte/servidor/servidor.cc:170] descarga de estatísticas

2019-09-08 12:08:53.759][1][debug][main] [fonte/servidor/servidor.cc:170] descarga de estatísticas

2019-09-08 12:08:58.762][1][debug][main] [fonte/servidor/servidor.cc:170] descarga de estatísticas

2019-09-08 12:09:03.762][1][debug][main] [fonte/servidor/servidor.cc:170] descarga de estatísticas

2019-09-08 12:09:08.762][1][debug][main] [fonte/servidor/servidor.cc:170] descarga de estatísticas

2019-09-08 12:09:13.765][1][debug][main] [fonte/servidor/servidor.cc:170] descarga de estatísticas

2019-09-08 12:09:18.766][1][debug][main] [fonte/servidor/servidor.cc:170] descarga de estatísticas

2019-09-08 12:09:23.766][1][debug][main] [fonte/servidor/servidor.cc:170] descarga de estatísticas

2019-09-08 12:09:28.765][1][debug][main] [fonte/servidor/servidor.cc:170] descarga de estatísticas

2019-09-08 12:09:33.766][1][debug][main] [fonte/servidor/servidor.cc:170] descarga de estatísticas

2019-09-08 12:09:37.190][26][debug][connection] [source/common/network/connection_impl.cc:520] [C9] remote Close

2019-09-08 12:09:37.190][26][depuração][conexão] [fonte/comum/rede/conexão_impl.cc:190] [C9] soquete de fechamento: 0

[2019-09-08 12:09:37.190][26][debug]

[fonte/comum/http/codec_client.cc:82] [C9] desconectar. resetando 1 pedidos pendentes

[2019-09-08 12:09:37.190][26][debug]

[fonte/comum/http/codec_client.cc:105] [C9] solicitar reset

2019-09-08 12:09:37.190][26][debug][roteador] [source/common/router/router.cc:868] [C8][S10834556124828578161] upstream reset: reset reason connection termination

2019-09-08 12:09:37.190][26][debug][http] [source/common/http/conn_manager_implpl.cc:187] [C8][S1083455656124828578161] doEndStream() resetting stream

[2019-09-08 12:09:37.191][26][debug][http] [source/common/http/conn_manager_implpl.cc:1596] [C8][S1083455656124828578161] stream reset

2019-09-08 12:09:37.191][26][debug][connection] [source/common/network/connection_impl.cc:101] [C8] closing data_to_write=0 type=2

2019-09-08 12:09:37.191][26][debug][connection] [source/common/network/connection_impl.cc:653] [C8] definindo temporizador de fechamento retardado com timeout 1000 ms

2019-09-08 12:09:37.191][26][debug][pool] [source/common/http1/conn_pool.cc:129] [C9] cliente desconectado, motivo da falha:

2019-09-08 12:09:38.192][26][depuração][conexão] [fonte/comum/rede/conexão_impl.cc:642] [C8] disparou o fechamento retardado

2019-09-08 12:09:38.192][26][debug][connection] [source/common/network/connection_impl.cc:190] [C8] soquete de fechamento: 1

[2019-09-08 12:09:38.192][26][depuração][conexão] [fonte/extensão/transporte_sockets/tls/ssl_socket.cc:270] [C8] Encerramento SSL: rc=0

[2019-09-08 12:09:38.192][26][debug][connection] [source/extensions/transport_sockets/tls/ssl_socket.cc:201] [C8]

2019-09-08 12:09:38.193][26][debug][main] [source/server/connection_handler_implpl.cc:80] [C8] adicionando à lista de limpeza

2019-09-08 12:09:38.767][1][debug][main] [fonte/servidor/servidor.cc:170] descarga de estatísticas

Isto coincide com a desconexão no cliente

imagem

Como você pode ver, isto é iniciado por

[C9] remoto Fechar

Isto mais uma vez lança a suspeita de que a VerneMQ desconecte o cliente devido a um problema de timeout.

Paho Opções de temporização

Uma boa fonte para ler sobre o MQTT e manter vivas é o Guia da Internet do Steve:

O cliente deve enviar pacotes keepalive (PINGREQ), na ausência de outros pacotes de controlo, e é responsável para o intervalo entre os pacotes que não excedam o valor do Keep Alive definido no corretor.

O cliente pode enviar os pacotes mais cedo

O próximo passo da investigação leva a olhar para o Paho e o Keepalive. Minha preocupação inicial era que Paho não enviasse keepalives sozinho, para que eu tivesse que fazer um loop e enviá-los.

Isto, no entanto, não era verdade - o Paho é que trata dos próprios Keepalives.

Aqui está o código fonte de Paho MQTT Javascript:

linha 703 e seguintes têm o "Pinger".

define uma função doTimeout, que por sua vez retorna doPing

doPing é definido um pouco mais abaixo, ele executa o ping real, e estabelece um timeout, com isto._keepAliveInterval.

this._client.socket.send(pingReq);
this.timeout = setTimeout(doTimeout(this), this._keepAliveInterval);

A esta altura, estou bastante certo de que o keepAliveInterval tem de ser diminuído para o Paho dos 60 segundos padrão. Possivelmente devido a latências de rede, o que funcionava para mim localmente não funciona mais remotamente.

como é que estabelecemos um intervalo de tempo diferente?

Documentação Paho:

A instância cliente Paho tem um método de conexão, que toma um objeto como seu parâmetro único.

O objeto tem atributos diferentes, sendo um deles o keepAliveInterval:

imagem

Eu defini este keepAliveInterval para 30 segundos. Aqui está o conjunto atual de parâmetros que eu uso:

var opções = {
     tempo esgotado: 3, /* se a conecção não for bem sucedida dentro de 3 segundos, considera-se que falhou */
     onSuccess: mqtt_onConnect_connect,
     onFailure: mqtt_onConnectFailure_connect,
     userName: params.userName,
     senha: params.password,
     keepAliveInterval: 30,
     useSSL: é verdade,
     voltar a ligar: falso,
     mqttVersion: 4
};
mqtt.connect(opções);   

Com mqtt sendo uma instância do Cliente Paho MQTT.