1 主从复制

1.1 主从复制简介

1.1.1 互联网的 三高架构

  • 高并发:可以保证系统能同时并发处理很多请求。
  • 高性能:程序处理速度非常快,所占内存少,cpu 占用率低。
  • 高可用:系统 7 * 24 小时,不宕机。

1.1.2 互联网三高架构之高可用

  • 可用性:一年中应用服务正常运行的时间占全年时间的百分比。

互联网三高架构之高可用.png

  • 上图描述了应用服务在全年宕机的时间,我们将这些时间加到一起就是全年应用服务不可用的时间。
  • 全年应用服务不可用的时间 = 4小时27分15秒 + 11分36秒+2分16秒 + 2分16秒 = 16867 秒。
  • 全年的总时间 = 365 24 3600 = 31536000 秒。
  • 可用性 = ((31536000 - 16867 )/ 31536000 )* 100 % = 99.9465151 %。
  • 业界可用性目标是 5个9 ,即 99.999%,即服务器年宕机时长低于 315 秒,约 5.25 分钟。

1.1.3 单机 Redis 的风险和问题

  • 问题一:机器故障
    • 现象:硬盘故障、系统崩溃。
    • 本质:数据丢失,可能会业务造成灾难性打击。
    • 结论:基本上会放弃使用 Redis 。
  • 问题二:容量瓶颈
    • 现象:内存不足,从 16 G 升级到 64 G ,从 64 G 升级到 128 G,无限升级内存。
    • 本质:穷,硬件条件跟不上。
    • 结论:基本上会放弃使用 Redis 。
  • 结论:为了避免单点 Redis 服务器故障,准备多台服务器,互相连通。将数据复制多个副本保存在不同的服务器上,连接在一起 ,并保证数据是 同步的。即使有其中的一台服务器宕机了,其他的服务器依然可以继续提供服务,实现 Redis 的高可用,同时实现数据 冗余备份

1.1.4 多台服务器的连接方案

  • 收集数据(写):master。
    • 也可以称为:主服务器、主节点、主库。
    • 连接的客户端称为:主客户端。
  • 提供数据(读):slave。
    • 也可以称为:从服务器、从节点、从库。
    • 连接的客户端称为:从客户端。

多台服务器的连接方案.png

  • 需要解决的问题:数据同步。
  • 核心工作:将 master 的数据复制到 slave 中。

1.1.5 主从复制

  • 主从复制:将 master 中的数据即时、有效的复制到 slave 中。
  • 特征:一个 master 可以有多个 slave ,一个 slave 只能对应一个 master 。
  • 职责:
    • master:
      • 写数据。
      • 执行写操作的时候,将出现变化的数据自动同步到 slave 中。
      • 读数据(可忽略)。
    • slave:
      • 读数据。
      • 写数据(禁止)。

1.1.6 主从复制的作用

  • ① 读写分离:master 写、slave 读,提高服务器的读写负载能力。
  • ② 负载均衡:基于主从结构、配合读写分离,由 slave 分担 master 负载,并根据需求的变化,改变 slave 的数量,通过多个从节点分担数据读取负载,大大提高 Redis 服务器并发量和数据吞吐量。
  • ③ 故障恢复:当 master 出现问题的时候,由 slave 提供服务,实现快速的故障恢复。
  • ④ 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式。
  • ⑤ 高可用基石:基于主从复制,构建哨兵模式和集群,实现 Redis 的高可用方案。

1.2 主从复制工作流程

1.2.1 主从复制过程

  • 主从复制过程大体可以分为 3 个阶段:
    • ① 建立连接阶段(即准备阶段)。
    • ② 数据同步阶段。
    • ③ 命令传播阶段。

主从复制过程.png

  • 命令传播有 4 种,如下图所示:

命令传播.png

1.2.2 准备工作

机器 角色 操作系统
192.168.65.100 master CentOS 7.9
192.168.65.101 slave CentOS 7.9
192.168.65.102 slave CentOS 7.9

学习过程中,可以将 Linux 服务器上的防火墙关闭,确保这三台机器能互相通信。

1.2.3 搭建主从复制

  • 三台机器关闭防火墙:
  1. systemctl stop firewalld
  1. systemctl disable firewalld
  • 三台机器都需要下载 Redis 的压缩包,并解压:
  1. cd /opt
  1. wget https://download.redis.io/releases/redis-5.0.10.tar.gz
  1. tar -zxvf redis-5.0.10.tar.gz
  • 三台机器都需要进入 Redis 的解压目录:
  1. cd redis-5.0.10
  • 三台机器都需要安装 c 语言编译环境:
  1. yum -y install gcc-c++
  • 三台机器修改 Redis 安装的目录路径:
  1. vim src/Makefile
  1. # 就 Redis 自身而言是不需要修改的,这里修改的目的是让 Redis 的运行程序不要和其他文件混杂在一起。
  2. PREFIX?=/usr/local/redis
  • 三台机器编译安装 Redis:
  1. make && make install
  • 三台机器复制 redis.conf 文件到 /usr/local/redis :
  1. cp redis.conf /usr/local/redis/
  • 修改 192.168.65.100(master) 机器上的 Redis 的配置文件:
  1. cd /usr/local/redis/
  1. vim redis.conf
  1. # 需要注释掉 bind
  2. # bind 127.0.0.1
  3. # 将保护模式关闭
  4. protected-mode no
  5. # 需要以守护进程方式启动
  6. daemonize yes
  7. # 需要配置日志
  8. logfile "/usr/local/redis/redis.logs"
  9. # 需要配置 rdb 文件的目录
  10. dir /usr/local/redis
  11. # 将 AOF 功能打开
  12. appendonly yes
  13. # 其他配置保存默认
  • 修改 192.168.65.101 和 192.168.65.102(slave) 机器上的 Redis 的配置文件:
  1. cd /usr/local/redis/
  1. vim redis.conf
  1. # 需要注释掉 bind
  2. # bind 127.0.0.1
  3. # 将保护模式关闭
  4. protected-mode no
  5. # 需要以守护进程方式启动
  6. daemonize yes
  7. # 需要配置日志
  8. logfile "/usr/local/redis/redis.logs"
  9. # 需要配置 rdb 文件的目录
  10. dir /usr/local/redis
  11. # 将 AOF 功能打开
  12. appendonly yes
  13. # 增加配置
  14. slaveof 192.168.65.100 6379
  15. # 其他配置保存默认
  • 分别启动三台机器上的 Redis 服务(先启动 master 机器上的 Redis 服务,再 启动 slave 机器上的 Redis 服务):
  1. /usr/local/redis/bin/redis-server /usr/local/redis/redis.conf
  • 分别使用各自机器上 redis-cli 连接各自的 Redis 服务:
  1. /usr/local/redis/bin/redis-cli
  • 使用 info replication 查看主从复制信息:
  1. info replication

