Redis
 sql >> Datenbank >  >> NoSQL >> Redis

Hochverfügbarkeit mit Redis Sentinels:Verbindung zu Redis Master/Slave-Sets

Die Verbindung zu einem einzelnen, eigenständigen Redis-Server ist ganz einfach:Zeigen Sie einfach auf den Host und den Port und geben Sie gegebenenfalls das Authentifizierungskennwort ein. Die meisten Redis-Clients bieten sogar Unterstützung für eine Art URI-Verbindungsspezifikation.

Um jedoch Hochverfügbarkeit (HA) zu erreichen, müssen Sie eine Master- und Slave-Konfiguration bereitstellen. In diesem Beitrag zeigen wir Ihnen, wie Sie über einen einzelnen Endpunkt eine Verbindung zu Redis-Servern in einer HA-Konfiguration herstellen.

Hochverfügbarkeit in Redis

Hohe Verfügbarkeit in Redis wird durch Master-Slave-Replikation erreicht. Ein Master-Redis-Server kann mehrere Redis-Server als Slaves haben, die vorzugsweise auf verschiedenen Knoten in mehreren Rechenzentren bereitgestellt werden. Wenn der Master nicht verfügbar ist, kann einer der Slaves zum neuen Master befördert werden und weiterhin Daten mit wenig oder ohne Unterbrechung bereitstellen.

Angesichts der Einfachheit von Redis stehen viele Hochverfügbarkeitstools zur Verfügung, die eine Master-Slave-Replikatkonfiguration überwachen und verwalten können. Die häufigste HA-Lösung, die mit Redis gebündelt geliefert wird, ist jedoch Redis Sentinels. Redis Sentinels werden als eine Reihe separater Prozesse ausgeführt, die in Kombination Redis-Master-Slave-Sätze überwachen und automatisches Failover und Neukonfiguration bereitstellen.

Verbindung über Redis Sentinels herstellen

Redis Sentinels fungieren auch als Konfigurationsanbieter für Master-Slave-Sets. Das heißt, ein Redis-Client kann sich mit den Redis Sentinels verbinden, um den aktuellen Master und den allgemeinen Zustand des Master/Slave-Replikatsatzes herauszufinden. Die Redis-Dokumentation enthält Details dazu, wie Clients mit den Sentinels interagieren sollten. Dieser Mechanismus zum Verbinden mit Redis hat jedoch einige Nachteile:

  • Kundensupport erforderlich :Für die Verbindung mit Redis Sentinels ist ein Sentinel-fähiger Client erforderlich. Die meisten beliebten Redis-Kunden haben jetzt damit begonnen, Redis Sentinels zu unterstützen, aber einige tun dies immer noch nicht. Zum Beispiel sind node_redis (Node.js), phpredis (PHP) und scala-redis (Scala) einige empfohlene Clients, die immer noch keine Redis Sentinel-Unterstützung haben.
  • Komplexität :Das Konfigurieren und Verbinden mit Redis Sentinels ist nicht immer einfach, insbesondere wenn die Bereitstellung über Rechenzentren oder Verfügbarkeitszonen hinweg erfolgt. Beispielsweise merken sich Sentinels IP-Adressen (nicht DNS-Namen) aller Datenserver und Sentinels, denen sie jemals begegnen, und können falsch konfiguriert werden, wenn Knoten dynamisch innerhalb der Rechenzentren verschoben werden. Die Redis Sentinels teilen auch IP-Informationen mit anderen Sentinels. Leider geben sie lokale IPs weiter, was problematisch sein kann, wenn sich der Client in einem separaten Rechenzentrum befindet. Diese Probleme können sowohl den Betrieb als auch die Entwicklung erheblich komplexer machen.
  • Sicherheit :Der Redis-Server selbst bietet eine primitive Authentifizierung über das Serverpasswort, die Sentinels selbst haben keine solche Funktion. Ein für das Internet offener Redis Sentinel legt also die gesamten Konfigurationsinformationen aller Master offen, für deren Verwaltung er konfiguriert ist. Daher sollten Redis Sentinels immer hinter richtig konfigurierten Firewalls eingesetzt werden. Die richtige Firewall-Konfiguration zu finden, insbesondere bei Konfigurationen mit mehreren Zonen, kann wirklich schwierig sein.

Einzelner Endpunkt

