JedisSentinelPool源码分析

1. 概述

    Redis-Sentinel作为官方推荐的HA解决方案,jedis也在客户端角度实现了对Sentinel的支持,主要实现在JedisSentinelPool这个类里。

2. 简单例子
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;

import java.util.HashSet;
import java.util.Set;

public class RedisSentinelClient {

    /**
     * @param args
     */
    public static void main(String[] args) {
        Set sentinels = new HashSet();
        sentinels.add(new HostAndPort("192.168.58.99", 26379).toString());
        sentinels.add(new HostAndPort("192.168.58.100", 26380).toString());
        sentinels.add(new HostAndPort("192.168.58.101", 26381).toString());
        JedisSentinelPool sentinelPool = new JedisSentinelPool("master", sentinels);
        System.out.println("Current master: " + sentinelPool.getCurrentHostMaster().toString());
        Jedis master = sentinelPool.getResource();
        master.set("username","liangzhichao");
        sentinelPool.returnResource(master);

        Jedis master2 = sentinelPool.getResource();
        String value = master2.get("username");
        System.out.println("username: " + value);
        master2.close();
        sentinelPool.destroy();
    }
3 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;
4 JedisSentinelPool构造器
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-Sentinel,允许多个,然后通过initSentinels与sentinel沟通后,确定所监视的master节点,接着通过masater节点来创建好对象池,方便后续从对象池中取出一个Jedis实例。

5 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,一个一个连接到sentinel,通过jedis.sentinelGetMasterAddrByName()方法去连接sentinel,询问关于masterName的消息。

 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);
  }

      接着为每个sentinel都启动了一个监听者MasterListener,MaserListener本身是一个线程,它会去订阅sentinel上关于master节点地址该表的消息。

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);
        }

    为啥master需要与实例变量currentHostMaster作比较,这是因为MasterListener会在发现master地址改变以后,去调用initPool方法。

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);
}
6 MasterListener监听者线程
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);
            }
        }
    }
}

    可以看出,MasterListener是委托了Jedis去与sentinel打交道,订阅了关于master地址变换的消息,当master地址变换时,就会再调用一次initPool方法,重新设置对象池相关的设置。

7 JedisSentinelPool不足:

    Jedis的JedisSentinelPool的实现仅仅适用于单个master-slave。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容