sql >> Database >  >> NoSQL >> Redis

Hoge beschikbaarheid met Redis Sentinels:verbinding maken met Redis Master/Slave-sets

Verbinding maken met een enkele, zelfstandige Redis-server is eenvoudig genoeg:wijs eenvoudig naar de host, poort en geef het authenticatiewachtwoord op, indien aanwezig. De meeste Redis-clients bieden zelfs ondersteuning voor een soort URI-verbindingsspecificatie.

Om High Availability (HA) te bereiken, moet u echter een master- en slave(s)-configuratie implementeren. In dit bericht laten we u zien hoe u verbinding kunt maken met Redis-servers in een HA-configuratie via één enkel eindpunt.

Hoge beschikbaarheid in Redis

Hoge beschikbaarheid in Redis wordt bereikt door master-slave-replicatie. Een master Redis-server kan meerdere Redis-servers als slaves hebben, bij voorkeur ingezet op verschillende nodes in meerdere datacenters. Wanneer de master niet beschikbaar is, kan een van de slaven worden gepromoveerd tot de nieuwe master en gegevens blijven leveren met weinig of geen onderbreking.

Gezien de eenvoud van Redis, zijn er veel tools met hoge beschikbaarheid beschikbaar die een master-slave-replicaconfiguratie kunnen bewaken en beheren. De meest voorkomende HA-oplossing die bij Redis wordt geleverd, is Redis Sentinels. Redis Sentinels worden uitgevoerd als een reeks afzonderlijke processen die in combinatie Redis master-slave-sets bewaken en automatische failover en herconfiguratie bieden.

Verbinding maken via Redis Sentinels

Redis Sentinels fungeren ook als configuratieproviders voor master-slave-sets. Dat wil zeggen, een Redis-client kan verbinding maken met de Redis Sentinels om de huidige master en algemene status van de master/slave-replicaset te achterhalen. Redis-documentatie geeft details over hoe klanten moeten omgaan met de Sentinels. Dit verbindingsmechanisme met Redis heeft echter enkele nadelen:

  • Klantondersteuning nodig :Verbinding met Redis Sentinels heeft een Sentinel 'bewuste' client nodig. De meeste populaire Redis-clients ondersteunen nu Redis Sentinels, maar sommige doen dat nog steeds niet. Node_redis (Node.js), phpredis (PHP) en Scala-redis (Scala) zijn bijvoorbeeld aanbevolen clients die nog steeds geen Redis Sentinel-ondersteuning hebben.
  • Complexiteit :Configureren en verbinden met Redis Sentinels is niet altijd eenvoudig, vooral wanneer de implementatie plaatsvindt in datacenters of beschikbaarheidszones. Sentinels onthouden bijvoorbeeld IP-adressen (geen DNS-namen) van alle dataservers en sentinels die ze ooit tegenkomen en kunnen verkeerd worden geconfigureerd wanneer knooppunten dynamisch worden verplaatst binnen de datacenters. De Redis Sentinels delen ook IP-informatie met andere Sentinels. Helaas geven ze lokale IP's door, wat problematisch kan zijn als de klant zich in een apart datacenter bevindt. Deze problemen kunnen aanzienlijke complexiteit toevoegen aan zowel de bedrijfsvoering als de ontwikkeling.
  • Beveiliging :De Redis-server zorgt zelf voor primitieve authenticatie via het serverwachtwoord, de Sentinels zelf hebben zo'n functie niet. Dus een Redis Sentinel die open staat voor internet, geeft de volledige configuratie-informatie weer van alle masters waarvoor hij is geconfigureerd om te beheren. Daarom moeten Redis Sentinels altijd achter correct geconfigureerde firewalls worden ingezet. Het kan erg lastig zijn om de firewallconfiguratie goed te krijgen, vooral voor configuraties met meerdere zones.

Enkel eindpunt

