Node-redis: retry_strategy não redefine após liberar o erro

Criado em 18 fev. 2017  ·  21Comentários  ·  Fonte: NodeRedis/node-redis

  • Versão : node_redis 2.6.5, redis 3.2.7
  • Plataforma : Node.js 7.5.0 no Mac OS 10.12.3
  • Descrição :

Estou usando o seguinte código

const redis = require('redis');

const client = redis.createClient({
  url: 'redis://localhost:6379',
  retry_strategy: options => new Error('Fail')
});

setInterval(() => client.incr('some-key', console.log), 5000);

executando contra um redis local ( docker run --rm -p 6379:6379 redis )

Depois de ver a primeira saída, mato o redis para simular a desconexão. Quando vi o erro entrar reinicio o redis. A conexão não é estabelecida novamente. Eu esperaria que, depois de liberar o erro para os manipuladores offline, o cliente tentasse se reconectar no próximo comando. em vez disso, ele permanece no estado fechado. Também está retornando com AbortError vez de new Error('Fail') .

Feature Request

Comentários muito úteis

Atualização rápida aqui.
Eu adicionei enable_offline_queue = false às minhas opções createClient e parece ter feito o que eu queria, que é:
"Continue tentando se reconectar ao meu servidor Redis com base na função retry_strategy , mas imediatamente lance erros em resposta às tentativas de usar o cliente nesse ínterim. Então, se conseguir reconectar, comece a trabalhar novamente "

Para sua informação: Mostra o seguinte erro em uma chamada de get : AbortError: SET can't be processed. Stream not writeable.

Para meu uso como uma camada direta entre o banco de dados e meu aplicativo, isso deve servir. Seria bom saber se existe uma solução mais "robusta" possível onde:

  • O cliente continua tentando se reconectar com base em retry_strategy
  • Chamadas / consumidores / usuários do Cliente não param durante a reconexão
  • Os comandos ainda podem ser enfileirados (condicionalmente?) Para serem executados se / quando uma conexão for restabelecida ... no caso de eu estar incrementando coisas, etc.

Provavelmente muitos cenários difíceis a serem considerados aqui ...

Todos 21 comentários

Por meio da documentação para retry_strategy, parece que node_redis só tentará se reconectar se a função retornar um número.

Se você retornar um número desta função, a nova tentativa acontecerá exatamente após esse tempo em milissegundos.

No seu caso, você está retornando um não número, o que não atende aos requisitos para tentar reconectar.

Se você retornar um não-número, nenhuma nova tentativa ocorrerá e todos os comandos off-line serão liberados com erros.

o fluxo que estou procurando é: ao desconectar, tente conectar novamente 3 vezes com intervalo de 1 segundo. Se na terceira vez ele ainda estiver desconectado, ele deve retornar todos os comandos pendentes com um erro. Eu não quero que ele pare de tentar reconectar.

Com uma versão modificada do exemplo fornecido no README.md, você deverá conseguir o que está procurando. No entanto, uma vez que o máximo de tentativas de 3 se esgote, o cliente não tentará se reconectar automaticamente no próximo comando. Acredito que você precisaria ligar manualmente para createClient() novamente.

const client = redis.createClient({
    url: 'redis://localhost:6379',
    retry_strategy: (options) => {
        if (options.times_connected >= 3) {
            // End reconnecting after a specific number of tries and flush all commands with a individual error
            return new Error('Retry attempts exhausted');
        }
        // reconnect after
        return 1000;
    }
});

Sim, deixei de lado as tentativas, pois isso não faz diferença aqui.

Então, basicamente, você quer dizer:

let client = createClient();
function createClient () {
  return redis.createClient({
    url: 'redis://localhost:6379',
    retry_strategy: (options) => {
      client = createClient();
      return new Error('Retry attempts exhausted');
    }
  });
}

O que significa que não posso mais passar meu cliente redis em meu código?

Você pode criar um light wrapper para gerenciar uma instância singleton do cliente redis.

// redisClient.js

let redis = require("redis");

let client = null;

const options = {
    url: 'redis://localhost:6379',
    retry_strategy: (options) => {
        client = null;
        return new Error("Redis client connection dropped.");
    }
};

module.exports = {
    getClient: () => {
        if (client == null) {
            client = redis.createClient(options);
        }
        return client;
    }
};

Usar:

// usage

let client = require("redisClient.js");

