Připojení k jedinému samostatnému serveru Redis je dostatečně jednoduché:jednoduše přejděte na hostitele, port a zadejte ověřovací heslo, pokud existuje. Většina klientů Redis dokonce poskytuje podporu pro určitý druh specifikace připojení URI.
Abyste však dosáhli vysoké dostupnosti (HA), musíte nasadit konfiguraci master a slave. V tomto příspěvku vám ukážeme, jak se připojit k serverům Redis v konfiguraci HA prostřednictvím jednoho koncového bodu.
Vysoká dostupnost v Redis
Vysoká dostupnost v Redis je dosažena prostřednictvím replikace master-slave. Hlavní server Redis může mít několik serverů Redis jako podřízené, nejlépe nasazené na různých uzlech napříč více datovými centry. Když je master nedostupný, jeden z slave může být povýšen, aby se stal novým masterem a nadále obsluhovat data s malým nebo žádným přerušením.
Vzhledem k jednoduchosti Redis je k dispozici mnoho vysoce dostupných nástrojů, které mohou monitorovat a spravovat konfiguraci replik master-slave. Nejběžnějším řešením HA, které je součástí dodávky Redis, je však Redis Sentinels. Redis Sentinely běží jako sada samostatných procesů, které v kombinaci monitorují sady Redis master-slave a zajišťují automatické převzetí služeb při selhání a rekonfiguraci.
Připojení přes Redis Sentinel
Redis Sentinely také fungují jako poskytovatelé konfigurace pro sady master-slave. To znamená, že klient Redis se může připojit k Redis Sentinel a zjistit aktuální hlavní a obecný stav sady replik master/slave. Dokumentace Redis poskytuje podrobnosti o tom, jak by klienti měli komunikovat se Sentinely. Tento mechanismus připojení k Redis má však některé nevýhody:
- Potřebuje podporu klienta :Připojení k Redis Sentinels vyžaduje klienta Sentinel „aware“. Nejoblíbenější klienti Redis nyní začali podporovat Redis Sentinel, ale někteří stále ne. Například node_redis (Node.js), phpredis (PHP) a scala-redis (Scala) jsou někteří doporučovaní klienti, kteří stále nemají podporu Redis Sentinel.
- Složitost :Konfigurace a připojení k Redis Sentinels není vždy přímočaré, zvláště když nasazení probíhá napříč datovými centry nebo zónami dostupnosti. Sentinely si například pamatují IP adresy (nikoli názvy DNS) všech datových serverů a hlídačů, na které kdy narazí, a mohou být špatně nakonfigurovány, když se uzly dynamicky pohybují v datových centrech. Redis Sentinely také sdílejí IP informace s ostatními Sentinely. Bohužel procházejí místními IP, což může být problematické, pokud je klient v samostatném datovém centru. Tyto problémy mohou významně zkomplikovat provoz i vývoj.
- Zabezpečení :Samotný server Redis poskytuje primitivní ověřování pomocí hesla serveru, samotné Sentinely takovou funkci nemají. Takže Redis Sentinel, který je otevřený pro internet, zpřístupňuje veškeré konfigurační informace všech masterů, pro jejichž správu je nakonfigurován. Redis Sentinely by proto měly být vždy nasazeny za správně nakonfigurovanými firewally. Správné nastavení firewallu, zejména pro vícezónové konfigurace, může být opravdu složité.
Jeden koncový bod
Jeden koncový bod síťového připojení pro sadu master-slave lze poskytnout mnoha způsoby. To by mohlo být provedeno prostřednictvím virtuálních IP adres nebo přemapování DNS jmen nebo pomocí proxy serveru (např. HAProxy) před servery Redis. Kdykoli je detekováno selhání aktuálního masteru (sentinel), IP nebo DNS jméno je předáno podřízenému, který byl povýšen na nového mastera Redis Sentinel. Upozorňujeme, že to nějakou dobu trvá a bude nutné znovu navázat síťové připojení ke koncovému bodu. Redis Sentinelové rozpoznají pána jako nefunkčního až poté, co byl nefunkční po určitou dobu (výchozí 30 sekund) a poté hlasují pro povýšení otroka. Po povýšení podřízeného zařízení se IP adresa/položka DNS/proxy musí změnit tak, aby ukazovala na nového mastera.
Připojování k sadám Master-Slave
Důležitým hlediskem při připojování k sadám replik master-slave pomocí jednoho koncového bodu je, že je třeba zajistit opakované pokusy o selhání připojení, aby bylo možné reagovat na všechna selhání připojení během automatického převzetí služeb při selhání sada replik.
Ukážeme si to na příkladech v Javě, Ruby a Node.js. V každém příkladu alternativně zapisujeme a čteme z clusteru HA Redis, zatímco na pozadí dochází k převzetí služeb při selhání. Ve skutečném světě budou pokusy o opakování omezeny na konkrétní dobu trvání nebo počet .
Připojení pomocí Java
Jedis je doporučený Java klient pro Redis.
Příklad jednoho koncového bodu
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(); } } } } ... }
Výstup tohoto testovacího kódu během převzetí služeb při selhání vypadá takto:
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...
Toto je jednoduchý testovací program. V reálném životě bude počet opakování pevně daný dobou trvání nebo počtem.
Příklad Redis Sentinel
Jedis podporuje také Redis Sentinely. Zde je tedy kód, který dělá to samé jako výše uvedený příklad, ale připojuje se k 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(); } } } } ... }
Podívejme se na chování výše uvedeného programu během převzetí služeb při selhání spravovaném Sentinelem:
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... ...
Jak je patrné z protokolů, klient, který podporuje Sentinely, se může zotavit z události převzetí služeb při selhání poměrně rychle.
Spojení s Ruby
Redis-rb je doporučeným klientem Ruby pro Redis.
Příklad jednoho koncového bodu
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
Zde je ukázkový výstup během převzetí služeb při selhání:
"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..."
Skutečný kód by měl opět obsahovat omezený počet opakování.
Příklad Redis Sentinel
Redis-rb podporuje také Sentinely.
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 spravuje převzetí služeb při selhání Sentinel bez jakéhokoli přerušení.
"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
Připojení k Node.js
Node_redis je doporučeným klientem Node.js pro Redis.
Příklad jednoho koncového bodu
... 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); ...
Přepnutí při selhání bude vypadat následovně:
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...
Během vytváření připojení můžete také experimentovat s možností ‘retry_strategy’, abyste mohli vyladit logiku opakování tak, aby vyhovovala vašim potřebám. Klientská dokumentace má příklad.
Příklad Redis Sentinel
Node_redis aktuálně nepodporuje Sentinely, ale populární klient Redis pro Node.js, ioredis, Sentinel podporuje. Informace o tom, jak se připojit k Sentinelům z Node.js, najdete v jeho dokumentaci.
Jste připraveni na rozšíření? Nabízíme hosting pro Redis™* a plně spravovaná řešení v cloudu dle vašeho výběru. Porovnejte nás s ostatními a uvidíte, proč vám ušetříme starosti a peníze.