当前位置: 首页 > Spring Boot, 缓存 > 正文

基于Spring 的 Redis Sentinel 读写分离 Slave 连接池

目 录
 [ 隐藏 ]

0. 背景 

Reids除了配置集群实现高可用之外,对于单机版的Redis,可以通过Master-Slave架构,配合使用Sentinel机制实现高可用架构, 
同时客户端可以实现自动失效转移。 

类似于JdbcTemplate,Spring中使用RedisTemplate来操作Redis。Spring Boot中只需引入如下Maven依赖,即可自动配置 
一个RedisTemplate实例。

RedisTemplate需要一个RedisConnectionFactory来管理Redis连接。 可以在项目中定义一个RedisSentinelConfiguration给 
RedisConnectionFactory,即可生成一个基于Sentinel的连接池,并且实现了自动失效转移:当master失效时,Sentinel自动提升一个slave 
成为master保证Redis的master连接高可用。 

下面是基于Sentinel的RedisConnectionFactory的典型配置 

查看 org.springframework.data.redis.connection.jedis.JedisConnectionFactory源码发现, 
当配置了RedisSentinelConfiguration后,RedisConnectionFactory会返回一个JedisSentinelPool连接池。该连接池里面所有的连接 
都是连接到Master上面的。 同时,在JedisSentinelPool中为每一个Sentinel都配置了+switch-master频道的监听。 当监听到+switch-master消息后 
表示发生了master切换,有新的Master产生,然后会重新初始化到新Master的连接池。 

至此,我们知道基于Sentinel可以创建RedisConnectionFactory,并可实现自动失效转移, 
但RedisConnectionFactory只会创建到Master的连接。 一般情况下,如果所有的连接都是连接到Master上面,Slave就完全当成Master的备份了,造成性能浪费。 
通常,Slave只是单纯的复制Master的数据,为避免数据不一致,不应该往Slave写数据,可以在Redis配置文件中配置slave-read-only yes,让Slave拒绝所有的写操作。 
于是,对于一个基于Sentinel的Master-Slave Redis 服务器来说,可以将Master配置为可读写服务器,将所有Slave配置为只读服务器来实现读写分离,以充分利用服务器资源, 
并提高整个Redis系统的性能。 

1. 提出问题 

JedisSentinelPool连接池中的连接都是到Master的连接,那么如何获取到Slave的连接池呢? 分析了spring-boot-starter-data-redis和jedis之后,发现, 
并没有现成的Slave连接池可以拿来用,于是决定写一个。 

2. 分析问题 

通过RedisSentinelConfiguration,可以拿到sentinel的IP和端口,就可以连接到sentinel,再调用sentinel slaves mymaster命令,就可以拿到slave的IP和port。 
然后就可以创建到slave的连接了。 

继续查看JedisFactory源码,了解到其实现了PooledObjectFactory<Jedis>接口,该接口来自org.apache.commons.pool2,由此可见,Jedis连接池是借助Apache 
commons.pool2来实现的。 

由图看到,JedisConnectionFactory创建一个JedisSentinelPool,JedisSentinelPool创建JedisFactory,JedisFactory实现了PooledObjectFactory接口 
,在MakeObject()方法中产生新的Redis连接。 在JedisSentinelPool中定义MasterListener还订阅+switch-master频道,一旦发生Master转移事件,自动作失效转移 
重新初始化master连接池。 

3. 解决问题 

模仿JedisConnectionFactory,JedisSentinelPool,和JedisFactory, 创建JedisSentinelSlaveConnectionFactory,JedisSentinelSlavePool和JedisSentinelSlaveFactory 
它们之间的关系,如图UML-2所示。 

其中,JedisSentinelSlaveConnectionFactory就是可以传递给RedisTemplate的。JedisSentinelSlaveConnectionFactory继承自JedisConnectionFactory 
并且覆盖了createRedisSentinelPool方法,在JedisConnectionFactory中,该方法会返回一个JedisSentinelPool,而新的方法会返回JedisSentinelSlavePool。 
JedisSentinelSlavePool和JedisSentinelPool都是继承自Pool<Jedis>的。 JedisSentinelSlavePool会生成JedisSentinelSlaveFactory, 
JedisSentinelSlaveFactory实现了PooledObjectFactory<Jedis>接口,在public PooledObject<Jedis> makeObject()方法中,通过sentinel连接, 
调用sentinel slaves命令,获取所有可用的slave的ip和port,然后随机的创建一个slave连接并返回。 

JedisSentinelSlaveConnectionFactory的createRedisSentinelPool方法 

1) 通过配置RedisSentinelConfiguration传递sentinel配置和master name给JedisSentinelSlaveConnectionFactory,然后sentinel配置和master name 
    会传递到JedisSentinelSlavePool和JedisSentinelSlaveFactory中 
2)创建 JedisSentinelSlavePool,在JedisSentinelSlavePool中启动监听,监听”+switch-master”频道,一旦新master产生,即初始化连接池 
3) 连接池有JedisSentinelSlaveFactory来代理,JedisSentinelSlaveFactory实现了PooledObjectFactory<Jedis> 
在makeObject()中首先根据配置的Sentinel Set找到一个可用的sentinel连接,然后执行sentinel slaves master_name获取所有slave列表 
随机选择一个slave创建连接。 如果连接不成功则重试,最大重试5次,依然不能成功创建连接则抛出异常。 
4) 由图uml-2可知,JedisConnectionFactory实现了InitializingBean,Spring会在Bean初始化之后,调用接口方法void afterPropertiesSet() throws Exception; 
在这个方法中创建连接池 
5) JedisConnectionFactory实现了DisposableBean,会在Spring 容器销毁时,调用public void destroy() 方法销毁连接池

4 实战 

4.1  redis-sentinel-slave-connection-factory 工程结构 

1) pom文件 

2) JedisSentinelSlaveFactory.java