Ein einzelner Netzwerkverbindungsendpunkt für ein Master-Slave-Set kann auf viele Arten bereitgestellt werden. Dies könnte durch virtuelle IPs oder die Neuzuordnung von DNS-Namen oder durch die Verwendung eines Proxy-Servers (z. B. HAProxy) vor den Redis-Servern erfolgen. Immer wenn ein Ausfall des aktuellen Masters (durch den Sentinel) erkannt wird, wird der IP- oder DNS-Name auf den Slave umgeleitet, der von den Redis Sentinels zum neuen Master befördert wurde. Beachten Sie, dass dies einige Zeit in Anspruch nimmt und die Netzwerkverbindung zum Endpunkt neu hergestellt werden muss. Die Redis Sentinels erkennen einen Master erst dann als down an, wenn er eine bestimmte Zeit lang (standardmäßig 30 Sekunden) down war, und stimmen dann für die Beförderung eines Slaves. Bei der Heraufstufung eines Slaves muss die IP-Adresse/der DNS-Eintrag/der Proxy geändert werden, um auf den neuen Master zu verweisen.

Verbindung zu Master-Slave-Sets

Die wichtige Überlegung beim Herstellen einer Verbindung zu Master-Slave-Replikatsätzen unter Verwendung eines einzigen Endpunkts ist, dass Wiederholungsversuche bei Verbindungsfehlern vorgesehen werden müssen, um etwaige Verbindungsfehler während eines automatischen Failover des zu berücksichtigen Replikatsatz.

Wir zeigen dies anhand von Beispielen in Java, Ruby und Node.js. In jedem Beispiel schreiben und lesen wir abwechselnd aus einem HA Redis-Cluster, während im Hintergrund ein Failover stattfindet. In der realen Welt sind die Wiederholungsversuche auf eine bestimmte Dauer oder Anzahl beschränkt .

Verbindung mit Java

Jedis ist der empfohlene Java-Client für Redis.

Beispiel für einen einzelnen Endpunkt

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();
                }
            }
        }
    }
...
}

Die Ausgabe dieses Testcodes während eines Failovers sieht folgendermaßen aus:

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...

Dies ist ein einfaches Testprogramm. Im wirklichen Leben wird die Anzahl der Wiederholungen durch Dauer oder Anzahl festgelegt.

Redis Sentinel-Beispiel

Jedis unterstützt auch Redis Sentinels. Hier ist also der Code, der dasselbe macht wie das obige Beispiel, aber indem er sich mit Sentinels verbindet.

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();
                }
            }
        }
    }
...
}

Sehen wir uns das Verhalten des obigen Programms während eines von Sentinel verwalteten Failovers an:

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...
...

Wie aus den Protokollen hervorgeht, kann sich ein Client, der Sentinels unterstützt, ziemlich schnell von einem Failover-Ereignis erholen.

Mit Ruby verbinden

Redis-rb ist der empfohlene Ruby-Client für Redis.

Beispiel für einen einzelnen Endpunkt

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

Hier ist die Beispielausgabe während eines Failovers:

"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..."

Auch hier sollte der tatsächliche Code eine begrenzte Anzahl von Wiederholungen enthalten.

Redis Sentinel-Beispiel

Redis-rb unterstützt auch 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

Redis-rb verwaltet Sentinel-Failover ohne Unterbrechungen.

"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

Verbindung mit Node.js

Node_redis ist der empfohlene Node.js-Client für Redis.

Beispiel für einen einzelnen Endpunkt

...
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);
...

So sieht ein Failover aus:

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...

Sie können auch mit der Option „retry_strategy“ während der Verbindungserstellung experimentieren, um die Wiederholungslogik an Ihre Anforderungen anzupassen. Die Client-Dokumentation enthält ein Beispiel.

Redis Sentinel-Beispiel

Node_redis unterstützt derzeit keine Sentinels, aber der beliebte Redis-Client für Node.js, ioredis, unterstützt Sentinels. Informationen zum Herstellen einer Verbindung zu Sentinels von Node.js finden Sie in der zugehörigen Dokumentation.

Bereit zur Skalierung? Wir bieten Hosting für Redis™* und vollständig verwaltete Lösungen in einer Cloud Ihrer Wahl. Vergleichen Sie uns mit anderen und sehen Sie, warum wir Ihnen Ärger und Geld ersparen.