mster 查看主从复制信息.png

slave 1 查看主从复制信息.png

slave 2 查看主从复制信息.png

  • 测试主从复制是否搭建成功:

测试主从复制是否搭建成功.gif

1.2.4 搭建主从复制的细节

  • slave 连接 master 的方式(无认证『需要用户名或密码登录』方式):

    • ① 客户端发送命令:

      1. # 可以,但是不推荐
      2. slaveof <master.ip> <master.port>
    • ② 客户端启动服务器,增加连接参数:

      1. # 可以,但是不推荐
      2. ./redis-server -slaveof <master.ip> <master.port>
    • ③ 客户端服务器配置(推荐):

      1. # 直接在配置文件中配置,推荐,上面的案例中就使用的此种方式
      2. slaveof <master.ip> <master.port>
  • slave 主动断开连接(客户端发送命令):

  1. slaveof no one
  • slave 连接 master 的方式(认证方式):

    • master 配置文件中设置密码(推荐):

      1. # 推荐
      2. # 需要注释掉 bind
      3. # bind 127.0.0.1
      4. # 将保护模式开启
      5. protected-mode yes
      6. # 设置密码
      7. requirepass <password>
    • master 客户端发送命令设置密码:

      1. config set requirepass <password>
      1. config get requirepass
    • slave 客户端发送命令设置密码:

      1. auth <password>
    • slave 配置文件设置密码(推荐):

      1. masterauth <password>
    • 其余的和 slave 连接 master 的方式(无认证『需要用户名或密码登录』方式)相同。

    • 启动客户端设置密码:
      1. ./redis-cli -a <password>

1.2.5 主从复制阶段一:建立连接阶段工作流程

  • 建立 salve 到 master 的连接,使得 master 能够识别到 slave ,并保存 slave 的 IP 和 端口号。
  • 步骤:
    • 步骤 1 :slave 设置 master 的地址和端口,连接成功后,在 slave 端保存 master 的信息。
    • 步骤 2 :slave 根据 保存的信息创建连接到 master 的 socket 连接。
    • 步骤 3 :slave 周期性的发送 ping 命令。
    • 步骤 4 :如果 master 开启了授权验证,那么 slave 还需要发送 auth password 指令,进行身份验证。
    • 步骤 5 :slave 发送 端口信息给 master,master 保存对应的 IP 和 端口。
  • 至此,连接成功。
  • 当前状态:
    • slave :保存 master 的地址和端口号。
    • master:保存 slave 的地址和端口号。
  • 总体:master 和 slave 之间创建了连接的 socket 。

主从复制阶段一:建立连接阶段工作流程.png

1.2.6 主从复制阶段二:数据同步阶段工作流程

  • 将 slave 的数据库状态更新成 master 当前的数据库状态。
  • 步骤:
    • 步骤 1 :slave 请求同步数据。
    • 步骤 2 :master 创建 RDB 文件,同步数据。
    • 步骤 3 :slave 接收 RDB 文件,清空旧的数据,执行 RDB 文件恢复过程。
    • 步骤 4 :slave 请求部分同步数据。
    • 步骤 5 :master 发送复制缓冲区信息,slave 恢复部分同步数据。
  • 至此,数据同步工作完成。

主从复制阶段二:数据同步阶段工作流程.png

  • 状态:
    • slave:具有 master 端全部的数据,包含 RDB 过程接收的数据。
    • master:保存 slave 当前数据同步的位置。
  • 总体:master 和 slave 之间完成了数据复制工作。
  • 数据同步阶段 master 说明:
    • ① 如果 master 数据量巨大,数据同步阶段应该避开流量高峰期,避免造成 master 阻塞,影响业务正常执行。
    • ② 复制缓冲区大小设定不合理,会导致数据溢出。如:进行全量复制周期太长,进行部分复制时发现数据已经存在丢失的情况,必须进行第二次全量复制,导致 slave 陷入死循环状态。

复制缓冲区大小设定不合理.png

  • master 端修改 redis.conf 文件:

    1. repl-backlog-size ?mb
  • 怎么修改?master 单机内存占用主机内存的比例不能过大,建议使用 50 % ~ 70 %的内存,留下 30 % ~ 50 % 的内存用于执行 bgsave 命令和创建复制缓冲区。

    • 数据同步阶段 slave 说明:
  • ① 为避免 slave 进行全量复制、部分复制的时候服务器响应阻塞或数据不同步,建议在此期间关闭对外服务。

    1. # yes 表示主从复制中,从服务器可以响应客户端请求;
    2. # no 表示主从复制中,从服务器将阻塞所有请求,有客户端请求时返回“SYNC with master in progress”;
    3. slave-serve-stale-data yes|no
  • ② 数据同步阶段,master 发送给 slave 的信息可以理解为 master 是 slave 的一个客户端,主动向 slave 发送命令。

  • ③ 多个 slave 同时对 master请求数据同步,master 发送的 RDB 文件增多,会对带宽造成巨大冲击,如果 master 带宽不足,那么数据同步需要根据业务需求,适量错峰。
  • ④ slave 过多的时候,建议调整拓扑结构,由一主多从变为树状结构,中间的节点既是 master,也是 slave。需要注意的是,使用树状结构时,由于层次深度,导致深度越高的 slave 和最顶层的 master 间数据同步延迟较大,数据一致性较差,应该慎重选择。

1.2.7 主从复制阶段三:命令传播阶段工作流程

  • 当 master 数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的状态,同步的动作称为命令传播。
  • master 将接收到的数据变更命令发送给 slave , slave 接收命令后执行命令。
  • 命令传播阶段出现断网现象:
    • 网络闪断闪连:忽略。
    • 长时间网络中断:全量复制。
    • 短时间网络中断:部分复制。
  • 部分复制的三个核心要素:
    • ① 服务器的运行 id (run id)。
      • 概念:服务运行 id 是每一台服务器每次运行的身份标识码,一台服务器多次运行可以生成多个运行的 id 。
      • 组成:运行 id 由 40 位字符串组成,是一个随机的十六进制字符。如:ad4216aa881930e5f3380679d87324196cfe0866 。
      • 作用:运行 id 被用于在服务器进行传输的时候,谁别身份。如果想两次操作均对同一台服务器进行,必须每次操作携带对应的运行 id ,用于对方识别。
      • 实现方式:运行 id 在每台服务器启动的时候自动生成的,master 在首次连接 slave 的时候,会将自己的运行 id 发送给 slave ,slave 保存此 id ,通过 Info server 命令,可以查看节点的运行 id 。
    • ② 主服务器的复制积压缓冲区。
      • 概念:复制缓冲区,又名复制积压缓冲区,是一个先进先出(FIFO)的队列,用于存储服务器执行过的命令,每次传播命令,master 都会将传播的命令记录下来,并存储在复制缓冲区。

