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);
}
(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.
Na minha implementação, o Paho é reconectado pelo código JavaScript ao redor a cada 10 segundos no timeout.
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
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:
- https://pi3g.com/2019/08/08/vernemq-how-to-disconnect-clients-forceably-per-command-line/
- https://docs.vernemq.com/administration/managing-sessions
Mostrar client_id completo e ponto de montagem para evitar truncagem:
vmq-admin sessão show -mountpoint -client_id
Procura um rasto:
vmq-admin trace client client-id=js.1567943187982KSWuLXb -mountpoint=5cc72b97a6b76f00013d35d2
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
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][client] [source/common/http/codec_client.cc:82] [C9] disconnect. resetting 1 pending requests
[2019-09-08 12:09:37.190][26][debug][client] [source/common/http/codec_client.cc:105] [C9] request 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
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:
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.