admin管理员组

文章数量:1651735


若文章内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系博主删除。


前言


  • 学习视频链接
    • SpringCloud + RabbitMQ + Docker + Redis + 搜索 + 分布式,史上最全面的 SpringCloud 微服务技术栈课程 | 黑马程序员 Java 微服务
  • 学习资料链接
    • https://pan.baidu/s/169SFtYEvel44hRJhmFTRTQ提取码:1234

  • 写这篇博客旨在制作笔记,巩固知识。同时方便个人在线阅览,回顾知识。
  • 博客的内容主要来自视频内容和资料中提供的学习笔记。

  • 参考博客《你知道在 Redis 中 daemonize 的 yes 和 no 有什么区别吗?》

系列目录


SpringCloud 微服务技术栈_实用篇①_基础知识

SpringCloud 微服务技术栈_实用篇②_黑马旅游案例


SpringCloud 微服务技术栈_高级篇①_微服务保护

SpringCloud 微服务技术栈_高级篇②_分布式事务

SpringCloud 微服务技术栈_高级篇③_分布式缓存

SpringCloud 微服务技术栈_高级篇④_多级缓存

SpringCloud 微服务技术栈_高级篇⑤_可靠消息服务


微服务技术栈导学




上一篇SpringCloud 微服务技术栈_高级篇②_分布式事务


11.解决单点 Redis 的问题


单点 Redis 的问题

  1. 数据丢失问题:Redis 是内存存储,服务重启可能会丢失数据
  2. 并发能力问题:单节点 Redis 的并发能力虽然不错,但也无法满足如 618 这样的高并发场景
  3. 故障恢复问题:如果 Redis 宕机,则服务不可用,需要一种自动的故障修复手段
  4. 存储能力问题:Reids 基于内存,单节点能存储的数据量难以满足海量数据需求

基于 Redis 集群解决单机 Redis 存在的问题


12.Redis 持久化


Redis 有两种持久化方案

  • RDB 持久化
  • AOF 持久化

12.1.RDB 持久化


12.1.1.RDB 定义


RDB 全称 Redis Database Backup file(Redis 数据备份文件),也被叫做 Redis 数据快照。

简单来说就是把内存中的所有数据都记录到磁盘中。

当 Redis 实例故障重启后,从磁盘读取快照文件,恢复数据。

快照文件称为 RDB 文件,默认是保存在当前运行目录。


12.1.2.执行时机


RDB 持久化在四种情况下会执行

  • 执行 save 命令
  • 执行 bgsave 命令
  • Redis 停机时会执行 save 命令
  • 触发 RDB 的条件(redis.conf 中的内容)

  • 额外记录
    • 太久没用过 redis 了,都快忘记了,故记录一下打开服务客户端的操作

  1. save 命令

执行下面的命令,可以立即执行一次 RDB

redis-cli


save 命令会导致主进程执行 RDB,这个过程中其它所有命令都会被阻塞。

只有在数据迁移时可能用到。


  1. bgsave 命令

下面的命令可以异步执行 RDB

bgsave

这个命令执行后会开启独立进程完成 RDB,主进程可以持续处理用户请求,不受影响。


  1. 停机时

Redis 停机时会执行一次 save 命令,实现 RDB 持久化。


但是 redis 的这个持久化在停机时才执行,如果服务器突然宕机了怎么办?


  1. 触发 RDB 条件

Redis 内部有触发 RDB 的机制,可以在 redis.conf 文件中找到,格式如下

  • 第一条的意思:900 秒内,如果至少有 1 个 key 被修改,则执行 bgsave,如果是 save "" 则表示禁用 RDB
  • 其余的意思与第一条意思差不多,只是数值变化了而已
  • 一般默认的设置就可以了,无特殊要求不需要更改
save 900 1  
save 300 10  
save 60 10000 

RDB 的其它配置也可以在 redis.conf 文件中设置

  • 是否压缩 ,建议不开启,压缩也会消耗 cpu,磁盘的话不值钱
rdbcompression yes

  • RDB 文件名称
dbfilename dump.rdb  
  • 文件保存的路径目录,./,即当前目录
dir ./ 


额外记录

  • 我的虚拟机上的 Redis 是在写这篇博客的时候安装的
  • 相关的安装操作、基本配置操作和基本语法都在该博客中说明了。
src/redis-server ./redis.conf # 启动 redis-server

之前在 redis.conf 文件中写入了 requirepass password,即设置了 Redis 的密码

故启动客户端时需要输入密码

有两种方式:src/redis-cli -h localhost -p 6379src/redis-cli -h localhost -p 6379 -a password

src/redis-cli -h localhost -p 6379 
auth password # 进入到 redis 客户端后输入密码

src/redis-cli -h localhost -p 6379 -a 123456 # 一步到位,直接命令行中添加密码


12.1.3.RDB 的底层原理


bgsave 开始时会 fork 主进程得到子进程,子进程共享主进程的内存数据。

完成 fork 后读取内存数据并写入 RDB 文件。

fork 采用的是 copy-on-write 技术

  • 当主进程执行读操作时,访问共享内存;
  • 当主进程执行写操作时,则会拷贝一份数据,执行写操作。


12.1.4.小结