复制缓冲区.png

  1. - 复制缓冲区内部工作原理:
  2. - 组成:
  3. - 偏移量。
  4. - 字节值。
  5. - 工作原理:
  6. - 通过 offset 区别不同的 slave 当前数据传播的差异。
  7. - master 记录已发送的信息对应的 offset
  8. - slave 记录已接收的信息对应的 offset

复制缓冲区内部工作原理.png

  1. - 由来:每台服务器启动的时候,如果开启 AOF 或被连接称为 master 节点,就会创建复制缓冲区。
  2. - 作用:用于保存 master 接收到的所有指令(仅影响数据变更的指令,如:setselect 等)。
  3. - 数据来源:当 master 接收到主客户端的指令的时候,除了将指令执行,还会将指令存储到缓冲区中。
  • ③ 主从服务器的复制偏移量:
    • 概念:一个数字,描述复制缓冲区中的指令字节位置。
    • 分类:
      • master 复制偏移量:记录发送给所有 slave 的指令对应字节的位置(多个)。
      • slave 复制偏移量:记录 slave 接收 master 发送过来的指令字节对应的位置(一个)。
    • 数据来源:
      • master 端:发送一次记录一次。
      • slave 端:接收一次记录一次。
    • 作用:同步信息,比对 master 和 slave 的差异,当 slave 断线后,恢复数据使用。

1.2.8 数据同步 + 命令传播阶段工作流程

数据同步 + 命令传播阶段工作流程.png

1.2.9 心跳机制

  • 进入命令传播阶段后,master 和 slave 之间需要进行信息交换,使用心跳机制进行维护,实现双方连接保持在线。
  • master 心跳:
    • 指令:ping 。
    • 周期:由 repl-ping-replica-period 决定,默认为 10 。
    • 作用:判断 slave 是否在线。
    • 查询:info replication,获取 slave 最后一次连接时间间隔,lag 项位置在 0 或 1 视为正常。
  • slave 心跳任务:
    • 执行:replconf ack {offset} 。
    • 周期:1 秒。
    • 作用 1 :汇报 slave 自己的复制偏移量,获取最新的数据变更指令。
    • 作用 2 :判断master 是否在线。
  • 心跳阶段注意事项:

    • 当 slave 多数掉线,或延迟过高时,master 为保证数据的稳定性,将拒绝所有信息同步操作。

      1. min-slaves-to-write 2
      2. min-slaves-max-lag 8
      3. # slave 数量少于 2 个,或者所有 slave 的延迟都大于等于 8 秒的时候,强制关闭 master 的写功能,停止数据同步
    • slave 数量由 slave 发送 replconf ack 命令确认。

    • slave 延迟由 slave 发送 replconf ack 命令确认。

1.2.10 主从复制工作完整流程

主从复制工作完整流程.png

1.3 主从复制常见问题

1.3.1 频繁的全量复制(1)

  • 伴随着系统的运行,master 的数据量会越来越大,一旦发生 master 重启,run id 将发生变化,会导致全部 slave 全量复制操作。
  • 内部优化调整方案:
    • ① master 内部创建 master_replid 变量,使用 run id 相同的策略生成,长度 41 位,并发送给所有 slave 。
    • ② 在 master 关闭时执行 shutdown save 命令,进行 RDB 持久化,将 run id 和 offset 保存到 RDB 文件中。
      • repl-id 、repl-offset。
      • 通过 redis-check-rdb 命令可以查看该信息。

通过 redis-check-rdb 命令可以查看该信息

  • ③ master 重启后加载 RDB 文件,恢复数据。
    • 重启后,将 RDB 文件中保存的 repl-id 和 repl-offset 加载到内存中。
    • master_replid = repl-id 。
    • master_repl_offset = repl-offset 。
    • 通过 info 命令可以查看该信息。
    • 作用:本机保存上次的 run id,重启后恢复该值,使得所有的 slave 认为还是之前的 master 。

1.3.2 频繁的全量复制(2)

  • 问题现象:网络环境不佳,出现网络中断,slave 不提供服务。
  • 问题原因:赋值缓冲区过小,断网后 slave 的 offset 越界,触发全量复制。
  • 最终结果:slave 反复进行全量复制。
  • 解决方案:修改复制缓冲区大小(repl-backlog-size)。
  • 建议设置:
    • ① 测算从 master 到 slave 的重连平均时长 second 。
    • ② 获取 master 平均每秒产生写命令数据总量 write_size_per_second 。
    • ③ 最优复制缓冲区空间 = 2 second write_size_per_second 。

1.3.3 频繁的网络中断(1)

  • 问题现象:master 的 CPU 占用过高或 slave 频繁断开连接。
  • 问题原因:
    • ① slave 每秒发送 replconf ack 命令到 master 。
    • ② 当 slave 接收到了慢查询(keys *,hgetall 等),会大量占用 CPU 性能。
    • ③ master 每 1 秒调用复制定时函数 replicationCron(),比对 slave发现长时间没有进行响应。
  • 最终结果:master 各种资源(输出缓冲区、带宽、连接等)被严重占用。
  • 解决方案:通过设置合理的超时时间,确认是否释放 slave 。
  1. # 该参数定义了超时时间的阈值(默认为 60 秒),超过该值,释放 slave
  2. repl-timeout seconds

1.3.4 频繁的网络中断(2)

  • 问题现象:slave 和 master 连接断开。
  • 问题原因:
    • ① master 发送 ping 指令频率较低。
    • ② master 设定超时时间较短。
    • ③ ping 指令在网络中存在丢包。
  • 解决方案:提高 ping 指令发送的频率。
  1. # 超时时间 repl-time 的时间至少是 ping 指令频度的 5 ~ 10 倍,否则 slave 很容易判断超时
  2. repl-ping-slave-period seconds

1.3.4 数据不一致

  • 问题现象:多个 slave 获取相同数据不同步。
  • 问题原因:网络信息不同步,数据发送有延迟。
  • 解决方案:
    • ① 优化主从间的网络环境,通常放置在同一个机房部署,如果使用阿里云等云服务器的时候需要注意此现象。
    • ② 监控主从节点延迟(通过 offset)判断,如果 slave 延迟过大,暂时屏蔽程序对该 slave 的数据访问。
      1. # 开启后仅响应 info、slaveof 等少数命令(慎用,除非对数据一致性要求很高)
      2. slave-serve-stale-data yes|no

2 哨兵模式

2.1 哨兵简介

2.1.1 哨兵概念

  • 如果 Redis 的 master 宕机了,此时应该怎么办?

master 宕机了.png

  • 需要从一堆 slave 中重新选举出一个新的 master,那么整个操作过程是怎么样的?会出现什么问题?

