sql >> データベース >  >> NoSQL >> Redis

Redisセンチネルによる高可用性:Redisマスター/スレーブセットへの接続

    単一のスタンドアロンRedisサーバーへの接続は非常に簡単です。ホストとポートをポイントし、認証パスワードがある場合はそれを入力するだけです。ほとんどのRedisクライアントは、ある種のURI接続仕様もサポートしています。

    ただし、高可用性(HA)を実現するには、マスターとスレーブの構成を展開する必要があります。この投稿では、単一のエンドポイントを介してHA構成でRedisサーバーに接続する方法を紹介します。

    Redisの高可用性

    Redisの高可用性は、マスタースレーブレプリケーションによって実現されます。マスターRedisサーバーは、スレーブとして複数のRedisサーバーを持つことができ、できれば複数のデータセンターの異なるノードにデプロイすることができます。マスターが使用できない場合、スレーブの1つを昇格させて新しいマスターにし、中断することなく、またはほとんど中断することなくデータを提供し続けることができます。

    Redisのシンプルさを考えると、マスタースレーブレプリカ構成を監視および管理できる高可用性ツールが多数あります。ただし、Redisにバンドルされている最も一般的なHAソリューションはRedisSentinelsです。 Redis Sentinelsは、Redisマスタースレーブセットを組み合わせて監視し、自動フェイルオーバーと再構成を提供する一連の個別のプロセスとして実行されます。

    Redisセンチネルを介した接続

    Redis Sentinelsは、マスタースレーブセットの構成プロバイダーとしても機能します。つまり、RedisクライアントはRedis Sentinelsに接続して、マスター/スレーブレプリカセットの現在のマスターと一般的な状態を確認できます。 Redisのドキュメントには、クライアントがセンチネルと対話する方法の詳細が記載されています。ただし、Redisに接続するこのメカニズムには、いくつかの欠点があります。

    • クライアントのサポートが必要です :Redis Sentinelsに接続するには、Sentinelの「認識」クライアントが必要です。最も人気のあるRedisクライアントは、Redis Sentinelsのサポートを開始しましたが、まだサポートしていないクライアントもあります。たとえば、node_redis(Node.js)、phpredis(PHP)、scala-redis(Scala)は、RedisSentinelをまだサポートしていない推奨クライアントです。
    • 複雑さ :Redis Sentinelsの構成と接続は、特にデータセンターやアベイラビリティーゾーン全体に展開する場合は、必ずしも簡単ではありません。たとえば、センチネルは、遭遇したすべてのデータサーバーとセンチネルのIPアドレス(DNS名ではない)を記憶しており、ノードがデータセンター内で動的に移動すると誤って構成される可能性があります。 Redisセンチネルは、他のセンチネルともIP情報を共有します。残念ながら、ローカルIPを渡すため、クライアントが別のデータセンターにある場合は問題が発生する可能性があります。これらの問題により、運用と開発の両方が大幅に複雑になる可能性があります。
    • セキュリティ :Redisサーバー自体は、サーバーパスワードを介したプリミティブ認証を提供しますが、Sentinel自体にはそのような機能はありません。そのため、インターネットに公開されているRedis Sentinelは、管理するように構成されているすべてのマスターの構成情報全体を公開します。したがって、RedisSentinelsは常に正しく構成されたファイアウォールの背後にデプロイする必要があります。特にマルチゾーン構成の場合、ファイアウォール構成を正しく行うのは非常に難しい場合があります。

    単一エンドポイント

    マスタースレーブセットの単一のネットワーク接続エンドポイントは、さまざまな方法で提供できます。これは、仮想IPまたはDNS名の再マッピングを介して、またはRedisサーバーの前にあるプロキシサーバー(HAProxyなど)を使用して実行できます。現在のマスターの障害が(Sentinelによって)検出されるたびに、IPまたはDNS名は、RedisSentinelsによって新しいマスターになるようにプロモートされたスレーブにフェイルオーバーされます。 これには時間がかかり、エンドポイントへのネットワーク接続を再確立する必要があることに注意してください。 Redis Sentinelsは、マスターが一定期間(デフォルトでは30秒)ダウンした後にのみマスターがダウンしていると認識し、スレーブを昇格させるために投票します。スレーブの昇格時に、IPアドレス/DNSエントリ/プロキシを変更して新しいマスターを指すようにする必要があります。

    マスタースレーブセットへの接続

    単一のエンドポイントを使用してマスタースレーブレプリカセットに接続する際の重要な考慮事項は、の自動フェイルオーバー中の接続障害に対応するために、接続障害の再試行をプロビジョニングする必要があることです。レプリカセット。

    Java、Ruby、Node.jsの例でこれを示します。各例では、フェイルオーバーがバックグラウンドで発生している間に、HARedisクラスターから交互に書き込みと読み取りを行います。 現実の世界では、再試行の試行は特定の期間またはカウントに制限されます

    Javaとの接続

    JedisはRedisに推奨されるJavaクライアントです。

    単一エンドポイントの例

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

    これは単純なテストプログラムです。実際には、再試行の回数は期間または回数によって決まります。

    RedisSentinelの例

    JedisはRedisSentinelsもサポートしています。したがって、これは上記の例と同じことを行うが、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はRedisに推奨されるRubyクライアントです。

    単一エンドポイントの例

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

    繰り返しになりますが、実際のコードには限られた回数の再試行が含まれている必要があります。

    RedisSentinelの例

    Redis-rbはSentinelもサポートしています。

    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は、Redisに推奨されるNode.jsクライアントです。

    単一エンドポイントの例

    ...
    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」オプションを試して、ニーズに合わせて再試行ロジックを調整することもできます。クライアントのドキュメントに例があります。

    RedisSentinelの例

    Node_redisは現在Sentinelsをサポートしていませんが、Node.js用の人気のあるRedisクライアントであるioredisはSentinelsをサポートしています。 Node.jsからSentinelsに接続する方法についてはドキュメントを参照してください。

    スケールアップする準備はできましたか?選択したクラウド上で、Redis™*のホスティングとフルマネージドソリューションを提供します。私たちを他の人と比較して、私たちがあなたの手間と現金を節約する理由を見てください。


    1. MongoDBプロジェクションで文字列を数値に変換する

    2. マングースドキュメントインスタンスをコピー/クローンする最も簡単な方法は?

    3. ClusterControlを使用したMongoDB4.0の監視と運用管理

    4. redisdbのすべてのキーと値を反復処理するためのより高速な方法