redis是基于内存的高性能key-value数据库,若要让redis的数据更稳定安全,须要引入多实例以及相关的高可用架构。而近年来redis的高可用架构亦不断改进,前后出现了本地持久化、主从备份、哨兵模式、redis-cluster群集高可用架构等等方案。java
经过持久化功能,Redis保证了即便在服务器重启的状况下也不会损失(或少许损失)数据,由于持久化会把内存中数据保存到硬盘上,重启会从硬盘上加载数据。 。可是因为数据是存储在一台服务器上的,若是这台服务器出现硬盘故障等问题,也会致使数据丢失。为了不单点故障,一般的作法是将数据库复制多个副本以部署在不一样的服务器上,这样即便有一台服务器出现故障,其余服务器依然能够继续提供服务。为此, Redis 提供了复制(replication)功能,能够实现当一台数据库中的数据更新后,自动将更新的数据同步到其余数据库上。node
在复制的概念中,数据库分为两类,一类是主数据库(master),另外一类是从数据库(slave)。主数据库能够进行读写操做,当写操做致使数据变化时会自动将数据同步给从数据库。而从数据库通常是只读的,并接受主数据库同步过来的数据。一个主数据库能够拥有多个从数据库,而一个从数据库只能拥有一个主数据库。
git
主从模式的配置,通常只须要再做为slave的redis节点的conf文件上加入“slaveof masterip masterport”, 或者做为slave的redis节点启动时使用以下参考命令:github
1redis |
redis-server --port 6380 --slaveof masterIp masterPort 算法 |
redis的普通主从模式,能较好地避免单独故障问题,以及提出了读写分离,下降了Master节点的压力。互联网上大多数的对redis读写分离的教程,都是基于这一模式或架构下进行的。但实际上这一架构并不是是目前最好的redis高可用架构。spring
当主数据库遇到异常中断服务后,开发者能够经过手动的方式选择一个从数据库来升格为主数据库,以使得系统可以继续提供服务。然而整个过程相对麻烦且须要人工介入,难以实现自动化。 为此,Redis 2.8开始提供了哨兵工具来实现自动化的系统监控和故障恢复功能。 哨兵的做用就是监控redis主、从数据库是否正常运行,主出现故障自动将从数据库转换为主数据库。sql
顾名思义,哨兵的做用就是监控Redis系统的运行情况。它的功能包括如下两个。数据库
(1)监控主数据库和从数据库是否正常运行。
(2)主数据库出现故障时自动将从数据库转换为主数据库。安全
能够用info replication查看主从状况 例子: 1主2从 1哨兵,能够用命令起也能够用配置文件里 可使用双哨兵,更安全,参考命令以下:
1 2 3 4 |
redis-server --port 6379 redis-server --port 6380 --slaveof 192.168.0.167 6379 redis-server --port 6381 --slaveof 192.168.0.167 6379 redis-sentinel sentinel.conf |
其中,哨兵配置文件sentinel.conf参考以下:
1 |
sentinel monitor mymaster 192.168.0.167 6379 1 |
其中mymaster表示要监控的主数据库的名字。配置哨兵监控一个系统时,只须要配置其监控主数据库便可,哨兵会自动发现全部复制该主数据库的从数据库。
Master与slave的切换过程:
(1)slave leader升级为master
(2)其余slave修改成新master的slave
(3)客户端修改链接
(4)老的master若是重启成功,变为新master的slave
即便使用哨兵,redis每一个实例也是全量存储,每一个redis存储的内容都是完整的数据,浪费内存且有木桶效应。为了最大化利用内存,能够采用cluster群集,就是分布式存储。即每台redis存储不一样的内容。
采用redis-cluster架构正是知足这种分布式存储要求的集群的一种体现。redis-cluster架构中,被设计成共有16384个hash slot。每一个master分得一部分slot,其算法为:hash_slot = crc16(key) mod 16384 ,这就找到对应slot。采用hash slot的算法,其实是解决了redis-cluster架构下,有多个master节点的时候,数据如何分布到这些节点上去。key是可用key,若是有{}则取{}内的做为可用key,不然整个能够是可用key。群集至少须要3主3从,且每一个实例使用不一样的配置文件。
在redis-cluster架构中,redis-master节点通常用于接收读写,而redis-slave节点则通常只用于备份,其与对应的master拥有相同的slot集合,若某个redis-master意外失效,则再将其对应的slave进行升级为临时redis-master。
在redis的官方文档中,对redis-cluster架构上,有这样的说明:在cluster架构下,默认的,通常redis-master用于接收读写,而redis-slave则用于备份,当有请求是在向slave发起时,会直接重定向到对应key所在的master来处理。但若是不介意读取的是redis-cluster中有可能过时的数据而且对写请求不感兴趣时,则亦可经过readonly命令,将slave设置成可读,而后经过slave获取相关的key,达到读写分离。具体能够参阅redis官方文档(https://redis.io/commands/readonly)等相关内容:
1 2 3 4 5 6 |
Enables read queries for a connection to a Redis Cluster slave node. Normally slave nodes will redirect clients to the authoritative master for the hash slot involved in a given command, however clients can use slaves in order to scale reads using the READONLY command. READONLY tells a Redis Cluster slave node that the client is willing to read possibly stale data and is not interested in running write queries. When the connection is in readonly mode, the cluster will send a redirection to the client only if the operation involves keys not served by the slave's master node. This may happen because: The client sent a command about hash slots never served by the master of this slave. The cluster was reconfigured (for example resharded) and the slave is no longer able to serve commands for a given hash slot. |
例如,咱们假设已经创建了一个三主三从的redis-cluster架构,其中A、B、C节点都是redis-master节点,A一、B一、C1节点都是对应的redis-slave节点。在咱们只有master节点A,B,C的状况下,对应redis-cluster若是节点B失败,则群集没法继续,由于咱们没有办法再在节点B的所具备的约三分之一的hash slot集合范围内提供相对应的slot。然而,若是咱们为每一个主服务器节点添加一个从服务器节点,以便最终集群由做为主服务器节点的A,B,C以及做为从服务器节点的A1,B1,C1组成,那么若是节点B发生故障,系统可以继续运行。节点B1复制B,而且B失效时,则redis-cluster将促使B的从节点B1做为新的主服务器节点而且将继续正确地操做。但请注意,若是节点B和B1在同一时间发生故障,则Redis群集没法继续运行。
Redis群集配置参数:在继续以前,让咱们介绍一下Redis Cluster在redis.conf文件中引入的配置参数。有些命令的意思是显而易见的,有些命令在你阅读下面的解释后才会更加清晰。
(1)cluster-enabled :若是想在特定的Redis实例中启用Redis群集支持就设置为yes。 不然,实例一般做为独立实例启动。
(2)cluster-config-file :请注意,尽管有此选项的名称,但这不是用户可编辑的配置文件,而是Redis群集节点每次发生更改时自动保留群集配置(基本上为状态)的文件。
(3)cluster-node-timeout :Redis群集节点能够不可用的最长时间,而不会将其视为失败。 若是主节点超过指定的时间不可达,它将由其从属设备进行故障切换。
(4)cluster-slave-validity-factor :若是设置为0,不管主设备和从设备之间的链路保持断开链接的时间长短,从设备都将尝试故障切换主设备。 若是该值为正值,则计算最大断开时间做为节点超时值乘以此选项提供的系数,若是该节点是从节点,则在主链路断开链接的时间超过指定的超时值时,它不会尝试启动故障切换。
(5)cluster-migration-barrier :主设备将保持链接的最小从设备数量,以便另外一个从设备迁移到不受任何从设备覆盖的主设备。有关更多信息,请参阅本教程中有关副本迁移的相应部分。
(6)cluster-require-full-coverage :若是将其设置为yes,则默认状况下,若是key的空间的某个百分比未被任何节点覆盖,则集群中止接受写入。 若是该选项设置为no,则即便只处理关于keys子集的请求,群集仍将提供查询。
如下是最小的Redis集群配置文件:
1 2 3 4 5 |
port 7000 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes |
注意:
(1)redis-cluster最小配置为三主三从,当1个主故障,你们会给对应的从投票,把从立为主,若没有从数据库能够恢复则redis群集就down了。
(2)在这个redis cluster中,若是你要在slave读取数据,那么须要带上readonly指令。redis cluster的核心的理念,主要是用slave作高可用的,每一个master挂一两个slave,主要是作数据的热备,当master故障时的做为主备切换,实现高可用的。redis cluster默认是不支持slave节点读或者写的,跟咱们手动基于replication搭建的主从架构不同的。slave node要设置readonly,而后再get,这个时候才能在slave node进行读取。对于redis -cluster主从架构,若要进行读写分离,官方实际上是不建议的,但也能作,只是会复杂一些。具体见下面的章节。
(3)redis-cluster的架构下,实际上自己master就是能够任意扩展的,你若是要支撑更大的读吞吐量,或者写吞吐量,或者数据量,均可以直接对master进行横向扩展就能够了。也扩容master,跟以前扩容slave进行读写分离,效果是同样的或者说更好。
(4)可使用自带客户端链接:使用redis-cli -c -p cluster中任意一个端口,进行数据获取测试。
因为Jedis类通常只能对一台redis-master进行数据操做,因此面对redis-cluster多台master与slave的群集,Jedis类就不能知足了。这个时候咱们须要引用另一个操做类:JedisCluster类。
例如咱们有6台机器组成的redis-cluster:
172.20.52.85:7000、 172.20.52.85:700一、172.20.52.85:700二、172.20.52.85:700三、172.20.52.85:700四、172.20.52.85:7005
其中master机器对应端口:7000、700四、7005
slave对应端口:700一、700二、7003
使用JedisCluster对redis-cluster进行数据操做的参考代码以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 添加nodes服务节点到Set集合 Set<HostAndPort> hostAndPortsSet = new HashSet<HostAndPort>(); // 添加节点 hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7000)); hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7001)); hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7002)); hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7003)); hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7004)); hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7005));
// Jedis链接池配置 JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxIdle(100); jedisPoolConfig.setMaxTotal(500); jedisPoolConfig.setMinIdle(0); jedisPoolConfig.setMaxWaitMillis(2000); // 设置2秒 jedisPoolConfig.setTestOnBorrow(true);
JedisCluster jedisCluster = new JedisCluster(hostAndPortsSet ,jedisPoolConfig); String result = jedisCluster.get("event:10"); System.out.println(result); |
运行结果截图以下图所示:
第一节中咱们已经介绍了redis-cluster架构下master提供读写功能,而slave通常只做为对应master机器的数据备份不提供读写。若是咱们只在hostAndPortsSet中只配置slave,而不配置master,实际上仍是能够读到数据,但其内部操做实际是经过slave重定向到相关的master主机上,而后再将结果获取和输出。
上面是普通项目使用JedisCluster的简单过程,若在spring boot项目中,能够定义JedisConfig类,使用@Configuration、@Value、@Bean等一些列注解完成JedisCluster的配置,而后再注入该JedisCluster到相关service逻辑中引用,这里介绍略。
Lettuce 和 Jedis 的定位都是Redis的client。Jedis在实现上是直接链接的redis server,若是在多线程环境下是非线程安全的,这个时候只有使用链接池,为每一个Jedis实例增长物理链接,每一个线程都去拿本身的 Jedis 实例,当链接数量增多时,物理链接成本就较高了。
Lettuce的链接是基于Netty的,链接实例(StatefulRedisConnection)能够在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,因此一个链接实例(StatefulRedisConnection)就能够知足多线程环境下的并发访问,固然这个也是可伸缩的设计,一个链接实例不够的状况也能够按需增长链接实例。
其中spring boot 2.X版本中,依赖的spring-session-data-redis已经默认替换成Lettuce了。
一样,例如咱们有6台机器组成的redis-cluster:
172.20.52.85:7000、 172.20.52.85:700一、172.20.52.85:700二、172.20.52.85:700三、172.20.52.85:700四、172.20.52.85:7005
其中master机器对应端口:7000、700四、7005
slave对应端口:700一、700二、7003
在spring boot 2.X版本中使用Lettuce操做redis-cluster数据的方法参考以下:
(1)pom文件参考以下:
parent中指出spring boot的版本,要求2.X以上:
1 2 3 4 5 6 7 |
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <!-- lookup parent from repository --> |
依赖中须要加入spring-boot-starter-data-redis,参考以下:
1 2 3 4 |
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> |
(2)springboot的配置文件要包含以下内容:
1 2 3 4 5 6 7 |
spring.redis.database=0 spring.redis.lettuce.pool.max-idle=10 spring.redis.lettuce.pool.max-wait=500 spring.redis.cluster.timeout=1000 spring.redis.cluster.max-redirects=3
spring.redis.cluster.nodes=172.20.52.85:7000,172.20.52.85:7001,172.20.52.85:7002,172.20.52.85:7003,172.20.52.85:7004,172.20.52.85:7005 |
(3)新建RedisConfiguration类,参考代码以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@Configuration public class RedisConfiguration { [@Resource](https://my.oschina.net/u/929718) private LettuceConnectionFactory myLettuceConnectionFactory;
<a href='http://www.jobbole.com/members/q890462235'>@Bean</a> public RedisTemplate<String, Serializable> redisTemplate() {
RedisTemplate<String, Serializable> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
//template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); template.setValueSerializer(new StringRedisSerializer());
template.setConnectionFactory(myLettuceConnectionFactory);
return template;
} } |
(4)新建RedisFactoryConfig类,参考代码以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@Configuration public class RedisFactoryConfig {
@Autowired private Environment environment;
<a href='http://www.jobbole.com/members/q890462235'>@Bean</a> public RedisConnectionFactory myLettuceConnectionFactory() { Map<String, Object> source = new HashMap<String, Object>();
source.put("spring.redis.cluster.nodes", environment.getProperty("spring.redis.cluster.nodes")); source.put("spring.redis.cluster.timeout", environment.getProperty("spring.redis.cluster.timeout")); source.put("spring.redis.cluster.max-redirects", environment.getProperty("spring.redis.cluster.max-redirects"));
RedisClusterConfiguration redisClusterConfiguration;
redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
return new LettuceConnectionFactory(redisClusterConfiguration);
} } |
(5)在业务类service中注入Lettuce相关的RedisTemplate,进行相关操做。如下是我化简到了springbootstarter中进行,参考代码以下:
1 2 3 4 5 6 7 8 9 10 11 |
@SpringBootApplication public class NewRedisClientApplication {
public static void main(String[] args) { ApplicationContext context = SpringApplication.run(NewRedisClientApplication.class, args); RedisTemplate redisTemplate = (RedisTemplate)context.getBean("redisTemplate"); String rtnValue = (String)redisTemplate.opsForValue().get("event:10"); System.out.println(rtnValue);
} } |
运行结果的截图以下:
以上的介绍,是采用Jedis以及Lettuce对redis-cluster数据的简单读取。Jedis也好,Lettuce也好,其对于redis-cluster架构下的数据的读取,都是默认是按照redis官方对redis-cluster的设计,自动进行重定向到master节点中进行的,哪怕是咱们在配置中列出了全部的master节点和slave节点。查阅了Jedis以及Lettuce的github上的源码,默认不支持redis-cluster下的读写分离,能够看出Jedis若要支持redis-cluster架构下的读写分离,须要本身改写和构建多一些包装类,定义好Master和slave节点的逻辑;而Lettuce的源码中,实际上预留了方法(setReadForm(ReadFrom.SLAVE))进行redis-cluster架构下的读写分离,相对来讲修改会简单一些,具体能够参考后面的章节。
在上面的一些章节中,已经有讲到redis近年来的高可用架构的演变,以及在redis-cluster架构下,官方对redis-master、redis-slave的其实有使用上的建议,即redis-master节点通常用于接收读写,而redis-slave节点则通常只用于备份,其与对应的master拥有相同的slot集合,若某个redis-master意外失效,则再将其对应的slave进行升级为临时redis-master。但若是不介意读取的是redis-cluster中有可能过时的数据而且对写请求不感兴趣时,则亦可经过readonly命令,将slave设置成可读,而后经过slave获取相关的key,达到读写分离。
实际上自己master就是能够任意扩展的,因此若是要支撑更大的读吞吐量,或者写吞吐量,或者数据量,均可以直接对master进行横向水平扩展就能够了。也就是说,扩容master,跟以前扩容slave并进行读写分离,效果是同样的或者说更好。
因此下面咱们将按照redis-cluster架构下分别进行水平扩展Master,以及在redis-cluster架构下对master、slave进行读写分离两套方案进行讲解。
redis官方在线文档以及一些互联网的参考资料都代表,在redis-cluster架构下,实际上不建议作物理的读写分离。那么若是咱们真的不作读写分离的话,可否经过简单的方法进行redis-cluster下的性能的提高?咱们能够经过master的水平扩展,来横向扩展读写吞吐量,而且能支撑更多的海量数据。
对master进行水平扩展有两种方法,一种是单机上面进行master实例的增长(建议每新增一个master,也新增一个对应的slave),另外一种是新增机器部署新的master实例(一样建议每新增一个master,也新增一个对应的slave)。固然,咱们也能够进行这两种方法的有效结合。
(1)单机上经过多线程创建新redis-master实例,即逻辑上的水平扩展:
通常的,对于redis单机,单线程的读吞吐是4w/s~5W/s,写吞吐为2w/s。
单机合理开启redis多线程状况下(通常线程数为CPU核数的倍数),总吞吐量会有所上升,但每一个线程的平均处理能力会有所降低。例如一个2核CPU,开启2线程的时候,总读吞吐能上升是6W/s~7W/s,即每一个线程平均约3W/s再多一些。但过多的redis线程反而会限制了总吞吐量。
(2)扩展更多的机器,部署新redis-master实例,即物理上的水平扩展:
例如,咱们能够再原来只有3台master的基础上,连入新机器继续新实例的部署,最终水平扩展为6台master(建议每新增一个master,也新增一个对应的slave)。例如以前每台master的处理能力假设是读吞吐5W/s,写吞吐2W/s,扩展前一共的处理能力是:15W/s读,6W/s写。若是咱们水平扩展到6台master,读吞吐能够达到总量30W/s,写能够达到12w/s,性能可以成倍增长。
(3)若本来每台部署redis-master实例的机器都性能良好,则能够经过上述二者的结合,进行一个更优的组合。
使用该方案进行redis-cluster性能的提高的优势有:
(1)符合redis官方要求和数据的准确性。
(2)真正达到更大吞吐量的性能扩展。
(3)无需代码的大量更改,只需在配置文件中从新配置新的节点信息。
固然缺点也是有的:
(1)须要新增机器,提高性能,即成本会增长。
(2)若不新增机器,则须要原来的实例所运行的机器性能较好,能进行以多线程的方式部署新实例。但随着线程的增多,而机器的能力不足以支撑的时候,实际上整体能力会提高不太明显。
(3)redis-cluster进行新的水平扩容后,须要对master进行新的hash slot从新分配,这至关于须要从新加载全部的key,并按算法平均分配到各个Master的slot当中。
经过上面的一些章节,咱们已经能够了解到Lettuce客户端读取redis的一些操做,使用Lettuce能体现出了简单,安全,高效。实际上,查阅了Lettuce对redis的读写,许多地方都进行了redis的读写分离。但这些都是基于上述redis架构中最普通的主从分离架构下的读写分离,而对于redis-cluster架构下,Lettuce多是遵循了redis官方的意见,在该架构下,Lettuce在源码中直接设置了只由master上进行读写(具体参见gitHub的Lettuce项目):
那么若是真的须要让Lettuce改成可以读取redis-cluster的slave,进行读写分离,是否可行?实际上仍是能够的。这就须要咱们本身在项目中进行二次加工,即不使用spring-boot中的默认Lettuce初始化方法,而是本身去写一个属于本身的Lettuce的新RedisClusterClient的链接,而且对该RedisClusterClient的链接进行一个比较重要的设置,那就是由connection.setReadFrom(ReadFrom.MASTER)改成connection.setReadFrom(ReadFrom.SLAVE)。
下面咱们开始对以前章节中的Lettuce读取redis-cluster数据的例子,进行改写,让Lettuce可以支持该架构下的读写分离:
spring boot 2.X版本中,依赖的spring-session-data-redis已经默认替换成Lettuce了。
一样,例如咱们有6台机器组成的redis-cluster:
172.20.52.85:7000、 172.20.52.85:700一、172.20.52.85:700二、172.20.52.85:700三、172.20.52.85:700四、172.20.52.85:7005
其中master机器对应端口:7000、700四、7005
slave对应端口:700一、700二、7003
在spring boot 2.X版本中使用Lettuce操做redis-cluster数据的方法参考以下:
(1)pom文件参考以下:
parent中指出spring boot的版本,要求2.X以上:
1 2 3 4 5 6 7 |
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <!-- lookup parent from repository --> |
依赖中须要加入spring-boot-starter-data-redis,参考以下:
1 2 3 4 |
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> |
(2)springboot的配置文件要包含以下内容:
1 2 3 4 5 6 7 |
spring.redis.database=0 spring.redis.lettuce.pool.max-idle=10 spring.redis.lettuce.pool.max-wait=500 spring.redis.cluster.timeout=1000 spring.redis.cluster.max-redirects=3
spring.redis.cluster.nodes=172.20.52.85:7000,172.20.52.85:7001,172.20.52.85:7002,172.20.52.85:7003,172.20.52.85:7004,172.20.52.85:7005 |
(3)咱们回到RedisConfiguration类中,删除或屏蔽以前的RedisTemplate方法,新增自定义的redisClusterConnection方法,而且设置好读写分离,参考代码以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
@Configuration public class RedisConfiguration {
@Autowired private Environment environment;
<a href='http://www.jobbole.com/members/q890462235'>@Bean</a> public StatefulRedisClusterConnection redisClusterConnection(){
String strRedisClusterNodes = environment.getProperty("spring.redis.cluster.nodes"); String[] listNodesInfos = strRedisClusterNodes.split(",");
List<RedisURI> listRedisURIs = new ArrayList<RedisURI>(); for(String tmpNodeInfo : listNodesInfos){ String[] tmpInfo = tmpNodeInfo.split(":"); listRedisURIs.add(new RedisURI(tmpInfo[0],Integer.parseInt(tmpInfo[1]),Duration.ofDays(10))); }
RedisClusterClient clusterClient = RedisClusterClient.create(listRedisURIs); StatefulRedisClusterConnection<String, String> connection = clusterClient.connect(); connection.setReadFrom(ReadFrom.SLAVE);
return connection; } } |
其中,这三行代码是能进行redis-cluster架构下读写分离的核心:
1 2 3 |
RedisClusterClient clusterClient = RedisClusterClient.create(listRedisURIs); StatefulRedisClusterConnection<String, String> connection = clusterClient.connect(); connection.setReadFrom(ReadFrom.SLAVE); |
在业务类service中注入Lettuce相关的redisClusterConnection,进行相关读写操做。如下是我直接化简到了springbootstarter中进行,参考代码以下:
1 2 3 4 5 6 7 8 9 10 11 12 |
@SpringBootApplication public class NewRedisClientApplication {
public static void main(String[] args) { ApplicationContext context = SpringApplication.run(NewRedisClientApplication.class, args);
StatefulRedisClusterConnection<String, String> redisClusterConnection = (StatefulRedisClusterConnection)context.getBean("redisClusterConnection"); System.out.println(redisClusterConnection.sync().get("event:10"));
} } |
运行的结果以下图所示:
能够看到,通过改写的redisClusterConnection的确能读取到redis-cluster的数据。但这一个数据咱们还须要验证一下究竟是不是经过slave读取到的,又或者仍是经过slave重定向给master才获取到的?
带着疑问,咱们能够开通debug模式,在redisClusterConnection.sync().get(“event:10”)等相似的获取数据的代码行上面打上断点。经过代码的走查,咱们能够看到,在ReadFromImpl类中,最终会select到key所在的slave节点,进行返回,并在该slave中进行数据的读取:
ReadFromImpl显示:
另外咱们经过connectFuture中的显示也验证了对于slave的readonly生效了:
这样,就达到了经过Lettuce客户端对redis-cluster的读写分离了。
使用该方案进行redis-cluster性能的提高的优势有:
(1)直接经过代码级更改,而不须要配置新的redis-cluster环境。
(2)无需增长机器或升级硬件设备。
但同时,该方案也有缺点:
(1)非官方对redis-cluster的推荐方案,由于在redis-cluster架构下,进行读写分离,有可能会读到过时的数据。
(2)需对项目进行全面的替换,将Jedis客户端变为Lettuce客户端,对代码的改动较大,并且使用Lettuce时,使用的并不是spring boot的自带集成Lettuce的redisTemplate配置方法,而是本身配置读写分离的 redisClusterConnetcion,往后遇到问题的时候,可能官方文档的支持率或支撑能力会比较低。
(3)需修改redis-cluster的master、slave配置,在各个节点中都须要加入slave-read-only yes。
(4)性能的提高没有水平扩展master主机和实例来得直接干脆。
整体上来讲,redis-cluster高可用架构方案是目前最好的redis架构方案,redis的官方对redis-cluster架构是建议redis-master用于接收读写,而redis-slave则用于备份(备用),默认不建议读写分离。但若是不介意读取的是redis-cluster中有可能过时的数据而且对写请求不感兴趣时,则亦可经过readonly命令,将slave设置成可读,而后经过slave获取相关的key,达到读写分离。Jedis、Lettuce均可以进行redis-cluster的读写操做,并且默认只针对Master进行读写,若要对redis-cluster架构下进行读写分离,则Jedis须要进行源码的较大改动,而Lettuce开放了setReadFrom()方法,能够进行二次封装成读写分离的客户端,相对简单,并且Lettuce比Jedis更安全。redis-cluster架构下能够直接经过水平扩展master来达到性能的提高。
欢迎学Java和大数据的朋友们加入java架构交流: 855835163
群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用本身每一分每一秒的时间来学习提高本身,不要再用"没有时间“来掩饰本身思想上的懒惰!趁年轻,使劲拼,给将来的本身一个交代!