从一堆 slave 中重新选举出一个新的 master.png

  • 操作过程:
    • ① 将宕机的 master 下线。
    • ② 找一个 slave 作为 master 。
    • ③ 通过所有的 slave 连接到新的 master 。
    • ④ 启动新的 master 和 slave 。
    • ⑤ 全量复制 N + 部分复制 N 。
  • 出现问题:
    • ① 谁来确认 master 宕机了。
    • ② 找一个主节点?怎么找?
    • ③ 修改配置后,原始的主节点恢复后怎么办?
  • 要实现上面的功能,就需要哨兵。

2.1.2 哨兵简介

  • 哨兵(Sentinel)是一个分布式系统,用于对主从结构中的每台服务器进行 监控 ,当出现故障的时候通过 投票机制 选择新的 master并将所有的 slave 连接到新的 master 。

哨兵简介.png

2.1.3 哨兵作用

  • ① 监控:
    • 不断的检查 master 和 slave 是否正常运行。
    • master 存活检测、master 和 slave 运行情况检测。
  • ② 通知(提醒):当被监控的服务器出现问题的时候,向其他(哨兵间、客户端)发送通知。
  • ③ 自动故障转移:断开 master 和 slave 之间的连接,选取一个 slave 作为 master ,将其它 slave 连接到新的 master 上,并告知客户端新的服务器地址。

注意:

  • 哨兵也是一台 Redis 服务器,只是不提供数据服务。
  • 通常哨兵配置数量为单数。

2.2 搭建哨兵

  • ① 配置一主二从的主从结构(略)。 | 机器 | 角色 | 操作系统 | 是否配置哨兵 | | —- | —- | —- | —- | | 192.168.65.100 | master | CentOS 7.9 | 是 | | 192.168.65.101 | slave | CentOS 7.9 | 是 | | 192.168.65.102 | slave | CentOS 7.9 | 是 |
  • ② 配置三个哨兵(三台机器复制 sentinel 文件到 /usr/local/redis):
  1. cd /opt/redis-5.0.10
  1. cp sentinel.conf /usr/local/redis
  • sentinel.conf 中重要的配置如下:

    • 设置哨兵监听的主服务信息,sentinel_number 表示参与投票的哨兵的数量(通常为哨兵数量 / 2 + 1):

      1. sentinel monitor master_name 127.0.0.1 6379 2
    • 设置判定主服务器宕机时长,该设置控制是否进行主从切换:

      1. sentinel down-after-milliseconds mymaster 30000
    • 设置故障切换的最大超时时间:

      1. sentinel failover-timeout mymaster 180000
    • 设置主从切换后,同时进行数据同步的slave数量,数值越大,要求网络资源越高,数值越小,同步时间越长:

      1. sentinel parallel-syncs mymaster 1
  • 修改三台机器上的 Sentinel 的配置:

  1. cd /usr/local/redis
  1. vim sentinel.conf
  1. port 26379
  2. daemonize yes
  3. pidfile /var/run/redis-sentinel.pid
  4. logfile "/usr/local/redis/sentinel.logs"
  5. dir /usr/local/redis/
  6. sentinel monitor mymaster 127.0.0.1 6379 2
  7. sentinel down-after-milliseconds mymaster 30000
  8. sentinel parallel-syncs mymaster 1
  9. sentinel failover-timeout mymaster 180000
  10. sentinel deny-scripts-reconfig yes
  • ③ 启动哨兵(三台机器都运行此命令,先启动主机,再启动从机,最后再启动哨兵):
  1. /usr/local/redis/bin/redis-sentinel /usr/local/redis/sentinel.conf

2.3 工作原理

2.3.1 哨兵在进行主从切换过程中经历三个阶段

  • ① 监控。
  • ② 通知。
  • ③ 故障转移。

2.3.2 监控阶段

  • 用于同步各个节点的状态信息。
    • 获取各个 sentinel 的状态(是否在线)。
    • 获取 master 的状态:
      • master 属性:run id 和 role :master。
      • 各个 slave 的详细信息。
    • 获取所有 slave 的状态(根据 master 中的 slave 信息)
      • slave 属性:
        • run id。
        • role :slave 。
        • master_host、master_port。
        • offset。
        • ……

哨兵的监控阶段.png

2.3.3 通知阶段

  • sentinel 在通知阶段要不断的去获取 master/slave 的信息,然后在各个 sentinel 之间进行共享,具体的流程如下:

哨兵的通知阶段.png

2.3.4 故障转移

  • 当 master 宕机后 sentinel 是如何知晓并判断出 master 是真的宕机了呢?我们来看具体的操作流程:

哨兵故障转移1.png

  • 当 sentinel 认定 master 下线之后,此时需要决定更换 master,那这件事由哪个 sentinel 来做呢?这时候 sentinel 之间要进行选举,如下图所示:

哨兵故障转移2.png

  • 由选举胜出的 sentinel 去从slave中选一个新的 master 出来的工作:
    • 不在线的 OUT 。
    • 响应慢的 OUT 。
    • 与原 master 断开时间久的 OUT 。
    • 优先原则:优先级、offset、run id 。
  • 选出新的master之后,发送指令( sentinel )给其他的 slave:
    • 向新的 master 发送 slaveof no one 。
    • 向其他 slave 发送 slaveof 新masterIP 端口。

哨兵故障转移3.png

2.3.6 总结

  • 监控:同步信息。
  • 通知:保持联通。
  • 故障转移:
    • 发现问题。
    • 竞选负责人。
    • 优选新 master 。
    • 新 master 上任,其他 slave 切换 master ,原来的 master 作为 slave 故障恢复后连接。

2.4 SpringBoot 整合 Redis 的 哨兵模式

