Redis-分布式缓存-Redis持久化、Redis主从、哨兵、分片集群

Redis单点问题

为什么要有Redis集群?利用Redis集群来解决redis单节点的问题,实现高可用,高并发

  • 1、数据丢失问题:一旦服务重启或宕机,会造成数据丢失
    • 解决:实现Redis数据的持久化-写到磁盘里
  • 2、并发能力问题:虽然是内存存储并发能力强,但是毕竟是单节点3、5、10万?但是如果是双11或618,它的并发达到数十万上百万(抢茅台)这种情况下单节点redis就难以满足了
    • 解决:搭建主从集群,实现读写分离
  • 3、故障恢复问题:一旦redis故障了,对整个微服务的影响就非常大了。必须保障整个redis集群是持续可用的,一旦redis集群一个服务出现故障,不能影响其他服务的使用。必须保证在运行的过程中去修复这些这故障的节点,实现在线边运行边修复的这样一种效果,单节点挂了就挂了,显然是不行
    • 解决:利用哨兵机制,实现健康检测和自动恢复(一旦发现有服务挂了,就去自动做故障恢复)
  • 4、存储能力问题,redis是基于内存的,内存存储相比较于磁盘存储不是一个级别的,内存是有上限的,而我们的数据只会越来越多,那么单节点内存存储就难以满足海量数据的存储需求了 
    • 解决:即使是主从集群,但是数据存储都是一样的,每个服务节点都存了,和单节点是一样的。搭建分片集群,理论上讲,它的存储能力是没有上限的

学习章节目录:

一、Redis持久化

解决数据丢失问题:RDB持久化、AOF持久化

  • RDB持久化:就是将redis在内存的数据拷贝-备份到磁盘里,RDB文件又叫快照文件,默认保存到当前目录,在哪运行redis就保存在哪里
    • 如何保存:
      • 1、利用redis-cli连接redis
      • 2、执行save命令,就会去执行RDB的备份操作了
        • 注意:RDB是由Redis的主进程来完成的,会阻塞所有命令;redis是单线程,一旦主进程来执行RDB,其它线程就不能进来了,有用户来做查询、新增都无法做了,RDB备份操作磁盘,而磁盘操作是比较慢的,如果数据量大,耗时就会比较久;直到等待执行完成,才会返回ok,此时才算结束,主进程才能去处理其他的请求(不推荐),合适redis进程马上就停机了,不想玩了可以去备份
      • 3、执行bgsave命令,在运行命令的那一刻会立即返回BSS;即:后台保存立即开始了,异步执行,是由一个额外的进程--子进程来执行RDB的,对主进程的影响几乎是0阻塞这种方式适合redis运行的过程中去做

注意:是停机(自己停的cont + c),不是宕机(突然坏了等),

Redis停机时会执行一次RDB

测试:

启动redis:redis-server

启动redis客户端:redis-cli (自己测试发现起不起客户端都可)

返回redis-server,执行ctr + c -- >snapshot 快照,翻译即:在退出之前,保存最后的RDB快照

disk:磁盘

 当前目录下就会出现dump.rdb文件

 如果再次启动redis-server ,则数据则会恢复,再次建立连接,可以继续拿到原来的数据,这样持久化就实现了 ,也就是说默认就有持久化的,但是这个持久化是在停机的那一刻才会执行,如果在启动的过程中突然宕机了呢?

假如redis 运行了一个月,突然宕机了,那数据就全丢了,理想的是每隔一段时间就备份一次

Redis内置触发RDB机制,300秒内 有10次修改则执行bgsave ;60秒内1万次修改 

还有其他配置,比如我们默认保存到当前目录,修改保存目录后,再重启redis-server 就找不到原来存储的数据了,找不到dump.rdb,读取的是我们新改的名字的rdb文件

修改默认的磁盘保存文件dump.rdb文件的名字:cd /usr/local/redis-6.2.6/redis.conf

save xx xx 一定是bgsave 可以在配置文件里修改

问:持久化时间能否配置为1秒保存一次?比如修改了就持久化保存呢?

答:不行!时间非常短的话,RDB执行频率太高,如果数据量很大,1秒执行1次写数据到磁盘的话,就太拉垮了,它也忙不过来的

