JedisSentinelPoolソースコード

15085 ワード

概要


JedisはRedisが公式に推薦したJavaクライアントであり、より多くのRedisのクライアントはRedis公式サイトのクライアントリストを参照することができる.Redis-Sentineは、公式に推奨されているHAソリューションとして、Jedisもクライアントの観点からSentineのサポートを実現しており、主にJedisSentinelPool.javaというクラスで実装されており、以下、このクラスの実装を分析する.

ツールバーの


JedisSentinelPoolクラスには、次の属性があります.
 //  apache commom-pool2       protected GenericObjectPoolConfig poolConfig; //    ,   2000 protected int timeout = Protocol.DEFAULT_TIMEOUT; //sentinel    protected String password; //redis       protected int database = Protocol.DEFAULT_DATABASE; //master   , master        ,         protected Set<MasterListener> masterListeners = new HashSet<MasterListener>(); protected Logger log = Logger.getLogger(getClass().getName()); //Jedis       private volatile JedisFactory factory; //   master,HostAndPort         ip port     private volatile HostAndPort currentHostMaster; 

コンストラクタ


コンストラクタのコードは次のとおりです.
public JedisSentinelPool(String masterName, Set<String> sentinels, final GenericObjectPoolConfig poolConfig, int timeout, final String password, final int database) { this.poolConfig = poolConfig; this.timeout = timeout; this.password = password; this.database = database; HostAndPort master = initSentinels(sentinels, masterName); initPool(master); } 

コンストラクタは最初にインスタンス変数に値を割り当て、パラメータsentinelsはクライアントが必要とするRedis-Entinelであり、複数を許可し、1つのセットで盛ることができます.
次にinitSentinels法によりsentinelとコミュニケーションをとり,現在のsentinelが監視しているmasterがどれであるかを決定する.次に、オブジェクトプールをmasterで作成し、その後、オブジェクトプールからJedisインスタンスを取り出してmasterを操作します.

义齿