2.4.1 准备工作

  • IDEA 2021+。
  • JDK 11。
  • Maven 3.8。
  • SpringBoot 2.6.1。
  • Redis 5.0.10。

  • pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.6.1</version>
  9. <relativePath/>
  10. </parent>
  11. <groupId>com.example</groupId>
  12. <artifactId>redis</artifactId>
  13. <version>1.0</version>
  14. <name>redis</name>
  15. <description>redis</description>
  16. <properties>
  17. <java.version>11</java.version>
  18. <skipTests>true</skipTests>
  19. </properties>
  20. <dependencies>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-data-redis</artifactId>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-web</artifactId>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.springframework.boot</groupId>
  31. <artifactId>spring-boot-starter-test</artifactId>
  32. <scope>test</scope>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.projectlombok</groupId>
  36. <artifactId>lombok</artifactId>
  37. <optional>true</optional>
  38. </dependency>
  39. <dependency>
  40. <groupId>cn.hutool</groupId>
  41. <artifactId>hutool-all</artifactId>
  42. <version>5.7.17</version>
  43. </dependency>
  44. <dependency>
  45. <groupId>org.springframework.boot</groupId>
  46. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  47. </dependency>
  48. </dependencies>
  49. <build>
  50. <plugins>
  51. <plugin>
  52. <groupId>org.springframework.boot</groupId>
  53. <artifactId>spring-boot-maven-plugin</artifactId>
  54. <version>2.6.1</version>
  55. <configuration>
  56. <excludes>
  57. <exclude>
  58. <groupId>org.projectlombok</groupId>
  59. <artifactId>lombok</artifactId>
  60. </exclude>
  61. </excludes>
  62. </configuration>
  63. </plugin>
  64. <plugin>
  65. <groupId>org.apache.maven.plugins</groupId>
  66. <artifactId>maven-compiler-plugin</artifactId>
  67. <version>3.8.1</version>
  68. </plugin>
  69. </plugins>
  70. </build>
  71. <repositories>
  72. <repository>
  73. <id>aliyunmaven</id>
  74. <url>https://maven.aliyun.com/repository/public</url>
  75. <releases>
  76. <enabled>true</enabled>
  77. </releases>
  78. <snapshots>
  79. <enabled>true</enabled>
  80. </snapshots>
  81. </repository>
  82. <repository>
  83. <id>spring</id>
  84. <url>https://maven.aliyun.com/repository/spring</url>
  85. <releases>
  86. <enabled>true</enabled>
  87. </releases>
  88. <snapshots>
  89. <enabled>true</enabled>
  90. </snapshots>
  91. </repository>
  92. <repository>
  93. <id>spring-releases</id>
  94. <url>https://repo.spring.io/libs-release</url>
  95. <releases>
  96. <enabled>true</enabled>
  97. </releases>
  98. <snapshots>
  99. <enabled>true</enabled>
  100. </snapshots>
  101. </repository>
  102. </repositories>
  103. <pluginRepositories>
  104. <pluginRepository>
  105. <id>spring-releases</id>
  106. <url>https://repo.spring.io/libs-release</url>
  107. </pluginRepository>
  108. </pluginRepositories>
  109. </project>

2.4.2 配置文件

  • application.yml
  1. server:
  2. port: 18082
  3. spring:
  4. redis: # redis
  5. database: 0 # 数据库索引,默认为 0
  6. timeout: 1800000 # 连接超时时间(毫秒),默认为 0 ,表示永不超时
  7. lettuce:
  8. pool:
  9. max-active: 20 # 连接池的最大连接数(负数表示没有限制)
  10. max-wait: -1 # 连接池的最大阻塞等待时间(负数表示没有限制)
  11. max-idle: 5 # 连接池的最大空闲连接
  12. min-idle: 0 # 连接池的最小空闲连接
  13. client-type: lettuce
  14. # sentinel 配置
  15. sentinel:
  16. master: mymaster
  17. nodes:
  18. - 192.168.65.100:26379
  19. - 192.168.65.101:26379
  20. - 192.168.65.102:26379

2.4.3 配置类

  1. package com.github.fairy.era.redis.config;
  2. import com.fasterxml.jackson.annotation.JsonAutoDetect;
  3. import com.fasterxml.jackson.annotation.JsonTypeInfo;
  4. import com.fasterxml.jackson.annotation.PropertyAccessor;
  5. import com.fasterxml.jackson.databind.ObjectMapper;
  6. import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
  7. import org.springframework.cache.CacheManager;
  8. import org.springframework.cache.annotation.CachingConfigurationSelector;
  9. import org.springframework.cache.annotation.EnableCaching;
  10. import org.springframework.context.annotation.Bean;
  11. import org.springframework.context.annotation.Configuration;
  12. import org.springframework.data.redis.cache.RedisCacheConfiguration;
  13. import org.springframework.data.redis.cache.RedisCacheManager;
  14. import org.springframework.data.redis.connection.RedisConnectionFactory;
  15. import org.springframework.data.redis.core.RedisTemplate;
  16. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  17. import org.springframework.data.redis.serializer.RedisSerializationContext;
  18. import org.springframework.data.redis.serializer.RedisSerializer;
  19. import org.springframework.data.redis.serializer.StringRedisSerializer;
  20. import java.time.Duration;
  21. /**
  22. * @author 许大仙
  23. * @version 1.0
  24. * @since 2021-12-13 11:02
  25. */
  26. @Configuration
  27. @EnableCaching
  28. public class RedisConfig extends CachingConfigurationSelector {
  29. @Bean
  30. public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
  31. RedisTemplate<String, Object> template = new RedisTemplate<>();
  32. RedisSerializer<String> stringSerializer = new StringRedisSerializer();
  33. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  34. ObjectMapper om = new ObjectMapper();
  35. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  36. om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
  37. jackson2JsonRedisSerializer.setObjectMapper(om);
  38. template.setConnectionFactory(factory);
  39. // key序列化方式
  40. template.setKeySerializer(stringSerializer);
  41. template.setHashKeySerializer(stringSerializer);
  42. // value序列化
  43. template.setValueSerializer(jackson2JsonRedisSerializer);
  44. // value hashmap序列化
  45. template.setHashValueSerializer(jackson2JsonRedisSerializer);
  46. template.afterPropertiesSet();
  47. return template;
  48. }
  49. @Bean
  50. public CacheManager cacheManager(RedisConnectionFactory factory) {
  51. RedisSerializer<String> redisSerializer = new StringRedisSerializer();
  52. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  53. // 解决查询缓存转换异常的问题
  54. ObjectMapper om = new ObjectMapper();
  55. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  56. om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
  57. jackson2JsonRedisSerializer.setObjectMapper(om);
  58. // 配置序列化(解决乱码的问题),过期时间600秒
  59. RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();
  60. RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();
  61. return cacheManager;
  62. }
  63. }

2.4.4 启动类

  1. package com.github.fairy.era.redis;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. /**
  5. * @author 许大仙
  6. */
  7. @SpringBootApplication
  8. public class RedisApplication {
  9. public static void main(String[] args) {
  10. SpringApplication.run(RedisApplication.class, args);
  11. }
  12. }

2.4.5 测试类

  1. package com.github.fairy.era.redis;
  2. import lombok.RequiredArgsConstructor;
  3. import org.junit.jupiter.api.Test;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.boot.test.context.SpringBootTest;
  6. import org.springframework.data.redis.core.RedisTemplate;
  7. /**
  8. * @author 许大仙
  9. * @version 1.0
  10. * @since 2021-12-14 10:39
  11. */
  12. @SpringBootTest(classes = RedisApplication.class)
  13. @RequiredArgsConstructor
  14. public class RedisTest {
  15. @Autowired
  16. private RedisTemplate<String, String> redisTemplate;
  17. @Test
  18. public void test() {
  19. redisTemplate.opsForValue().set("name", "张三");
  20. String name = redisTemplate.opsForValue().get("name");
  21. System.out.println("name = " + name);
  22. }
  23. }

