概述

Jedis是Redis官方推荐的Java客户端,更多Redis的客户端可以参考Redis官网客户端列表。Redis-Sentinel作为官方推荐的HA解决方案,Jedis也在客户端角度实现了对Sentinel的支持,主要实现在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-Sentinel,允许有多个,用一个集合来盛装。

然后通过initSentinels方法与sentinel沟通后,确定当前sentinel所监视的master是哪一个。然后通过master来创建好对象池,以便后续从对象池中取出一个Jedis实例,来对master进行操作。

initSentinels方法

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,去询问关于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。如果询问到了master的地址,那么将不再遍历sentinel集合,直接break退出循环遍历。

如果循环结束后,master的值为null,那么有两种可能:

  • 一种是所有的sentinel实例都不可用了

  • 另外一种是,sentinel实例有可用的,但是没有监控名字为masterName的Redis。

如果master为null,程序会抛出异常,不再往下走了。如果master不为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);
}
}

可以看到,作为参数传进来的master会与实例变量currentHostMaster作比较,看看是否是相同的,为什么要作这个比较呢,因为前文中提到的MasterListener会在发现master地址改变以后,去调用initPool方法。
如果是第一次调用initPool方法(构造函数中调用),那么会初始化Jedis实例创建工厂,如果不是第一次调用(MasterListener中调用),那么只对已经初始化的工厂进行重新设置。
从以上也可以看出为什么currentHostMasterfactory这两个变量为什么要声明为volatile,它们会在多线程环境下被访问和修改,因此必须保证可见性。
第一次调用时,会调用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提供的自动主备切换机制,又需要客户端能够做数据分片(Sharding),类似于memcached用一致性哈希进行数据分片。
接下来可能会自己在现有Jedis上实现一个支持一致性哈希分片的ShardedJedisSentinelPool。

Jedis的JedisSentinelPool源代码分析的更多相关文章

  1. Jedis的Sharded源代码分析

    概述 Jedis是Redis官方推荐的Java客户端,更多Redis的客户端可以参考Redis官网客户端列表.当业务的数据量非常庞大时,需要考虑将数据存储到多个缓存节点上,如何定位数据应该存储的节点, ...

  2. android-plugmgr源代码分析

    android-plugmgr是一个Android插件加载框架,它最大的特点就是对插件不需要进行任何约束.关于这个类库的介绍见作者博客,市面上也有一些插件加载框架,但是感觉没有这个好.在这篇文章中,我 ...

  3. Twitter Storm源代码分析之ZooKeeper中的目录结构

    徐明明博客:Twitter Storm源代码分析之ZooKeeper中的目录结构 我们知道Twitter Storm的所有的状态信息都是保存在Zookeeper里面,nimbus通过在zookeepe ...

  4. 转:SDL2源代码分析

    1:初始化(SDL_Init()) SDL简介 有关SDL的简介在<最简单的视音频播放示例7:SDL2播放RGB/YUV>以及<最简单的视音频播放示例9:SDL2播放PCM>中 ...

  5. 转:RTMPDump源代码分析

    0: 主要函数调用分析 rtmpdump 是一个用来处理 RTMP 流媒体的开源工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps://. ...

  6. 转:ffdshow 源代码分析

    ffdshow神奇的功能:视频播放时显示运动矢量和QP FFDShow可以称得上是全能的解码.编码器.最初FFDShow只是mpeg视频解码器,不过现在他能做到的远不止于此.它能够解码的视频格式已经远 ...

  7. UiAutomator源代码分析之UiAutomatorBridge框架

    上一篇文章<UIAutomator源代码分析之启动和执行>我们描写叙述了uitautomator从命令行执行到载入測试用例执行測试的整个流程.过程中我们也描写叙述了UiAutomatorB ...

  8. MyBatis架构设计及源代码分析系列(一):MyBatis架构

    如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处. 一.概述 MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己 The MyBa ...

  9. hostapd源代码分析(三):管理帧的收发和处理

    hostapd源代码分析(三):管理帧的收发和处理 原文链接:http://blog.csdn.net/qq_21949217/article/details/46004379 这篇文章我来讲解一下h ...

随机推荐

  1. 解决maven-dependency-plugin (goals "copy-dependencies", "unpack") is not supported by m2e.错误

    POM文件报错maven-dependency-plugin (goals "copy-dependencies", "unpack") is not supp ...

  2. mysql 多表 update sql语句总结

    mysql 多表 update 有几种不同的写法. 假定我们有两张表,一张表为Product表存放产品信息,其中有产品价格列Price:另外一张表是ProductPrice表,我们要将ProductP ...

  3. MAC OS JAVA环境变量配置

    在  /etc/profile 中 加上这些 #临时提权 sudo su #输入密码 vi /etc/profile #配置JAVA_HOME,此处路径根据自己的版本填写 JAVA_HOME=&quo ...

  4. A. Sorting Railway Cars

    A. Sorting Railway Cars time limit per test 2 seconds memory limit per test 256 megabytes input stan ...

  5. Python图片转换成矩阵,矩阵数据转换成图片

    # coding=gbk from PIL import Image import numpy as np # import scipy def loadImage(): # 读取图片 im = Im ...

  6. [topcoder]HappyLetterDiv2

    http://community.topcoder.com/stat?c=problem_statement&pm=13245 就是有字符串,里面的字符可以随意两两消除,如果不等的话,那么最后 ...

  7. EJB--事务管理 .

    在我们对事务的基本概念以及出现的问题和隔离级别有进一步的了解之后,接下来看看EJB是如何进行事务管理. 在EJB中有两种使用事务的方式.第一种方式通过容器管理的事务,叫CMT(Container-Ma ...

  8. 使用 powershell 的 grep 过滤文本

    使用 powershell 的 grep 过滤文本 有个log文件,大小在4M左右,要求找出里面耗时超过100s 的记录.首先想到了强大的 grep ,那么就搞起. 先在网上找一下资料,这篇文章,有几 ...

  9. 【转载】R6034错误,C Runtime Error

    能查到的解决方法都在里面有提及: 我是使用 stdafx.h加入这句 code #pragma comment(linker, "\"/manifestdependency:typ ...

  10. BZOJ 3140 消毒(最小顶点覆盖)

    题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=3140 题意:最近在生物实验室工作的小T遇到了大麻烦. 由于实验室最近升级的缘故,他的分格 ...