Redis
 sql >> Base de Dados >  >> NoSQL >> Redis

Alta disponibilidade com Redis Sentinels:conectando-se a conjuntos mestre/escravo Redis

Conectar-se a um único servidor Redis autônomo é bastante simples:basta apontar para o host, porta e fornecer a senha de autenticação, se houver. A maioria dos clientes Redis também fornece suporte para algum tipo de especificação de conexão URI.

No entanto, para obter alta disponibilidade (HA), você precisa implantar uma configuração de mestre e escravo(s). Nesta postagem, mostraremos como se conectar a servidores Redis em uma configuração de alta disponibilidade por meio de um único endpoint.

Alta disponibilidade no Redis

Alta disponibilidade no Redis é alcançada por meio da replicação mestre-escravo. Um servidor Redis mestre pode ter vários servidores Redis como escravos, preferencialmente implantados em nós diferentes em vários data centers. Quando o mestre não está disponível, um dos escravos pode ser promovido para se tornar o novo mestre e continuar a servir dados com pouca ou nenhuma interrupção.

Dada a simplicidade do Redis, há muitas ferramentas de alta disponibilidade disponíveis que podem monitorar e gerenciar uma configuração de réplica mestre-escravo. No entanto, a solução de alta disponibilidade mais comum fornecida com o Redis é o Redis Sentinels. Os Redis Sentinels são executados como um conjunto de processos separados que, em combinação, monitoram os conjuntos mestre-escravo Redis e fornecem failover e reconfiguração automáticos.

Conexão via Redis Sentinels

Os Redis Sentinels também atuam como provedores de configuração para conjuntos mestre-escravo. Ou seja, um cliente Redis pode se conectar ao Redis Sentinels para descobrir o mestre atual e a integridade geral do conjunto de réplicas mestre/escravo. A documentação do Redis fornece detalhes sobre como os clientes devem interagir com os Sentinels. No entanto, esse mecanismo de conexão ao Redis tem algumas desvantagens:

  • Precisa de suporte ao cliente :a conexão com o Redis Sentinels precisa de um cliente "reconhecido" do Sentinel. Os clientes Redis mais populares agora começaram a oferecer suporte ao Redis Sentinels, mas alguns ainda não. Por exemplo, node_redis (Node.js), phpredis (PHP) e scala-redis (Scala) são alguns clientes recomendados que ainda não têm suporte ao Redis Sentinel.
  • Complexidade :Configurar e conectar-se ao Redis Sentinels nem sempre é simples, especialmente quando a implantação ocorre em data centers ou zonas de disponibilidade. Por exemplo, os Sentinels lembram os endereços IP (não os nomes DNS) de todos os servidores de dados e sentinelas que encontram e podem ser configurados incorretamente quando os nós são movidos dinamicamente dentro dos data centers. Os Redis Sentinels também compartilham informações de IP com outros Sentinels. Infelizmente, eles passam IPs locais, o que pode ser problemático se o cliente estiver em um data center separado. Esses problemas podem adicionar complexidade significativa às operações e ao desenvolvimento.
  • Segurança :O próprio servidor Redis fornece autenticação primitiva por meio da senha do servidor, os próprios Sentinels não possuem esse recurso. Portanto, um Redis Sentinel aberto à Internet expõe todas as informações de configuração de todos os mestres que ele está configurado para gerenciar. Assim, o Redis Sentinels deve sempre ser implantado atrás de firewalls configurados corretamente. Acertar a configuração do firewall, especialmente para configurações de várias zonas, pode ser muito complicado.

Ponto de extremidade único

