Redis 高级
Redis 部署模式
Redis 有三种部署模式,分别为主从复制、哨兵模式(Sentinel)、集群模式(Cluster),它们的区别如下:
| 模式 | 写能力 | 适用场景 | 工作方式 |
|---|---|---|---|
| 主从复制 | 仅单节点 | 简单备份 | 主节点负责写,从节点复制主节点数据并负责读;主节点故障,服务中断 |
| 哨兵 | 仅单节点 | 保证高可用,数据量不大的中小项目 | 高可用,主节点故障,哨兵们自动投票,选举新的主节点;但写操作仍然在单个主节点中 |
| 集群 | 分布式 | 海量数据、高并发读写的大型项目 | 多主多从,数据被分片到多个主节点 |
主从复制
Redis 的主从复制是指将一台 Redis 服务器的数据复制到其他的 Redis 服务器,数据的复制只能由主节点到从节点。可以使用 slaveof(5.0 之前) 或 replicaof(5.0+) 开启主从复制功能。
# 使127.0.0.1:6379成为127.0.0.1:7000的从服务器
127.0.0.1:6379> slaveof 127.0.0.1:7000Redis 主服务器和从服务器是通过 TCP 长连接交互数据的。
通过主从复制可实现读写分离(主节点只处理写请求,从节点只处理读请求)、数据容灾(主节点宕机,用户可以立即将请求切换到从服务器)。
主从复制示例
docker-compose.yml
version: "3.8" services: master: image: redis:7.2 restart: always container_name: redis-master ports: - 6379:6379 command: redis-server --port 6379 --requirepass "day-day-hard-work" slave1: image: redis:7.2 restart: always container_name: redis-slave-1 ports: - 6380:6379 command: redis-server --replicaof redis-master 6379 --masterauth "day-day-hard-work" slave2: image: redis:7.2 restart: always container_name: redis-slave-2 ports: - 6381:6379 command: redis-server --replicaof redis-master 6379 --masterauth "day-day-hard-work"
Sentinel
简介
Redis 的主从复制架构存在一些缺点,当 Master 节点出现故障时,无法继续外提供服务;Redis Sentinel是 Redis 官方推荐的一种高可用解决方案。
借助 Redis Sentinel,当 master 节点失效时,使用投票协议选举出一个 slave 节点作为新的 master(sentinel 节点个数应该大于等于 3 且为奇数)。
默认情况下,Sentinel 占用 TCP 端口 26379 的连接,服务器的端口 26379 必须打开。
Redis Sentinel 可以监测多个 Redis 主节点,但为了不增加系统的复杂性,一般指定监听一个主节点即可。Redis Sentinel 可以通过 Redis 主节点自动发现 Redis 从节点,因此不需要指定。
安装
为了使 Redis Sentinel 也能高可用,至少需要创建三个 Sentinel 服务,Sentinel 服务之间也可以通过 Redis 主节点自动发现,因此无需额外配置与其他节点建立通信。在启动 Sentinel 服务之前,确保 Redis 主从复杂 已启动,docker-compose.yml 配置如下:
version: "3.8" services: sentinel1: image: redis:7.2 restart: unless-stopped container_name: redis-sentinel-1 ports: - 26379:26379 command: redis-sentinel /usr/local/etc/redis/sentinel.conf volumes: - ./sentinel1.conf:/usr/local/etc/redis/sentinel.conf sentinel2: image: redis:7.2 restart: unless-stopped container_name: redis-sentinel-2 ports: - 26380:26379 command: redis-sentinel /usr/local/etc/redis/sentinel.conf volumes: - ./sentinel2.conf:/usr/local/etc/redis/sentinel.conf sentinel3: image: redis:7.2 restart: unless-stopped container_name: redis-sentinel-3 ports: - 26381:26379 command: redis-sentinel /usr/local/etc/redis/sentinel.conf volumes: - ./sentinel3.conf:/usr/local/etc/redis/sentinel.confsentinel.conf 配置文件如下:
配置内容完全一样,复制三份重命名一下即可
port 26379 sentinel monitor mymaster 192.168.1.100 6379 2 sentinel down-after-milliseconds mymaster 30000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 180000 sentinel deny-scripts-reconfig yes sentinel auth-pass mymaster "day-day-hard-work" dir /tmp提示
自定义集群名 mymaster,后接 redis-master 的 ip + 端口,2 为最小投票数(3 台 Sentinel 可以设置为 2,注意避免脑裂问题);注意 ip 不要写 localhost 或 127.0.0.1,可能导致 Sentinel 节点自动发现失效。
sentinel monitor mymaster 192.168.1.100 6379 2
down-after-milliseconds:Redis 节点多少毫秒内不可访问就认为它已关闭
parallel-syncs:发生主备切换时,最多有多少个从节点同时对新的主节点进行同步
failover-timeout:故障转移中 Sentinel 等待的最大时间(毫秒)
deny-scripts-reconfig:禁止 Sentinel 通过脚本进行配置更改,以提高安全风险。
auth-pass:Redis 主节点密码
dir /tmp:Sentinel 持久化目录,重启时能够恢复其状态查看集群是否生效
进入任意 Sentinel 容器,使用 Sentinel API 查看监控情况:
docker exec -it redis-sentinel-1 bash redis-cli -p 26379 sentinel master mymaster sentinel slaves mymaster # 输出中有如下内容则成功 31) "num-slaves" 32) "2" 33) "num-other-sentinels" 34) "2"整合项目
application.yml
# Spring boot application spring: redis: lettuce: pool: max-active: 8 max-idle: 8 max-wait: -1ms min-idle: 0 sentinel: master: mymaster nodes: 192.168.1.131:26379, 192.168.1.131:26380, 192.168.1.131:26381
集群
有了主从复制和哨兵机制,单实例的 Redis 高可用性就有保障了。然而,一个实例的性能和容量毕竟有限,Redis Cluster 是 Redis 引入的分布式存储方案。
集群所面临的问题
多个主节点,数据该如何分布?
一个可能想到的方式就是,计算出 key 对应的 Hash 值,再对节点数量取余,从而决定数据存储位置,然而,该方案一旦节点数发生变化,所有映射关系需要重新计算,数据也需要大规模迁移。
在 Redis Cluster 中,使用哈希槽(Hash Slot)来管理数据的分布,Redis Cluster 中有 16384(2^14)个哈希槽,每个主节点负责一部分哈希槽,通过计算 key 的 CRC16 哈希值,对 16384 取模确定数据的槽位。当增加或移除主节点时,Redis Cluster 会自动重新分配哈希槽。
节点间如何通信?
每个节点周期性地与其他随机选择的节点交换信息,特定的消息类型主要包括:PING(检测目标节点是否在线),PONG(对 PING 消息的回复),MEET(新节点加入),FAIL(节点挂了)。
这么多节点,客户端如何访问?
客户端至少要知道集群中的一个节点地址,连接到该节点,客户端可以获取整个集群的拓扑信息。每个节点都维护一个包含 16384 个槽的映射表,表示哪些槽由哪些节点负责。客户端根据键的哈希值计算其槽,并根据映射表将请求直接发送到负责该槽的节点。许多 Redis 客户端(Jedis、Lettuce 等)已经内置了对 Redis Cluster 的支持,能够自动处理集群拓扑的发现、请求路由、重定向和槽映射表更新,因此无需关心客户端具体实现,大概了解一下即可。
某个节点挂了,该节点的数据如何处理?
在主节点故障时,从节点可以接管读写请求,保证系统的可用性。客户端收到相关响应,会更新本地的槽映射表,并将请求发送到新的主节点。
搭建三主三从示例
下面演示在一台机器上使用 docker compose 搭建三主三从。
提示
注意使用 Redis 集群,网关层负载均衡可配置为 iphash,以免
创建配置文件
复制如下命令并运行,可快速创建 redis 集群配置文件:
for port in $(seq 1 6); \ do \ mkdir -p /usr/local/docker/redis-cluster/redis-${port}/conf touch /usr/local/docker/redis-cluster/redis-${port}/conf/redis.conf cat << EOF >> /usr/local/docker/redis-cluster/redis-${port}/conf/redis.conf port 6379 bind 0.0.0.0 # 当前节点密码 requirepass day-day-hard-work # 主节点密码 masterauth day-day-hard-work # 保护模式,默认是yes,开启保护模式 protected-mode no # 是否守护模式,docker方式不需要 daemonize no # 是否开启AOF持久化模式 appendonly yes # 是否开启集群模式 cluster-enabled yes # 集群节点信息文件 cluster-config-file nodes.conf # 集群节点连接超时时间 cluster-node-timeout 15000 # 当前节点IP cluster-announce-ip 192.168.0.1${port} # 节点端口 cluster-announce-port 6379 # 集群节点总线端口 cluster-announce-bus-port 16379 EOF doneredis-cluster 目录参考如下:
redis-cluster/ ├── docker-compose.yml ├── redis-1 │ └── conf │ └── redis.conf ├── redis-2 │ └── conf │ └── redis.conf ... └── redis-6 └── conf └── redis.confdocker-compose.yml
每个 Redis 容器配置非常相似,为了文档美观,以下只写了部分,可
复制完整内容。Redis 的 16379 端口用于集群之间的节点通信。
services: redis-1: image: redis:7.2 container_name: redis-1 restart: unless-stopped ports: - 6371:6379 - 16371:16379 volumes: - /usr/local/docker/redis-cluster/redis-1/conf/redis.conf:/etc/redis/redis.conf - /usr/local/docker/redis-cluster/redis-1/data:/data command: redis-server /etc/redis/redis.conf networks: my_redis_cluster_net: ipv4_address: 192.168.0.11 ... redis-6: image: redis:7.2 container_name: redis-6 restart: unless-stopped ports: - 6376:6379 - 16376:16379 volumes: - /usr/local/docker/redis-cluster/redis-6/conf/redis.conf:/etc/redis/redis.conf - /usr/local/docker/redis-cluster/redis-6/data:/data command: redis-server /etc/redis/redis.conf networks: my_redis_cluster_net: ipv4_address: 192.168.0.16 networks: my_redis_cluster_net: driver: bridge ipam: config: - subnet: 192.168.0.0/24创建集群并测试是否生效
使用 docker compose 运行,此时每台 Redis 之间还没有建立集群关系,可运行如下指令:
# 随意进入一个Redis容器内 # --cluster-replicas 1 其中的1表示一主一从 docker exec -it redis-1 /bin/bash redis-cli -a day-day-hard-work --cluster create \ 192.168.0.11:6379 192.168.0.12:6379 \ 192.168.0.13:6379 192.168.0.14:6379 \ 192.168.0.15:6379 192.168.0.16:6379 \ --cluster-replicas 1查看集群:
# 随意连接一台Redis,查看集群信息或节点 redis-cli -a day-day-hard-work --cluster # 查看集群信息 cluster info cluster nodes
SpringBoot 整合 Redis Cluster
<!--redis相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>spring:
redis:
cluster:
# 集群节点
nodes: 192.168.0.110:6371,192.168.0.110:6372,192.168.0.110:6373
# 最大重定向次数
max-redirects: 5
# 密码
password: day-day-hard-work
lettuce:
pool:
min-idle: 0
max-active: 8
max-wait: -1
max-idle: 8
enabled: true持久化
还没来得及研究
过期策略 & 内存淘汰机制
因为 Redis 是单线程的,对于有过期时间的 key 来说,过期删除这个操作也会占用线程处理时间。
过期策略
Redis 的过期策略分别有定时扫描和惰性删除,定时扫描会根据贪心策略删除部分 key,惰性删除会在客户端访问时检查 key 是否过期,过期则立即删除并不做任何返回。
内存淘汰机制
当 Redis 使用的内存超出物理内存时,内存会和磁盘产生交互(swap),严重影响性能。Redis 提供了配置参数 maxmemory 来限制内存,以防止交互发生。
maxmemory 支持多种内存淘汰机制,以保障内存瓶颈时服务的性能。
指令# 使用指令方式设置淘汰策略,会在重启Redis使失效 # 设置最大内存 127.0.0.1:6379> config set maxmemory 8gb # 设置内存淘汰策略 127.0.0.1:6379> config set maxmemory-policy volatile-lfu 127.0.0.1:6379> config get maxmemory-policy配置# 修改配置文件需要重启Redis,但一直有效 # 在redis.conf配置文件中加入如下内容 maxmemory 256mb maxmemory-policy volatile-lfu淘汰策略 描述 noeviction 默认值,不提供写服务 allkeys-lru 对于所有 key,最少使用的优先淘汰(Least Recently Used) volatile-lru 尝试淘汰设置了过期时间的 key,最少使用的优先淘汰 volatile-random 随机淘汰设置了过期时间的 key allkeys-random 对于所有 key, 随机淘汰 volatile-ttl ttl(过期时间)越小优先淘汰