RDB异步持久化原理

  • 子进程来执行RDB的,对主进程的影响几乎是0阻塞
  • 有一个物理内存(可以理解为内存条)
  • 有一个主进程(redis主进程)要实现对物理内存读写,但是在linux系统中,所有的进程都无法直接操作物理内存,而是由Linux系统给每个进程分配一个虚拟内存,主进程只能操作虚拟内存,而操作系统会维护一个虚拟内存与物理内存的映射关系表,这个表称为 页表  ,所以主进程操作虚拟内存,而虚拟内存基于页表的映射关系到我们的物理内存真正的存储位置  这样主进程就成实现对物理内存的读写了
  • 执行fork的时候,会去创建一个子进程,fork的过程不是把内存数据做拷贝,仅仅是把页表做拷贝,也就是把主进程的虚拟内存和物理内存的映射关系做拷贝 ,子进程有了和主进程相同的映射关系,当子进程在操作自己的虚拟内存时,因为映射关系和主进程一致,最终一定能映射到和主进程相同的物理内存区域,这样就实现了子进程和主进程内存空间的共享,这样就无需拷贝物理内存中的数据,直接就实现了内存共享,速度就会变的非常快,阻塞的时间也就尽可能的缩短了;而后子进程就可以放心大胆的读取自己内存的数据了,其实读取的就是主进程的数据,而后再将数据写到一个磁盘的文件里去RDB文件,写完之后会去替换旧的RDB文件
  • 但是:子进程再异步的写RDB中,主进程还可以接收请求来去修改内存中的数据,如果此时,主进程再修改数据,而子进程再读取数据,读与写之间就会出现一些冲突,可能出现一些脏数据,怎么办呢?
    • 为了避免这个问题的发生,fork底层采用的是 copy-on-write技术
    • 当写的时候,去执行一次拷贝,fork会把共享内存标记成 read-only 任何一个进程都只能来读数据而不能写数据在这个read-only内存。
    • 当主线程真的来了一个写的操作时候写B的时候,会将数据B完整的拷贝一份,而后主线程再去完成写操作,写操作是在拷贝的内存完成的;即:一旦完成拷贝,主进程对数据B的不管读、写都是在拷贝的内存完成的,也就是说主进程页表对数据B的的映射关系映射到了拷贝的里边了 ,每一次只要有写都会拷贝,这样就会避免脏写的问题了
    • 但是:在极端的请短的情况下有这么一种情况:
      • RDB磁盘操作,子进程写到有点慢,耗时较久,而就在写的过程中,不断地有新的请求进来,不短的再修改共享数据,结果是所有的数据都被修改了一遍,这种情况下就意味着所有的数据都要拷贝一份新的。也就意味着redis对于内存的占用翻倍了,极端情况,理论上是有可能发生的,实际应该不会发生;如果内存已经占用了16个G内存,一旦翻倍则是32G内存,因此我们的Redis都要预留一些内存空间,如果一台服务器是32G,不能都交给Redis,让他都给消耗完,将来在做RDB时就可能内存不够,溢出了

 总结:

 AOF持久化

AOF文件:redis把所有接收到的写操作都记录到一个文件里,因为每次都去记录新的命令,这种文件是一个逐渐累加的过程,所以叫追加文件,又因为里面记录的是命令,也可以看做是日志文件

比方说,有一个redis,和磁盘文件,当我们执行命令行时,将数据记录到redis的key-value结构里,而后还会把这条命令写到aof文件中,格式如下,$3是在记录命令中字符的长度,如果长度是4即$4,例如:set name 1 

将来如果redis出现了故障,主需要去读取aof文件,把它的命令从头开始再执行一次,那么我们的数据就一定能恢复到原始状态了

这种方式默认是关闭的,如果需要开启,需要修改redis.conf配置文件来开启AOF