Um único ponto de extremidade de conexão de rede para um conjunto mestre-escravo pode ser fornecido de várias maneiras. Isso pode ser feito por meio de IPs virtuais ou remapeamento de nomes DNS ou usando um servidor proxy (por exemplo, HAProxy) na frente dos servidores Redis. Sempre que uma falha do mestre atual é detectada (pelo Sentinel), o nome IP ou DNS é submetido a failover para o escravo que foi promovido para se tornar o novo mestre pelo Redis Sentinels. Observe que isso leva tempo e a conexão de rede com o endpoint precisará ser restabelecida. Os Redis Sentinels reconhecem um mestre como inativo somente depois que ele está inativo por um período de tempo (padrão 30 segundos) e, em seguida, vota para promover um escravo. Após a promoção de um escravo, o endereço IP/entrada DNS/proxy precisa ser alterado para apontar para um novo mestre.

Conectando a conjuntos mestre-escravo

A consideração importante ao conectar-se a conjuntos de réplicas mestre-escravo usando um único ponto de extremidade é que é preciso provisionar novas tentativas em falhas de conexão para acomodar quaisquer falhas de conexão durante um failover automático do conjunto de réplicas.

Mostraremos isso com exemplos em Java, Ruby e Node.js. Em cada exemplo, alternativamente escrevemos e lemos de um cluster HA Redis enquanto ocorre um failover em segundo plano. No mundo real, as tentativas de repetição serão limitadas a uma duração ou contagem específica .

Conexão com Java

Jedis é o cliente Java recomendado para Redis.

Exemplo de endpoint único

public class JedisTestSingleEndpoint {
...
    public static final String HOSTNAME = "SG-cluster0-single-endpoint.example.com";
    public static final String PASSWORD = "foobared";
...
    private void runTest() throws InterruptedException {
        boolean writeNext = true;
        Jedis jedis = null;
        while (true) {
            try {
                jedis = new Jedis(HOSTNAME);
                jedis.auth(PASSWORD);
                Socket socket = jedis.getClient().getSocket();
                printer("Connected to " + socket.getRemoteSocketAddress());
                while (true) {
                    if (writeNext) {
                        printer("Writing...");
                        jedis.set("java-key-999", "java-value-999");
                        writeNext = false;
                    } else {
                        printer("Reading...");
                        jedis.get("java-key-999");
                        writeNext = true;
                    }
                    Thread.sleep(2 * 1000);
                }
            } catch (JedisException e) {
                printer("Connection error of some sort!");
                printer(e.getMessage());
                Thread.sleep(2 * 1000);
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }
    }
...
}

A saída deste código de teste durante um failover se parece com:

Wed Sep 28 10:57:28 IST 2016: Initializing...
Wed Sep 28 10:57:31 IST 2016: Connected to SG-cluster0-single-endpoint.example.com/54.71.60.125:6379 << Connected to node 1
Wed Sep 28 10:57:31 IST 2016: Writing...
Wed Sep 28 10:57:33 IST 2016: Reading...
..
Wed Sep 28 10:57:50 IST 2016: Reading...
Wed Sep 28 10:57:52 IST 2016: Writing...
Wed Sep 28 10:57:53 IST 2016: Connection error of some sort! << Master went down!
Wed Sep 28 10:57:53 IST 2016: Unexpected end of stream.
Wed Sep 28 10:57:58 IST 2016: Connected to SG-cluster0-single-endpoint.example.com/54.71.60.125:6379
Wed Sep 28 10:57:58 IST 2016: Writing...
Wed Sep 28 10:57:58 IST 2016: Connection error of some sort!
Wed Sep 28 10:57:58 IST 2016: java.net.SocketTimeoutException: Read timed out  << Old master is unreachable
Wed Sep 28 10:58:02 IST 2016: Connected to SG-cluster0-single-endpoint.example.com/54.71.60.125:6379
Wed Sep 28 10:58:02 IST 2016: Writing...
Wed Sep 28 10:58:03 IST 2016: Connection error of some sort!
...
Wed Sep 28 10:59:10 IST 2016: Connected to SG-cluster0-single-endpoint.example.com/54.214.164.243:6379  << New master ready. Connected to node 2
Wed Sep 28 10:59:10 IST 2016: Writing...
Wed Sep 28 10:59:12 IST 2016: Reading...

Este é um programa de teste simples. Na vida real, o número de tentativas será fixado por duração ou contagem.

Exemplo do Redis Sentinel

Jedis também suporta Redis Sentinels. Então aqui está o código que faz a mesma coisa que o exemplo acima, mas conectando-se ao Sentinels.

public class JedisTestSentinelEndpoint {
    private static final String MASTER_NAME = "mymaster";
    public static final String PASSWORD = "foobared";
    private static final Set sentinels;
    static {
        sentinels = new HashSet();
        sentinels.add("mymaster-0.servers.example.com:26379");
        sentinels.add("mymaster-1.servers.example.com:26379");
        sentinels.add("mymaster-2.servers.example.com:26379");
    }