Een enkel netwerkverbindingseindpunt voor een master-slave-set kan op vele manieren worden geleverd. Dit kan worden gedaan via virtuele IP's of het opnieuw toewijzen van DNS-namen of door een proxyserver (bijv. HAProxy) voor de Redis-servers te gebruiken. Telkens wanneer een storing van de huidige master wordt gedetecteerd (door de Sentinel), wordt de IP- of DNS-naam overgedragen aan de slave die door de Redis Sentinels is gepromoveerd tot de nieuwe master. Houd er rekening mee dat dit tijd kost en dat de netwerkverbinding met het eindpunt opnieuw tot stand moet worden gebracht. De Redis Sentinels herkennen een master pas als down als deze een tijd niet actief is geweest (standaard 30 seconden) en stemmen vervolgens om een ​​slave te promoten. Bij promotie van een slave moet het IP-adres/DNS-invoer/proxy worden gewijzigd om naar een nieuwe master te verwijzen.

Aansluiten op Master-Slave-sets

De belangrijke overweging bij het verbinden met master-slave replicasets met behulp van een enkel eindpunt is dat men moet voorzien in nieuwe pogingen bij verbindingsfouten om eventuele verbindingsfouten op te vangen tijdens een automatische failover van de replicaset.

We laten dit zien met voorbeelden in Java, Ruby en Node.js. In elk voorbeeld schrijven en lezen we vanuit een HA Redis-cluster terwijl een failover op de achtergrond plaatsvindt. In de echte wereld zijn de pogingen om opnieuw te proberen beperkt tot een bepaalde duur of aantal .

Verbinding maken met Java

Jedis is de aanbevolen Java-client voor Redis.

Voorbeeld van één eindpunt

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

De uitvoer van deze testcode tijdens een failover ziet er als volgt uit:

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

Dit is een eenvoudig testprogramma. In het echte leven wordt het aantal nieuwe pogingen bepaald door de duur of het aantal.

Redis Sentinel-voorbeeld

Jedis ondersteunt ook Redis Sentinels. Dus hier is de code die hetzelfde doet als het bovenstaande voorbeeld, maar door verbinding te maken met 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();
                }
            }
        }
    }
...
}

Laten we eens kijken naar het gedrag van het bovenstaande programma tijdens een door Sentinel beheerde failover:

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

Zoals blijkt uit de logboeken, kan een client die Sentinels ondersteunt vrij snel herstellen van een failover-gebeurtenis.

Verbinding maken met Ruby

Redis-rb is de aanbevolen Ruby-client voor Redis.

Voorbeeld van één eindpunt

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

Dit is de voorbeelduitvoer tijdens een 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..."

Nogmaals, de daadwerkelijke code zou een beperkt aantal nieuwe pogingen moeten bevatten.

Redis Sentinel-voorbeeld

Redis-rb ondersteunt ook 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 beheert Sentinel-failovers zonder onderbrekingen.

"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

Verbinding maken met Node.js

Node_redis is de aanbevolen Node.js-client voor Redis.

Voorbeeld van één eindpunt

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

Zo ziet een failover eruit:

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

Je kunt tijdens het maken van de verbinding ook experimenteren met de optie 'retry_strategy' om de logica voor opnieuw proberen aan te passen aan je behoeften. De klantdocumentatie heeft een voorbeeld.

Redis Sentinel-voorbeeld

Node_redis ondersteunt momenteel geen Sentinels, maar de populaire Redis-client voor Node.js, ioredis ondersteunt Sentinels. Raadpleeg de documentatie over hoe u verbinding kunt maken met Sentinels vanuit Node.js.

Klaar om op te schalen? We bieden hosting voor Redis™* en volledig beheerde oplossingen op een cloud naar keuze. Vergelijk ons ​​met anderen en zie waarom we u gedoe en geld besparen.


  1. Hoe de volgorde van de array wijzigen met MongoDB?

  2. Redis::CommandError:ERR Client heeft AUTH verzonden, maar er is geen wachtwoord ingesteld

  3. ConnectionMultiplexer.Verbinding verbreekt tijdens verbinding met redis-server

  4. MongoDB telling Commando