同时AOF记录也是有频率的

  • 第一种:写内存和写磁盘是同时进行的,安全得到保障,性能却是最差的
  • 第二种:每格1秒,如果在这1秒之间有服务宕机了,那么数据就丢失了,它最多会丢失1秒之内的数据,牺牲一定的安全去提高性能(默认方案
  • 第三种,由操作系统定期去写入,性能是最好的,安全性确实最差的

启动AOF 需要先禁用RDB 即:save "",生成的aof文件也是在当前目录,如果我们重启redis 那就是加载 aof文件了 

通过cat  addendonly.aof 可以查看 文件内容

注意:AOF记录的是命令,不管命令重复不重复,即使对一个key 操作了N次,AOF也会记录N条命令,而RDB记录的是值,所以AOF文件一定回避RDB文件大,如果说操作一个key记录了多次num,最后缺del了,再记录之前的命令是不是有些多余?

是的,通过命令bgrewriteof命令,重写aof文件,会开启一个独立线程,异步的执行

直接通过命令行 执行 bgrewriteof

可以配置触发AOF的阈值

  • 对比上次文件超过的百分比 触发

RDB 与 AOF 的优缺点

数据优先级:如果同时启动RDB和AOF,会优先启动AOF,RDB就相当于备份了,实际使用的是二者结合使用,不是单独使用1个

Redis主从-解决并发能力问题

  • 搭建主从架构
  • 主从数据同步原理

为什么要搭架主从集群?因为Redis大多的场景都是读多写少的场景,查询比较多,增删改比较少,更多的是应对读的压力;

主从可以实现读写分离,我们的写操作是发布到maste主节点上,读的操作发布到其余的从节点,这样一主多从,多个从节点一起承担读的请求,这样读的并发能力就可以得到一个极大的提升,这也就是要搭建主从节点的一个原因

但是,必须保证一点,客户端不管访问哪个从节点,获取到的数据必须相同--这也就需要主节点master将数据同步到从节点。也就是说搭建主从集群,最基本也得有三台服务器(一主两从)

1、搭建主从架构 

mac上搭建主从集群 

1、创建文件夹,再cd /usr/local 下,因为redis目录在该文件夹下,所以创建也就在这个目录下了,创建7001、7002、7003 三个目录,sudo mkdir 7001

2、恢复默认配置,开起RDB,关闭AOF

3、拷贝redis.conf配置文件到 7001、7002、7003

方式二我这里不好使,所以就用方法一就可以 前边加上sudo管理员命令 sudo cp... 

ls 目录文件夹,可以查看文件夹下的所有文件

4、拷贝了以后还不可以去运行,因为redis的配置文件端口默认配置成了port 6379 ,需要修改这三个端口

还有现在我们的保存目录下,默认是dir ./ 目录,即当前目录下,我们要求7001、2、3保存的都是保存到他们的目录下

利用sed 命令,是一个快捷的对文档操作的命令,sed ....  文件下/文档文件

命令内容

正斜杠 / (人往前倾),常代表目录,反斜杠 \ (人往后倾)用用于特殊符号转义

  • -i :sed -i 就是直接对文本文件进行操作的。
  • -e:可以在同一行里执行多条命令
  • /tmp 是一个文件
  • /tmp/ 是一个目录
  • 's/6379/7001/g' ----- s 代表替换,/g 代表全局 ,将文档里的6379全部替换成7001
  • 's/dir .\ //dir \ /tmp\ /7001\ //g   ----将dir .\ 全部替换为 dir \ tmp \ 7001

哈哈,真尴尬,sed命令不会用,那就换一种命令

如下:

修改端口6379 为7001 全局修改,6379-7002同理

  • vim 7001/redis.conf.,打开编辑文件
  • :%s/6379/7001/g,该命令的意思是将文件的所有6379都替换为7001

修改dir ./ 为 dir /usr/local/7001/

  • 手动修改
  • 查询 dir命令 -> /dir,N是查找下一个,输入 i 是修改该行命令 
  • :wq 是保存退出,:wq!强制保存退出,:q不保存退出,:q!强制不保存退出
  • 如果没有权限,就输入 sudo vim ...
  • 按 u 取消上次命令

以上7001、7002、7003都做一次,即完成对每一个实力端口的配置了

5、修改每个实例生命的IP(这一步先不做,后续看看有没有问题)

sed -i ' 1a ..' ,1a的意思就是在第一行的后边追加一行

就是在文件的第一行后写一行数据

mac使用sed命令与linux不一样,就是不会用,这里就一个一个的去配置文件加了

还是配置吧

  • cd /usr/ local
  • sudo vim 7001/redis-conf
  • replica-announce-ip 192.168.159.101
  • 7002、7003同理 配置

 如上,配置文件配置好了以后,开始运行

先cd /redis目录  ,人后运行redis 并制定目录

目前这三台redis之间并没有主从关系,是独立的

修改配置文件是永久生效

执行redis-cli命令,是临时生效,关闭就失效了 

  • redis-cli -p 7002
  • slaveof 192.168.159.101 7001 
  • 独立窗口链接7002,然后将7002作为7001的从节点

 

 链接方式:redis-cli -p 7001,-p的意思是指定端口

再主控制台执行

ping,若出现pong,说明链接成功,没有问题 

然后就可以去slaveof了,跟上主节点的ip192.168.150.101,主节点的端口7001

我们是启动的7002,在7002里输入,主的,就说明,7002要成为7001的slaveof(从节点)

执行完之后,在7002上就会有日志,主从同步数据的,从节点就有主的数据嘞 

如7003的操作配置主从后,7003打印的日志

 以上代码,理论上来讲,集群就已经搭建完了,7001是主,7002、7003是从

可以查看主从关系

  • 我们连接主节点的客户端, redis-cli -p 7001
  • 执行info replication,可以看见,连接的slave有两个,ip、端口分别是下边的显示,状态state=online,在线显示,即可确认主从搭建完成了

 可以测试一下:

我们在7001set一个num,然后去7002去get,获取成功,7003也可以拿到,说明主从同步完成了,我们在主节点上写,从节点可以拿到

注意,不可以在从节点上写 ,该节点是只读的,无法实现写操作,即:天然的实现了读写分离,主节点做写操作,从节点做读操作

 同一台机器下需要修改端口,不同机器下就不用修改端口了

总结:

查看redis进程、杀掉进程:

  • ps -ef | grep redis,查看redis进程
  • sudo kill -9 端口号,杀死进程

经过测试得知,7001、7002、7003,不能正常保存关闭,rdb文件不能生成,个人感觉是要管理员的原因,以后建立文件夹操作的话,在自己的用户下

redis实例配置文件放到/tmp/7001下

配置文件路径修改 :dir  /Users/yuhaiyang/redisConf/7001

 生成的dump.rdb文件放到user/redisConf下, redisConf是我新建的目录

里面包含7001、7002、7003 

 7001下是redis配置文件修改的保存dump.rdb的文件目录

经过测试得知主从搭建正常

注意:我们这里修改ip好像没有起到作用,三台redis的ip仍然是127.0.0.1 

2、数据同步原理-增量

 数据主从同步的的原理

  • 1、从节点执行slaveof命令,是与master建立链接,开始请求数据同步。。。
  • 2、master 交给一个独立线程 执行bgsave操作,主进程不影响写操作,生成以后将完整的信息rdb 交给从节点
  • 从节点拿到,清空本地,加载RDB到内存,这样,从节点的数据就与主节点数据基本一致了,因为bgsave是异步执行的,在处理的过程,主进程还会去处理新的请求的,也就是会有新的数据写入,但是新数据并没有写入到RDB里
  • 再执行bgsave的时候,主节点会记录收到的新请求的命令,将这些命令写到内存缓冲区repl_baklog里(它记录的就是RDB期间收到的新的命令)
  • 换从去的命令+RDB=完成的命令,RDB发送过去,内存缓冲区保存好了=第二阶段
  • 然后主节点会将命令发送给从节点,从节点去执行这些命令,再有命令来的的话,主节点还会继续写入到缓冲区了,再去发送个从节点,这样一个重复操作,两者之间确保是永远同步的

这种方式为什么叫全量同步: 就是因为它有一个RDB的过程,主节点会把内存生成快照,整体发送给从节点。是比较消耗新能的,因为生成RDB是个文件,需要把文件发过去。所以全量同步只有再第一次建立链接的时候才会去做

那么问题来了,master是如何知道是第一次呢?

由上图可知,必须根据replid 去 判断是否是第一次数据同步,id存在说明不是第一次,id不存在或不同,说明是第一次

节点在执行slaveof 的之前,他自己也是个master,肯定会有自己replid和offset,第一次,会带着自己的id 和 偏移量,会去和master比,不一致则

一切过程都会在redis的日志里有体现的,包括RDB存到磁盘里 

3、数据同步原理-增量 

什么时候做增量同步:一般是slave从,重启后执行增量同步,因为在我们重启的时候,master主,还会接收数据,数据不一致,此时做的就是增量同步

  • 1、从,会带着id过来,因为不是第一次来的,所以id肯定是和master是一样的
  • 2、主判断不是第一次,就会发送继续continue,该干活了
  • 3、这次发送的也不是缓冲区的完整(全量)数据,会带着offset过来,记录着在缓冲区读取到的哪个位置,从这个位置往后的命令,才是挂掉之后的数据
  • 4、会执行相关命令

问:offset会记录在repl_baklog的哪个位置?怎么找到offset之后的命令呢

答:repl_baklog本质是一个数组,这个数组比较特殊,大小是固定的,当这个数组记满了以后,会接着记(继续从下标0开始),这样就把原来的数据给覆盖了,所以可以理解为圆形的记录方式

比方说,从0开始记,有命令进来了开始记,记录到哪里,就是我们master的offset,然后slave也在不断地同步,slave同步到哪个位置了,slave的offset就是它,如果slave突然宕机了,那同步的就是slave的offset到master的offset之间的命令

repl_baklog可以理解为就是slave与master的一个数据差异的缓冲区,只要slave与master的差异,别超过这个环的上限,就永远可以从这个环里找到所需要的数据,永远可以实现增量同步,如果差距太多,已经超过了上限了,那就没法做增量同步了

什么时候无法做增量同步呢?salve与master差距超过了这个环,即:slave宕机了,master还在做数据记录,当master记满了整个数组而且马上就要超过环的上限了,salve还没有好,又有新的命令进来,master就会覆盖还尚未备份的信息的数据,slave欠的债就太多了,一圈记不下,又多了一部分,此时slave的offset就在这个环里消失了,此时slave醒过来了后,就无法做增量同步了,只能去master的内存当中找,这个时候就只能去做全量同步了

 这个问题没有办法避免,只能尽量优化Redis主从集群,尽量去减少全量同步

  • 提高性能
  • 启用无磁盘复制,redis.conf里操作,将no改成yes。在写RDB的时候不直接写到磁盘了,而是写到网络,直接发给slave,减少一次磁盘读写,提高了性能(什么场景下用:磁盘比较慢,但是网络非常的快
  • Redis单节点占用内存不要太多,

尽可能 减少全量同步的优化

  • 提高repl_baklo大小
  • 发现从宕机,尽快恢复

主从同步链式结构

主节点同步压力问题:从节点太多,都去找主做数据同步,就会给主节点带来非常大的压力

  • 采用主-从-从 链式结构,如图:1是2的主,2是3的主,,3在做主从的时候slaveof 主ip指向的是2的节点,端口也是2的端口,确实妙呀

总结:

二、Redis哨兵 

思考:master节点宕机了怎么办?

:监控集群中的节点状态,当master节点宕机后立即 选择一个slave当做新的master节点,使得整个集群依然是健康的,可以做写、读操作;挂掉的master将来起来以后,让他当slave就好了,这样就可以实现主从的切换了!利用redis的哨兵机制

1、哨兵的作用和原理

  • 哨兵的作用
    • 哨兵(sentienl)本身也是个集群,会不断监控redis集群的健康状态
    • 故障恢复,实现主从的切换
    • 通知功能:当故障恢复,主从切换后,java客户端并不知道这件事情,java客户端去找主从的时候,不会直接去找,甚至都不知道,这几台redis谁是主谁是从,而是去找哨兵sentienl,由哨兵告诉java端主从地址是什么,将来主从一旦发生切换,sentienl会立即将服务状态变更过通知java客户端(RedisTemplate),从而java客户端就会知道新的主从,从而改变这样一个节点的访问的地址了
  • 哨兵是如何监控节点的状态的?
    • 利用心态机制ping以下,你会给我一个pang吗,乒乓,哈哈,搞笑
    • 假如一个哨兵节点给一个master节点发送ping,超过一秒了master节点没有给回pang,则认为是主观下线了,(哨兵认为是下线了,但是真的下线了吗?不一定)因为这里是超时未响应,有可能是网络阻塞了
    • 但是超过了指定quorum的哨兵都认为该节点实例是主观下线,那么该实例客观下线,quorum最好是超过哨兵节点的一半,该quorum可以在redis.conf配置文件里配置;即:(多数哨兵都认为你下线了,那你就确实下线了,不允许反驳)
  • 那么问题了来了 ,剩下slave该选择哪个为主呢? 
    • 首先会判断slave节点与master节点的offset之间的大小,也可以说是断开时间长短,超过一个指定值(redis.conf配置)则会被排除,不具备选举权
    • 判断slave节点的一个属性值,默认是1,也是在配置文件配置,既然默认值是1,也就是说,这个配置可以不用管
    • 如果优先级都一样,都是1,就会去判断offset的值,越大优先级越高
    • 如果offset的值一样,那就随便了,可以去判断slave节点的运行id的大小,越小优先级越高;这个id是再slave启动的那一刻,由redis自动生成的一个id,当然了,这个id大小并不重要,就是随便挑一个,看id,id谁小,就用谁
  •  一旦选中了从节点作为新的主节点,如何实现故障转移?
    • 假设:7001master宕机了,7002slave1被选中作为master,哨兵会给slave1发送一个请求,你去给我执行一条命令(你不再是奴隶了)
    • 哨兵会给其他从节点发送一个命令,让他们去执行成为slave1的从节点
    • 而我们的故障节点原master会被哨兵标记为奴隶slave,即:强制修改它的配置文件,将他标记为slave,即配置文件会加1行 slaveof  7002ip  7002端口;既然配置文件都改了,那么当它重启以后,一定会认7002为主了
    • 以上整个主从切换就完成了

总结:

2、搭建哨兵集群

 

进入tmp目录,创建3个文件夹,mkdir s1 s2 s3 

sentinel announce-ip 实例,声明一个我们的ip得知,避免将来ip有多个引起混乱 

执行:sudo vim s1/sentinel.conf,就会自动创建一个文件了 

port 27001
sentinel announce-ip 192.168.150.101
sentinel monitor mymaster 192.168.150.101 7001 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
dir "/tmp/s1"

 我们虽然监控的是master,但是在master是可以得到slave相关的所有信息的,我们在master执行info replication 就会获取有几个从节点,以及从节点的信息,所以,我们告诉的sentinel哨兵虽然是master的,但是我们监控的是整个集群的!

剩下两个是超时时间,一个是slave与master断开的最长超时时间,还有一个是slave故障恢复的超时时间,不配的话,默认也是这个值

dir ”/tmp/s1“,这是一个工作目录,s1的实力工作目录就在s1。。。

 这里我们手动去修改

 

将工作目录也修改了

通过cat命令,可以查看配置文件的内容 

启动

 即:再起三个窗口取运行哨兵

  • cd /tmp 目录
  • 执行redis-sentinel s3/sentinel.con

然后发现起不来,哈哈哈,权限不足,无法写入,我发现只有我自己的文件下才不需要sudo命令,那就换一下

tmp 目录下,将文件拷贝到我的目录下,s2,s3 同理,记得将配置文件里的dir目录也改成这个
cp s1/sentinel.conf /Users/yuhaiyang/redisConf/s1

拷贝完之后就可以启动了

如下图所示,上边三台是redis-server,下边三台是redis-sentinel 

 sentinel 哨兵启动以后,就已经开始去监控我们的集群了 !,有了主从的信息了 

我自己搭建发现有问题,并没有从节点信息,将主节点ip改成自己本地的,老师这个不行

然后再启动就可以了,主节点ip的问题 

然后再测试,发现行个锤子,全都是权限问题,和ip问题

这里需要修改:

  • 将tmp下redis.conf 实例全部移到我的用户下新建redisConf下,
  • 将redis.conf配置文件添加的192.168.150.101删掉,
  • 将第sentinel.conf第二行ip改为127.0.0.1
cp 7001/redis.conf /Users/yuhaiyang/redisConf/7001

测试一下,让7001主节点宕机 ,看下效果 是可以的

主节点一但宕机,从几点就会开始报错,连接不上主节点,而sentinel会做重新的选举

我么测试得知,会选一个7002成为master,7003成为7002的从节点,当7001再起来后,会自动成为7002的从节点

让7002下线,查看日志

27001日志

 27002日志

27003日志

 7003的日志

 7001日志

 解析日志:

  • 一开始仅仅是sdown就是一个主观下线, 7002主观下线,通过观察日志三台哨兵都认为7002是主观下线了
  • 超过半数了已经,就来了一个odown,就是客观下线,并且quorum值已经满足了,2/2,确定7002就是宕机了
  • 然后是try-failover,失败处理,就是一个故障恢复对7002
  • 开始选举,去选一个新的主节点,首先三台哨兵之间要选一个主,这三个哨兵现在是平等的,他们为什么要选主呢?因为最终有一个哨兵去做故障恢复就可以了
  • vote-for-leader,领导者,怎么选的呢?其实就是谁先发现宕机了,谁就能选上了;我测试得知是27002选上了,它接下来就要去做故障恢复了

  • 做故障恢复,首先要从slave中选择一个主,selected-slave slave 这里是选择了7003作为主

  • failover-state-send-slaveof-noone slave 127.0.0.1:7003,就是send发送了一个slaveof-noone命令给7003,让他别做奴隶了,7003一执行,就从奴隶变成主人了;

  • 查看7003的日志,由一直连不上突然有了MASTER MODE enabled,进入master模式,为什么?因为它执行了slave noone命令,自己变成了一个主节点

  • reconf重新配置,返回到27003日志,failover-state-reconf-slaves:原来的故障节点要去做标记,标记为一个从节点,就是重新配置,此时7002就会从master节点切换为一个从节点了

  • sent发送,7003已经成为主节点了,接下来要把主节点的信息广播给所有的从节点slave-reconf-sent slave 127.0.0.1:7001,首先发给7001了

  • 查看7001日志可以看出来,它执行了一条replicaok ip 端口(ip端口是7003的,7003是主),7001变成7003的从节点了,然后重新去做一次全量同步

  • 以上,切换就完成了

  • 如果此时7002再重新启动,它其实会去尝试做一次主从同步

  • 再次查看7003主节点日志可以发现:Replica 127.0.0.1:7002 asks for synchronization,7001节点在尝试做同步,然后下边的操作就是尝试去同步了,最后succeeded,同步完成了
  • 以上7001也成为7003的slave了,到此为止,整个主从切换才是彻底完成了

以上:哨兵集群的搭架即完成了 

删除文件操作

  • sudo rm 文件的路径
  • sudo rmdir 文件夹的路

RedisTemplate的哨兵模式

配置java哨兵集群

  • 引入redis的starter依赖,它里面就是基于lettuce完成redis的各种功能,就是引入redis的依赖
  • 配置sentinel的集群地址,不是redis的集群地址,因为在哨兵模式下,redis主从地址是有可能变更的,所以客户端是不需要知道redis集群地址的,只需要知道sentinel的地址就可以了。将来是基于sentinel来做redis主从服务发现的;nodes地址列表是一个集合,可以写多个
  • master名称是我们再配置文件里写好的

还需要配置主从读写分离,master做写操作,slave做读操作,这个bean可以在任意配置类里配置,这里我们在启动类里配置

RedisTimplate的底层就是用的Lettuce,需要给Lettuce做自定义配置,就可以去指定读取,枚举值有常见的4个选项 

 启动类里添加Bean,因为是个接口,返回的就是这个接口类型,我们如果要new接口的话,就必须做匿名内部类

@SpringBootApplication
public class RedisDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(RedisDemoApplication.class, args);
    }

    @Bean
    public LettuceClientConfigurationBuilderCustomizer lettuceClientConfigurationBuilderCustomizer() {
        // 接口,return的话需要做匿名内部类
        return new LettuceClientConfigurationBuilderCustomizer() {
            @Override
            public void customize(LettuceClientConfiguration.LettuceClientConfigurationBuilder clientConfigurationBuilder) {
                clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
            }
        };
    }
}

