Łączenie się z pojedynczym, samodzielnym serwerem Redis jest dość proste:wystarczy wskazać hosta, port i podać hasło uwierzytelniające, jeśli takie istnieje. Większość klientów Redis zapewnia nawet obsługę pewnego rodzaju specyfikacji połączenia URI.
Jednak aby osiągnąć wysoką dostępność (HA), musisz wdrożyć konfigurację master i slave. W tym poście pokażemy, jak połączyć się z serwerami Redis w konfiguracji HA za pośrednictwem jednego punktu końcowego.
Wysoka dostępność w Redis
Wysoką dostępność w Redis osiąga się dzięki replikacji master-slave. Główny serwer Redis może mieć wiele serwerów Redis jako podrzędnych, najlepiej wdrożonych w różnych węzłach w wielu centrach danych. Gdy master jest niedostępny, jeden z slave'ów może zostać awansowany na nowego mastera i nadal obsługiwać dane z niewielką lub żadną przerwą.
Biorąc pod uwagę prostotę Redis, dostępnych jest wiele narzędzi o wysokiej dostępności, które mogą monitorować konfigurację repliki master-slave i zarządzać nią. Jednak najczęstszym rozwiązaniem HA dołączanym do Redis jest Redis Sentinels. Redis Sentinels działają jako zestaw oddzielnych procesów, które w połączeniu monitorują zestawy master-slave Redis i zapewniają automatyczne przełączanie awaryjne i rekonfigurację.
Łączenie przez Redis Sentinels
Strażnicy Redis działają również jako dostawcy konfiguracji dla zestawów master-slave. Oznacza to, że klient Redis może połączyć się ze Strażnikami Redis, aby sprawdzić aktualny stan główny i ogólny stan zestawu replik master/slave. Dokumentacja Redis zawiera szczegółowe informacje na temat interakcji klientów ze Strażnikami. Jednak ten mechanizm łączenia się z Redis ma pewne wady:
- Wymaga obsługi klienta :połączenie z Redis Sentinels wymaga „świadomego” klienta Sentinel. Większość popularnych klientów Redis zaczęła już wspierać Redis Sentinels, ale niektórzy nadal tego nie robią. Na przykład node_redis (Node.js), phpredis (PHP) i scala-redis (Scala) to niektórzy polecani klienci, którzy nadal nie mają obsługi Redis Sentinel.
- Złożoność :Konfigurowanie i łączenie się z Redis Sentinels nie zawsze jest proste, zwłaszcza gdy wdrożenie odbywa się w centrach danych lub strefach dostępności. Na przykład Strażnicy zapamiętują adresy IP (nie nazwy DNS) wszystkich serwerów danych i strażników, z którymi się spotykają, i mogą zostać źle skonfigurowane, gdy węzły będą dynamicznie przemieszczane w centrach danych. Strażnicy Redis dzielą się również informacjami o IP z innymi Strażnikami. Niestety przekazują lokalne adresy IP, co może być problematyczne, jeśli klient znajduje się w oddzielnym centrum danych. Te problemy mogą znacznie zwiększyć złożoność zarówno operacji, jak i rozwoju.
- Zabezpieczenia :Sam serwer Redis zapewnia prymitywne uwierzytelnianie za pomocą hasła serwera, sami Strażnicy nie mają takiej funkcji. Tak więc Redis Sentinel, który jest otwarty na Internet, ujawnia wszystkie informacje o konfiguracji wszystkich masterów, którymi ma zarządzać. W związku z tym Redis Sentinels należy zawsze instalować za prawidłowo skonfigurowanymi zaporami ogniowymi. Uzyskanie właściwej konfiguracji zapory, szczególnie w przypadku konfiguracji wielostrefowych, może być naprawdę trudne.
Pojedynczy punkt końcowy
Pojedynczy punkt końcowy połączenia sieciowego dla zestawu master-slave można zapewnić na wiele sposobów. Można to zrobić za pomocą wirtualnych adresów IP lub zmiany mapowania nazw DNS lub przy użyciu serwera proxy (np. HAProxy) przed serwerami Redis. Za każdym razem, gdy wykryta zostanie awaria aktualnego mastera (przez Sentinela), adres IP lub nazwa DNS jest przekierowywana do slave'a, który został promowany na nowego mastera przez Sentinelów Redis. Pamiętaj, że zajmuje to trochę czasu i konieczne będzie ponowne nawiązanie połączenia sieciowego z punktem końcowym. Strażnicy Redis rozpoznają mistrza jako powalonego dopiero po pewnym czasie (domyślnie 30 sekund), a następnie głosują za awansem niewolnika. Po promocji slave'a adres IP/wpis DNS/proxy musi się zmienić, aby wskazywał na nowego mastera.
Łączenie z zestawami Master-Slave
Ważną kwestią przy łączeniu się z zestawami replik master-slave przy użyciu jednego punktu końcowego jest to, że należy zapewnić ponowne próby w przypadku awarii połączenia, aby uwzględnić wszelkie awarie połączeń podczas automatycznego przełączania awaryjnego zestaw replik.
Pokażemy to na przykładach w Javie, Ruby i Node.js. W każdym przykładzie alternatywnie zapisujemy i odczytujemy z klastra HA Redis, podczas gdy w tle występuje przełączenie awaryjne. W świecie rzeczywistym próby ponownych prób będą ograniczone do określonego czasu trwania lub liczby .
Łączenie z Javą
Jedis jest zalecanym klientem Java dla Redis.
Przykład pojedynczego punktu końcowego
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(); } } } } ... }
Wyjście tego kodu testowego podczas przełączania awaryjnego wygląda następująco:
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...
To jest prosty program testowy. W prawdziwym życiu liczba ponownych prób będzie ustalana na podstawie czasu trwania lub liczby.
Przykład Redis Sentinel
Jedis również obsługuje Strażników Redis. Oto kod, który robi to samo, co w powyższym przykładzie, ale łączy się z 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(); } } } } ... }
Zobaczmy zachowanie powyższego programu podczas przełączania awaryjnego zarządzanego przez Sentinel:
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 wynika z dzienników, klient obsługujący Sentinels może dość szybko odzyskać sprawność po awarii.
Łączenie z Rubim
Redis-rb jest zalecanym klientem Ruby dla Redis.
Przykład pojedynczego punktu końcowego
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
Oto przykładowe dane wyjściowe podczas przełączania awaryjnego:
"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..."
Ponownie, rzeczywisty kod powinien zawierać ograniczoną liczbę ponownych prób.
Przykład Redis Sentinel
Redis-rb obsługuje również Strażników.
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 zarządza przełączaniem awaryjnym Sentinel bez żadnych zakłóceń.
"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
Łączenie z Node.js
Node_redis jest zalecanym klientem Node.js dla Redis.
Przykład pojedynczego punktu końcowego
... 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); ...
Oto jak będzie wyglądać przełączenie awaryjne:
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...
Możesz także poeksperymentować z opcją „retry_strategy” podczas tworzenia połączenia, aby dostosować logikę ponawiania do swoich potrzeb. Dokumentacja klienta zawiera przykład.
Przykład Redis Sentinel
Node_redis obecnie nie obsługuje Sentinel, ale popularny klient Redis dla Node.js, ioredis, obsługuje Sentinels. Zapoznaj się z jego dokumentacją, jak połączyć się z Sentinels z Node.js.
Czy chcesz skalować w górę? Oferujemy hosting dla Redis™* i w pełni zarządzane rozwiązania w wybranej przez Ciebie chmurze. Porównaj nas z innymi i zobacz, dlaczego oszczędzamy Ci kłopotów i pieniędzy.