3 集群

3.1 问题引入

  • 现状问题:业务发展过程中遇到的峰值瓶颈
    • Redis 提供的服务 QPS 可以到达 10 万/秒,当前业务 QPS 已经达到 20 万/秒。
    • 内存单机容量到达 256 G,当前业务需求内容容量 1 T。
  • 使用集群的方式可以快速解决上述问题。

3.2 集群简介

  • 集群就是使用网络将若干台计算机联通起来,并提供统一的管理方式,使其对外呈现单机的服务效果。

集群架构.png

  • 集群作用:
    • ① 分散单台服务器的访问压力,实现负载均衡。
    • ② 分散单台服务器的存储压力,实现可扩展性。
    • ③ 降低单台服务器宕机带来的业务灾难。

集群作用.png

3.3 Redis 集群结构设计

3.3.1 数据存储设计

  • ① 通过算法设计,计算出 key 应该保存的位置。
  • ② 将所有的存储空间切割成 16384 份,每台主机保存一部分。

注意:每份代表的是一个存储空间,不是一个 key 的保存空间。

  • ③ 将 key 按照计算出的结果放到对应的存储空间。

Redis 集群结构设计之数据存储设计.png

  • 那么 Redis 集群是如何增强可扩展性的呢?比如,我们需要增加一个集群节点:

Redis 集群结构设计之数据存储设计2.png

  • 其实,增加一个节点,就是从原来的节点中根据算法将一些存储空间(上面说的份,如:37 对应的存储空间,也称为槽)移入到新的节点;换言之,如果减去一个节点,就将这个节点的槽移入到剩下的节点中。

注意:Redis 集群采用的是无中心化结构。

3.3.2 集群内部的通信设计

  • 当我们需要查找数据的时候,集群是如何操作的?
  • ① 各个数据库相互通信,保存各个库中的槽的编号数据。
  • ② 一次命中,直接返回。
  • ② 一次没有命中,告知具体位置。

Redis 集群结构设计之集群内部通信设计.png

3.4 cluster 集群结构搭建

3.4.1 准备工作

机器 操作系统
192.168.65.100 CentOS 7.9
192.168.65.101 CentOS 7.9
192.168.65.102 CentOS 7.9
192.168.65.103 CentOS 7.9
192.168.65.104 CentOS 7.9
192.168.65.105 CentOS 7.9

3.4.2 关闭防火墙、下载和解压

  • 关闭 6 台机器的防火墙,确保所有机器能互相联通:
  1. systemctl stop firewalld
  1. systemctl disable firewalld
  • 6 台机器都需要下载 Redis 的压缩包,并解压:
  1. cd /opt
  1. wget https://download.redis.io/releases/redis-5.0.10.tar.gz
  1. tar -zxvf redis-5.0.10.tar.gz

3.4.3 编译安装

  • 6 台机器都需要进入 Redis 的解压目录:
  1. cd redis-5.0.10/
  • 6 台机器都需要安装 c 语言编译环境:
  1. yum -y install gcc-c++
  • 6 台机器修改 Redis 安装的目录路径:
  1. vim src/Makefile
  1. # 就 Redis 自身而言是不需要修改的,这里修改的目的是让 Redis 的运行程序不要和其他文件混杂在一起。
  2. PREFIX?=/usr/local/redis
  • 6 台机器编译安装 Redis:
  1. make && make install
  • 6 台机器配置环境变量:
  1. vim /etc/profile
  1. # ======================== 添加如下内容 ========================
  2. # REDIS HOME
  3. export REDIS_HOME=/usr/local/redis
  4. export PATH=$REDIS_HOME/bin:$PATH
  1. # 执行生效
  2. source /etc/profile

3.4.4 拷贝配置文件

  • 6 台机器复制 redis.conf 文件到 /usr/local/redis :
  1. cp redis.conf /usr/local/redis/

3.4.5 修改配置文件

  • 6 台机器修改配 redis.conf 配置文件:
  1. vim /usr/local/redis/redis.conf
  1. ## =========================== 修改内容说明如下 ===========================
  2. # 需要注释掉 bind
  3. # bind 0.0.0.0
  4. # 将保护模式关闭
  5. protected-mode no
  6. # 需要以守护进程方式启动
  7. daemonize yes
  8. # 设置 redis 服务日志存储路径
  9. logfile "/usr/local/redis/redis.logs"
  10. # 需要配置 Redis 持久化文件的目录
  11. dir /usr/local/redis
  12. # 将 AOF 功能打开
  13. appendonly yes
  14. # 启动Redis Cluster
  15. cluster-enabled yes
  16. # Redis服务配置保存文件名称
  17. cluster-config-file nodes-6379.conf
  18. # 超时时间
  19. cluster-node-timeout 15000
  20. # 其他配置保存默认

3.4.6 启动 Redis 服务

  • 6 台机器启动 Redis 服务:
  1. redis-server /usr/local/redis/redis.conf

3.4.7 启动集群

  • 任选一台机器执行如下命令,创建集群:
  1. redis-cli --cluster create --cluster-replicas 1 192.168.65.100:6379 192.168.65.101:6379 192.168.65.102:6379 192.168.65.103:6379 192.168.65.104:6379 192.168.65.105:6379

注意:--replicas 1 采用最简单的方式配置集群,一台主机,一台从机,正好三组。

