源码走读-从JedisCluster的设计来发现对象池的奥秘

时间:2022-06-04
本文章向大家介绍源码走读-从JedisCluster的设计来发现对象池的奥秘,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

JedisCluster究竟是如何获得一个connection的?内部对象池又是如何工作的?

今天我们就去JedisCluster的源码看看,一探究竟。

好,先从JedisCluster开始。

JedisCluster

可以看到JedisCluster中除了构造函数之外,就是各种命令。建立连接是从构造函数开始,而JedisCluster作为一个子类一般是不会去直接去负责连接这件事情的,而是交给父类。所以我们去父类BinaryJedisCluster看看。

BinaryJedisCluster

可以发现,BinaryJedisCluster也没有去管新建连接这件事,而是交给了一个叫JedisClusterConnectionHandler的连接处理器去完成“连接”这件事情。

JedisClusterConnectionHandler

可以看到JedisClusterConnectionHandler把初始化连接的事交给了JedisClusterCache。

我们看到在构造函数的最后一个方法是initialSlotsCache。此方法负责把槽和传入的master节点初始化。就是用两个循环来完成初始化。现在我们去看看JedisClusterInfoCache。

JedisClusterInfoCache

通过上面的源码,我知道了传入的master列表最终被put进入了一个hashmap。key是"ip:port",value则是一个JedisPool。

接下来就看看JedisPool的实现吧。

JedisPool

我们发现,JedisPool并没有自己去实现对象池,而是直接使用了GenericObjectPool。而且是著名的apache commons包里的pool2。

现在去父类看看。

JedisPoolAbstract

Pool<Jedis>

可以发现此处使用了apache的pool2来实现的Jedis对象池。

好,现在就去看看GenericObjectPool的实现吧。

GenericObjectPool

在整个的构造函数里,我们只发现这么一个动态执行方法,其他的都是传入参数,这里应该就是初始化pool的入口,如上图:

this.startEvictor(this.getTimeBetweenEvictionRunsMillis());

现在去看看这个方法吧:

如图所述,我们现在就去BaseGenericObjectPool的Evictor类去看看这个任务的具体实现吧。

BaseGenericObjectPool的Evictor

可以看到Evictor果然是一个定时任务实现,里边有个关键调用是ensureMinIdle()方法,现在去看看这个方法吧:

可以看到ensureMinIdle()方法在BaseGenericObjectPool是一个抽象实现,现在就去子类GenericObjectPool。

GenericObjectPool

可以看到是通过LinkedBlockingDeque来存储批量创建好的一批对象。

LinkedBlockingDeque是双向链表实现的双向并发阻塞队列。该阻塞队列同时支持FIFO和FILO两种操作方式,即可以从队列的头和尾同时操作(插入/删除);并且,该阻塞队列是支持线程安全。 此外,LinkedBlockingDeque还是可选容量的(防止过度膨胀),即可以指定队列的容量。如果不指定,默认容量大小等于Integer.MAX_VALUE。

以上就是初始化JedisCluster的过程以及初始化JedisPool的过程。

那么如何获取一个JedisCluster的连接呢?

上图中的获取随机的JedisPool列表的方法getShuffledNodesPool():

这也就是JedisCluster如何获取一个和redis服务端连接的过程。

那么如何从JedisPool中获取一个Jedis对象呢?

JedisPool通过getResource()来获取到Jedis对象。

通过internalPool的borrowObject方法来弹出一个实例供使用。

现在我们就去GenericObjectPool看看borrowObject(弹出对象)这个方法的实现。

GenericObjectPool-->borrowObject()

可以发现通过多个while循环最初弹出一个对象返回。

以上就是一个对象池的弹出对象的过程。

其实对象池算是一种设计模式,比较经典。网上也有很多它的实现。我们也可以从GenericObjectPool所实现的接口就可以看出对象池的一些基本的操作。

总结

通过上面的源码走读,我们知道了JedisCluster会随机获取一个master作为和redis服务端交互的客户端。

ps:由此我们也知道JedisCluster由于设置的都是master。所以当master出现问题时,要做故障转移,那么此时客户端就得重新启动,重新设置最新的master。所以如果我们要做故障转移的支持,可以尝试通过动态的修改nodes那个map里边的master来实现快速的无重启的故障转移。原生的JedisCluster是不支持无重启的故障转移的。

同时在JedisCluster初始化连接的过程中,内部使用JedisPool来初始化redis连接。

而JedisPool的实现又是通过apache commons下的pool2来实现的。而ObjectPool本身就是一个经典的设计模式。核心就是得在池里生成一批对象,就不用在每次调用时都去new一个Jedis。

你可以写个for循环去测试JedisPool和Jedis,据我的测试,性能简直天壤之别,这也正是对象池的美妙之处。