client.getClient().get("some key");
client.getClient().set("some key", "some value");

Suponho que isso seja muito semelhante a chamar createClient() na estratégia de nova tentativa, mas não sou um grande fã desse método.

Se você quiser reduzir a necessidade de chamar getClient() o tempo todo, poderá fazer um grande invólucro que envolva todas as chamadas de redis como get() , set() , etc, e chame getClient() em cada um desses métodos. No entanto, este método de getClient() é um método bastante comum de gerenciar conexões preguiçosamente em vez de ansiosamente.

claro, mas então por que essa biblioteca fornece qualquer estratégia de nova tentativa se você precisa envolvê-la em um wrapper de nova tentativa extra de qualquer maneira?

Não sou o autor, então não tenho certeza das motivações por trás da implementação específica de retry_strategy. Em meus próprios usos de node_redis, descobri que a funcionalidade atual de retry_strategy é útil, mas concordo que falta a capacidade de tentar novamente de forma automática, não importa o que aconteça. Em minhas utilizações, retornei consistentemente um número de retry_strategy e nunca retornei um erro, pois sempre quero que a tentativa de conexão seja feita. No entanto, registro os erros de dentro da retry_strategy.

Que tal algo como isso? https://github.com/viglucci/node_redis/tree/feature-reconnect-after-flush#retry_strategy -example

var client = redis.createClient({
    retry_strategy: function (options) {
        if (options.error && options.error.code === 'ECONNREFUSED') {
            // End reconnecting on a specific error and flush all commands with a individual error
            return new Error('The server refused the connection');
        }
        if (options.total_retry_time > 1000 * 60 * 60) {
            // End reconnecting after a specific timeout and flush all commands with a individual error
            return new Error('Retry time exhausted');
        }
        // attempt reconnect after retry_delay, and flush any pending commands
        return {
            retry_delay: Math.min(options.attempt * 100, 3000),
            error: new Error('Your custom error.');
        }
    }
});

Faz muito sentido conceitualmente. Outra forma de implementar isso poderia ser dividindo-o em duas estratégias:

var client = redis.createClient({
  // this strategy handles the pending redis commands when the connection goes down
  flush_strategy (options) {
    if (options.attempt >= 3) {
      // flush all pending commands with this error
      return new Error('Redis unavailable')
    }
    // let the connection come up again on its own
    return null;
  },
  // this strategy handles the reconnect of a failing redis
  retry_strategy (options) {
    if (options.total_retry_time > 1000 * 60 * 60) {
      // The connection is never going to get up again
      // kill the client with the error event
      return new Error('Retry time exhausted');
    }
    // attempt reconnect after this delay
    return Math.min(options.attempt * 100, 3000)
  }
});

Eu realmente não gosto do nome flush_strategy mas alguém poderia inventar algo melhor.

Que interessante. O que você está sugerindo aqui parece mais com renomear o retry_strategy atual para connection_strategy e usar o retry_strategy como o que você chamou de flush_strategy .

var client = redis.createClient({
    // this strategy handles reconnection
    connection_strategy (options) {
        if (options.total_retry_time > 1000 * 60 * 60) {
            // The connection is never going to get up again
            // kill the client with the error event
            return new Error('Retry time exhausted');
        }
        // attempt reconnect after this delay
        return Math.min(options.attempt * 100, 3000)
    },
    // this strategy handles the pending redis commands when the connection goes down
    retry_strategy (options) {
        if (options.attempt >= 3) {
            // flush all pending commands with this error
            return new Error('Redis unavailable');
        }
        // let the client attempt the commands once a connection is available
        return null;
    },
});

Implementado: https://github.com/viglucci/node_redis/tree/feature-connection-strategy#retry_strategy -example

@Janpot você pode elaborar o caso de uso onde isso é necessário? Tentei diminuir a quantidade de opções para torná-lo mais simples de usar e retry_strategy já é uma opção poderosa.

@viglucci, obrigado por responder aqui e dar um bom feedback! Pessoalmente, gosto da sua sugestão de retornar um objeto mais no caso de isso ser implementado. Esta não seria realmente uma mudança significativa, embora eu esteja planejando abrir um branch v3 em breve. Atualmente é muito trabalhoso implementar muitos novos recursos e remover todas as coisas obsoletas ajudará muito.