InitSentinelsメソッドのコードは次のとおりです.いくつかのコメントを追加しました.
 private HostAndPort initSentinels(Set<String> sentinels, final String masterName) { HostAndPort master = null; boolean sentinelAvailable = false; log.info("Trying to find master from available Sentinels..."); //    sentinels,     sentinels for (String sentinel : sentinels) { // host:port   sentinel       HostAndPort  。 final HostAndPort hap = toHostAndPort(Arrays.asList(sentinel.split(":"))); log.fine("Connecting to Sentinel " + hap); Jedis jedis = null; try { //    sentinel jedis = new Jedis(hap.getHost(), hap.getPort()); //   masterName  master   ,    list,host= list[0], port = // list[1] List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName); // connected to sentinel... sentinelAvailable = true; if (masterAddr == null || masterAddr.size() != 2) { log.warning("Can not get master addr, master name: " + masterName + ". Sentinel: " + hap + "."); continue; } master = toHostAndPort(masterAddr); log.fine("Found Redis master at " + master); //        sentinel    master,    sentinels break; } catch (JedisConnectionException e) { log.warning("Cannot connect to sentinel running @ " + hap + ". Trying next one."); } finally { //    sentinel    if (jedis != null) { jedis.close(); } } } //    ,  master null,        : //       sentinels   down     master        sentinels   
 if (master == null) { if (sentinelAvailable) { // can connect to sentinel, but master name seems to not // monitored throw new JedisException("Can connect to sentinel, but " + masterName + " seems to be not monitored..."); } else { throw new JedisConnectionException( "All sentinels down, cannot determine where is " + masterName + " master is running..."); } } //      ,     master    log.info("Redis master running at " + master + ", starting Sentinel listeners..."); //     sentinels    for (String sentinel : sentinels) { final HostAndPort hap = toHostAndPort(Arrays.asList(sentinel.split(":"))); MasterListener masterListener = new MasterListener(masterName, hap.getHost(), hap.getPort()); masterListeners.add(masterListener); masterListener.start(); } return master; } 
initSentinelsメソッドのパラメータにはmasterNameがあります.これは、私たちが検索するmasterの名前です.最初は、複数のsentinelsを巡り、1つずつsentinelに接続し、masterNameに関するメッセージを尋ねると、jedis.sentinelGetMasterAddrByName()の方法でsentinelに接続し、現在のmasterのアドレスを尋ねることがわかります.この方法をクリックしてみてください.ソースコードはこのように書かれています.
/** * <pre> * redis 127.0.0.1:26381> sentinel get-master-addr-by-name mymaster * 1) "127.0.0.1" * 2) "6379" * </pre> * @param masterName * @return two elements list of strings : host and port. */ public List<String> sentinelGetMasterAddrByName(String masterName) { client.sentinel(Protocol.SENTINEL_GET_MASTER_ADDR_BY_NAME, masterName); final List<Object> reply = client.getObjectMultiBulkReply(); return BuilderFactory.STRING_LIST.build(reply); } 

Jedisにバインドされたclientが「get-master-addr-by-name」コマンドを送信するように呼び出されます.initSentinelsメソッドに戻り、masterのアドレスを尋ねなければ、次のsentinelに尋ねる.マスターのアドレスを尋ねた場合、sentinelコレクションは巡回せず、直接breakはループループループを終了します.
ループが終了した後、masterの値がnullの場合、次の2つの可能性があります.
  • すべてのsentinelインスタンスが使用できない
  • もう1つは、sentinelインスタンスは使用可能であるが、masterNameという名前のRedisは監視されていない.

  • masterがnullの場合、プログラムは異常を投げ出し、下に行かない.マスターがnullでなければ、下へ歩き続けます.
    コードから、sentinelごとにリスナーMasterListenerが起動されていることがわかります.MasterListener自体はスレッドであり、sentinel上のmasterノードアドレスの変更に関するメッセージを購読します.
    次に、構造方法の別の方法:initPoolを分析する.その後、MasterListenerの実装を見ます.

    InitPoolメソッド


    InitPoolの実装ソースコードは以下の通りです.
    private void initPool(HostAndPort master) { if (!master.equals(currentHostMaster)) { currentHostMaster = master; if (factory == null) { factory = new JedisFactory(master.getHost(), master.getPort(), timeout, password, database); initPool(poolConfig, factory); } else { factory.setHostAndPort(currentHostMaster); // although we clear the pool, we still have to check the // returned object // in getResource, this call only clears idle instances, not // borrowed instances internalPool.clear(); } log.info("Created JedisPool to master at " + master); } } 

    パラメータとして渡されたマスターは、インスタンス変数currentHostMasterと比較され、同じかどうか見てみましょう.なぜこの比較を行うのか.前述したMasterListenerは、マスターアドレスの変更が発見された後、initPoolメソッドを呼び出すからです.initPoolメソッドが最初に呼び出された場合(コンストラクション関数で呼び出された場合)、Jedisインスタンス作成ファクトリが初期化され、最初の呼び出しでなければ(MasterListenerで呼び出された場合)、初期化されたファクトリのみが再設定されます.currentHostMasterfactoryの2つの変数がvolatileとして宣言される理由も、マルチスレッド環境でアクセスおよび変更されるため、可視性を保証する必要があります.1回目の呼び出しでは、initPool(poolConfig, factory)メソッドが呼び出されます.このメソッドのソースコードを見てみましょう.
    public void initPool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory) { if (this.internalPool != null) { try { closeInternalPool(); } catch (Exception e) { } } this.internalPool = new GenericObjectPool<T>(factory, poolConfig); } 

    基本的には、内部オブジェクトプールを初期化するだけです.

    MasterListenerリスナースレッド


    そのrunメソッドを直接見て実現しましょう.
     public void run() { running.set(true); while (running.get()) { j = new Jedis(host, port); try { //  sentinel   master        j.subscribe(new JedisPubSub() { @Override public void onMessage(String channel, String message) { log.fine("Sentinel " + host + ":" + port + " published: " + message + "."); String[] switchMasterMsg = message.split(" "); if (switchMasterMsg.length > 3) { if (masterName.equals(switchMasterMsg[0])) { initPool(toHostAndPort(Arrays.asList( switchMasterMsg[3], switchMasterMsg[4]))); } else { log.fine("Ignoring message on +switch-master for master name " + switchMasterMsg[0] + ", our master name is " + masterName); } } else { log.severe("Invalid message received on Sentinel " + host + ":" + port + " on channel +switch-master: " + message); } } }, "+switch-master"); } catch (JedisConnectionException e) { if (running.get()) { log.severe("Lost connection to Sentinel at " + host + ":" + port + ". Sleeping 5000ms and retrying."); try { Thread.sleep(subscribeRetryWaitTimeMillis); } catch (InterruptedException e1) { e1.printStackTrace(); } } else { log.fine("Unsubscribing from Sentinel at " + host + ":" + port); } } } } 

    Jedisがsentinelと付き合うように依頼し、masterアドレス変換に関するメッセージを購読していることがわかります.masterアドレス変換の場合、initPoolメソッドが呼び出され、オブジェクトプールに関する設定が再設定されます.

    の最後の部分


    JedisのJedisSentinelPoolの実装は単一のmaster−slaveにのみ適用される.sentinelが提供する自動プライマリ・スタンバイ・スイッチング・メカニズムと、memcachedがコンシステンシ・ハッシュでデータ・スライスを行うのと同様に、クライアントがデータ・スライス(Sharding)を行う必要があるというより多くのニーズがある.次に、整合性ハッシュスライスをサポートするShardedJeddisSentinelPoolを、既存のJedis上で独自に実装することができる.