1 为什么要搭建Redis集群
Redis的分片机制能对Redis数据库进行扩容,但由于每个Redis分片中的内容都是自己独有的,所以万一有一个宕机了,用户就可能查不到数据了。没法做到高可用。
Redis的哨兵机制能让Redis能监控主机的状态,保证Redis的高可用。但是每个主机和从机中保存的数据都是相同的。存储这些相同的海量的缓存数据相当浪费内存。
怎么才能做到二者兼得呢?
那就是 Redis集群
采用redis集群,可以保证数据分散存储,同时保证数据存储的一致性.
并且在内部实现高可用的机制(不用引进第三方,如哨兵).实现了服务故障的自动迁移.
1.1 Redis集群搭建的步骤
1.1.1 集群搭建计划
共要有6台Redis数据库,端口号7000到7005
主从划分:
3台Redis主机,3台Redis从机
1.1.2 准备集群文件夹
在redis根目录下新建个文件夹 名为cluster
由于这个集群中有6台Redis,要想把这么多节点管理得井井有条,就给每个Redis节点都在cluster文件夹中,再单独设置个文件夹。
命名为7000到7005。
1.1.3 给每个Redis数据库安排一个redis.conf文件
哪个redis.conf文件还一直保持原样,没有被我修改过呢?
就是redis根目录下的那个redis.conf。这个文件不要改。留作最原始的备份。
先把这个redis.conf文件分别复制到7000文件夹下。
然后配置7000文件夹下的这个redis.conf文件。
配置好后,再把这个配置好的redis.conf文件复制到7001到7005中,各一份,这样改动的地方就不至于那么多了。
1.1.4 编辑7000中的redis.conf文件
进到7000文件夹中,再进入到redis.conf文件中,开始修改。(共11处需修改)
- 注释掉原conf文件中绑定的本机的IP地址
- 关闭保护模式 (默认是开启的)
- 修改端口号(默认是6379)
- 启动后台启动(默认不在后台启动,会将运行信息全打印出来,并且客户端一关闭,redis集群也就不干活了)
- 修改pid文件 (默认写的是 /var/run/redis_6379.pid pid就是进程号 这个文件保存的就是当前这个redis数据的进程号信息)
为了管理方便,这个.pid文件 我可以放在每个redis数据库自己的文件夹中。
- 修改持久化文件路径
由于缓存数据都是保存在内容中的,为了防止万一断电,导致数据丢失。就要定期将缓存数据写到一个文件中,进行数据持久化。
这个文件就是dump.rdb文件。
可以给每个redis数据库都配置这么一个文件,放在各自的文件夹下就好了。
(默认的是 dir ./)
- 设定内存优化策略 (默认是maxmemory-policy noeviction)
改为:通过lru算法优化redis内存
- 关闭AOF模式 (默认就是no)
关闭持久化方式
- 开启集群配置 (默认是被注释掉的,把注释打开即可)
表示要不要开启集群。(当然是yes)
- 开启集群配置文件 (默认是# cluster-config-file nodes-6379.conf)
把注释去掉,再将-6379去掉即可
这个nodes.conf文件中的内容如下:(大体就是主机和从机的关系)
- 修改集群超时时间 (默认是15s)
这个设置的就是 集群在发现主节点在宕机了多长时间之后开始选举新主机(保持默认的15s即可)
这样7000的redis.conf文件就修改完了。
1.1.5 复制修改后的7000的redis.conf文件 到7001到7005文件夹中
1.1.6 批量修改redis.conf文件
每个redis.conf文件中只需要修改1.1.4中的第3条,第5条,第6条中的端口号就行了。
只需要分别进入到每个redis的redis.conf文件中
比如7001
再通过命令:%s/7000/7001/g
就能快速定位到这3处需要修改端口号的地方,并自动进行修改。
之后:wq 保存退出即可。
7002,7003,7004,7005的操作相同
1.1.7 通过脚本start.sh和shutdown.sh 一键开启/关闭6个redis服务器
1.一键开启6台服务器
在cluster目录下,创建一个start.sh脚本文件
2.一键关闭6台服务器
在cluster目录下,创建一个shutdown.sh脚本文件
1.1.8 启动6台redis,检查是否都能正常运行
1.1.9 创建redis集群
上面8步只是创建了6台redis数据库服务器,还没有将它们联系起来。
下面要通过命令将它们联系起来。
redis-cli --cluster create --cluster-replicas 1 192.168.35.130:7000 192.168.35.130:7001 192.168.35.130:7002 192.168.35.130:7003 192.168.35.130:7004 192.168.35.130:7005
说明: --cluster-replicas 1
这个1 指的是 主机后面 跟 几个从机
1 就是 主机后面跟 1个从机
2 就是 主机后面跟 2个从机
······
在出现如下内容后,输入yes
然后 如果出现以下信息,说明集群搭建成功
1.2 测试Redis集群
1.2.1 Redis集群高可用推选原理:
Redis的所有节点都会保存当前redis集群中的全部主从状态信息.并且每个节点都能够相互通信.当一个节点发生宕机现象.则集群中的其他节点通过PING-PONG检测机制检查Redis节点是否宕机.当有半数以上的节点认为宕机.则认为主节点宕机.同时由Redis剩余的主节点进入选举机制.投票选举链接宕机的主节点的从机.实现故障迁移.
1.2.2 Redis集群存储数据的原理:
Redis hash槽算法 (也叫Redis分区算法) 储数据的原理
CRC16哈希函数是 哈希算法中众多函数中的一种。
hash槽算法就是通过将key传给CRC16这个哈希函数,得到一个数,再将得到的数字%16384。(即CRC16[key]%16384)
算得的结果的范围就是 0到16383 (共16384个数)
Redis集群就规定个“槽”,并把这个“槽”平均分割成16834个“槽位”,“槽位”的编号从 0 到 16383
如果我的Redis集群中有3台主机,
那么Redis集群就会把这16384个“槽位”分成3份:
然后让3台redis主机去管理这些“槽位”。
比如:我想存入一条数据{“aa”,“abc”}
那我该存到哪台redis主机中呢? 7000?7001?还是7002?
蒙一个,我进入到7002主机中,尝试存入
结果人家提示我,redis集群通过CRC16[“aa”]%16384 得到的结果是 1180
它归7000这个主机管。所以我得把这个数据存到7000的主机中。
面试题
1.理论上,Redis集群中最多可以设置多少台Redis主机?
16384台,即每个主机管1个“槽位”。
2.Redis集群中 可以存放多少个“Key”?
注意:key的个数跟“槽位”的个数没关系。
比如有5个key:key1,key2,key3,key4,key5
它们经过CRC16[key]%16384 计算后 ,假如都为 2000
那么它们5个就都属于 2000号这个“槽位”。
集群中能存多少个Key,取决于Redis内存容量的大小。内存无限大,就能无限存。
1.2.3 测试Redis集群是否实现了高可用
以7000这个主机为例。
之前7000为主机,它的从机是7003。
现在将7000的服务器关掉(gg了)。
15s后,再将7000的服务器开启。
查询7000服务器的状态,发现它已经变成了从机,它的主机成了7003
查询7003的服务器的状态,发现它已经成了主机,它有一个从机,是7000
1.3 万一Redis集群搭建错了,补救措施
1.3.1 关闭所有的Redis服务
1.3.2 删除7000到7005 每个文件夹中的nodes.conf和dump.rdb文件
由于搭建集群之后,所有的集群的信息(谁是主机,谁是从机,以及主机与从机之间的练习)都会写入nodes.conf文件中,
如果下次重启,还是会读取其中的配置信息,实现redis集群的主从的搭建.
所以如果需要重新搭建集群,则必须删除该文件重新生成.
1.3.3 重启Redis服务器之后重新搭建集群
redis-cli --cluster create --cluster-replicas 1 192.168.126.129:7000 192.168.126.129:7001 192.168.126.129:7002 192.168.126.129:7003 192.168.126.129:7004 192.168.126.129:7005
2 关于Redis集群的面试题
原则: Redis的内存缺失则集群崩溃。(即如果主机宕机了,没有从机能顶上去,那么原主机中的数据就丢失了,就是内存缺失)
2.1 如果3主3从(1主1从) 最少宕机几台集群崩溃?
(答:2台:主机A和它的从机a 都gg了)
2.2 如果3主6从(1主2从) 最少宕机几台集群崩溃?
(答:5台:主机A和它的两个从机a1,a2都gg了,并且管主机B和主机C借来的从机b1和c1也都gg了)
3 SpringBoot整合Redis集群
3.1 测试:在测试类中测试往集群中插入一条数据
在TestRedis.java中添加一个测试方法,用于测试往Redis集群中插入一条数据{“clusterKey”,“集群的测试”},看能否通过key,get出value。
/**
* redis集群的入门案例
*/
@Test
public void testCluster(){
//第2步 新建一个set集合,里面装集群中Redis各数据库的ip和port
Set<HostAndPort> sets = new HashSet<>();
//第3步 将各个Redis节点的信息都封装到HostAndPort对象中,再将HostAndPort对象放到sets集合中
sets.add(new HostAndPort("192.168.126.129",7000));
sets.add(new HostAndPort("192.168.126.129",7001));
sets.add(new HostAndPort("192.168.126.129",7002));
sets.add(new HostAndPort("192.168.126.129",7003));
sets.add(new HostAndPort("192.168.126.129",7004));
sets.add(new HostAndPort("192.168.126.129",7005));
//第1步 新建一个JedisCluster对象 它需要的参数是一个set集合
//这个set集合里装的是集群中Redis各数据库的ip和port
JedisCluster jedisCluster = new JedisCluster(sets);
//第4步 往集群中插入一条数据
jedisCluster.set("clusterKey","集群的测试");
//第5步 打印
System.out.println(jedisCluster.get("clusterKey"));
}
3.2 正式:将Redis集群对象—JedisCluster交给Spring管理
3.2.1 修改redis.properties文件
将原有代码注释掉,加上redis集群中6台redis节点的host和port信息
# 添加redis集群
redis.nodes=192.168.126.129:7000,192.168.126.129:7001,192.168.126.129:7002,192.168.126.129:7003,192.168.126.129:7004,192.168.126.129:7005
3.2.2 编辑JedisConfig.java
将JedisConfig.java中原来的redis单台机和redis分片的配置注释掉,新加上redis缓存的配置代码
@Value("${redis.nodes}")
private String nodes; //redis集群时的设置node1,node2,node3,node4,node5,node6
/**
* 将redis集群对象JedisCluster交给Spring管理
*/
@Bean
public JedisCluster jedisCluster(){
//第2步 将从redis.properties中获取到的nodes进行切割,得到一个个的redis节点
// ["192.168.126.129:7000","192.168.126.129:7001","192.168.126.129:7002","192.168.126.129:7003","192.168.126.129:7004","192.168.126.129:7005"]
String[] strNodes = nodes.split(",");
//第3步 新建一个set集合,用来存放HostAndPort对象,因为人家JedisCluster就要这样的对象
Set<HostAndPort> sets = new HashSet<>();
//第4步,遍历第1步中得到的字符串数组strNodes,得到String类型的数据,命名为node,(node的格式就是:"host:port")
for (String node: strNodes) {
//第5步,将node用:分隔,取前半部分,就是String类型的host数据
String host = node.split(":")[0];
//第6步,取后半部分数据,就是String类型的port数据
String port = node.split(":")[1];
//第7步,然而,人家HostAndPort对象 要的port是int类型,还得转一下类型
int port1 = Integer.parseInt(port);
//第8步,将String类型的host数据 和 int类型的port1数据 赋值给HostAndPort对象
HostAndPort hostAndPort = new HostAndPort(host,port1);
//第9步,将HostAndPort对象放到set集合中
sets.add(hostAndPort);
}
//第1步 新建个JedisCluster对象
//要的参数是一个set集合 里面存放的是集群中的redis各数据库的ip和port
JedisCluster jedisCluster = new JedisCluster(sets);
//第10步,将jedisCluster对象上交给Spring
return jedisCluster;
}
3.2.3 在CacheAOP.java这个切面文件中,引入redis集群对象jedisCluster
只需改一处:
将原来的注入对象的代码删掉,注入在上一步配置好的jedisCluster对象
3.2.4 验证 京淘项目中Redis集群是否布置成功
在后台管理主页中,如果叶子类目中能显示出分类的名字,再刷新几次,依然可以正确显示,就说明Redis集群布置成功了。
因为后几次叶子类目的数据都是从Redis集群中得到的。
4 Redis持久化策略
4.1 Redis数据持久化需求的引出
Redis数据都保存在内存中,如果内存断电则导致数据的丢失.为了保证用户的内存数据不丢失,需要开启持久化机制.
什么是持久化: 定期将内存中的数据保存到磁盘中.
4.2 Redis中持久化的2种方式
方式1: RDB模式 dump.rdb 它是redis默认的持久化方式
方式2: AOF模式 appendonly.aof 它在redis.conf文件中默认是关闭的,需要手动开启.
4.2.1 RDB模式
说明: RDB模式是Redis中默认的持久化策略.
特点:
1. RDB模式可以实现定期的持久化,但是可能导致数据丢失(在上一次持久化到下一次持久化的时间中途发生了断电,那么上一次持久化以后到断电时,的数据将丢失).
2. RDB模式作的是内存数据的快照,并且后拍摄的快照会覆盖之前的快照.所以持久化文件较小.恢复数据的速度较快. 工作的效率较高.
命令:
我可以通过save命令和bgsave命令去告诉redis 什么时候去持久化数据。
1.save 是要求redis立即执行持久化操作。 (同步操作)
在redis.conf文件中,如图:
这样,当我又想往redis里存数据时,如果此时redis正在持久化数据,那我是存不了新数据的。只能等它持久化完以后,我才能存新数据。阻塞了我。
2.bgsave 即(backgroundsave,后台保存) 。 (异步操作)
如果我在redis.conf中 218行这里 设置成了bgsave,redis会单独再开启一个线程执行持久化操作。
我可以随时往redis里添加数据,查询数据。等redis闲下来了,它还记得要bgsave数据呢,就去持久化数据了。
持久化方案:
我怎么设置save才合理?
redis.conf文件中给了3种默认方案:
save 900 1 的意思是 如果redis在900s内执行了1次set操作,那么它就会持久化一次
save 300 10 的意思是 如果redis在300s内执行了10次set操作,它也会持久化一次
save 60 10000 的意思是 如果redis在60s内 执行了10000次操作,它也会持久化一次
用户操作越频繁则持久化的周期越短.
持久化文件存放的目录:
我可以指定redis执行持久化时,产生的dump.rdb文件存放的位置
在redis.conf的第263行设置
设置持久化文件的名字:
我可以设置持久化后产生的rdb文件的名字,默认是dump
在redis.conf的第253行设置
4.2.2 AOF模式
特点:
- AOF模式默认的条件下是关闭状态,需要手动开启. (redis.conf文件中的第699行)
- AOF模式记录的是用户对redis的每一步操作。所以持久化文件占用空间相对较大.恢复数据的速度较慢.所以效率较低.
- 但它的优势就是可以保证用户的数据尽可能不丢失.
配置:
1.开启AOF配置
2. AOF模式的持久化策略
在redis.conf文件中的第728到第730行,设置了3中持久化策略
① appendfsync always 如果用户执行了一次set操作,redis就持久化一次
② appendfsync everysec redis每秒持久化一次
③ appendfsync no 不主动持久化.
**更改完redis配置后,记得要重启一下redis!!!**然后就会发现多了一个appendonly.aof文件。
4.3 关于RDB/AOF模式特点
1.如果用户可以允许少量的数据丢失可以选用RDB模式(快).
2.如果用户不允许数据丢失则选用AOF模式.
3.实际开发过程中一般2种方式都会配置. 一般主机开启RDB模式,从机开启AOF模式.
4.4 情景题:
1.公司的redis.conf中没开启AOF模式,只开启了RDB模式,如果我不小心执行了flushAll,清空了redis中的数据。
那么即使有dump.rdb,但dump.rdb里面存的是内存数据的快照,我flushAll以后,内存数据都没了,快照里面也啥都没有了。
所以即使我重启redis,也无法找回redis中的数据了!(后果很严重,不要轻易去执行flushAll语句。)
2.公司的redis.conf中开启了AOF模式,如果我不小心执行了flushAll,清空了redis中的数据。
但还有补救措施。
我要马上将redis服务器关闭!!!不要让用户再往redis服务器中存数据了,就当是服务器在维护中···
打开appendonly.aof文件 看一眼 我都干了啥:
于是,我要赶紧用vim命令,进到appendonly.aof文件中,把“FLUSHALL”这条语句删掉!!!
然后再重启redis服务器。
然后就惊喜地发现,数据又回来了!!!!!!
不要单纯地认为: redis只是缓存服务器,里面的数据也都是缓存数据,丢了的话,再从数据库查一遍就又有了。
要注意,redis也可以是普通数据库,消息中间件,这时它里面保存的都是业务数据。就比如记录着我对redis数据库都做了哪些操作,等等。这些数据在MySQL数据库中可是没有的,Redis中如果丢了可就真丢了!!!!
5 Redis内存优化的说明
5.1 问题引出
Redis可以当做内存使用,但是如果一直往里存储不删除数据,则必然导致内存溢出.
思考: 怎么管理Redis中的数据, 可以避免内存溢出,让用户永远都可以存数据呢?
5.2 方法一:借助LRU算法
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法。
删除那些到目前为止,最长时间未使用的数据。
该算法赋予redis数据库中每条数据一个访问字段t,用来记录这个数据自上次被访问以来所经历的时间 ,
当需要淘汰一个数据时,选择现有数据中, t 值最大的,即最近最少使用的数据予以淘汰。
维度:上一次最后使用 到 目前为止的 时间长度 t
LRU算法是当下实现内存清理的最优算法.
5.3 方法二:借助LFU算法
LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用的数据的置换算法。
要求是,在需要更换redis数据库中的数据时,置换掉那些之前被引用次数最少的数据,因为经常被访问的数据的被访问次数一般都会很大。
但是有些数据在开始存入redis数据库时使用次数很多,但以后就不再被访问了,这类数据将会长时间留在redis内存中,占用着redis宝贵的资源,也不合适。
针对这种情况,可以将计数器定时右移一位,让这条老数据的使用次数定期不断地减少。
(比如:最开始这条数据被访问了10000次,经过一段时间后,即要把这个次数变成1000,再经过一段时间,变成100,再经过一段时间,变成10.。。。以此类推,当小到一定程度时,就被淘汰删除了)
维度: 被引用的次数
5.4 方法三:Random算法
随机删除Redis数据库中的数据!!!这个方法很危险!!!
5.5 方法四:TTL算法
对于那些被设置了生命时长的数据,可以监控它们的剩余生命时长。
当redis内存不足,需要删除一些数据时,就选择那些剩余生命时长最短的数据,优先删除。
5.6 内存优化方法的指定
可以在redis.conf文件中,人为地指定选择哪种方法去优化redis内存。
在第597行进行设置:
共有8中选择,想换哪个,就把后面的方法名换掉即可。
1.volatile-lru 在设定了超时时间的数据中,采用lru算法.
2.allkeys-lru 所有数据采用lru算法
3.volatile-lfu 在设定了超时时间的数据中,采用lfu算法
4.allkeys-lfu -> 所有数据采用lfu算法
5.volatile-random -> 在设定了超时时间的数据中,采用随机算法
6.allkeys-random -> 所有数据随机删除
7.volatile-ttl -> 在设定了超时时间的数据中,删除存活时间少的数据
8.noeviction -> 不会删除数据,如果内存溢出报错返回.