启动集群.gif

  • 信息如下:
  1. >>> Performing hash slots allocation on 6 nodes...
  2. ## ====== 进行Slot槽范文划分 ======
  3. Master[0] -> Slots 0 - 5460
  4. Master[1] -> Slots 5461 - 10922
  5. Master[2] -> Slots 10923 - 16383
  6. ## ====== 主从分配,一主一从 ======
  7. Adding replica 192.168.65.104:6379 to 192.168.65.100:6379
  8. Adding replica 192.168.65.105:6379 to 192.168.65.101:6379
  9. Adding replica 192.168.65.103:6379 to 192.168.65.102:6379
  10. ## ====== 主从节点配对信息 ======
  11. M: 7657b7cf2114831a754f006a671b84dfe107db94 192.168.65.100:6379
  12. slots:[0-5460] (5461 slots) master
  13. M: 62a625d68939fc0afc8194c97c1c32e6c4adb5b2 192.168.65.101:6379
  14. slots:[5461-10922] (5462 slots) master
  15. M: 2ff8d87ea62a27bbf18b6ed1a60cf28bc274479c 192.168.65.102:6379
  16. slots:[10923-16383] (5461 slots) master
  17. S: f33737fbb89771e059eb5d73794e6b9b7201b300 192.168.65.103:6379
  18. replicates 2ff8d87ea62a27bbf18b6ed1a60cf28bc274479c
  19. S: d557830e11ffe2cce1fd28f8217fd340d6fb0ae1 192.168.65.104:6379
  20. replicates 7657b7cf2114831a754f006a671b84dfe107db94
  21. S: c88e94982d6ef1273f80baec5e7271c77fc7664e 192.168.65.105:6379
  22. replicates 62a625d68939fc0afc8194c97c1c32e6c4adb5b2
  23. ## ====== 是否同意上述划分,一般都是yes ======
  24. Can I set the above configuration? (type 'yes' to accept): yes
  25. ## ====== 节点配置更新,进入集群,主从节点,高可用 ======
  26. >>> Nodes configuration updated
  27. >>> Assign a different config epoch to each node
  28. >>> Sending CLUSTER MEET messages to join the cluster
  29. Waiting for the cluster to join
  30. .
  31. >>> Performing Cluster Check (using node 192.168.65.100:6379)
  32. M: 7657b7cf2114831a754f006a671b84dfe107db94 192.168.65.100:6379
  33. slots:[0-5460] (5461 slots) master
  34. 1 additional replica(s)
  35. M: 2ff8d87ea62a27bbf18b6ed1a60cf28bc274479c 192.168.65.102:6379
  36. slots:[10923-16383] (5461 slots) master
  37. 1 additional replica(s)
  38. S: f33737fbb89771e059eb5d73794e6b9b7201b300 192.168.65.103:6379
  39. slots: (0 slots) slave
  40. replicates 2ff8d87ea62a27bbf18b6ed1a60cf28bc274479c
  41. M: 62a625d68939fc0afc8194c97c1c32e6c4adb5b2 192.168.65.101:6379
  42. slots:[5461-10922] (5462 slots) master
  43. 1 additional replica(s)
  44. S: c88e94982d6ef1273f80baec5e7271c77fc7664e 192.168.65.105:6379
  45. slots: (0 slots) slave
  46. replicates 62a625d68939fc0afc8194c97c1c32e6c4adb5b2
  47. S: d557830e11ffe2cce1fd28f8217fd340d6fb0ae1 192.168.65.104:6379
  48. slots: (0 slots) slave
  49. replicates 7657b7cf2114831a754f006a671b84dfe107db94
  50. [OK] All nodes agree about slots configuration.
  51. >>> Check for open slots...
  52. >>> Check slots coverage...
  53. [OK] All 16384 slots covered.

3.4.8 测试集群

  • 任选一台机器,使用 redis-cli 客户端命令连接 Redis 服务:
  1. redis-cli -c -p 6379
  • 查看集群信息:
  1. cluster nodes

查看集群信息.png

  • 查看主从信息:
  1. info replication

查看集群主从信息.png

3.4.9 在集群中插入值

  • 在 redis-cli 每次录入、查询键值,redis 都会计算出该 key 应该送往的插槽,如果不是该客户端对应服务器的插槽,redis 会报错,并告知应前往的 redis 实例地址和端口。
  • redis-cli 客户端提供了 –c 参数实现自动重定向。
  • 集群中,不能使用 mset、mget 等多键命令:
  1. mset k1 v1 k2 v2

集群中,不能使用 mset、mget 等多键命令.png

  • 可以通过 {} 来定义组的概念,从而使 key 中 {} 内相同内容的键值对放到一个 slot 中去:
  1. mset k1{user} v1 k2{user} v2


可以通过 {} 来定义组的概念,从而使 key 中 {} 内相同内容的键值对放到一个 slot 中去.png

3.4.10 故障恢复

  • 如果主节点下线,那么从节点可以自动升为主节点,但是如果超过 15 秒就会失败。
  • 主节点宕机然后又恢复后,会自动变为从节点。
  • 如果所有某一段插槽的主从节点都宕掉,如果配置文件 redis.conf 中的 cluster-require-full-coverage 为 yes ,整个集群都会挂掉;如果配置文件 redis.conf 中的 cluster-require-full-coverage 为 no,那么该插槽数据全都不能使用,也无法存储。

3.4.11 Redis 集群管理

  • redis-cli 集群帮助命令:
  1. redis-cli --cluster help

redis - cli 集群帮助命令.gif

  • 在实际开发中,可能由于 Redis 集群中节点宕机或增加新节点,需要操作命令管理,主要操作如下:
  • 添加新主节点:
  1. # new_host:new_port 要向集群中添加新的主节点
  2. # existing_host:existing_port 原集群中任意节点
  3. redis-cli --cluster add-node new_host:new_port existing_host:existing_port
  • 添加新从节点:
  1. # new_host:new_port 要向集群中添加新的主节点
  2. # existing_host:existing_port 原集群中任意节点
  3. # node_id 要添加到哪一个主节点,id是****
  4. redis-cli --cluster add-node new_host:new_port existing_host:existing_port --cluster-slave --cluster-master-id node_id
  • 删除节点:
  1. redis-cli --cluster del-node host:port node_id
  • hash 槽重新分配:添加新节点后,需要对新添加的主节点进行 hash 槽重新分配,此时主节点才能存储数据,Redis 一共有 16384 个槽:
  1. redis-cli --cluster reshard host:port --cluster-from node_id --cluster-to node_id --cluster-slots <arg> --cluster-yes

3.5 Redis 集群的优缺点

  • 优点:
    • 实现扩容。
    • 分摊压力。
    • 无中心配置相对简单。
  • 缺点:
    • 多键操作是不被支持的。
    • 多键的 Redis 事务是不被支持的。lua 脚本不被支持。
    • 由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至 redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。

3.6 SpringBoot 整合 Redis 集群