如果一个接口内部只有一个方法,并且是@FunctionalInterface标记的,是可以用lambda表达式来代替的,做法就是以内部类的方法结果直接返回,以方法参数作为入参就可以了

 这里可以快捷替换,new 接口是灰色的,可以用lambda来表示

到此为止,项目就可以启动了 

测试项目,get、set方法,我的项目懒得跑,还得搭建redis集群,哨兵集群,投个懒,下面的分析就用老师的日志了

@RestController
public class HelloController {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping("/get/{key}")
    public String hi(@PathVariable String key) {
        return redisTemplate.opsForValue().get(key);
    }

    @GetMapping("/set/{key}/{value}")
    public String hi(@PathVariable String key, @PathVariable String value) {
        redisTemplate.opsForValue().set(key, value);
        return "success";
    }
}

执行读操作

  • 1、去7001set一个num=123
  • 2、浏览器输入loaclhost:8080/get/num,得到结果123
  •  
  • 3、到idea查看分析运行日志
  •  4、一上来会先去尝试链接sentinel哨兵,有几台哨兵,就会去尝试连接几台
  •  5、然后会选中一台哨兵链接,这里链接的27001
  • 6、然后开始去与27001建立连接
  •  7、连接以后会去尝试获取redis集群的真实地址,这里采用了一种类似于订阅的一种机制subscriptionCommand,订阅命令,订阅集群的状态
  • 8、订阅了状态以后,哨兵就会把redis集群的信息发布给客户端,我们会先得到master7002的信息ip+端口
  • 然后客户端还会得到slave的信息,多台slave,得到的就是一个数组,这里是两个对象,第一个对象是slave7003也就是第一个节点,第二个节点就是slave7001 -ip+端口
  • 以上客户端得到了 主+从的信息 都有了以后,就要去和主从建立连接了
  • RedisClient它底层维护了一个连接池,不仅会连主节点7002,还会去连从节点7003和7001,即:集群中的每个节点都会去建立连接的
  •  当我们查询的时候他是走的哪个节点呢?
  • 在lettcue里面,所有的请求都被分成了command,类型get,代表查询,查询请求这里是交给了7003去处理了