RDB 方式 bgsave 的基本流程?

  • fork 主进程得到一个子进程,共享内存空间
  • 子进程读取内存数据并写入新的 RDB 文件
  • 用新 RDB 文件替换旧的 RDB 文件

RDB 会在什么时候执行?save 60 1000 代表什么含义?

  • 默认是服务停止时
  • 代表 60 秒内至少执行 1000 次修改则触发 RDB

RDB 的缺点?

  • RDB 执行间隔时间长,两次 RDB 之间写入数据有丢失的风险
  • fork 子进程、压缩、写出 RDB 文件都比较耗时

12.2.AOF 持久化


12.2.1.AOF 原理


AOF 全称为 Append Only File(追加文件)。

Redis 处理的每一个写命令都会记录在 AOF 文件,可以看做是命令日志文件。


12.2.2.AOF 配置


AOF 默认是关闭的,需要修改 redis.conf 配置文件来开启 AOF

  • 是否开启 AOF 功能,默认是 no
appendonly yes
  • 指定 AOF 文件的名称
appendfilename "appendonly.aof"

AOF 的命令记录的频率也可以通过 redis.conf 文件来配(有三种方式)

  • 表示每执行一次写命令,立即记录到 AOF 文件(该操作完全由主进程完成,安全性最高,性能最低)
appendfsync always 
  • 写命令执行完先放入 AOF 缓冲区,然后表示每隔 1 秒将缓冲区数据写到 AOF 文件,是默认方案
appendfsync everysec 
  • 写命令执行完先放入 AOF 缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no

三种策略对比

配置项刷盘时机优点缺点
Always同步刷盘可靠性高,几乎不丢数据性能影响大
everysec每秒刷盘性能适中最多丢失 1 秒数据
no操作系统控制性能最好可靠性较差,可能丢失大量数据

12.2.3.AOF 文件重写


因为是记录命令,AOF 文件会比 RDB 文件大的多。

而且 AOF 会记录对同一个 key 的多次写操作,但只有最后一次写操作才有意义。

通过执行 bgrewriteaof 命令,可以让 AOF 文件执行重写功能,用最少的命令达到相同效果。

bgrewriteaof


如图,AOF 原本有三个命令

  • 但是 set num 123set num 666 都是对 num 的操作
  • 第二次会覆盖第一次的值,因此第一个命令记录下来没有意义。
  • 所以重写命令后,AOF 文件内容就是:mset name jack num 666

Redis 也会在触发阈值时自动去重写 AOF 文件。

阈值也可以在 redis.conf 中配置

  • AOF 文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
  • AOF 文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb 

12.3.RDB 和 AOF 的区别


RDB 和 AOF 各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会 结合 两者来使用

RDBAOF
持久化方式定时对整个内存做快照记录每一次执行的命令
数据完整性不完整,两次备份之间会丢失相对完整,取决于刷盘策略
文件大小会有压缩,文件体积小记录命令,文件体积很大
宕机恢复速度很快
数据恢复优先级低,因为数据完整性不如 AOF高,因为数据完整性更高
系统资源占用高,大量 CPU 和内存消耗低,主要是磁盘 IO 资源
但 AOF 重写时会占用大量的 CPU 和内存资源
使用场景可以容忍数分钟的数据丢失,追求更快的启动速度对数据安全性要求较高常见

13.Redis 主从


13.1.搭建主从结构


单节点 Redis 的并发能力是有上限的,要进一步提高 Redis 的并发能力,就需要搭建主从集群,实现读写分离。

具体搭建流程参考课前资料 Redis集群.md


13.1.0.单机安装 redis-6.2.4


这里就稍稍提一下吧(其实是我自己也想换一下版本:4.0.0 ~ 6.2.4)


  1. 安装依赖
  2. 解压
  3. 编译
  4. 修改配置
  5. 启动 Redis 服务和客户端

  1. 安装依赖

首先需要安装 Redis 所需要的依赖

yum install -y gcc tcl

  1. 解压

然后将课前资料提供的 Redis 安装包上传到虚拟机的任意目录

我这里放的位置是 /usr/local/redis 目录


解压缩

tar -xvf redis-6.2.4.tar.gz

  1. 编译

进入 redis 目录:

cd redis-6.2.4

运行编译命令

make && make install

如果没有出错的话,那应该就是安装成功了。


  1. 修改配置

然后修改 redis.conf 文件中的一些配置

  • 绑定地址,默认是 127.0.0.1,会导致只能在本地访问。修改为 0.0.0.0 则可以在任意 IP 访问
bind 0.0.0.0
  • 可以设置数据库数量,这里我设置为 1,之前是 16
databases 1
  • Linux 系统中 redis 服务启动时为霸屏模式(将 daemonize no 改为 daemonize yes 就可以后台运行了)
