1 关于Spring整合Redis分片
1.1 关于redis分片的说明
redis分片的主要的作用是:实现内存数据的扩容。
redis分片如果宕机了,就不能实现高可用了。
redis的分片的计算发生在业务服务器(比如tomcat服务器)中。(tomcat先计算,再存到redis中)
redis分片的执行的效率是最高的。
1.2 Spring整合redis分片
1.2.1 编辑redis.properties文件
每个node的ip与port用:连接
node与node之间用,连接
1.2.2 编辑Redis配置类 JedisConfig.java
需要将之前 单个redis的配置注释掉。
将redis分片对象ShardedJedis 交给Spring去管理
@Configuration //标识 这是一个配置类
@PropertySource("classpath:/properties/redis.Properties")
public class JedisConfig {
@Value("${redis.nodes}")
private String nodes; //node1,node2,node3
/**
* 将ShardedJedis交给spring管理
*/
@Bean
public ShardedJedis shardedJedis(){
//第2步:获取每个节点的信息
//从redis.properties中读取到的redis.nodes的信息为:
//192.168.126.129:6379,192.168.126.129:6380,192.168.126.129:6381
//2.1 将nodes用在,处分隔,形成这样的字符串数组["192.168.126.129:6379","192.168.126.129:6380","192.168.126.129:6381"]
String[] strNodes = nodes.split(","); //[node1,node2,node3]
//第3步 准备一个集合shards,用来存放JedisShardInfo,即每个redis单体的信息(host和port)
List<JedisShardInfo> shards = new ArrayList<>();
//第4步 遍历strNodes这个字符串数组,把结果放进List集合中
for (String node : strNodes) {
//得到的每一个元素:["192.168.126.129:6379"] 和 ["192.168.126.129:6380"] 和 ["192.168.126.129:6381"]
//将每一个元素在 :处分隔,脚标为[0]的地方就是字符串形式的host:"192.168.126.129",
// 脚标为[1]的地方就是字符串形式的port: "6379" 等
String host = node.split(":")[0];
String port = node.split(":")[1];
//由于新建JedisShardInfo时,人家要求host可以是string类型,但port得是int类型。
//所以通过Integer.parseInt()的方法将string转化为int
int port1 = Integer.parseInt(port);
//第5步 创建一个redis分片信息对象,每个这样的对象里都封装着1个redis单体的信息(host和port)
JedisShardInfo jedisShardInfo = new JedisShardInfo(host,port1);
//第6步 将每个redis分片信息对象存入shards集合中
shards.add(jedisShardInfo);
}
//第1步 由于分片后的redis对象是ShardedJedis,所以得新建个ShardedJedis对象。
//新建这个对象时需要shards参数,shards就是包含各个redis单体的信息的集合,所以得想办法得到redis单体的信息(host和port)
ShardedJedis shardedJedis = new ShardedJedis(shards);
//第7步
return shardedJedis;
}
}
1.2.3 切换CacheAop切面中的redis对象
原来的CacheAop切面中的redis缓存对象是 单体的redis对象。
这次配置了redis分片,就替换成redis分片对象ShardedJedis吧。
1.3 Redis高可用机制的实现
与数据库的高可用机制类似,Redis作为缓存数据库也有它自己的高可用机制。
如果主机gg了,就要在从机中选拔出一个去当主机。
1.3.1 Redis哨兵(sentinel)机制说明
问题分析:
之前的redis分片中(假如有3台redis数据库),如果有1台redis(eg:6379)宕机了,那它里面的数据就没了。用户再从它里查询数据时就查不到了。
要想实现redis数据库的高可用(即1台redis挂到了,它里的缓存数据会转移到其他的redis服务器,保证用户能正常访问到他的数据),还要先设置redis的主从复制功能。
1.3.2 Redis主从机制说明
redis的主从复制与数据库的主从复制功能类似,也是设置redis主机,与redis从机,主机定时将数据与从机同步。
当主机宕机后,从机上任,充当主机。
1.3.3 Redis高可用的实现过程----复制哨兵目录
提前准备:先关闭Redis服务器。
1.复制shards文件到sentinel文件夹中
由于在之前分片的阶段,新建了一个shards文件,里面放的是6379.conf,6380.conf,6381.conf这3个redis数据库的配置文件。
所以,在实现Redis高可用的过程中,也要用到这3个文件。我就直接把这3个配置文件CV过来用就行了。
执行复制命令后,会在redis目录下,自动生成一个sentinel文件夹,里面放着shards文件夹中的3个conf文件和1个dump.db的持久化文件。
2.删除持久化文件dump.rdb
使sentinel文件夹中只有3个redis数据库的配置文件。
3.同时开启3台redis服务器
由此可以看出,6379,6380,6381三台redis服务器都已经开始运行了。
1.3.4 实现主从挂载
搭建规则:
6379当做主机 6380/6381 当做从机
1.3.4.1 查看当前redis服务器是主机还是从机
进入当前的redis数据库(eg:6379)
根据语句:info replication
然鹅~~~~在没设置主从机的关系时,进入每一个redis数据,一查主从状态,都是主机状态!!
谁都不服谁,谁都想当主机。
于是,我只能人为地去指定从机。
1.3.4.2 设置从机
语句:slaveof 主机IP 主机端口
进入6380库中,执行下面语句
3.实现主从的挂载 slave of 主机IP 主机端口
进入6380中,想让它当从机。让它去连接6379主机。
命令:SLAVEOF 192.168.126.129 6379
再进入6381中,执行相同的操作。
1.3.4.3 查看主机和从机的状态
1.进入到主机6379中,执行:
2.进入到从机6380和6381中,执行
1.3.4.4 一不小心主从关系挂载错了,怎么办
比如,不小心将6380设置成了主机,6379设置成了从机===
方法:重启redis数据库(比如:6380和6379 ,注意:这两个都要重启)后,6380和6379的挂载关系就恢复了默认状态,就都成了主机,并且没有从机。重新设置就好了。
解释:
由于slaveof指令是在内存中生效的. (注意:SLAVEOF是连着写的)
如果内存资源释放,则主从的关系将失效.
为了实现永久有效,应该将主从的关系写在配置文件中即可.
新问题 :如果主机意外宕机,则由谁来完成配置文件的修改呢,即在从机中选出一个作为主机?
那就是“哨兵”。
1.4 redis哨兵的工作原理
哨兵的作用:
监控当前redis主机的状态,并且哨兵通过读取redis主机的配置文件,还能知道主机下的从机都有谁。
如果当前主机gg了,哨兵们就会通过选举,在当前的从机中选出一个升任为主机(修改从机的配置文件)。
过一会儿,如果原主机又活过来了,哨兵就会让原主机降职为从机(修改原主机的配置文件)
工作原理:
- 当哨兵启动时,会监控当前的主机的信息,同时获取连接在当前主机的从机们的信息。
- 当哨兵利用心跳检测机制(PING-PONG),检验当前主机是否正常.
如果连续3次发现主机没有响应回信息.则哨兵们开始进行选举. - 当哨兵选举完成之后.其他的节点都会当做新主机的从机.
1.5 哨兵机制的实现
1.5.1 复制哨兵配置文件sentinel.conf
在redis的跟目录下,原本就有一个哨兵的配置文件sentinel.conf,但我不能随意改动它,这个文件就这一份,万一改错了,找都找不回来。
我新建个文件夹,叫sentinel,把sentinel.conf复制到sentinel中,通过修改这个配置文件复制品,来设置哨兵。
1.5.2 修改这个配置文件
- 关闭保护模式(默认情况下,保护模式是开启着的)
- 哨兵的默认端口号:26379
- 开启后台运行功能
- 修改哨兵对主机的监控 monitor:监控 , mymaster:当前主机 , 最后面的数字代表从机获得几票就能升任为主机
- 主机gg多长时间后,哨兵们进行选举
比如设置主机宕机10s后,哨兵们就开始选举新的主机 - 修改哨兵选举的超时时间
哨兵选举超过多长时间 还没选举出主机,那么就不选举了。脑裂吧。
这个涉及到一个概念:脑裂现象
如果有偶数个哨兵(比如2个),它俩在2个从机中进行选举。每次投票两个哨兵选的都不是同一个从机,那意见就永远都不能统一,就永远都选不出主机来,即投票失败。
一般规定,如果连续3次投票失败,就可能发生脑裂现象。
问:脑裂现象发生的最高的概率为多大(就是上面那个,2个哨兵,在2个从机中进行选举,投票3次):
(第1次投票:AA AB BA BB ,平票的情况:AB和BA 概率为1/2)
(第2次投票:AA AB BA BB ,平票的情况:AB和BA 概率为1/2)
(第3次投票:AA AB BA BB ,平票的情况:AB和BA 概率为1/2)
那么3次下来,平票的概率为(1/2)^3 即 1/8
解决策略:增加选举的次数,可以降低脑裂现象的发生。(哨兵数量最好为奇数个)
1.5.3 哨兵高可用测试
测试策略:当redis主机(6379)宕机后,10s后是否有从机被选为了主机;然后再开启原主机(6379),看它是否变成了从机。
- 启动哨兵
[root@localhost sentinel]# redis-sentinel sentinel.conf
- 关闭redis主机(6379),之后等待10s(因为上一步设置的是主机gg10s后开始选举新主机),哨兵们开始选举出一个新的主机。然后我再把原主机(6379)拯救回来,看看它现在是不是降职为从机了。
1.6 在程序中进行哨兵机制的测试
在TestRedis.java中,测试哨兵
/**
* 哨兵sentinel的测试
*/
@Test
public void testSentinel(){
//第2步 新建个set集合,用于存放哨兵们
Set<String> sets = new HashSet<>();
//第3步 把本例中设置的哨兵放进去
sets.add("192.168.126.129:26379");
//第1步 新建个哨兵池对象,注意是 哨兵池,里面可能有多个哨兵。本例中先放1个哨兵用着
//第一个参数是主机的名字,这个是在sentinelPool中第84行定义的
//第二个参数人家规定要一个set集合,里面放的是一个一个的哨兵们(本例中只有一个哨兵)
JedisSentinelPool sentinelPool = new JedisSentinelPool("mymaster",sets);
//第4步 通过哨兵池sentinelPool去.getResource() 就可以得到当前的redis主机对象jedis
Jedis jedis = sentinelPool.getResource();
//第5步 往jedis中存一个数据进行测试
jedis.set("aaa","测试哨兵");
//第6步 真的能取出来吗?
System.out.println(jedis.get("aaa"));
//把这个连接还给sentinelPool池
jedis.close();
}
1.7 关于 redis分片和redis哨兵的优缺点的总结
分片机制:
优点:
1. 主要的作用是 实现redis内存的扩容。
2. 由于运算(一致性hash)发生在业务服务器(比如tomcat),所以执行的效率更高。
缺点:
1. Redis的分片没有高可用的效果,如果其中一个节点gg了,则当用户通过这个节点查数据时,程序就会报错。
哨兵机制:
优点:
1.实现了Redis的高可用,哨兵可以灵活地监控各个redis数据库的状态。当redis的主机发生了宕机,哨兵可以自动进行选举,选出新主机,实现redis数据的迁移。
缺点:
1.哨兵所监控的各个redis节点中的数据都是相同的,如果存的都是相同的海量数据,就会浪费大量的内存空间。不划算。
2.哨兵虽然可以实现redis的高可用,但如果哨兵本身gg了,没有人替他干活了,程序依然运行不下去。不好不好。
所以,如果想要在最大程度上减少损耗,那么不建议引入第三方监控(比如哨兵)。