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つの可能性があります.
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
で呼び出された場合)、初期化されたファクトリのみが再設定されます.currentHostMaster
とfactory
の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上で独自に実装することができる.