daemonize yes
  • 开启 redis 的密码校验,设置 redis 的密码(把 # requirepass foobared 修改为 requirepass 你想设的密码)即可。
requirepass 你想设的密码
  • 改为 daemonize 的值 为 yes 后,还想看到日志信息就必须指定日志文件(这个文件可以不创建)
mkdir redisLog # 创建新目录
logfile /usr/local/redis/redis-6.2.4/redisLog/run.log

  1. 启动 Redis 服务和客户端
  • 启动 redis 服务
src/redis-server redis.conf
  • 进入 redis 客户端
src/redis-cli -h localhost -p 6379 -a 你设的密码

  • 也可以先进入 redis 客户端,再输入密码
src/redis-cli -h localhost -p 6379
auth 你设置的密码

  • 当然,如果没有设置密码的话,直接使用 redis-server redis.conf 命令就行了
src/redis-server redis.conf

13.1.1.集群结构


我们搭建的主从集群结构如图

共包含三个节点,一个主节点,两个从节点。

这里我们会在同一台虚拟机中开启 3 个 redis 实例,模拟主从集群,信息如下

IPPORT角色
192.168.150.1017001master
192.168.150.1017002slave
192.168.150.1017003slave

13.1.2.准备实例和配置


要在同一台虚拟机开启 3 个实例,必须准备三份不同的配置文件和目录,配置文件所在目录也就是工作目录。

  1. 创建目录
  2. 恢复原始配置
  3. 拷贝配置文件到每个实例目录
  4. 修改每个实例的端口、工作目录
  5. 修改每个实例的声明 IP

  1. 创建目录

我们创建三个文件夹,名字分别叫 700170027003

mkdir colonyTests
cd colonyTests
mkdir 7001 7002 7003


  1. 恢复原始配置(如果你之前改动过配置文件的话)

修改 redis-6.2.4/redis.conf 文件,将其中的持久化模式改为默认的 RDB 模式,AOF 保持关闭状态。

开启 RDB(即注释掉 save ""

# save ""
save 3600 1
save 300 100
save 60 10000

关闭 AOF

appendonly no

  1. 拷贝配置文件到每个实例目录

然后将 redis-6.2.4/redis.conf 文件拷贝到三个目录中

方式一:逐个拷贝

cp /usr/local/redis/redis-6.2.4/redis.conf 7001
cp /usr/local/redis/redis-6.2.4/redis.conf 7002
cp /usr/local/redis/redis-6.2.4/redis.conf 7003

方式二:管道组合命令,一键拷贝

echo 7001 7002 7003 | xargs -t -n 1 cp /usr/local/redis/redis-6.2.4/redis.conf

  1. 修改每个实例的端口、工作目录

我是在该目录下进行操作的:/usr/local/redis/redis-6.2.4/colonyTests

  • 修改每个文件夹内的配置文件,将端口分别修改为 7001、7002、7003
  • 将 rdb 文件保存位置都修改为自己所在目录
sed -i -e 's/6379/7001/g' -e 's/dir .\//dir \/usr\/local\/redis\/redis-6.2.4\/colonyTests\/7001\//g' 7001/redis.conf
sed -i -e 's/6379/7002/g' -e 's/dir .\//dir \/usr\/local\/redis\/redis-6.2.4\/colonyTests\/7002\//g' 7002/redis.conf
sed -i -e 's/6379/7003/g' -e 's/dir .\//dir \/usr\/local\/redis\/redis-6.2.4\/colonyTests\/7003\//g' 7003/redis.conf

  1. 修改每个实例的声明 IP

我是在该目录下进行操作的:/usr/local/redis/redis-6.2.4/colonyTests

虚拟机本身有多个 IP,为了避免将来混乱,我们需要在 redis.conf 文件中指定每一个实例的绑定 ip 信息,格式如下

  • 设置 redis 实例的声明 IP
replica-announce-ip 192.168.150.101

每个目录都要改,我们一键完成修改

  • 可以逐一执行
sed -i '1a replica-announce-ip 192.168.150.101' 7001/redis.conf
sed -i '1a replica-announce-ip 192.168.150.101' 7002/redis.conf
sed -i '1a replica-announce-ip 192.168.150.101' 7003/redis.conf
  • 也可以一键修改
printf '%s\n' 7001 7002 7003 | xargs -I{} -t sed -i '1a replica-announce-ip 192.168.150.101' {}/redis.conf

13.1.3.启动 / 一键停止


src/redis-server colonyTests/7001/redis.conf 
src/redis-server colonyTests/7002/redis.conf 
src/redis-server colonyTests/7003/redis.conf 

如果要一键停止,可以运行下面命令

printf '%s\n' 7001 7002 7003 | xargs -I{} -t redis-cli -p {} shutdown


13.1.4.开启主从关系


现在三个实例还没有任何关系,要配置主从可以使用 replicaof 或者 slaveof5.0 以前)命令。

有临时和永久两种模式

  • 修改配置文件(永久生效)
    • redis.conf 中添加一行配置:slaveof <masterip> <masterport>
  • 使用 redis-cli 客户端连接到 redis 服务,执行 slaveof 命令(重启后失效)
slaveof <masterip> <masterport>

注意:在 5.0 以后新增命令 replicaof,与 salveof 效果一致。


这里我们为了演示方便,使用方式二。

以下的命令都是在 /usr/local/redis/redis-6.2.4 目录下执行的


通过 redis-cli 命令连接 7002,执行下面命令

  • 连接 7002
src/redis-cli -p 7002
  • 执行 slaveof
slaveof 192.168.150.101 7001

通过 redis-cli 命令连接 7003,执行下面命令

  • 连接 7003
redis-cli -p 7003
  • 执行 slaveof
slaveof 192.168.150.101 7001


然后连接 7001 节点,查看集群状态

  • 连接 7001
redis-cli -p 7001
  • 查看状态
info replication

最终结果


13.1.5.测试


执行下列操作以测试

  • 利用 redis-cli 连接 7001,执行 set num 123
  • 利用 redis-cli 连接 7002,执行 get num,再执行 set num 666
  • 利用 redis-cli 连接 7003,执行 get num,再执行 set num 888

可以发现,只有在 7001 这个 master 节点上可以执行写操作,7002 和 7003 这两个 slave 节点只能执行读操作。


13.1.6.小结


可以发现,这里的 redis 主从搭建就是启动三个实例

如果是在不同机器上运行的话,我们连端口都不用改了


  • 问:假设有 A、B 两个 Redis 实例,如何让 B 作为 A 的 slave 节点?
  • 答:在 B 节点执行命令:slave of A的IP A的port

13.2.主从数据同步原理


13.2.1.全量同步


主从第一次建立连接时,会执行全量同步,将 master 节点的所有数据都拷贝给 slave 节点


这里有一个问题:master 如何得知 slave 是第一次来连接呢?


有两个概念,可以作为判断依据

  • Replication Id
    • 简称 replid,是数据集的标记,id 一致则说明是同一数据集。
    • 每一个 master 都有唯一的 replidslave 则会继承 master 节点的 replid
  • offset
    • 偏移量,随着记录在 repl_baklog 中的数据增多而逐渐增大。
    • slave 完成同步时也会记录当前同步的 offset
    • 如果 slaveoffset 小于 masteroffset,说明 slave 数据落后于 master,需要更新。

因此 slave 做数据同步,必须向 master 声明自己的 replication idoffsetmaster 才可以判断到底需要同步哪些数据。

  • 因为 slave 原本也是一个 master ,有自己的 replidoffset
  • 当第一次变成 slave,与 master 建立连接时,发送的 replidoffset 是自己的 replidoffset

master 判断发现 slave 发送来的 replid 与自己的不一致,说明这是一个全新的 slave,就知道要做全量同步了。

master 会将自己的 replidoffset 都发送给这个 slaveslave 保存这些信息。

以后 slavereplid 就与 master一致了。

因此,master 判断一个节点是否是第一次同步的依据,就是看 replid 是否一致



全量同步流程描述

  • slave 节点请求增量同步
  • master 节点判断 replid ,发现不一致,拒绝增量同步
  • master 将完整内存数据生成 RDB,发送 RDBslave
  • slave 清空本地数据,加载 masterRDB
  • masterRDB 期间的命令记录在 repl_baklog,并持续将 log 中的命令发送给 slave
  • slave 执行接收到的命令,保持与 master 之间的同步

13.2.2.增量同步


全量同步需要先做 RDB,然后将 RDB 文件通过网络传输个 slave,成本太高了。

因此除了第一次做 全量同步,其它大多数时候 slavemaster 都是做 增量同步

什么是增量同步?就是只更新 slavemaster 存在差异的部分数据。



那么 master 怎么知道 slave 与自己的数据差异在哪里呢?


13.2.3.repl_backlog 原理


master 怎么知道 slave 与自己的数据差异在哪里呢?


这就要说到全量同步时的 repl_baklog 文件了。

这个文件是一个固定大小的数组,只不过数组是环形

也就是说:角标到达数组末尾后,会再次从 0 开始读写,这样数组头部的数据就会被覆盖。


repl_baklog 中会记录 Redis 处理过的命令日志及 offset,包括 master 当前的 offset,和 slave 已经拷贝到的 offset


slavemasteroffset 之间的差异,就是 salve 需要增量拷贝的数据了。

随着不断有数据写入,masteroffset 逐渐变大,slave 也不断的拷贝,追赶 masteroffset


直到数组被填满

此时,如果有新的数据写入,就会覆盖数组中的旧数据。

不过,旧的数据只要是绿色的,说明是已经被同步到 slave 的数据,即便被覆盖了也没什么影响。

因为未同步的仅仅是红色部分。


但是,如果 slave 出现网络阻塞,导致 masteroffset 远远超过了 slaveoffset


如果 master 继续写入新数据,其 offset 就会覆盖旧的数据,直到将 slave现在的 offset 也覆盖

棕色框中的红色部分,就是尚未同步,但是却已经被覆盖的数据。

此时如果 slave 恢复,需要同步,却发现自己的 offset 都没有了,无法完成增量同步了。

只能做全量同步。


注意

  • repl_baklog 大小有上限,写满后会覆盖最早的数据。
  • 如果 slave 断开时间过久,导致尚未备份的数据被覆盖,则无法基于 log 做增量同步,只能再次全量同步

13.3.主从同步优化


主从同步可以保证主从数据的一致性,非常重要。


可以从以下几个方面来优化 Redis 主从集群

  • 提高全量同步的性能
    • master 中配置 repl-diskless-sync yes 启用无磁盘复制,避免全量同步时的磁盘 IO
    • Redis 单节点上的内存占用不要太大,减少 RDB 导致的过多磁盘 IO
  • 尽可能避免全量同步
    • 适当提高 repl_baklog 的大小,发现 slave 宕机时尽快实现故障恢复,尽可能避免全量同步
  • 减小主节点压力同步
    • 限制一个 master 上的 slave 节点数量,如果实在是太多 slave,则可以采用 主-从-从 链式结构,减少 master 压力

主从从架构图

13.4.小结


简述全量同步和增量同步区别?

  • 全量同步:master将完整内存数据生成 RDB,发送 RDBslave。后续命令则记录在 repl_baklog,逐个发送给 slave
  • 增量同步:slave 提交自己的 offsetmastermaster 获取 repl_baklog 中从 offset 之后的命令给 slave

什么时候执行全量同步?

  • slave 节点第一次连接 master 节点时
  • slave 节点断开时间太久,repl_baklog 中的 offset 已经被覆盖时

什么时候执行增量同步?

  • slave 节点断开又恢复,并且在 repl_baklog 中能找到 offset

14.Redis 哨兵


思考slave 节点宕机回复后可以找 master 节点同步数据,那 master 节点宕机怎么办?


Redis 提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。


14.1.哨兵原理


14.1.1.集群结构和作用


哨兵的结构图

哨兵的作用

  • 监控:Sentinel 会不断检查您的 masterslave 是否按预期工作
  • 自动故障恢复:如果 master 故障,Sentinel 会将一个 slave 提升为 master。当故障实例恢复后也以新的 master 为主
  • 通知:Sentinel 充当 Redis 客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给 Redis 的客户端

14.1.2.集群监控原理


Sentinel 基于心跳机制监测服务状态,每隔 1 秒向集群的每个实例发送 ping 命令

  • 主观下线
    • 如果某 sentinel 节点发现某实例未在规定时间响应,则认为该实例 主观下线
  • 客观下线
    • 若超过指定数量(quorum)的 sentinel 都认为该实例主观下线,则该实例 客观下线
    • quorum 值最好超过 Sentinel 实例数量的一半。


14.1.3.集群故障恢复原理


一旦发现 master 故障,sentinel 需要在 salve 中选择一个作为新的 master,选择依据是这样的

  • 首先会判断 slave 节点与 master 节点断开时间长短
    • 如果超过指定值(down-after-milliseconds * 10)则会排除该 slave 节点
  • 然后判断 slave 节点的 slave-priority 值,越小优先级越高,如果是 0 则永不参与选举
    • slave priority 基本上都一样,默认都是 1
  • 如果 slave-prority 一样,则判断 slave 节点的 offset 值,越大说明数据越新,优先级越高
    • 到这一步还没有区别的话,其实就说明几个节点完全没有区别,之后其实就是随便选
  • 最后是判断 slave 节点的运行 id 大小,越小优先级越高。
    • redis 集群启动的由 redis 那一刻生成的运行 id,它的大小是随机的
    • 所以这最后一步其实就是随机选的

显然,最重要的衡量标准还是 offset


当选出一个新的 master后,该如何实现切换呢?

流程如下

  • sentinel 给备选的 slave1 节点发送 slaveof no one 命令,让该节点成为 master
  • sentinel 给所有其它 slave 发送 slaveof 192.168.150.101 7002 命令
    • 让这些 slave 成为新 master 的从节点,开始从新的 master 上同步数据。
  • 最后,sentinel 将故障节点标记为 slave,当故障节点恢复后会自动成为新的 masterslave 节点


14.1.4.小结


Sentinel 的三个作用是什么?

  • 监控
  • 故障转移
  • 通知

Sentinel 如何判断一个 redis 实例是否健康?

  • 每隔 1 秒发送一次 ping 命令,如果超过一定时间没有相向则认为是主观下线
  • 如果大多数 sentinel 都认为实例主观下线,则判定服务下线

故障转移步骤有哪些?

  • 首先选定一个 slave 作为新的 master,执行 slaveof no one
  • 然后让所有节点都执行 slaveofmaster
  • 修改故障节点配置,添加 slaveofmaster

14.2.搭建哨兵集群


具体搭建流程参考课前资料:Redis集群.md


14.2.1.集群结构


这里我们搭建一个三节点形成的 Sentinel 集群,来监管之前的 Redis 主从集群。


三个 sentinel 实例信息如下

节点IPPORT
s1192.168.150.10127001
s2192.168.150.10127002
s3192.168.150.10127003

14.2.2.准备实例和配置


要在同一台虚拟机开启 3 个实例,必须准备三份不同的配置文件和目录,配置文件所在目录也就是工作目录。


我们创建三个文件夹,名字分别叫 s1s2s3

  • 进入诸位自己安装 redis 的目录
 cd /usr/local/redis/redis-6.2.4/
  • 创建目录
mkdir sentinelTests
cd sentinelTests
mkdir s1 s2 s3


然后我们在 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 "/usr/local/redis/redis-6.2.4/sentinelTests/s1"
daemonize yes

解读:

  • port 27001:是当前 sentinel 实例的端口
  • sentinel monitor mymaster 192.168.150.101 7001 2:指定主节点信息
    • mymaster:主节点名称,自定义,任意写
    • 192.168.150.101 7001:主节点的 ip 和端口
    • 2:选举 master 时的 quorum 值
  • sentinel down-after-milliseconds mymaster 5000slavemaster 断开最长超时时间
  • sentinel failover-timeout mymaster 60000slave 故障恢复超时时间
  • dir "/usr/local/redis/redis-6.2.4/sentinelTests/s1"":工作目录
  • daemonize yes:指定 redis 用守护线程的方式启动


参考博客:《你知道在 Redis 中 daemonize 的 yes 和 no 有什么区别吗?》

  • 采用 yes
    • redis 会在后台运行,此时 redis 将一直运行,除非手动 kill 该进程。
    • 同时将进程 pid 号写入至 redis.conf 选项 pidfile 设置的文件中(默认会生成在 /var/run/redis.pid)
    • 也可以通过 pidfile 来指定 pid 文件生成的位置(例如:pidfile /path/redis.pid
  • 采用 no
    • 当前界面将进入 redis 的命令行界面,exit 强制退出或者关闭连接工具(putty、xshell 等)都会导致 redis 进程退出。

然后将 s1/sentinel.conf 文件拷贝到 s2s3 两个目录中

(我的操作目录:/usr/local/redis/redis-6.2.4/sentinelTests/

  • 方式一:逐个拷贝
cp s1/sentinel.conf s2
cp s1/sentinel.conf s3
  • 方式二:管道组合命令,一键拷贝
echo s2 s3 | xargs -t -n 1 cp s1/sentinel.conf

修改 s2s3 两个文件夹内的配置文件,将端口分别修改为 27002、27003

(我的操作目录:/usr/local/redis/redis-6.2.4/sentinelTests/

sed -i -e 's/27001/27002/g' -e 's/s1/s2/g' s2/sentinel.conf
sed -i -e 's/27001/27003/g' -e 's/s1/s3/g' s3/sentinel.conf


14.2.3.启动


分别启动 3 个 redis 实例,启动命令

(我的操作目录:/usr/local/redis/redis-6.2.4/sentinelTests/

  • 第 1 个
src/redis-sentinel sentinelTests/s1/sentinel.conf
  • 第 2 个
src/redis-sentinel sentinelTests/s2/sentinel.conf
  • 第 3 个
src/redis-sentinel sentinelTests/s3/sentinel.conf


14.2.4.测试


在尝试让 7001(master) 断开后,进入 redis 客户端(端口:7002)

发现其已经变为了 master 节点


再次尝试启动 7001 端口时,master 也不会发生变化了,仍然是 7002


  • 以下的三张图片是黑马官方免费公开的笔记文档中的图片
  • 视频中采用的是霸屏模式启动的 redis-serversentinel
  • 图片上有详细介绍。字太多了,所以我直接把他们的图片贴上去了

  • 尝试让 master 节点 7001 宕机,查看 sentinel 日志


  • 查看 7003 的日志


  • 查看 7002 的日志


14.3.RedisTemplate


在 Sentinel 集群监管下的 Redis 主从集群,其节点会因为自动故障转移而发生变化。

Redis 的客户端必须感知这种变化,及时更新连接信息。

Spring 的 RedisTemplate 底层利用 lettuce 实现了节点的感知和自动切换。

下面,我们通过一个测试来实现 RedisTemplate 集成哨兵机制。


14.3.1.导入 Demo 工程


引入课前资料提供的 Demo 工程



14.3.2.引入依赖


在项目的 pom 文件中引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

14.3.3.配置 Redis 地址


然后在配置文件 application.yml 中指定 redis 的 sentinel 相关信息

spring:
  redis:
    sentinel:
      master: mymaster # 指定 master 名称
      nodes: # 指定 redis-sentinel 集群信息
        - 192.168.2.7:27001
        - 192.168.2.7:27002
        - 192.168.2.7:27003

14.3.4.配置读写分离


在项目的启动类中,添加一个新的 bean

src/main/java/cn/itcast/redisdemo/RedisDemoApplication.java

@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
    return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}

这个 bean 中配置的就是读写策略,包括四种

  • MASTER:从主节点读取
  • MASTER_PREFERRED:优先从 master 节点读取,master 不可用才读取 replica
  • REPLICA:从 slavereplica)节点读取
  • REPLICA _PREFERRED:优先从 slavereplica)节点读取,所有的 slave 都不可用才读取 master

15.Redis 分片集群


15.1.搭建分片集群


15.1.1.分片集群基本介绍


主从和哨兵可以解决高可用、高并发读的问题。

但是依然有两个问题没有解决:

  • 海量数据存储问题
  • 高并发写的问题

使用 分片集群 可以解决上述问题,如图


分片集群特征

  • 集群中有多个 master,每个 master 保存不同数据
  • 每个 master 都可以有多个 slave 节点
  • master 之间通过 ping 监测彼此健康状态
  • 客户端请求可以访问集群任意节点,最终都会被转发到正确节点

具体搭建流程参考课前资料:Redis 集群.md


15.1.2.集群结构


分片集群需要的节点数量较多,这里我们搭建一个最小的分片集群

  • 包含 3 个 master 节点,每个 master 包含一个 slave 节点

结构如下

这里我们会在同一台虚拟机中开启 6 个 redis 实例,模拟分片集群

信息如下

IPPORT角色
192.168.150.1017001master
192.168.150.1017002master
192.168.150.1017003master
192.168.150.1018001slave
192.168.150.1018002slave
192.168.150.1018003slave

15.1.3.准备实例和配置


我的 redis 安装在了 /usr/local/redis/redis/redis-6.2.4


删除之前的 700170027003 这几个目录,重新创建出 700170027003800180028003 目录

  • 进入 redis 的目录
cd /usr/local/redis/redis/redis-6.2.4
  • 删除旧的目录,避免配置干扰
rm -rf colonyTests/7001
rm -rf colonyTests/7002
rm -rf colonyTests/7003
  • 创建目录
cd colonyTests/
mkdir 7001 7002 7003 8001 8002 8003


在目录 /usr/local/redis/redis-6.2.4/colonyTests 准备一个新的 redis.conf 文件

  • 新的 redis.conf 内容如下
port 7001
# 开启集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要我们创建,由 redis 自己维护
cluster-config-file /usr/local/redis/redis-6.2.4/colonyTests/7001/nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /usr/local/redis/redis-6.2.4/colonyTests/7001
# 绑定地址
bind 0.0.0.0
# 守护进程,让 redis 后台运行
daemonize yes
# 注册的实例 ip
replica-announce-ip 192.168.150.101
# 关闭掉保护模式后,将来就不做什么用户名密码之类的校验了
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /usr/local/redis/redis-6.2.4/colonyTests/7001/run.log

将这个文件拷贝到每个目录下

  • 进入相应的目录
cd /usr/local/redis/redis-6.2.4/colonyTests
  • 执行拷贝
echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf

修改每个目录下的 redis.conf,将其中的 7001 修改为与所在目录一致

  • 进入相应目录
cd /usr/local/redis/redis-6.2.4/colonyTests
  • 修改配置文件
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/7001/{}/g' {}/redis.conf


15.1.4.一键开启所有进程


因为已经配置了后台启动模式,所以可以直接启动服务

  • 进入相应的目录
cd /usr/local/redis/redis-6.2.4/colonyTests
  • 一键启动所有服务
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf
  • 通过 ps 查看状态
ps -ef | grep redis


15.1.5.一键关闭所有进程


  • 如果要关闭所有进程,可以执行如下命令
ps -ef | grep redis | awk '{print $2}' | xargs kill
  • 或使用该命令关闭所有 redis 的进程(推荐
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-cli -p {} shutdown

15.1.6.创建集群(Redis 5.0 之前)


虽然服务启动了,但是目前每个服务之间都是独立的,没有任何关联。

我们需要执行命令来创建集群,在 Redis 5.0 之前创建集群比较麻烦

Redis 5.0 之后集群管理命令都集成到了 redis-cli 中。


Redis 5.0 之前集群命令都是用 redis 安装包下的 src/redis-trib.rb 来实现的。

因为 redis-trib.rb 是有 ruby 语言编写的所以需要安装 ruby 环境。

  • 安装依赖
yum -y install zlib ruby rubygems
gem install redis

然后通过命令来管理集群

  • 进入 redis 的 src目录
cd /tmp/redis-6.2.4/src
  • 创建集群
./redis-trib.rb create --replicas 1 192.168.150.101:7001 \
192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:8003

15.1.7.创建集群(Redis 5.0 之后)


虽然服务启动了,但是目前每个服务之间都是独立的,没有任何关联。

我们需要执行命令来创建集群,在 Redis 5.0 之前创建集群比较麻烦

Redis 5.0 之后集群管理命令都集成到了 redis-cli 中。


我们使用的是 redis-6.2.4 版本,集群管理以及集成到了 redis-cli 中,格式如下

redis-cli --cluster create --cluster-replicas 1 192.168.150.101:7001 \
192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:8003

命令说明

  • redis-cli --cluster 或者 ./redis-trib.rb:代表集群操作命令
  • create:代表是创建集群
  • --replicas 1 或者 --cluster-replicas 1:指定集群中每个 master 的副本个数为 1
    • 此时 节点总数 ÷ (replicas + 1) 得到的就是 master 的数量。
    • 因此节点列表中的前 n 个就是 master,其它节点都是 slave 节点,随机分配到不同 master

运行上面的命令后,需要输入 yes


之后集群就开始创建了


  • 通过命令可以查看集群状态
redis-cli -p 7001 cluster nodes


15.2.散列插槽


15.2.1.插槽原理


这里我偷个懒,直接贴的官方免费公开提供的资料里的图片


Redis 会把每一个 master 节点映射到 0~16383 共 16384个 插槽hash slot)上,查看集群信息时就能看到


每个 master 都可以存储数据。

当数据存储完毕时,如何得知去哪个 master 找数据?

插槽就可以帮我们解决这个问题


数据 key 不是与节点绑定,而是与插槽绑定。

redis 会根据 key 的有效部分计算插槽值,分两种情况

  • key 中包含 “{}”,且 “{}”中至少包含 1 个字符,“{}” 中的部分是有效部分
  • key 中不包含 “{}”,整个 key 都是有效部分

例如:keynum,那么就根据 num 计算,如果是 {itcast} num,则根据 itcast 计算。


计算方式是利用 CRC16 算法得到一个 hash 值,然后对 16384 取余,得到的结果就是 slot 值。

在 7001 这个节点执行 set a 1 时,对 a 做 hash 运算,对 16384 取余,得到的结果是 15495,因此要存储到 7003 节点。

到了 7003 后,执行 get num 时,对 num 做 hash 运算,对 16384 取余,得到的结果是 2765,因此需要切换到 7001 节点


插槽与 key 绑定,而不与节点绑定,还可以有效避免节点宕机的情况


15.2.2.小结


Redis 如何判断某个 key 应该在哪个实例?

  • 将 16384 个插槽分配到不同的实例
  • 根据 key 的有效部分计算哈希值,对 16384 取余
  • 余数作为插槽,寻找插槽所在实例即可

如何将同一类数据固定的保存在同一个 Redis 实例?

  • 这一类数据使用相同的有效部分(例如 key 都以 {typeId} 为前缀)

例如:


15.3.集群伸缩


这里我偷个懒,直接贴的官方免费公开提供的资料里的图片


15.3.1.简述


集群可以添加节点,也可以删除节点


redis-cli --cluster 提供了很多操作集群的命令,可以通过下面方式查看

redis-cli --cluster help


比如,添加节点的命令

在没有 --cluster-slave 这个参数前,默认新增节点是主节点

--cluster-master-id 则是指定谁是它的 master(显然这要配合 --cluster-slave 来使用)


15.3.2.需求分析


需求:向集群中添加一个新的 master节点,并向其中存储 num = 10

  • 启动一个新的 redis 实例,端口为 7004
  • 添加 7004 到之前的集群,并作为一个 master 节点
  • 给 7004 节点分配插槽,使得 num 这个 key 可以存储到 7004 实例

这里需要两个新的功能

  • 添加一个节点到集群中
  • 将部分插槽分配到新插槽

15.3.3.创建新的 redis 实例


  • 进入相应的目录
cd /usr/local/redis/redis-6.2.4/colonyTests
  • 创建一个文件夹
mkdir 7004
  • 拷贝配置文件
cp 7001/redis.conf 7004
  • 修改配置文件
sed -i /s/7001/7004/g 7004/redis.conf
  • 启动
redis-server 7004/redis.conf

15.3.4.添加新节点到 redis


添加节点的语法如下:


  • 执行命令
redis-cli --cluster add-node  192.168.150.101:7004 192.168.150.101:7001

  • 通过命令查看集群状态
redis-cli -p 7001 cluster nodes

如图,7004 加入了集群,并且默认是一个 master 节点:

但是,可以看到 7004 节点的插槽数量为 0,因此没有任何数据可以存储到 7004 上


15.3.5.转移插槽


我们要将 num 存储到 7004 节点,因此需要先看看 num 的插槽是多少

如上图所示,num 的插槽为 2765.


我们可以将 0~3000 的插槽从 7001 转移到 7004,命令格式如下


具体命令如下

建立连接

redis-cli --cluster reshard 192.168.150.101:7001

得到下面的反馈

询问要移动多少个插槽,我们计划是 3000 个

新的问题来了:

哪个 node 来接收这些插槽???

显然是 7004,那么 7004 节点的 id 是多少呢?

复制这个 id,然后拷贝到刚才的控制台后

这里询问,你的插槽是从哪里移动过来的?

  • all:代表全部,也就是三个节点各转移一部分
  • 具体的 id:目标节点的 id
  • done:没有了

这里我们要从7001获取,因此填写 7001 的 id

填完后,点击 done,这样插槽转移就准备好了

确认要转移吗?输入 yes

然后,通过命令查看结果

redis-cli -p 7001 cluster nodes

可以看到

显然,目的达成。


15.4.故障转移


集群初始状态是这样的

其中 7001、7002、7003 都是 master,我们计划让 7002 宕机。


15.4.1.自动故障转移


当集群中有一个 master 宕机会发生什么呢?

直接停止一个 redis 实例,例如 7002

redis-cli -p 7002 shutdown

  1. 首先是该实例与其它实例失去连接

  1. 然后是疑似宕机


  1. 最后是确定下线,自动提升一个 slave为新的 master


  1. 当 7002 再次启动,就会变为一个 slave 节点了


15.4.2.手动故障转移


利用 cluster failover 命令可以手动让集群中的某个 master 宕机

切换到执行 cluster failover 命令的这个 slave 节点,实现无感知的数据迁移。


其流程如下:


这种 failover 命令可以指定三种模式

  • 缺省:默认的流程:如图 1~6 步
  • force:省略了对 offset 的一致性校验
  • takeover:直接执行第 5 歩,忽略数据一致性、忽略 master 状态和其它 master 的意见

案例需求:在 7002 这个 slave 节点执行手动故障转移,重新夺回 master 地位


步骤如下

  1. 利用 redis-cli 连接 7002 这个节点
  2. 执行 cluster failover 命令

如图:

效果:


15.5.RedisTemplate 访问分片集群


RedisTemplate 底层同样基于 lettuce 实现了分片集群的支持,而使用的步骤与哨兵模式基本一致

  1. 引入 redis 的 starter 依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 配置分片集群地址
  2. 配置读写分离

src/main/java/cn/itcast/redisdemo/RedisDemoApplication.java

@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
    return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}

与哨兵模式相比,其中只有分片集群的配置方式略有差异

spring:
  redis:
    cluster:
      nodes:
        - 192.168.150.101:7001
        - 192.168.150.101:7002
        - 192.168.150.101:7003
        - 192.168.150.101:8001
        - 192.168.150.101:8002
        - 192.168.150.101:8003

下一篇SpringCloud 微服务技术栈_高级篇④_多级缓存


本文标签: 分布式缓存学习笔记高级技术