接下来执行写操作

  • set/num/666
  • 接下来查询日志,分析写的日志,command类型=是一个set操作
  • 交给了主节点master 7002,不应该有从节点来处理了,这就是读写的分离 

接下来,再试一下主从故障切换

  • 我们将7002节点弄宕机,主节点一旦宕机,哨兵就发现了,哨兵发现了就要去做主从切换,看日志知道7001变成了主节点,7003变成7001的slave,重启7002,7002也变成7001的slave
  • 我们的客户端又会开始新的尝试链接,尝试去链接所有的哨兵
  • 然后链接到了27001哨兵
  • 又要尝试去获取新的redis集群信息
  • 这次获取的master是7001,动态的发现了节点的最新信息
  • 然后发现从信息7003和7002
  • 以上主从切换就自动完成了,再去get就会去找7002或7003了,set就会去找7001,实现读写分离

三、分片集群

为了提高主从同步的性能,单节点的Redis它的内存设置不要太高,如果内存占用的过多,那在做RDB的持久化,或者做全量同步的时候就会导致大量的io,性能就会下降;

问题:

  • 如果说单节点的内存上限降低了,比如说只能存10g、20g,那么当有海量数据存储的时候怎么办呢?
  • 而且,主从集群应对了高并发读的问题,但是写的并发也很高怎么办呢?
  • 那就需要分片集群来取解决了

