Свързването с единичен, самостоятелен Redis сървър е достатъчно просто:просто посочете хоста, порта и посочете паролата за удостоверяване, ако има такава. Повечето клиенти на Redis дори предоставят поддръжка за някакъв вид спецификация на URI връзката.
Въпреки това, за да постигнете висока достъпност (HA), трябва да разположите главна и подчинена конфигурация. В тази публикация ще ви покажем как да се свържете със сървъри на Redis в конфигурация на HA чрез една крайна точка.
Висока наличност в Redis
Високата наличност в Redis се постига чрез репликация главен-подчинен. Главният Redis сървър може да има множество Redis сървъри като подчинени, за предпочитане разгърнати на различни възли в множество центрове за данни. Когато главният не е наличен, един от подчинените може да бъде повишен да стане нов главен и да продължи да обслужва данни с малко или никакво прекъсване.
Като се има предвид простотата на Redis, има много налични инструменти с висока наличност, които могат да наблюдават и управляват конфигурация главен-подчинен реплика. Въпреки това, най-често срещаното HA решение, което идва в комплект с Redis, е Redis Sentinels. Redis Sentinels се изпълнява като набор от отделни процеси, които в комбинация наблюдават Redis главен-подчинен набори и осигуряват автоматично преминаване при отказ и преконфигуриране.
Свързване чрез Redis Sentinels
Redis Sentinels също действат като доставчици на конфигурация за комплекти главен-подчинен. Това означава, че Redis клиент може да се свърже с Redis Sentinels, за да разбере текущия главен и общото състояние на комплекта главен/подчинен реплика. Документацията на Redis предоставя подробности за това как клиентите трябва да взаимодействат със Sentinels. Този механизъм за свързване с Redis обаче има някои недостатъци:
- Има нужда от клиентска поддръжка :Връзката с Redis Sentinels се нуждае от клиент, който знае за Sentinel. Повечето популярни клиенти на Redis вече са започнали да поддържат Redis Sentinels, но някои все още не го правят. Например, node_redis (Node.js), phpredis (PHP) и scala-redis (Scala) са някои препоръчителни клиенти, които все още нямат поддръжка на Redis Sentinel.
- Сложност :Конфигурирането и свързването с Redis Sentinels не винаги е лесно, особено когато внедряването е в центрове за данни или зони за наличност. Например, Sentinels запомнят IP адресите (а не DNS имената) на всички сървъри за данни и стражи, на които някога са се натъкнали, и могат да бъдат неправилно конфигурирани, когато възлите динамично се преместват в центровете за данни. Redis Sentinels също споделят IP информация с други Sentinels. За съжаление, те преминават около локални IP адреси, което може да бъде проблематично, ако клиентът е в отделен център за данни. Тези проблеми могат да добавят значителна сложност както към операциите, така и към развитието.
- Сигурност :Самият сървър Redis осигурява примитивна автентификация чрез паролата на сървъра, самите Sentinels нямат такава функция. Така Redis Sentinel, който е отворен към Интернет, разкрива цялата информация за конфигурацията на всички главни устройства, които е конфигуриран да управлява. По този начин Redis Sentinels трябва винаги да се разполага зад правилно конфигурирани защитни стени. Правилната конфигурация на защитната стена, особено за многозонови конфигурации, може да бъде наистина трудна.
Единична крайна точка
Единична крайна точка на мрежова връзка за комплект главен-подчинен може да бъде предоставена по много начини. Това може да се направи чрез виртуални IP адреси или пренасочване на DNS имена или чрез използване на прокси сървър (например HAProxy) пред сървърите на Redis. Всеки път, когато се открие неизправност на текущия главен (от Sentinel), IP или DNS името се прехвърля на подчинения, който е бил повишен да стане нов главен от Redis Sentinels. Обърнете внимание, че това отнема време и мрежовата връзка с крайната точка ще трябва да бъде възстановена. Redis Sentinels разпознават капитана като неактивен само след като е бил неактивен за определен период от време (по подразбиране 30 секунди) и след това гласуват за повишаване на роб. При повишение на подчинен, IP адресът/DNS входът/проксито трябва да се промени, за да сочи към нов главен.
Свързване към комплекти Master-Slave
Важното съображение при свързване към набори реплики главен-подчинен с помощта на една крайна точка е, че трябва да се предвидят повторни опити при неуспехи на връзката, за да се приспособят за всякакви неуспехи на връзката по време на автоматично превключване при отказ на комплект реплика.
Ще покажем това с примери в Java, Ruby и Node.js. Във всеки пример ние алтернативно пишем и четем от HA Redis клъстер, докато преминаването на отказ се случва във фонов режим. В реалния свят опитите за повторен опит ще бъдат ограничени до определена продължителност или брой .
Свързване с Java
Jedis е препоръчителният Java клиент за Redis.
Пример за единична крайна точка
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(); } } } } ... }
Изходът на този тестов код по време на отказ изглежда така:
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...
Това е проста тестова програма. В реалния живот броят на повторните опити ще бъде фиксиран според продължителността или броя.
Пример на Redis Sentinel
Jedis също поддържа Redis Sentinels. Ето кода, който прави същото като горния пример, но чрез свързване към 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(); } } } } ... }
Нека видим поведението на горната програма по време на управление на отказ от 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... ...
Както е видно от регистрационните файлове, клиент, който поддържа Sentinels, може да се възстанови от събитие при отказ при отказ доста бързо.
Свързване с Ruby
Redis-rb е препоръчителният клиент на Ruby за Redis.
Пример за единична крайна точка
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
Ето примерния изход по време на отказ:
"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..."
Отново действителният код трябва да съдържа ограничен брой повторни опити.
Пример на Redis Sentinel
Redis-rb също поддържа 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 управлява отказите на Sentinel без никакви смущения.
"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
Свързване с Node.js
Node_redis е препоръчителният клиент Node.js за Redis.
Пример за единична крайна точка
... 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); ...
Ето как ще изглежда отказът:
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...
Можете също да експериментирате с опцията „retry_strategy“ по време на създаване на връзка, за да настроите логиката за повторен опит, за да отговори на вашите нужди. Документацията на клиента има пример.
Пример на Redis Sentinel
Node_redis в момента не поддържа Sentinels, но популярният клиент Redis за Node.js, ioredis поддържа Sentinels. Обърнете се към неговата документация за това как да се свържете със Sentinels от Node.js.
Готови ли сте за увеличаване? Ние предлагаме хостинг за Redis™* и напълно управлявани решения в облак по ваш избор. Сравнете ни с други и вижте защо ви спестяваме караница и пари.