redis 源码分析:Jedis 哨兵模式连接原理
1. 可以从单元测试开始入手
查看类JedisSentinelPool
private static final String MASTER_NAME = "mymaster";
protected static final HostAndPort sentinel1 = HostAndPorts.getSentinelServers().get(1);
protected static final HostAndPort sentinel2 = HostAndPorts.getSentinelServers().get(3);
@Before
public void setUp() throws Exception {
sentinels.clear();
sentinels.add(sentinel1.toString());
sentinels.add(sentinel2.toString());
}
@Test
public void repeatedSentinelPoolInitialization() {
for (int i = 0; i < 20; ++i) {
GenericObjectPoolConfig<Jedis> config = new GenericObjectPoolConfig<>();
JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, config, 1000,
"foobared", 2);
pool.getResource().close();
pool.destroy();
}
}
可以看到首先是创建了sentinel 的HostAndPort 对象,然后创建了连接池
2. 查看 JedisSentinelPool 构造器,正式进入源码
public JedisSentinelPool(String masterName, Set<String> sentinels,
final GenericObjectPoolConfig<Jedis> poolConfig, int timeout, final String password,
final int database) {
this(masterName, sentinels, poolConfig, timeout, timeout, null, password, database);
}
...
public JedisSentinelPool(String masterName, Set<String> sentinels,
final GenericObjectPoolConfig<Jedis> poolConfig,
final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,
final String user, final String password, final int database, final String clientName,
final int sentinelConnectionTimeout, final int sentinelSoTimeout, final String sentinelUser,
final String sentinelPassword, final String sentinelClientName) {
this(masterName, parseHostAndPorts(sentinels), poolConfig,
DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout)
.socketTimeoutMillis(soTimeout).blockingSocketTimeoutMillis(infiniteSoTimeout)
.user(user).password(password).database(database).clientName(clientName).build(),
DefaultJedisClientConfig.builder().connectionTimeoutMillis(sentinelConnectionTimeout)
.socketTimeoutMillis(sentinelSoTimeout).user(sentinelUser).password(sentinelPassword)
.clientName(sentinelClientName).build()
);
}
...
public JedisSentinelPool(String masterName, Set<HostAndPort> sentinels,
final GenericObjectPoolConfig<Jedis> poolConfig, final JedisClientConfig masterClientConfig,
final JedisClientConfig sentinelClientConfig) {
this(masterName, sentinels, poolConfig, new JedisFactory(masterClientConfig), sentinelClientConfig);
}
public JedisSentinelPool(String masterName, Set<HostAndPort> sentinels,
final GenericObjectPoolConfig<Jedis> poolConfig, final JedisFactory factory,
final JedisClientConfig sentinelClientConfig) {
super(poolConfig, factory);
this.factory = factory;
this.sentinelClientConfig = sentinelClientConfig;
HostAndPort master = initSentinels(sentinels, masterName);
initMaster(master);
}
这里执行了两个重要方法
initSentinels
和 initMaster
1. initSentinels 负责初始化sentinel ,并获得master的地址
2. 有了master地址,就可以 initMaster 了
private HostAndPort initSentinels(Set<HostAndPort> sentinels, final String masterName) {
HostAndPort master = null;
boolean sentinelAvailable = false;
LOG.info("Trying to find master from available Sentinels...");
for (HostAndPort sentinel : sentinels) {
LOG.debug("Connecting to Sentinel {}", sentinel);
//连接sentinel 节点
try (Jedis jedis = new Jedis(sentinel, sentinelClientConfig)) {
// 向sentinel发送命令 sentinel get-master-addr-by-name mymaster
List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);
// connected to sentinel...
sentinelAvailable = true;
if (masterAddr == null || masterAddr.size() != 2) {
LOG.warn("Can not get master addr, master name: {}. Sentinel: {}", masterName, sentinel);
continue;
}
master = toHostAndPort(masterAddr);
LOG.debug("Found Redis master at {}", master);
break;
} catch (JedisException e) {
// resolves #1036, it should handle JedisException there's another chance
// of raising JedisDataException
LOG.warn(
"Cannot get master address from sentinel running @ {}. Reason: {}. Trying next one.", sentinel, e);
}
}
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...");
}
}
LOG.info("Redis master running at {}, starting Sentinel listeners...", master);
for (HostAndPort sentinel : sentinels) {
MasterListener masterListener = new MasterListener(masterName, sentinel.getHost(), sentinel.getPort());
// whether MasterListener threads are alive or not, process can be stopped
masterListener.setDaemon(true);
masterListeners.add(masterListener);
masterListener.start();
}
return master;
}
这里最终要的一步就是jedis.sentinelGetMasterAddrByName(masterName);
,即向sentinel发送命令
sentinel get-master-addr-by-name mymaster
, 用来获取master节点的地址,并将地址返回
然后initMaster(master);
private void initMaster(HostAndPort master) {
synchronized (initPoolLock) {
if (!master.equals(currentHostMaster)) {
currentHostMaster = master;
// 这里是容易忽略但非常关键的一步
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
super.clear();
LOG.info("Created JedisSentinelPool to master at {}", master);
}
}
}
在这里对factory的连接地址进行了设置(在之前这里还是空值)
它是由 构造方法中的 new JedisFactory(masterClientConfig)
构造出来,在单元测试中我们得知masterClientConfig里面的属性都是空值
到这里 sentinelpool 就构造完毕了,其实这里还没有初始化出一个连接到master节点的实例,我们继续往后看
3. 单元测试中下一步 getResouce()
@Override
public Jedis getResource() {
while (true) {
// 关键一步
Jedis jedis = super.getResource();
// 这里没啥大用,容易误导
jedis.setDataSource(this);
// get a reference because it can change concurrently
final HostAndPort master = currentHostMaster;
final HostAndPort connection = jedis.getClient().getHostAndPort();
if (master.equals(connection)) {
// connected to the correct master
return jedis;
} else {
returnBrokenResource(jedis);
}
}
}
这里调用 super.getResource()
, 父类是Pool, 而Pool 的对象一般是由Factory 构建出来
public JedisSentinelPool(String masterName, Set<HostAndPort> sentinels,
final GenericObjectPoolConfig<Jedis> poolConfig, final JedisClientConfig masterClientConfig,
final JedisClientConfig sentinelClientConfig) {
this(masterName, sentinels, poolConfig, new JedisFactory(masterClientConfig), sentinelClientConfig);
}
public JedisSentinelPool(String masterName, Set<HostAndPort> sentinels,
final JedisFactory factory, final JedisClientConfig sentinelClientConfig) {
super(factory);
this.factory = factory;
this.sentinelClientConfig = sentinelClientConfig;
HostAndPort master = initSentinels(sentinels, masterName);
initMaster(master);
}
由此可知 factory 是 new JedisFactory(masterClientConfig)
, 并且由 父类子类都引用到,并且在 initMaster
方法中调用factory.setHostAndPort(currentHostMaster);
更新了master的地址。
而Pool extends GenericObjectPool , 这里GenericObjectPool 来自包 org.apache.commons.pool2
public T getResource() {
try {
return super.borrowObject();
} catch (JedisException je) {
throw je;
} catch (Exception e) {
throw new JedisException("Could not get a resource from the pool", e);
}
}
这里borrowObject 时,实际是调用工厂的方法干活,直接看工厂类JedisFactory
@Override
public PooledObject<Jedis> makeObject() throws Exception {
Jedis jedis = null;
try {
jedis = new Jedis(jedisSocketFactory, clientConfig);
return new DefaultPooledObject<>(jedis);
} catch (JedisException je) {
logger.debug("Error while makeObject", je);
throw je;
}
}
在这里会构建出Jedis对象 ,注意这里的jedisSocketFactory对象,实在构造方法中构造出
protected JedisFactory(final URI uri, final int connectionTimeout, final int soTimeout,
final int infiniteSoTimeout, final String clientName, final SSLSocketFactory sslSocketFactory,
final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {
if (!JedisURIHelper.isValid(uri)) {
throw new InvalidURIException(String.format(
"Cannot open Redis connection due invalid URI. %s", uri.toString()));
}
this.clientConfig = DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout)
.socketTimeoutMillis(soTimeout).blockingSocketTimeoutMillis(infiniteSoTimeout)
.user(JedisURIHelper.getUser(uri)).password(JedisURIHelper.getPassword(uri))
.database(JedisURIHelper.getDBIndex(uri)).clientName(clientName)
.protocol(JedisURIHelper.getRedisProtocol(uri))
.ssl(JedisURIHelper.isRedisSSLScheme(uri)).sslSocketFactory(sslSocketFactory)
.sslParameters(sslParameters).hostnameVerifier(hostnameVerifier).build();
this.jedisSocketFactory = new DefaultJedisSocketFactory(new HostAndPort(uri.getHost(), uri.getPort()), this.clientConfig);
}
void setHostAndPort(final HostAndPort hostAndPort) {
if (!(jedisSocketFactory instanceof DefaultJedisSocketFactory)) {
throw new IllegalStateException("setHostAndPort method has limited capability.");
}
((DefaultJedisSocketFactory) jedisSocketFactory).updateHostAndPort(hostAndPort);
}
4. 再看Jedis
public Jedis(final JedisSocketFactory jedisSocketFactory, final JedisClientConfig clientConfig) {
connection = new Connection(jedisSocketFactory, clientConfig);
RedisProtocol proto = clientConfig.getRedisProtocol();
if (proto != null) commandObjects.setProtocol(proto);
}
这里直接构造出Connectrion 对象, 并传入socketFactory
public Connection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig) {
this.socketFactory = socketFactory;
this.soTimeout = clientConfig.getSocketTimeoutMillis();
this.infiniteSoTimeout = clientConfig.getBlockingSocketTimeoutMillis();
initializeFromClientConfig(clientConfig);
}
在这个构造方法中执行关键方法initializeFromClientConfig
private void initializeFromClientConfig(final JedisClientConfig config) {
try {
connect();
......
}
connect()
public void connect() throws JedisConnectionException {
if (!isConnected()) {
try {
socket = socketFactory.createSocket();
soTimeout = socket.getSoTimeout(); //?
outputStream = new RedisOutputStream(socket.getOutputStream());
inputStream = new RedisInputStream(socket.getInputStream());
broken = false; // unset broken status when connection is (re)initialized
} catch (JedisConnectionException jce) {
setBroken();
throw jce;
} catch (IOException ioe) {
setBroken();
throw new JedisConnectionException("Failed to create input/output stream", ioe);
} finally {
if (broken) {
IOUtils.closeQuietly(socket);
}
}
}
}
最终通过 soeckFactory 构建出socket,完成对redis 的master节点的连接
redis 源码分析:Jedis 哨兵模式连接原理的更多相关文章
- redis源码分析之事务Transaction(下)
接着上一篇,这篇文章分析一下redis事务操作中multi,exec,discard三个核心命令. 原文地址:http://www.jianshu.com/p/e22615586595 看本篇文章前需 ...
- Redis源码分析:serverCron - redis源码笔记
[redis源码分析]http://blog.csdn.net/column/details/redis-source.html Redis源代码重要目录 dict.c:也是很重要的两个文件,主要 ...
- 并发编程学习笔记(9)----AQS的共享模式源码分析及CountDownLatch使用及原理
1. AQS共享模式 前面已经说过了AQS的原理及独享模式的源码分析,今天就来学习共享模式下的AQS的几个接口的源码. 首先还是从顶级接口acquireShared()方法入手: public fin ...
- Guava 源码分析之Cache的实现原理
Guava 源码分析之Cache的实现原理 前言 Google 出的 Guava 是 Java 核心增强的库,应用非常广泛. 我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Goog ...
- 从SpringBoot源码分析 配置文件的加载原理和优先级
本文从SpringBoot源码分析 配置文件的加载原理和配置文件的优先级 跟入源码之前,先提一个问题: SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数( ...
- redis源码分析之发布订阅(pub/sub)
redis算是缓存界的老大哥了,最近做的事情对redis依赖较多,使用了里面的发布订阅功能,事务功能以及SortedSet等数据结构,后面准备好好学习总结一下redis的一些知识点. 原文地址:htt ...
- redis源码分析之有序集SortedSet
有序集SortedSet算是redis中一个很有特色的数据结构,通过这篇文章来总结一下这块知识点. 原文地址:http://www.jianshu.com/p/75ca5a359f9f 一.有序集So ...
- redis源码分析之事务Transaction(上)
这周学习了一下redis事务功能的实现原理,本来是想用一篇文章进行总结的,写完以后发现这块内容比较多,而且多个命令之间又互相依赖,放在一篇文章里一方面篇幅会比较大,另一方面文章组织结构会比较乱,不容易 ...
- Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式
在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概 ...
- Java并发系列[3]----AbstractQueuedSynchronizer源码分析之共享模式
通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...
随机推荐
- 快速把PDF文档里的表格粘贴到excel的方法
1 打开需要复制的PDf文件,点一下页面上方的"选择文本"按钮(如下图中手图标左边的箭头),以便选中文本 2 ctrl c 需要复制的表格,到excel中ctrl v.这时候所有类 ...
- 【python基础】类-初识类
1.面向对象思想 在认识类之前,我们需要理解面向对象思想和面向过程思想. 面向过程思想:要拥有一间房屋,面向过程像是自己来修盖房屋,如果需要经过选址.购买材料.砌墙.装修等步骤,面向过程编程,就相当于 ...
- 洛谷 P4859 已经没有什么好害怕的了
题目描述 学姐 4 了. 有 \(n\) 个糖果和 \(n\) 个药片,它们要进行一一配对.每个糖果或药片都具有互不相同的能量值,要求配对后,糖果比药片能量高的对数,比剩下的对数恰好多 \(k\),求 ...
- 常见的Web安全攻击类型及其应对方法
目录 1. 引言 2. 技术原理及概念 3. 实现步骤与流程 4. 应用示例与代码实现讲解 5. 优化与改进 6. 结论与展望 7. 附录:常见问题与解答 常见的Web安全攻击类型及其应对方法 随着网 ...
- 记录一个在写项目中遇到的Maven依赖无法导入的问题
记录一个在写项目中遇到的Maven依赖无法导入的问题 项目是一个父项目做依赖管理,三个子项目,今天遇到一个问题: 子项目中导入的依赖,怎么都导入不进去,maven仓库中已经有了,idea提示也没有问题 ...
- IoTOS-v1.5.3 新增 智能诊断&会话记录导出
IoTOS v1.5.3 一.新增智能诊断 智能诊断功能: 智能诊断会根据不同上游接口能力开放提供接近官方甚至比官方更加完善的智能诊断功能. 目前还原OneLink官方智能诊断功能包括动效.诊断建议等 ...
- Mysql基础6-常用数据库函数
一.字符串函数 1.常见Mysql内置字符串函数 concat(s1,s2,s3,...):字符串拼接,将s1,s2,s3...等拼接成一个字符串 lower(str):将字符串str全部转为小写 u ...
- 如何动态修改 spring aop 切面信息?让自动日志输出框架更好用
业务背景 很久以前开源了一款 auto-log 自动日志打印框架. 其中对于 spring 项目,默认实现了基于 aop 切面的日志输出. 但是发现一个问题,如果切面定义为全切范围过大,于是 v0.2 ...
- ITIL4与Devops(一)
目录 一.服务管理与ITIL 1.1 服务管理现状 1.2 服务管理原则 1.3 ITIL版本发展历程 ITIL2 服务支持 服务交付 服务战略 ITIL3 框架 职能 ITIL 2011 流程的基本 ...
- 【overcome error】dereferencing pointer to incomplete type
@ 目录 前言 解决 代码情况 分析问题 尾声 前言 这个问题是我在学习数据结构链栈部分遇到的,英文报错如题所示,中文意思是:取消引用不完整类型的指针,在百度一圈也没明白,(百度搜索,看一个和全看基本 ...