1、搭建分片集群

  • redis不再是主从关系了,每一个redis都是master主节点,每一个master保存不同数据;
  • 比如一个master保存20g数据,那么3台master合在一起就可以保存60g了,因此数据存储的上限取解决与master节点的数量,理论上讲就可以应对海量数据存储了
  • 同时,高并发写也可以解决了,有多个master,每个master都可以写操作,并发写的能力也得到了提升
  • 每个master都可以有多个slave节点,也就是说每个master本身还可以形成主从关系,它就同时具备了主从集群的特性了,既能高并发的读,在slave上,又能高并发的写,在多个master上
  • 同时master与master之间还能ping通,可以彼此监视健康状态;也就是说以前做主从需要哨兵,现在就不需要哨兵了,master之间互相监测就起到了哨兵的效果(通过心跳);如果多个master都认为某一个master主观下线,那么该节点就客观下线;而且将来也可以做主从的切换,就类似于哨兵的功能了
  • 那么问题来了,这么多master,客户端访问谁呢?又没有哨兵
  • 事实上客户端请求可以访问集群任意节点,最终都会被转发到正确节点,将来这些节点之间会做一种自动的路由,它会把你的请求正确的路由到正确的节点上去访问,所以就不再需要哨兵机制了,同时却具备了哨兵所有的功能
  • 以上就是分片集群的一个结构