@BridgeAR É mais ou menos que agora, se a conexão do Redis for interrompida por algum motivo, todos os meus comandos serão interrompidos. Suponha que eu tenha um fallback para a operação que o redis está fazendo, então esse fallback também trava. Prefiro que o redis retorne um erro após algumas tentativas, que posso detectar e usar o fallback. Isso não significa que eu não quero que o cliente pare de tentar se conectar. O Redis pode voltar em um minuto.

Simplesmente não vejo qual é o objetivo do comportamento atual. Você retorna um erro no manipulador de nova tentativa e, em seguida, seu cliente simplesmente morre para sempre. Quem precisa desse tipo de comportamento, a menos que você crie um cliente para cada comando?

Ou posso não ver o caso de uso para isso, é claro 😄

@BridgeAR Obrigado. Bem, se você tem um roteiro "caminho para a V3" com tarefas simples como remover suporte para parâmetros de configuração / remover avisos de depreciação, etc, você deve estar interessado em enviar solicitações pull. Provavelmente não seria capaz de lidar com um grande trabalho como adicionar suporte para comandos redis adicionais, mas coisas simples de limpeza da casa eu provavelmente poderia fazer se eles estivessem abertos para PR assim que um branch de recurso V3 estivesse disponível.

@viglucci eu convidei para colaborar. Eu vou fazer um plano em breve

1 para uma solução para este problema.

Estou usando o Express e, embora meu retry_strategy ainda esteja retornando inteiros para tentar se conectar novamente no futuro, os comandos (e solicitações da web) continuam a empilhar / fazer backup em vez de jogar algo para que possam obter com suas vidas ... e espero que uma conexão seja _eventualmente_ restabelecida antes que a estratégia diga para desistir ou algo assim.

Isso pode ser realizado? @Janpot você encontrou algo atualmente disponível para resolver esta situação?

Talvez algo com as opções enable_offline_queue e / ou retry_unfulfilled_commands possa fazer isso?

Obrigado a todos pelo seu trabalho árduo e ajuda!

Atualização rápida aqui.
Eu adicionei enable_offline_queue = false às minhas opções createClient e parece ter feito o que eu queria, que é:
"Continue tentando se reconectar ao meu servidor Redis com base na função retry_strategy , mas imediatamente lance erros em resposta às tentativas de usar o cliente nesse ínterim. Então, se conseguir reconectar, comece a trabalhar novamente "

Para sua informação: Mostra o seguinte erro em uma chamada de get : AbortError: SET can't be processed. Stream not writeable.

Para meu uso como uma camada direta entre o banco de dados e meu aplicativo, isso deve servir. Seria bom saber se existe uma solução mais "robusta" possível onde:

  • O cliente continua tentando se reconectar com base em retry_strategy
  • Chamadas / consumidores / usuários do Cliente não param durante a reconexão
  • Os comandos ainda podem ser enfileirados (condicionalmente?) Para serem executados se / quando uma conexão for restabelecida ... no caso de eu estar incrementando coisas, etc.

Provavelmente muitos cenários difíceis a serem considerados aqui ...

@newhouse ,

Obrigado por mencionar isso. Queríamos o mesmo comportamento para nosso aplicativo e sua solução funcionava para retornar imediatamente um erro ao chamador, mas nos permite tentar conectar novamente indefinidamente.

Obrigado @newhouse , não tenho certeza se algum dia encontraria isso.

IMO, este deve ser o comportamento padrão, ou pelo menos explicitamente mencionado nos documentos em torno de retry_strategy . Deixe-me saber se você acha que o último é razoável, e abrirei um PR para adicionar os documentos.

@bwhitty um RP do docs de qualquer pessoa seria muito bem-vindo 👍

Uma pegadinha com o uso de enable_offline_queue = false é que os comandos enviados logo após chamar createClient , mas antes que a conexão seja realmente aberta, também falharão.

Uma pegadinha com o uso de enable_offline_queue = false é que os comandos enviados logo após chamar createClient , mas antes que a conexão seja realmente aberta, também falharão.

@jkirkwood Parece que você vai querer atrasar a tentativa de enviar seus primeiros comandos até que o evento ready (ou evento similar apropriado) seja emitido?

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

Mickael-van-der-Beek picture Mickael-van-der-Beek  ·  6Comentários

betimer picture betimer  ·  5Comentários

lemon707 picture lemon707  ·  3Comentários

gpascale picture gpascale  ·  4Comentários

juriansluiman picture juriansluiman  ·  3Comentários