sql >> Databasteknik >  >> NoSQL >> Redis

Hög tillgänglighet med Redis Sentinels:Ansluter till Redis Master/Slave Sets

Att ansluta till en enda, fristående Redis-server är tillräckligt enkelt:peka helt enkelt på värden, porten och ange eventuellt autentiseringslösenord. De flesta Redis-klienter ger till och med stöd för någon form av URI-anslutningsspecifikation också.

Men för att uppnå High Availability (HA) måste du distribuera en master- och slavkonfiguration. I det här inlägget visar vi hur du ansluter till Redis-servrar i en HA-konfiguration via en enda slutpunkt.

Hög tillgänglighet i Redis

Hög tillgänglighet i Redis uppnås genom master-slave replikering. En master Redis-server kan ha flera Redis-servrar som slavar, helst utplacerade på olika noder över flera datacenter. När mastern är otillgänglig kan en av slavarna befordras till att bli den nya mastern och fortsätta att leverera data med lite eller inga avbrott.

Med tanke på Redis enkelhet finns det många tillgängliga verktyg med hög tillgänglighet som kan övervaka och hantera en master-slave replikkonfiguration. Den vanligaste HA-lösningen som levereras med Redis är dock Redis Sentinels. Redis Sentinels körs som en uppsättning separata processer som i kombination övervakar Redis master-slave set och ger automatisk failover och omkonfigurering.

Ansluter via Redis Sentinels

Redis Sentinels fungerar också som konfigurationsleverantörer för master-slave set. Det vill säga, en Redis-klient kan ansluta till Redis Sentinels för att ta reda på den aktuella master- och allmänna hälsan för master/slav-replikuppsättningen. Redis dokumentation ger information om hur klienter ska interagera med Sentinels. Denna mekanism för att ansluta till Redis har dock några nackdelar:

  • Behöver klientsupport :Anslutning till Redis Sentinels behöver en Sentinel "medveten" klient. De flesta populära Redis-klienterna har nu börjat stödja Redis Sentinels men vissa gör det fortfarande inte. Till exempel är node_redis (Node.js), phpredis (PHP) och scala-redis (Scala) några rekommenderade klienter som fortfarande inte har Redis Sentinel-stöd.
  • Komplexitet :Att konfigurera och ansluta till Redis Sentinels är inte alltid okomplicerat, särskilt när distributionen sker över datacenter eller tillgänglighetszoner. Till exempel kommer Sentinels ihåg IP-adresser (inte DNS-namn) för alla dataservrar och sentinels de någonsin stöter på och kan bli felkonfigurerade när noder flyttas dynamiskt inom datacentren. Redis Sentinels delar också IP-information med andra Sentinels. Tyvärr skickar de runt lokala IP:er vilket kan vara problematiskt om klienten är i ett separat datacenter. Dessa frågor kan lägga till betydande komplexitet till både driften och utvecklingen.
  • Säkerhet :Redis-servern själv tillhandahåller primitiv autentisering genom serverlösenordet, Sentinels själva har ingen sådan funktion. Så en Redis Sentinel som är öppen för Internet exponerar hela konfigurationsinformationen för alla masters den är konfigurerad att hantera. Redis Sentinels bör därför alltid distribueras bakom korrekt konfigurerade brandväggar. Att få brandväggskonfigurationen rätt, särskilt för konfigurationer med flera zoner, kan vara riktigt knepigt.

Enskild slutpunkt

En enda nätverksanslutningsändpunkt för en master-slav-uppsättning kan tillhandahållas på många sätt. Det kan göras genom virtuella IP-adresser eller ommappning av DNS-namn eller genom att använda en proxyserver (t.ex. HAProxy) framför Redis-servrarna. Närhelst ett fel hos den aktuella mastern upptäcks (av Sentinel), misslyckas IP- eller DNS-namnet till slaven som har befordrats till att bli den nya mastern av Redis Sentinels. Observera att detta tar tid och nätverksanslutningen till slutpunkten måste återupprättas. Redis Sentinels känner igen en master som nere först efter att den har varit nere under en tid (standard 30 sekunder) och röstar sedan för att främja en slav. Vid marknadsföring av en slav måste IP-adressen/DNS-posten/proxyn ändras för att peka på en ny master.

Ansluter till Master-Slave-set

Det viktiga att tänka på när du ansluter till master-slav-replikuppsättningar med en enda slutpunkt är att man måste sörja för återförsök på anslutningsfel för att tillgodose eventuella anslutningsfel under en automatisk failover av replikuppsättning.

Vi visar detta med exempel i Java, Ruby och Node.js. I varje exempel skriver och läser vi alternativt från ett HA Redis-kluster medan en failover sker i bakgrunden. I den verkliga världen kommer försöken på nytt att begränsas till en viss varaktighet eller antal .

Ansluter med Java

Jedis är den rekommenderade Java-klienten för Redis.

Exempel på en enda 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();
                }
            }
        }
    }
...
}

Utmatningen av denna testkod under en failover ser ut så här:

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

Detta är ett enkelt testprogram. I verkligheten kommer antalet återförsök att fastställas efter varaktighet eller antal.

Exempel på Redis Sentinel

Jedis stöder Redis Sentinels också. Så här är koden som gör samma sak som exemplet ovan men genom att ansluta till 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();
                }
            }
        }
    }
...
}

Låt oss se beteendet hos ovanstående program under en Sentinel-hanterad 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 framgår av loggarna kan en klient som stöder Sentinels återhämta sig från en failover-händelse ganska snabbt.

Ansluter med Ruby

Redis-rb är den rekommenderade Ruby-klienten för Redis.

Exempel på en enda 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

Här är exempelutdata 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..."

Återigen bör den faktiska koden innehålla ett begränsat antal försök.

Exempel på Redis Sentinel

Redis-rb stöder även 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 hanterar Sentinel failovers utan några störningar.

"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

Ansluter till Node.js

Node_redis är den rekommenderade Node.js-klienten för Redis.

Exempel på en enda 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å här kommer en failover att se ut:

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 också experimentera med alternativet 'retry_strategy' under skapandet av anslutningar för att justera logiken igen för att möta dina behov. Kunddokumentationen har ett exempel.

Exempel på Redis Sentinel

Node_redis stöder för närvarande inte Sentinels, men den populära Redis-klienten för Node.js, ioredis stöder Sentinels. Se dess dokumentation om hur du ansluter till Sentinels från Node.js.

Redo att skala upp? Vi erbjuder hosting för Redis™* och helt hanterade lösningar på ett valfritt moln. Jämför oss med andra och se varför vi sparar dig krångel och pengar.


  1. Matcha ObjectId till String för $graphLookup

  2. Redis klient

  3. En introduktion till MongoDB Zone Basics

  4. Hur fungerar ServiceStack Redis för att hämta data