开始搭建分片集群 

这里我们搭建一个最小的分片集群,三主三从 

 这里我们的ip全都用127.0.0.1,不用老师的了,3主3从

rm -rf 文件夹目录,可以删除包括里边的内容 

 那就重新配置吧

在/redisConf下准备一个新的redis.conf文件,内容如下:7001、7002、7003、8001、8002、8003都需要修改端口,修改成自己目录的

port 7001
# 开启集群功能,默认是关闭的
cluster-enabled yes
# 集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file /Users/yuhaiyang/redisConf/7001/nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /Users/yuhaiyang/redisConf/7001
# 绑定地址,0.0.0.0 就是任何人都可以访问我
bind 0.0.0.0
# 让redis后台运行 守护进程,默认是no,之前运行的时候是在控制台打印日志,yes就不打印日志了
daemonize yes
# 注册的实例ip 声明主的ip地址
replica-announce-ip 127.0.0.1
# 保护模式 no 将来就不用做用户名密码的校验了
protected-mode no
# 数据库数量,总共16个,我们就用1个就行
databases 1
# 日志,我们是守护进程后台运行了,不会再控制台打印日志了,如果想看日志信息就指定一下日志的位置
logfile /Users/yuhaiyang/redisConf/7001/run.log

因为是后台启动了,不在是前台启动了,不需要打开6个窗口了,前台窗口也不会看见日志信息的

通过查看端口命令 ps -ef | grep redis,可以发现6台redis已经全部启动了

但是,这6台redis并没有联系起来,并没有形成主从分片关系 ,因为我们并没有在配置文件里指定集群之中,谁和谁是有联系的

如果要关闭进程的话,那就kill命令杀死端口:sudo kill -9 端口号

创建集群的两种方式

 解析命令

  •  redis-cli --cluster help,就可以得到redis集群的所有命令了,包括创建,添加节点,删除节点等
  • --cluster-replicas 集群的副本数量,就是slave节点的数量,在往后跟的就是集群中所有的ip和端口了
  • 那么redis怎么知道这6个哪个是主,哪个是从呢?这里是有判断依据的。
    • 总数/(副本+1) = 3主3从,前三个是主,后三个是从

命令:redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003 

日志会打印一些信息,这些节点我这么给你配置,M主节点 7001、7002、7003;S从节点8001、8002、8003作为附加 ;

如果同意,他就会去尝试去建立集群了

输入yes,All nodes 所有的节点都同意了,集群就创建好了

通过命令查看集群的状态: redis-cli -p 7001 cluster nodes ;可以看到8003是slave;7001是master...

以上操作集群的搭建就完成了 

测试一下

# 连接
redis-cli -p 7001
# 存储数据
set num 123
# 读取数据
get num
# 再次存储s
set a 1

结果发现悲剧了

集群操作时,需要给redis-cli加上-c参数才可以:

redis-cli -c -p 7001

我门写操作,会随机写到任意一台maste上,读操作也是读取主节点的slave节点 

  

2、散列插槽

为什么要做这样一个插槽呢slots?比如说现在存储一个num=123到集群里,这个num应该存在哪一个master里呢,如果说随便存到7002,将来取的时候怎么知道num在7002里呢?这个插槽就是来解决这个问题的

因为:这里redis当中数据的key不是与节点绑定了,而是与插槽绑定

什么叫有效部分:

  • 例如set num,num是不包含大括号{} 的,那么这个key num整体就是有效部分,将来就会根据key 这个num 字符串来去计算插槽
  • 如果key 是 {itcast}num,那么有效部分就是itcast,将来计算插槽时就会根据itcast来计算,
    • 利用crc16算法的到一个hash值
    • 然后将这个hash值对16384取余(任何一个数字对16384取余,结果一定是在0~16383之间)刚好是在插槽范围内
    • 得到的结果就是slot值
  • 既然每个节点有一部分slot值,那么就可以判断slot值在哪个节点上,那么就知道应该存在哪个节点了
  • 那为什么我们的key要去和插槽绑定,而不是和节点绑定?
    • 是因为我们Redis的主节点是可能出现宕机的情况,或者是集群扩容-增加了节点,或者是集群伸缩-删除了节点!如果说一个master节点宕机、或者节点删除了,里边的数据也就都跟着丢失了!
  • 而如果数据是和插槽绑定
    • 当master节点宕机或者删掉了,可以将该节点的插槽转移到活着的节点;
    • 集群扩容时,也可以将插槽进行转移,这样数据跟着插槽走,永远都可以找到数据所在的位置。这就是redis为什么要将数据和插槽进行绑定

