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源码分析之共享模式
通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...
随机推荐
- 有关 python 切片的趣事
哈喽大家好,我是咸鱼 今天来讲一个我在实现 python 列表切片时遇到的趣事 在正式开始之前,我们先来了解一下切片(slice) 切片操作是访问序列(列表.字符串......)中元素的另一种方法,它 ...
- 看懂java序列化,这篇就够了
前言 相信大家日常开发中,经常看到 Java 对象 "implements Serializable".那么,它到底有什么用呢?本文带你全方位的解读序列化与反序列化这一块知识点. ...
- 图书商城Vue+Element+Node项目练习(...)
本系列文章是为学习Vue的项目练习笔记,尽量详细记录一下一个完整项目的开发过程.面向初学者,本人也是初学者,搬砖技术还不成熟.项目在技术上前端为主,包含一些后端代码,从基础的数据库(Sqlite).到 ...
- 详解Django请求与响应:深入理解Web Http交互的核心机制
本文深入探讨了 Django 中的请求与响应处理,从 Django 请求和响应的基础知识.生命周期,到 HttpRequest 和 HttpResponse 对象的详细介绍.同时,讨论了 Django ...
- 处理.git文件夹过大出现臃肿问题-filter-branch和BFG工具
Git开发手册 git一些不常用的命令记不住,可以查看git开发手册(https://m.php.cn/manual/view/34957.html) 1..git/objects/pack 文件过大 ...
- AI 时代的视频云转码移动端化——更快、更好、更低、更广
编者按: AI技术的落地是渐渐地从服务器端.云端落地,逐步到移动端及边缘设备上.这些年随着AI技术的进步,轻量级算法模型开始在移动端实时跑起来,并且移动端算法也在不断进行迭代和完善,而对于实时直播场景 ...
- ObjectInputStream_报错问题
报错: Exception in thread "main" java.io.StreamCorruptedException: invalid stream header: CE ...
- 基于ClickHouse解决活动海量数据问题
1.背景 魔笛活动平台要记录每个活动的用户行为数据,帮助客服.运营.产品.研发等快速处理客诉.解决线上问题并进行相关数据分析和报警.可以预见到需要存储和分析海量数据,预估至少几十亿甚至上百亿的数据量, ...
- 2023-07-17:给定一个数组arr,长度为n, 再给定一个数字k,表示一定要将arr划分成k个集合, 每个数字只能进一个集合。 返回每个集合内部的平均值都累加起来最小的值。 平均值向下取整。 1
2023-07-17:给定一个数组arr,长度为n, 再给定一个数字k,表示一定要将arr划分成k个集合, 每个数字只能进一个集合. 返回每个集合内部的平均值都累加起来最小的值. 平均值向下取整. 1 ...
- Linux 如何删除乱码的文件
事情是这样,服务器很多人在使用,以前的离职同事留了一大堆不知道是什么东西. 那些文件看不了,又删不掉,非常碍眼. 我搜索了挺多资料,没有一篇文章能真的解决问题(感觉都是抄来抄去的). 用 SFTP 工 ...