sql >> Database teknologi >  >> NoSQL >> Redis

Høj tilgængelighed med Redis Sentinels:Tilslutning til Redis Master/Slave Sets

At oprette forbindelse til en enkelt, selvstændig Redis-server er simpel nok:peg blot på værten, porten og angiv godkendelsesadgangskoden, hvis nogen. De fleste Redis-klienter yder endda også support til en slags URI-forbindelsesspecifikation.

Men for at opnå High Availability (HA), skal du implementere en master- og slave-konfiguration. I dette indlæg viser vi dig, hvordan du opretter forbindelse til Redis-servere i en HA-konfiguration via et enkelt slutpunkt.

Høj tilgængelighed i Redis

Høj tilgængelighed i Redis opnås gennem master-slave-replikering. En master Redis-server kan have flere Redis-servere som slaver, fortrinsvis installeret på forskellige noder på tværs af flere datacentre. Når masteren er utilgængelig, kan en af ​​slaverne forfremmes til at blive den nye master og fortsætte med at betjene data med ringe eller ingen afbrydelse.

I betragtning af Redis' enkelhed er der mange tilgængelige værktøjer med høj tilgængelighed, som kan overvåge og administrere en master-slave-replikkonfiguration. Den mest almindelige HA-løsning, der følger med Redis, er dog Redis Sentinels. Redis Sentinels kører som et sæt separate processer, der i kombination overvåger Redis master-slave-sæt og giver automatisk failover og rekonfiguration.

Opretter forbindelse via Redis Sentinels

Redis Sentinels fungerer også som konfigurationsudbydere for master-slave-sæt. Det vil sige, at en Redis-klient kan oprette forbindelse til Redis Sentinels for at finde ud af master/slave-repliksættets aktuelle master og generelle helbred. Redis dokumentation giver detaljer om, hvordan klienter skal interagere med Sentinels. Denne mekanisme til at oprette forbindelse til Redis har dog nogle ulemper:

  • Har brug for klientsupport :Forbindelse til Redis Sentinels kræver en Sentinel "bevidst" klient. De fleste populære Redis-klienter er nu begyndt at støtte Redis Sentinels, men nogle gør det stadig ikke. For eksempel er node_redis (Node.js), phpredis (PHP) og scala-redis (Scala) nogle anbefalede klienter, der stadig ikke har Redis Sentinel-support.
  • Kompleksitet :Konfiguration og forbindelse til Redis Sentinels er ikke altid ligetil, især når implementeringen er på tværs af datacentre eller tilgængelighedszoner. For eksempel husker Sentinels IP-adresser (ikke DNS-navne) på alle dataservere og vagtposter, de nogensinde støder på, og kan blive forkert konfigureret, når noder dynamisk flyttes i datacentrene. Redis Sentinels deler også IP-oplysninger med andre Sentinels. Desværre passerer de lokale IP'er, hvilket kan være problematisk, hvis klienten er i et separat datacenter. Disse problemer kan tilføje betydelig kompleksitet til både driften og udviklingen.
  • Sikkerhed :Redis-serveren sørger selv for primitiv autentificering gennem serveradgangskoden, selve Sentinels har ingen sådan funktion. Så en Redis Sentinel, der er åben til internettet, afslører hele konfigurationsinformationen for alle de mastere, den er konfigureret til at administrere. Redis Sentinels bør derfor altid installeres bag korrekt konfigurerede firewalls. Det kan være meget vanskeligt at få firewall-konfigurationen rigtigt, især for multi-zone-konfigurationer.

Enkelt slutpunkt

Et enkelt netværksforbindelsesendepunkt for et master-slave-sæt kan leveres på mange måder. Det kan gøres gennem virtuelle IP'er eller gentilknytning af DNS-navne eller ved at bruge en proxyserver (f.eks. HAProxy) foran Redis-serverne. Hver gang en fejl i den nuværende master opdages (af Sentinel), bliver IP- eller DNS-navnet mislykket til slaven, der er blevet forfremmet til at blive den nye master af Redis Sentinels. Bemærk, at dette tager tid, og netværksforbindelsen til slutpunktet skal genetableres. Redis Sentinels anerkender først en mester som nede, efter at den har været nede i en periode (standard 30 sekunder) og stemmer derefter for at fremme en slave. Ved promovering af en slave skal IP-adressen/DNS-indgangen/proxyen ændres for at pege på en ny master.

Opretter forbindelse til Master-Slave-sæt

Den vigtige overvejelse, når du forbinder til master-slave replikasæt ved hjælp af et enkelt slutpunkt er, at man skal sørge for genforsøg på forbindelsesfejl for at imødekomme eventuelle forbindelsesfejl under en automatisk failover af replikasæt.

Vi viser dette med eksempler i Java, Ruby og Node.js. I hvert eksempel skriver og læser vi alternativt fra en HA Redis-klynge, mens der sker en failover i baggrunden. I den virkelige verden vil genforsøgsforsøgene være begrænset til en bestemt varighed eller et bestemt antal .

Opretter forbindelse til Java

Jedis er den anbefalede Java-klient til Redis.

Eksempel på enkelt slutpunkt

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

Oputtet af denne testkode under en failover ser sådan ud:

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

Dette er et simpelt testprogram. I det virkelige liv vil antallet af genforsøg blive fastsat efter varighed eller antal.

Redis Sentinel Eksempel

Jedis understøtter også Redis Sentinels. Så her er koden, der gør det samme som ovenstående eksempel, men ved at forbinde til 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();
                }
            }
        }
    }
...
}

Lad os se opførselen af ​​ovenstående program under en Sentinel-administreret 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...
...

Som det fremgår af logfilerne, kan en klient, der understøtter Sentinels, retablere sig efter en failover-hændelse ret hurtigt.

Opretter forbindelse til Ruby

Redis-rb er den anbefalede Ruby-klient til Redis.

Eksempel på enkelt slutpunkt

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

Her er eksempeloutputtet under en 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..."

Igen skal den faktiske kode indeholde et begrænset antal genforsøg.

Redis Sentinel Eksempel

Redis-rb understøtter også 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 administrerer Sentinel failovers uden nogen afbrydelser.

"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

Opretter forbindelse til Node.js

Node_redis er den anbefalede Node.js-klient til Redis.

Eksempel på enkelt slutpunkt

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

Sådan vil en failover se ud:

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

Du kan også eksperimentere med muligheden "genforsøg_strategi" under oprettelse af forbindelse for at justere logikken for at prøve igen, så den opfylder dine behov. Kundedokumentationen har et eksempel.

Redis Sentinel Eksempel

Node_redis understøtter i øjeblikket ikke Sentinels, men den populære Redis-klient til Node.js, ioredis understøtter Sentinels. Se dens dokumentation om, hvordan du opretter forbindelse til Sentinels fra Node.js.

Klar til at skalere op? Vi tilbyder hosting til Redis™* og fuldt administrerede løsninger på en sky efter eget valg. Sammenlign os med andre og se, hvorfor vi sparer dig for besvær og penge.


  1. Mongodb Mongoimport for stor:Fejl ved parsing

  2. MongoDB $eq Aggregation Pipeline Operator

  3. Kunne ikke automatisk konfigurere en datakilde:'spring.datasource.url' er ikke angivet

  4. Redis Cluster - klar til produktion?