3.6.1 准备工作

  • IDEA 2021+。
  • JDK 11。
  • Maven 3.8。
  • SpringBoot 2.6.1。
  • Redis 5.0.10。

  • pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.6.1</version>
  9. <relativePath/>
  10. </parent>
  11. <groupId>com.example</groupId>
  12. <artifactId>redis</artifactId>
  13. <version>1.0</version>
  14. <name>redis</name>
  15. <description>redis</description>
  16. <properties>
  17. <java.version>11</java.version>
  18. <skipTests>true</skipTests>
  19. </properties>
  20. <dependencies>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-data-redis</artifactId>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-web</artifactId>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.springframework.boot</groupId>
  31. <artifactId>spring-boot-starter-test</artifactId>
  32. <scope>test</scope>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.projectlombok</groupId>
  36. <artifactId>lombok</artifactId>
  37. <optional>true</optional>
  38. </dependency>
  39. <dependency>
  40. <groupId>cn.hutool</groupId>
  41. <artifactId>hutool-all</artifactId>
  42. <version>5.7.17</version>
  43. </dependency>
  44. <dependency>
  45. <groupId>org.springframework.boot</groupId>
  46. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  47. </dependency>
  48. </dependencies>
  49. <build>
  50. <plugins>
  51. <plugin>
  52. <groupId>org.springframework.boot</groupId>
  53. <artifactId>spring-boot-maven-plugin</artifactId>
  54. <version>2.6.1</version>
  55. <configuration>
  56. <excludes>
  57. <exclude>
  58. <groupId>org.projectlombok</groupId>
  59. <artifactId>lombok</artifactId>
  60. </exclude>
  61. </excludes>
  62. </configuration>
  63. </plugin>
  64. <plugin>
  65. <groupId>org.apache.maven.plugins</groupId>
  66. <artifactId>maven-compiler-plugin</artifactId>
  67. <version>3.8.1</version>
  68. </plugin>
  69. </plugins>
  70. </build>
  71. <repositories>
  72. <repository>
  73. <id>aliyunmaven</id>
  74. <url>https://maven.aliyun.com/repository/public</url>
  75. <releases>
  76. <enabled>true</enabled>
  77. </releases>
  78. <snapshots>
  79. <enabled>true</enabled>
  80. </snapshots>
  81. </repository>
  82. <repository>
  83. <id>spring</id>
  84. <url>https://maven.aliyun.com/repository/spring</url>
  85. <releases>
  86. <enabled>true</enabled>
  87. </releases>
  88. <snapshots>
  89. <enabled>true</enabled>
  90. </snapshots>
  91. </repository>
  92. <repository>
  93. <id>spring-releases</id>
  94. <url>https://repo.spring.io/libs-release</url>
  95. <releases>
  96. <enabled>true</enabled>
  97. </releases>
  98. <snapshots>
  99. <enabled>true</enabled>
  100. </snapshots>
  101. </repository>
  102. </repositories>
  103. <pluginRepositories>
  104. <pluginRepository>
  105. <id>spring-releases</id>
  106. <url>https://repo.spring.io/libs-release</url>
  107. </pluginRepository>
  108. </pluginRepositories>
  109. </project>

3.6.2 配置文件

  • application.yml
  1. server:
  2. port: 18082
  3. spring:
  4. redis: # redis
  5. database: 0 # 数据库索引,默认为 0
  6. timeout: 1800000 # 连接超时时间(毫秒),默认为 0 ,表示永不超时
  7. lettuce:
  8. pool:
  9. max-active: 20 # 连接池的最大连接数(负数表示没有限制)
  10. max-wait: -1 # 连接池的最大阻塞等待时间(负数表示没有限制)
  11. max-idle: 5 # 连接池的最大空闲连接
  12. min-idle: 0 # 连接池的最小空闲连接
  13. client-type: lettuce
  14. cluster:
  15. max-redirects: 3 # 获取失败 最大重定向次数
  16. nodes:
  17. - 192.168.65.100:6379
  18. - 192.168.65.101:6379
  19. - 192.168.65.102:6379
  20. - 192.168.65.103:6379
  21. - 192.168.65.104:6379
  22. - 192.168.65.105:6379

3.6.3 配置类

  1. package com.github.fairy.era.redis.config;
  2. import com.fasterxml.jackson.annotation.JsonAutoDetect;
  3. import com.fasterxml.jackson.annotation.JsonTypeInfo;
  4. import com.fasterxml.jackson.annotation.PropertyAccessor;
  5. import com.fasterxml.jackson.databind.ObjectMapper;
  6. import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
  7. import org.springframework.cache.CacheManager;
  8. import org.springframework.cache.annotation.CachingConfigurationSelector;
  9. import org.springframework.cache.annotation.EnableCaching;
  10. import org.springframework.context.annotation.Bean;
  11. import org.springframework.context.annotation.Configuration;
  12. import org.springframework.data.redis.cache.RedisCacheConfiguration;
  13. import org.springframework.data.redis.cache.RedisCacheManager;
  14. import org.springframework.data.redis.connection.RedisConnectionFactory;
  15. import org.springframework.data.redis.core.RedisTemplate;
  16. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  17. import org.springframework.data.redis.serializer.RedisSerializationContext;
  18. import org.springframework.data.redis.serializer.RedisSerializer;
  19. import org.springframework.data.redis.serializer.StringRedisSerializer;
  20. import java.time.Duration;
  21. /**
  22. * @author 许大仙
  23. * @version 1.0
  24. * @since 2021-12-13 11:02
  25. */
  26. @Configuration
  27. @EnableCaching
  28. public class RedisConfig extends CachingConfigurationSelector {
  29. @Bean
  30. public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
  31. RedisTemplate<String, Object> template = new RedisTemplate<>();
  32. RedisSerializer<String> stringSerializer = new StringRedisSerializer();
  33. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  34. ObjectMapper om = new ObjectMapper();
  35. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  36. om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
  37. jackson2JsonRedisSerializer.setObjectMapper(om);
  38. template.setConnectionFactory(factory);
  39. // key序列化方式
  40. template.setKeySerializer(stringSerializer);
  41. template.setHashKeySerializer(stringSerializer);
  42. // value序列化
  43. template.setValueSerializer(jackson2JsonRedisSerializer);
  44. // value hashmap序列化
  45. template.setHashValueSerializer(jackson2JsonRedisSerializer);
  46. template.afterPropertiesSet();
  47. return template;
  48. }
  49. @Bean
  50. public CacheManager cacheManager(RedisConnectionFactory factory) {
  51. RedisSerializer<String> redisSerializer = new StringRedisSerializer();
  52. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  53. // 解决查询缓存转换异常的问题
  54. ObjectMapper om = new ObjectMapper();
  55. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  56. om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
  57. jackson2JsonRedisSerializer.setObjectMapper(om);
  58. // 配置序列化(解决乱码的问题),过期时间600秒
  59. RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();
  60. RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();
  61. return cacheManager;
  62. }
  63. }

3.6.4 启动类

  1. package com.github.fairy.era.redis;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. /**
  5. * @author xuwei
  6. */
  7. @SpringBootApplication
  8. public class RedisApplication {
  9. public static void main(String[] args) {
  10. SpringApplication.run(RedisApplication.class, args);
  11. }
  12. }

3.6.5 测试类

  1. package com.github.fairy.era.redis;
  2. import lombok.RequiredArgsConstructor;
  3. import org.junit.jupiter.api.Test;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.boot.test.context.SpringBootTest;
  6. import org.springframework.data.redis.core.RedisTemplate;
  7. /**
  8. * @author 许大仙
  9. * @version 1.0
  10. * @since 2021-12-14 10:39
  11. */
  12. @SpringBootTest(classes = RedisApplication.class)
  13. @RequiredArgsConstructor
  14. public class RedisTest {
  15. @Autowired
  16. private RedisTemplate<String, String> redisTemplate;
  17. @Test
  18. public void test() {
  19. redisTemplate.opsForValue().set("name", "张三");
  20. String name = redisTemplate.opsForValue().get("name");
  21. System.out.println("name = " + name);
  22. }
  23. }