打开控制台

下边我们尝试去链接:一定要加-c,代表-cluster

# 注意:在分片集群模式下不仅要-p 还要-c
redis-cli -c -p 7001

说明 a 计算的插槽位置是15495 

  

由该日志可以看见计算的a 的这个插槽是分配在7003上 ,所以上边的日志里显示的是重定向到7003的节点里,所以我门可以看见上图日志的光标前边的端口是7003

 我们在7003get a 可以获取1,那么我们的num是不在7003节点,而是在7001节点,当我们在7003get num的时候,又会计算key的slot值=2765,又会重定向到7001,光标的端口又会回到7001上了

以上就是key和插槽绑定,操作任意一个key的时候,先计算插槽值,再判断在哪个节点,完成一个请求的路由,或者叫重定向

总结:

如果想将某一类实力保存到一个实力,比如电器都保存到7003上,那么就可以利用{有效部分}+后缀;如下图的a 与{a}num ,有效部分都是a,那么插槽都一样,都是15495,这样就定位到同一个实例了,提高了性能 

3、集群伸缩

分片集群有一个非常重要的功能,就是集群伸缩!

就是说我们的这个集群必须可以实现动态的增加节点,或者移除节点;

--cluster 操作集群相关的命令:add-node 新节点ip:新节点端口 已经存在的节点ip:端口 ;我们向集群中添加节点,要通知集群中每个角色;只需要指定集群中一个已经存在的ip+端口就行

  • 不加下边那两条命令的话,默认该节点是一个主节点
  • 如果加了--clustor slave 那他就是个从节点,并且还可以指定它的master是谁,指定主节点是谁

案例:难点:插槽分配问题

  • 添加节点:redis-cli --cluster add-node 127.0.0.1:7004 127.0.0.1:7001 
  • 查看节点状态:redis-cli -p 7001 cluster nodes

注意: 添加完以后,7004节点上是没有任何插槽的

问题:现在我们要set num 的值到我们的7004实例,原来num=11,num的插槽实在7001上的,现在我们要将7001上的插槽分配到7004上;

经过测试我们知道num在7001上,先将节点重定向到7003上,再重定向会7001上,就可以看见重定向时候,该节点的插槽位置,和要重定向的端口实例了,测试可知端口是7001,插槽位置是2765

也就是说要把2765这个插槽分配到7004上,那么num就会到7004上,做插槽的分配,不然新增的节点就没有意义了

分配插槽的命令:reshard ip+端口(集群中任意一个ip和端口) redis-cli reshard 127.0.0.1:7001

  • 1、你想移动多少插槽?3000 = 0-3000
  • 2、谁来接收这些插槽?7004来接收,这里写的是7004的id
  • 3、从哪里去拷贝,拷贝的源id是谁?num在7001上,所以从7001拷贝,写的是7001id

输入done,提示 yes/no ,yes ;执行完毕后,可以查看节点信息cluster nodes,可知7004的插槽变成了0-2999,7001的插槽变成了3000-5460,分配成功了

 我们链接7001客户端,获取num,可以发现成功路由到7004上了,即:成功了~,set num 10

作业: 

  • 添加节点:先加一个节点,然后分配插槽
  • 删除节点,先将节点的数据放到7001上,然后删除节点 

查看 redis-cli --cluster help,del-node       host:port node_id是删除节点

1、分配插槽将7004放到7001上

查看日志可知分配成功,num又回到了7001上

将7004节点删除

redis-cli --cluster del-node 127.0.0.1:7004 df16299eea5e90ce5e009ea8cb1e637479649d15 

 以上操作即可实现删除节点 

4、故障转移

自动故障转移,自动实现了,并不需要我们操作什么

执行命令 redis-cli -p 7002 shutdown,停止7002,让他假装宕机了,那么看日志发现8001成为了新的master,当我们再重新启动7002的时候,他又自动成为了从节点

手动故障转移,比如某个节点服务老化了,不想要了,换一个性能更好的机器,有目的性的做一种服务的迁移等

就是让某个master宕机,先让slave升级master,在slave节点执行命令 cluster failover,告诉master我要替换你

手动让集群中的某个master宕机 

 让7002重新夺回master

只需要在slave节点执行 cluster failover即可,如下图,7002又变成了主,8001又变成了从 

5、RedisTimplate访问分片集群

1、就是引入redis的依赖,

3、就是注入@Bean实现读写分离,在springboot的启动类里

2、与哨兵模式不同的就是第2点,配置成分片的cluster 而不是 哨兵sentinel的 

即:yaml文件里的配置如下 

logging:
  level:
    io.lettuce.core: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
# 以下sentinel 是哨兵的配置
#spring:
#  redis:
#    sentinel:
#      master: mymaster
#      nodes:
#        - 127.0.0.1 27001
#        - 127.0.0.1 27002
#        - 127.0.0.1 27003

# 以下的cluster是分片集群的
spring:
  redis:
    cluster:
      nodes:
        - 127.0.0.1 7001
        - 127.0.0.1 7002
        - 127.0.0.1 7003
        - 127.0.0.1 8001
        - 127.0.0.1 8002
        - 127.0.0.1 8003

重新再浏览器调用接口

测试可以得到读写分离,写的时候动态切换,玩法基本一样 

以上即实现了分片集群

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值