    public JedisTestSentinelEndpoint() {
    }

    private void runTest() throws InterruptedException {
        boolean writeNext = true;
        JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels);
        Jedis jedis = null;
        while (true) {
            try {
                printer("Fetching connection from pool");
                jedis = pool.getResource();
                printer("Authenticating...");
                jedis.auth(PASSWORD);
                printer("auth complete...");
                Socket socket = jedis.getClient().getSocket();
                printer("Connected to " + socket.getRemoteSocketAddress());
                while (true) {
                    if (writeNext) {
                        printer("Writing...");
                        jedis.set("java-key-999", "java-value-999");
                        writeNext = false;
                    } else {
                        printer("Reading...");
                        jedis.get("java-key-999");
                        writeNext = true;
                    }
                    Thread.sleep(2 * 1000);
                }
            } catch (JedisException e) {
                printer("Connection error of some sort!");
                printer(e.getMessage());
                Thread.sleep(2 * 1000);
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }
    }
...
}

Vamos ver o comportamento do programa acima durante um failover gerenciado pelo Sentinel:

Wed Sep 28 14:43:42 IST 2016: Initializing...
Sep 28, 2016 2:43:42 PM redis.clients.jedis.JedisSentinelPool initSentinels
INFO: Trying to find master from available Sentinels...
Sep 28, 2016 2:43:42 PM redis.clients.jedis.JedisSentinelPool initSentinels
INFO: Redis master running at 54.71.60.125:6379, starting Sentinel listeners...
Sep 28, 2016 2:43:43 PM redis.clients.jedis.JedisSentinelPool initPool
INFO: Created JedisPool to master at 54.71.60.125:6379
Wed Sep 28 14:43:43 IST 2016: Fetching connection from pool
Wed Sep 28 14:43:43 IST 2016: Authenticating...
Wed Sep 28 14:43:43 IST 2016: auth complete...
Wed Sep 28 14:43:43 IST 2016: Connected to /54.71.60.125:6379
Wed Sep 28 14:43:43 IST 2016: Writing...
Wed Sep 28 14:43:45 IST 2016: Reading...
Wed Sep 28 14:43:48 IST 2016: Writing...
Wed Sep 28 14:43:50 IST 2016: Reading...
Sep 28, 2016 2:43:51 PM redis.clients.jedis.JedisSentinelPool initPool
INFO: Created JedisPool to master at 54.214.164.243:6379
Wed Sep 28 14:43:52 IST 2016: Writing...
Wed Sep 28 14:43:55 IST 2016: Reading...
Wed Sep 28 14:43:57 IST 2016: Writing...
Wed Sep 28 14:43:59 IST 2016: Reading...
Wed Sep 28 14:44:02 IST 2016: Writing...
Wed Sep 28 14:44:02 IST 2016: Connection error of some sort!
Wed Sep 28 14:44:02 IST 2016: Unexpected end of stream.
Wed Sep 28 14:44:04 IST 2016: Fetching connection from pool
Wed Sep 28 14:44:04 IST 2016: Authenticating...
Wed Sep 28 14:44:04 IST 2016: auth complete...
Wed Sep 28 14:44:04 IST 2016: Connected to /54.214.164.243:6379
Wed Sep 28 14:44:04 IST 2016: Writing...
Wed Sep 28 14:44:07 IST 2016: Reading...
...

Como fica evidente nos logs, um cliente que oferece suporte ao Sentinels pode se recuperar de um evento de failover com bastante rapidez.

Conectando com Ruby

Redis-rb é o cliente Ruby recomendado para Redis.

Exemplo de endpoint único

require 'redis'

HOST = "SG-cluster0-single-endpoint.example.com"
AUTH = "foobared"
...

def connect_and_write
  while true do
    begin
      logmsg "Attempting to establish connection"
      redis = Redis.new(:host => HOST, :password => AUTH)
      redis.ping
      sock = redis.client.connection.instance_variable_get(:@sock)
      logmsg "Connected to #{sock.remote_address.ip_address}, DNS: #{sock.remote_address.getnameinfo}"
      while true do
        if $writeNext
          logmsg "Writing..."
          redis.set("ruby-key-1000", "ruby-value-1000")
          $writeNext = false
        else
          logmsg "Reading..."
          redis.get("ruby-key-1000")
          $writeNext = true
        end
        sleep(2)
      end
    rescue Redis::BaseError => e
      logmsg "Connection error of some sort!"
      logmsg e.message
      sleep(2)
    end
  end
end

...
logmsg "Initiaing..."
connect_and_write

Aqui está o exemplo de saída durante um failover:

"2016-09-28 11:36:42 +0530: Initiaing..."
"2016-09-28 11:36:42 +0530: Attempting to establish connection"
"2016-09-28 11:36:44 +0530: Connected to 54.71.60.125, DNS: [\"ec2-54-71-60-125.us-west-2.compute.amazonaws.com\", \"6379\"] " << Connected to node 1
"2016-09-28 11:36:44 +0530: Writing..."
"2016-09-28 11:36:47 +0530: Reading..."
...
"2016-09-28 11:37:08 +0530: Writing..."
"2016-09-28 11:37:09 +0530: Connection error of some sort!"  << Master went down!
...
"2016-09-28 11:38:13 +0530: Attempting to establish connection"
"2016-09-28 11:38:15 +0530: Connected to 54.214.164.243, DNS: [\"ec2-54-214-164-243.us-west-2.compute.amazonaws.com\", \"6379\"] " << Connected to node 2
"2016-09-28 11:38:15 +0530: Writing..."
"2016-09-28 11:38:17 +0530: Reading..."

Novamente, o código real deve conter um número limitado de tentativas.

Exemplo do Redis Sentinel

O Redis-rb também suporta Sentinels.

AUTH = 'foobared'

SENTINELS = [
  {:host => "mymaster0.servers.example.com", :port => 26379},
  {:host => "mymaster0.servers.example.com", :port => 26379},
  {:host => "mymaster0.servers.example.com", :port => 26379}
]
MASTER_NAME = "mymaster0"

$writeNext = true
def connect_and_write
  while true do
    begin
      logmsg "Attempting to establish connection"
      redis = Redis.new(:url=> "redis://#{MASTER_NAME}", :sentinels => SENTINELS, :password => AUTH)
      redis.ping
      sock = redis.client.connection.instance_variable_get(:@sock)
      logmsg "Connected to #{sock.remote_address.ip_address}, DNS: #{sock.remote_address.getnameinfo} "
      while true do
        if $writeNext
          logmsg "Writing..."
          redis.set("ruby-key-1000", "ruby-val-1000")
          $writeNext = false
        else
          logmsg "Reading..."
          redis.get("ruby-key-1000")
          $writeNext = true
        end
        sleep(2)
      end
    rescue Redis::BaseError => e
      logmsg "Connection error of some sort!"
      logmsg e.message
      sleep(2)
    end
  end
end

O Redis-rb gerencia failovers do Sentinel sem interrupções.

"2016-09-28 15:10:56 +0530: Initiaing..."
"2016-09-28 15:10:56 +0530: Attempting to establish connection"
"2016-09-28 15:10:58 +0530: Connected to 54.214.164.243, DNS: [\"ec2-54-214-164-243.us-west-2.compute.amazonaws.com\", \"6379\"] "
"2016-09-28 15:10:58 +0530: Writing..."
"2016-09-28 15:11:00 +0530: Reading..."
"2016-09-28 15:11:03 +0530: Writing..."
"2016-09-28 15:11:05 +0530: Reading..."
"2016-09-28 15:11:07 +0530: Writing..."
...
<<failover>>
...
"2016-09-28 15:11:10 +0530: Reading..."
"2016-09-28 15:11:12 +0530: Writing..."
"2016-09-28 15:11:14 +0530: Reading..."
"2016-09-28 15:11:17 +0530: Writing..."
...
# No disconnections noticed at all by the application

Conexão com Node.js

Node_redis é o cliente Node.js recomendado para Redis.

Exemplo de endpoint único

...
var redis = require("redis");
var hostname = "SG-cluster0-single-endpoint.example.com";
var auth = "foobared";
var client = null;
...

function readAndWrite() {
  if (!client || !client.connected) {
    client = redis.createClient({
      'port': 6379,
      'host': hostname,
      'password': auth,
      'retry_strategy': function(options) {
        printer("Connection failed with error: " + options.error);
        if (options.total_retry_time > 1000 * 60 * 60) {
          return new Error('Retry time exhausted');
        }
        return new Error('retry strategy: failure');
      }});
    client.on("connect", function () {
      printer("Connected to " + client.address + "/" + client.stream.remoteAddress + ":" + client.stream.remotePort);
    });
    client.on('error', function (err) {
      printer("Error event: " + err);
      client.quit();
    });
  }

  if (writeNext) {
    printer("Writing...");
    client.set("node-key-1001", "node-value-1001", function(err, res) {
      if (err) {
        printer("Error on set: " + err);
        client.quit();
      }
      setTimeout (readAndWrite, 2000)
    });

    writeNext = false;
  } else {
    printer("Reading...");
    client.get("node-key-1001", function(err, res) {
      if (err) {
        client.quit();
        printer("Error on get: " + err);
      }
      setTimeout (readAndWrite, 2000)
    });
    writeNext = true;
  }
}
...
setTimeout(readAndWrite, 2000);
...

Veja como será um failover:
2016-09-28T13:29:46+05:30: Writing...
2016-09-28T13:29:47+05:30: Connected to SG-meh0-6-master.devservers.mongodirector.com:6379/54.214.164.243:6379 << Connected to node 1
2016-09-28T13:29:50+05:30: Reading...
...
2016-09-28T13:30:02+05:30: Writing...
2016-09-28T13:30:04+05:30: Reading...
2016-09-28T13:30:06+05:30: Connection failed with error: null << Master went down
...
2016-09-28T13:30:50+05:30: Connected to SG-meh0-6-master.devservers.mongodirector.com:6379/54.71.60.125:6379 << Connected to node 2
2016-09-28T13:30:52+05:30: Writing...
2016-09-28T13:30:55+05:30: Reading...

Você também pode experimentar a opção 'retry_strategy' durante a criação da conexão para ajustar a lógica de repetição para atender às suas necessidades. A documentação do cliente tem um exemplo.

Exemplo do Redis Sentinel

Node_redis atualmente não suporta Sentinels, mas o popular cliente Redis para Node.js, ioredis suporta Sentinels. Consulte sua documentação sobre como se conectar ao Sentinels do Node.js.

Pronto para aumentar a escala? Oferecemos hospedagem para Redis™* e soluções totalmente gerenciadas em uma nuvem de sua escolha. Compare-nos com outros e veja por que economizamos aborrecimentos e dinheiro.