一、概述
Remote Dictionary Server(远程字典服务)是完全开源的,使用ANSIC语言编写遵守BSD协议,是一个高性能的Key-Value数据库提供了丰富的数据结构,例如String、Hash、List、Set、sortedset等等。数据是存在内存中的,同时Redis文持事务、持久化、LUA脚本、发布/订阅、缓存海汰、流技术等多种功能特性提供了主从模式、Redis Sentinel和Redis Cluster集群架构方案
1. Redis的好处
- 运行在内存上,效率高
- 除了K-V数据,其他数据也很类型丰富
- 自带持久化功能,虽然运行在内存中,数据也会被保持在硬盘里
- 可以多台Redis服务器链接配合,处理能力强
- 可以有类似Git同款的master-slave主从数据储存
二、Redis 十大数据类型
类比javaSE的HashMap,键固定,但是对应的值的类型不固定
1. 总述
- String(字符串)
string是redis最基本的类型,一个key对应一个value。
string类型是二进制安全的,意思是redis的string可以包含任何数据,比如jpg图片或者序列化的对象 。
string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M
- List(列表)
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
它的底层实际是个双端链表,最多可以包含 2^32 - 1 个元素 (4294967295, 每个列表超过40亿个元素)
- Redis hash
Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)
- Set(集合)
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据,集合对象的编码可以是 intset 或者 hashtable。
Redis 中Set集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)
- Redis zset
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数,redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
zset集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 2^32 - 1
- Redis GEO
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,包括
添加地理位置的坐标。
获取地理位置的坐标。
计算两个位置之间的距离。
根据用户给定的经纬度坐标来获取指定范围内的地理位置集合
- HyperLogLog
HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
- bitmap
由0和1状态表现的二进制位的bit数组

- bitfield
通过bitfield命令可以一次性操作多个比特位域(指的是连续的多个比特位),它会执行一系列操作并返回一个响应数组,这个数组中的元素对应参数列表中的相应操作的执行结果。
说白了就是通过bitfield命令我们可以一次性对多个比特位域进行操作。
- Stream
Redis Stream 是 Redis 5.0 版本新增加的数据结构。
Redis Stream 主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。
简单来说发布订阅 (pub/sub) 可以分发消息,但无法记录历史消息。
而 Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失
2. String 字符串
String是Redis中最重要的类型,可以说其他类型都是由String转换而成的
Get Set指令

(1)基础Get 指令
SET命令有EX、PX、N、xx以及KEEPTTL五个可选参数,其中KEEPTTL为6.0版本添加的可选参数,其它为2.6.12版本添加的可选参数。
GET key value [种种设置项]
|---------|----------------|-----------------------------|
| 设置项 | 参数 | 作用 |
| EX {} | 数字(代表秒) | 以秒为单位设置过期时间 |
| PX {} | 数字(代表毫秒) | 以毫秒为单位设置过期时间 |
| EXAT | UNIX时间戳(单位是秒) | 设置以秒为单位的UNIX时间戳所对应的时间为过期时间 |
| PXAT | UNIX时间戳(单位是毫秒) | 设置以毫秒为单位的UNIX时间戳所对应的时间为过期时间 |
| NX | 无 | 键不存在的时候设置键值 |
| XX | 无 | 键存在的时候设置键值 |
| KEEPTTL | 无 | 保留设置前指定键的生存时间 |
| GET | 无 | 返回指定键原本的值,若键不存在时返回nil |
(2)同时对多个K-V进行Get Set
- MSET key1 value1 key2 value2 ....
- MGET key1 key2 ......
- MSETNX key1 value1 key2 value2 ....
- 同时添加(如果已经存在则覆盖原值)
- 同时获取(会分段)
- 同时添加(如果已经存在则每一个设置都无法生效,如:key1存在,那么key2 key3都设置失败)
(3)数值增加
虽然是String,但是对于纯数值的String我们仍然可以对其增减
|----------------------|------------------|
| 指令 | 作用 |
| INCR key | 增加整数1 |
| INCRBY key increment | 增加指定的整数increment |
| DECR key | 减少整数1 |
| DECRBY key decrement | 减少指定的整数decrement |
(4)获取字符串长度和内容追加
|------------------|---------|
| 指令 | 作用 |
| STRLEN key | 获取字符串长度 |
| APPEND key value | 内容追加 |
(5)分布式锁
暂时知道有即可
3. List 列表
在Redis中List是一个双端链表,不过这个链表虽然是双端的但只能从一边进入(添加)
常用:

说明
|--------------------------|------------------------------------|------------------------------------|
| 指令 | 参数 | 作用 |
| LPUSH key LEN v1 v2 ... | LEN(长度) | 新增一个List |
| RPUSH key L R | L(左起点)R(右终点) | 遍历List(在Redis中-1代表最后一个元素,0代表第一个元素) |
| LLEN | 无 | 获取列表中元素的个数 |
| LREM key N ValueN | N(去重数量,+-代表方向正反,0代表所有)ValueN(重复元素) | 去重,自头向尾 |
| LTRIM key L R | L(左起点)R(右终点) | 截取指定范围的值后再赋值给原key |
| ROPLPUSH List1 list2 | List1(原列表)List2(转入的列表) | 把原列表头转移到指定列表尾 |
| LSET key index value | inde(指定位置下标) | 把指定位置元素替换 |
| LINSERT key BEFORE/AFTER | BEFORE/AFTE(左/右) | 向指定位置元素的左/右添加元素 |
4. Hash 哈希
和javaSE的HashMap一样
常用指令:

5. Set 集合
集合不可重复,没有顺序
常用指令:

6. ZSet 有序集合
在set基础上,每个val值前加一个score 权重值。之前set是k1 v1 v2 v3,现在zset是k1 score1 v1 score2 v2
常用指令:

7. BitMap 位图
本质是String实现的由0/1二进制位组成的二进制数组:常作为true/false来按顺序记录判断结果
使用场景:
日历签到,电影座位是否被占,状态记录
8. HyperLogLog 基数统计
用于大数据量下的数据统计:去重复统计功能的基数估计算法-就是HyperLogLog
- 基数统计:用于统计一个集合中不重复的元素个数,就是对集合去重复后剩余元素的计算,有0.81%的误差
基本命令:

9. GEO 地理空间
根据经纬度保存空间坐标,底层是ZSet类型,但是权重变成经纬度了

10. Stream 流
和javaSE的不太一样,Redis引入这种数据结构是为了减少项目中间件,是MQ消息中间件+阻塞队列
- MQ消息队列:先把订单请求往队列里一扔,让数据库按自己能承受的速度慢慢处理/弹出。
(1)队列相关指令

- XTRIM加上:
-
- maxlen关键字 + 数量,截取前2个的话,会保留后面的
- minid关键字 + 指定时间戳id,保留该id后的所有
(2)消费组相关指令

(3)4个特殊字符
|-----|-----------------------------------|
| 符号 | 作用 |
| - + | 最小/最大可能出现的id |
| $ | 表示当前队列最后一个的下一个--表示将来才会出现的添加入队列的消息 |
| > | 用于XREADGROUP命令中,表示指针指向最新未消费的消息 |
| * | 用于XADD,不指定id,让Redis根据时间戳生成id |
11. Redis 位域(了解即可)
BITFIELD 命令可以将一个 Redis 字符串看作是一个由二进制位组成的数组,并对这个数组中任意偏移进行访问。可以使用该命令对一个有符号的5 位整型数的第 1234 位设置指定值,也可以对一个 31 位无符号整型数的第 4567 位进行取值。类似地,本命令可以对指定的整数进行自增和自减操作,可配置的上溢和下溢处理操作。
- 把字符串按字符转换为二进制,然后把所有二进制码拼成位图数组
基本命令:

使用:

三、Redis 持久化
为了将数据同步保存下来,我们需要对Redis进行持久化处理,
1. RDB--RedisDataBase
在指定时间内如果修改到了一定次数,快照记录Redis的数据到磁盘(dump.rdb文件)
(1)配置文件修改
- 修改自动保存的触发间隔
该为5秒内如果修改了2次就自动保存

- 修改dump文件的储存位置
改为黑字对应位置

- 修改dump文件名称

(2)触发快照的方式
- 在系统设置的时间内进行了规定次数的操作
- 配置默认的快照配置时
- 手动sava/dgsave
- 清空dump文件时(此时生成的dump为空)
- 主从复制时,主节点触发
(3)恢复备份
将dump备份文件移动到Redis安装目录后启动Redis服务就能自动读取
- 注意:在实际生产时推荐把备份的位置设置在其他服务器上进行隔离,以防生产机物理损坏后备份文件也挂了
(4)手动备份--save
- save指令 与 dgsave指令的区别
- save指令是阻塞式的,如果执行会导致redis服务停止,直到持久化完成
- dgsave指令是非阻塞式的,会通过linux的fork创建一个子进程,在子进程中完成持久化,主进程仍然正常运行
- fork是什么?
- 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,尽量避免膨胀。
- lastsave指令
- 通过该指令可以获取最后一次执行持久化的时间戳
(5)RDB的优缺点
优点:
- 适合大规模数据的恢复
- 能定时备份
- 对数据完整性、一致性要求不高
- RDB文件的加载速度在内存中比AOF快的多
缺点:
- 如果突然结束服务,最近的信息可能因为不满足快照机制而丢失
- 如果数据量过大,强io会影响服务器性能
- fork会导致数据2倍膨胀,需要时刻小心
2. AOF--AppendOnlyFile
(1)概述
和RDB的全部记录不同,他们最大的区别是AOF记录所有的写操作,使用他恢复时会遍历所有写操作来恢复数据
- **写操作:**不是指添加操作,而是指除了读取数据以外的添加、修改、删除等操作
(2)使用流程
- Client提供命令到Redis
- 命令不是直接写入磁盘的AOF文件,而是先进入内存的AOF缓存区中保存,(减少磁盘io)
- 如果满足三种写回策略的指定的那一种后,将缓存的命令写入AOF文件
- 为了防止文件内容过大,根据规则进行压缩(又称AOF重写)
- Redis重启时根据AOF重新加载到内存中
(3)回写策略
就是把缓存写入磁盘的策略
- Always:同步写回,每个写命令执行完立刻同步地将日志写回磁盘
- everysec:每个写命令执行完,每秒写回,只是先把日志写到AOF文件的内存缓冲区,每隔1秒把缓冲区中的内容写入磁盘
- no:决定何时将缓冲区内容写回磁盘操作系统控制的写回,每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统
|--------------|----------|------------|---------|
| 配置项 | 写回时机 | 优点 | 缺点 |
| Always | 同步写回 | 可靠,数据不易丢失 | io多,效率低 |
| everysec(默认) | 每秒写回 | 性能适中,留存率适中 | 会丢失1秒数据 |
| no | 操作系统控制写回 | 性能高 | 丢失数据多 |
(4)配置文件
a. 开启方式:
默认不开启,配置开启--修改配置文件:appendonly no yes
b. 储存方式
路径:默认储存在/myredis/appendonlydir目录下
在redis7以后,rdb文件储存在/myredis目录下,而dir文件储存在/myredis/appendonlydir目录下
c. 具体文件:
- BASE:AOF基础文件,只有一个
- INCR:AOF开启文件,在AOF开启时就会被创建,可能存在多个
- HISTORY:AOF历史文件,由更新前的INCR文件转换,Redis会自动清理该类型文件
在Redis7前,只存在一个aof文件
(5)优劣之分
优势:
- 相比RDB,储存所有写操作能更好保护数据不丢失、写入磁盘的性能高、适合各种情况下的紧急恢复
劣势:
- AOF文件占用远大于RDB文件,恢复速度慢
- 虽然同步写入io效率高,但是AOF运行时效率慢
(6)AOF重写
AOF运行久了,文件过大,我们需要压缩其空间,也就是重写
AOF在没有RDB配合的情况下重写,写入的INCR文件会压缩后存入BASE文件中,同时创建一个新的INCR文件
a. 自动触发重写

同时满足以下条件:
- 占用100%
- 最大64mb
如果设置为50%,那么当文件达到32mb时就会重写
b. 手动触发重写
输入:bgrewriteaof
c. 重写的具体流程
- 在重写开始前,redis会创建一个"重写子进程",这个子进程会读取现有的AOF文件,并将其包含的指令进行分析压缩并写入到一个临时文件中。
- 与此同时,主进程会将新接收到的写指令一边累积到内存缓冲区中,一边继续写入到原有的AOF文件中,这样做是保证原有的AOF文件的可用性,避免在重写过程中出现意外。
- 当"重写子进程"完成重写工作后,它会给父进程发一个信号,父进程收到信号后就会将内存中缓存的写指令追加到新AOF文件中
- 当追加结束后,redis就会用新AOF文件来代替旧AOF文件,之后再有新的写指令,就都会追加到新的AOF文件中
- 重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似
3. RDB+AOF 混合
(1)开启方式
设置aof-use-rdb-preamble的值为 yes yes表示开启,设置为no表示禁用
- 记得提前开启AOF
(2)运行逻辑
RDB镜像做全量持久化,AOF做增量持久化
先使用RDB进行快照存储,然后使用AOF持久化记录所有的写操作,当重写策略满足或手动触发重写的时候,将最新的数据存储为新的RDB记录。这样的话,重启服务的时候会从RDB和AOF两部分恢复数据,既保证了数据完整性,又提高了恢复数据的性能。简单来说:混合持久化方式产生的文件一部分是RDB格式,一部分是AOF格式。----》AOF包括了RDB头部+AOF混写
4. 关闭持久化,Redis只作为缓存区
在实际场景下,我们未必会使用redis自带的持久化功能,而是使用其他的东西完成持久化来分清各类功能
(1)禁用RDB:
save ""

- 在这种情况下,我们仍然可以使用RDB的手动指令sve、bgsave生成rdb文件
(2)禁用AOF:
appendonly no
- 同理,我们仍然可以使用命令bgrewriteaof生成aof文件
四、Redis的事务
1. Redis事务的特点
|---------------------|------------------------------------------------------|
| 操作永远是单独的 | Redis由于本身就是单线程的,所以其事务也仅仅是保障了操作的连续性,在事务执行期间自然无法处理其他请求 |
| 没有隔离级别(读未提交、可重复读..) | 因为同时只能执行事务内的操作,自然没有同时读入写出的问题,本身就是最高隔离级别 |
| 不保证原子性 | Redis事务不保证所有指令同时成功或者失败,而是决定是否开始执行全部指令,无法执行错误后回滚 |
| 排它性 | Redis保证事务内指令一次执行,不会被插入指令 |
2. 具体使用
和其他数据库的事务不同,Redis的事务原子性对于语法错误时事务队列不会执行(以此达到回滚的效果),但是当出现运行错误则其他指令正常运行(类似于类型不匹配等等,可以认为此时没有原子性了,需要手动操作)
(1)一般使用
Redis的事务其实是一个队列,有点像消息队列,创建事务其实就是创建队列
- MULTI:事务队列开始
- EXEC:事务队列结束
- DISCARD:事务队列取消
正常执行:

手动取消:

(2)Redis事务薛定谔的原子性
存在原子性:
- 对于语法错误时,事务队列不会执行
(以此达到回滚的效果)
没有原子性:
- 当出现运行错误则其他指令正常运行
(类似于类型不匹配等等,可以认为此时没有原子性了,需要手动操作)
(3)WATCH乐观锁
乐观锁:关注版本号,而不是先后顺序,如果版本号被改,那么我就别乱搞
WATCH:相当于保存了当前数据的版本号,如果数据在别的访问端被修改,此时真实版本号更新,而对于执行了WATCH指令的访问端,再对该数据修改时就会报错
WATCH key:设置观察锁

- 只有访问端事先准备了WATCH锁,才会对当前访问端的EXEC指令生效
UNWATCH:取消当前所有WATCH锁

- 一旦执行了EXEC指令,在这之前所有的WATCH指令在之后都不再生效了
五、Redis 管道 Pipeline
Redis本质上仍然是 请求接受-数据处理-结果返回,对于大量的网络指令,我们可以使用队列来优化其批处理效率
- 类似与Redis原生的mget和mset指令
1. 基础使用
对某一文件打开的同时使用linux的"|"管道符配合Redis的指令,将其传入Redis管道中,系统会返回执行结果

-
如果执行文件中有get指令,那么get结果不会显示在终端上,需要指定输出位置或使用工具解析
将结果保存到文件
cat cmd.txt | redis-cli -a 111111 --pipe > result.txt
查看结果文件
cat result.txt
2. Pipeline管道对比原生批处理(mget/mset)
Pipeline管道
原生批处理
- 非原子性
- 一次内可以执行多种指令
- 需要客户端与服务端配合完成
(客户端要把命令打包,而服务端解包并执行)
- 原子性
- 一次只能执行同一种指令
- 只在Redis服务端实施
3. Pipeline管道对比事务
这里指的是更常见的事务,不是专指Redis事务
Pipeline管道
事务
- 非原子性
- 一次发送多条指令
- 管道指令不中断其他指令运行
- 有原子性
- 在事务队列中一条一条发指令,还需要EXEC指令结束并执行
- 阻塞式,会导致其他指令被阻塞
4. 注意
- Pipeline非原子性,执行就算出问题了,其他指令仍然会继续执行
- Pipeline中不要放过多指令,虽然Pipeline不完全阻塞,但是实际上会导致服务端使用队列答复的方法,占用内存大
六、Redis复制
多Redis 服务器配合的基础,实现了最基本的主从关系
1. 概述:
说是复制,其实更像数据库的主从关系,当主数据库更新后其数据会被异步同步入其下从数据库。
- 默认情况下,当从库(Slave)与主库(Master)建立主从关系后,从库中原有的数据会被清空,并完全替换为主库的数据。
作用:
- 读写分离
(主数据库用来写入,从数据库用来读取) - 容灾恢复
- 数据备份
- 水平扩容支撑高并发
2. 设置方式
(1)利用redis-cli 的指令设置
|---------------------|--------------------------|
| 指令 | 说明 |
| REPLICAOF 主库ip 主库端口 | 设置当前数据库,初次连接的数据库(一定得是初次) |
| SLAVEOF 新主库ip 新主库端口 | 设置当前数据库,新连接的数据库 |
| SLAVEOF no one | 设置当前数据库,没有主库 |
- REPLICA:复制 ;SLAVE:奴隶
注意事项:
-
当我们要连接有密码的主库时,当前数据库的配置文件中需要写好主库的密码,如果想要动态地连接,可以这样写,相当于临时更改了密码
第一步:设置密码(必须最先执行!)
127.0.0.1:6380> CONFIG SET masterauth your_master_password
OK第二步:指定主库地址和端口
127.0.0.1:6380> REPLICAOF <master_ip> <master_port>
OK
(2)配置文件中规定
对于主从关系,我们可以把当前Redis 的主库配置好,当该Redis 启动后会自动绑定复制
流程:
-
指定连接的主库ip和端口
-
指定主库的密码
-
(可选)设置本机密码
(3)三种场景
所有的情况都可以由这三种情况推出
一主多仆:
- 顾名思义,只有一个主机,但是有多个复制其的副机
代代相传:
- 由一个主机往下一代代复制,从机之下还有从机复制它
自力更生:
- 把从机独立出去,分开运行
3. 情景问题
- 从机能否执行写命令?
不行,就算是该从机作为其他从机的主机,还是无法改变其本质。
- 从机启动时会复制主机已经存在的数据吗?
肯定是会的,不然为什么叫复制呢。
- 主机宕机后从机会成为新的主机吗?
不一定,如果只有复制的话,从机会等待主机上线(但是有Redis哨兵的话,可以自动分配成为新主机)
- 主机宕机重启后,其原来连接好的主从关系还在吗?
看情况,如果配置文件没配,那么我们还要手动重连
- 主机宕机重启后,数据能同步吗?
我们知道Redis的复制本质是读取主机的持久化数据,而主机恢复时也是使用持久化数据的
4. 启动流程
为了详细,我使用了AI来写
(1) 从节点启动并初始化连接
- 触发条件 :从节点(Slave)启动并配置 REPLICAOF 指向主节点(Master)。
- 操作流程:
-
- 从节点向主节点发送 PSYNC****命令 (Redis 2.8+ 使用 PSYNC 替代旧版 SYNC,支持部分同步)。
- 主节点根据从节点提供的复制历史(replication ID 和 offset)判断是否需要全量或增量同步。
(2)首次全量复制(Full Sync)
- 触发条件:从节点首次连接主节点,或复制历史不匹配(如主节点切换、数据丢失)。
- 操作流程:
-
- 主节点生成 RDB 快照:
-
-
- 主节点调用 bgsave 在后台生成 RDB 文件。
- 生成 RDB 期间,主节点将新写入的命令缓存到 复制缓冲区(Replication Buffer)。
-
-
- 传输 RDB 文件:
-
-
- RDB 生成完成后,主节点将 RDB 文件发送给从节点。
-
-
- 从节点加载数据:
-
-
- 从节点清空旧数据,加载 RDB 文件到内存,完成数据初始化。
-
-
- 同步缓冲命令:
-
-
- 主节点将 RDB 生成期间缓存的命令发送给从节点,确保数据一致性。
-
(3) 增量复制(Partial Sync)
- 触发条件 :主从已建立复制关系,且从节点断线后重连时,其复制偏移量(offset)仍存在于主节点的 复制积压缓冲区(Repl Backlog) 中。
- 操作流程:
-
- 主节点检查偏移量:
-
-
- 主节点根据从节点上报的 offset,从复制积压缓冲区中提取断线期间缺失的命令。
-
-
- 发送增量数据:
-
-
- 主节点将缺失的增量命令流(AOF-like)发送给从节点。
-
-
- 从节点应用增量命令:
-
-
- 从节点执行收到的增量命令,追上主节点数据状态。
-
(4)心跳保持与长连接维护
- 主从心跳机制:
-
- 主从节点通过 PING-PONG 心跳包保持长连接(默认间隔由 repl-ping-replica-period 控制,如 10 秒)。
- 从节点定期上报自身复制偏移量(offset),主节点据此监控同步进度。
- 复制积压缓冲区(Repl Backlog):
-
- 主节点维护一个固定大小的环形缓冲区(由 repl-backlog-size 配置,默认 1MB)。
- 缓冲区保存最近的写入命令,用于支持从节点断线后的增量复制。
(5) 从节点断线重连恢复
- 断线重连流程:
-
- 从节点重新连接主节点,发送 PSYNC <repl_id> <offset>。
- 主节点根据 repl_id 和 offset 判断是否允许增量同步:
- 若 offset 仍在积压缓冲区范围内 → 增量同步。
- 若 offset 已超出缓冲区 → 触发全量同步。
5. Redis复制的缺点
(1)延迟复制,信号渐衰
由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
(2)master宕机后不好处理
- 默认状态下,不会有从机自动成为新的master'
- 每次宕机后从机的处理,和宕机后的恢复都需要手动处理
七、Redis哨兵 sentinel
本质上是无人值守运维,用来补充Redis复制的缺点,提高效率和稳定性
1. 概述
因为Redis复制的主从过于死板,一旦master宕机,slave都要停止。为了解决宕机和重启的种种问题,Redis设计一种自动监控(哨兵),监控主从状态,通过多哨兵联合投票来更换主机
2. 如何使用哨兵
Redis哨兵也是由Redis数据库更改设置而来,可以认为是一种特殊的从机,它绑定的主库就是其监视对象,所以配置项和主从复制很像(但是哨兵不储存数据,只有监视和通报的功能)
(1)配置Redis哨兵文件
创建sentinel.conf文件
参考以下配置:
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| bind 0.0.0.0 daemonize yes protected-mode no port 26379 logfile "/myredis/sentinel26379.log" pidfile /var/run/redis-sentinel26379.pid dir /myredis sentinel monitor mymaster 192.168.111.169 6379 2 sentinel auth-pass mymaster 111111 |
配置项:
- bind:哨兵监听地址,默认情况下为本机
- daemonize:是否以后台daemon方式运行
- protected-mode:是否开启安全保护模式
- port:哨兵运行的端口
- logfile:日志文件路径
- pidfile:pid文件路径
- dir:工作目录
- sentinel monitor <master-name> <redis-port> <Ip> <quorum>:
-
- <master-name> <redis-port> <Ip>:设置监控的master服务器
- <quorum>:故障迁移的判定票数(最少有几个哨兵认可才能下线)
- sentinel auth-pass <master-name> <password>:master的密码
(2)启动主从机
---------省略------------
(3)启动哨兵
输入:
redis-sentinel sentinelXXX.conf --sentinel
3. 哨兵故障迁移的底层
(1)主观下线和客观下线
主观下线:
这里的主体指的是当前的一个哨兵,如果哨兵发现监视的master在一定时间内没有心跳包响应则认为该master下线了
客观下线:
当多个哨兵发现并数量满足哨兵中设置最大的quorum 值,那么哨兵们集体认为该master下线了
(2)选举哨兵代表
当master被集体认为客观下线后,哨兵集群通过Raft算法,选举一个哨兵代表,哨兵代表执行操作,使故障的master被failover(故障迁移)
▽ Raft算法
监视该主节点的所有哨兵都有可能被选为领导者,选举使用的算法是Raft算法;Raft算法的基本思路是先到先得:
即在一轮选举中,哨兵A向B发送成为领导者的申请,如果B没有同意过其他哨兵,则会同意A成为领导者

八、Redis集群 Cluster
Redis复制+哨兵分布式的升级版,安全性、效率都远高于原结构
1. 概述
(1)是什么?
Redis集群是多个Redis节点共享数据构成了一个整体程序集
- 与Redis复制最大才差异是可以有多台master

(2)有什么用?
- 多master+多slave:
在支持读写分离的同时,使数据高可用(安全、不易丢失),有利于海量数据的储存和读写 - 自带Sentinel的故障转移机制:
无需使用哨兵来维护故障 - 客户端与Redis的节点连接,不再需要连接集群中所有的节点,只需要任意连接集群中的一个可用节点即可
- 利用HASH槽分配到服务器储存
2. 集群的HASH槽位 slot
(1)是什么?
Redis设置有16384(2^14)个HASH槽,通过算法计算key的HASH值后将其内容储存到对应槽位
a. HASH槽
和javaSE的HashMap类似,不过HASH槽是分配到各个Redis服务器中的,所以获取数据时可以借此分流向各个服务器

b. 槽位分片
就是按一定规律将HASH槽分配到各个服务器,一个服务器对应就是一个片区
- 注意槽位的分配大多是打乱的,没有什么连续性的说法
(2)有什么好的?
- 方便集群整体的扩缩容:
我们在扩容时相当于新增一个片区,由于使用槽位来储存数据,新增的片区只要由老片区提供部分槽位及其数据就能完成扩容,并且不影响后续的服务 - 便于分派查找:
利用HASH槽,我们分开了各种key,在修改、查询时压力就很好地分散了
(3)三种HASH算法
对于HASH槽其实不止分片来处理,还有别的方法
a. 哈希取余分区
基础的哈希算法
概述:
- 通过直接取余HASH来储存,若有3台机器,就用3取余
优点:
- 简单直接,适合与小型工程
缺点:
- 因为节点数在初期就定死了,如果要扩/缩容或者有机器宕机的情况下,需要变动取模公式,而且原来的值也要感觉HASH重新刷新
b. 一致性哈希算法
补充哈希算法分母的动态问题,保证无论多少服务器都不会出现一致性错误
概述:
- 保持总量的不变,动态地装载数据
*流程:
- 构建一致性哈希环:
-
- 先定死HASH取模的大小,此时可以认为HASH槽为环形,
- Redis服务器节点映射:
-
- 每个Redis服务器通过算法对应哈希环上某点。
- key通过环添加到指定服务器:
-
- 保持Redis节点不动,key也以节点的形式加入哈希环,key节点朝顺时针旋转遇到的第一个Redis节点就是其所属数据库

作用:
- 容错性--宕机不影响正常运行:
-
- 根据定义,我们可以使着取出一个Redis节点,可以发现,宕机的数据会迁移到顺时针方向的下一个数据库
- 扩展性--可以动态的扩展:
-
- 同样根据定义,我们添加一个Redis节点,可以发现,新加入的节点截断了部分数据,这样也就完成了扩展
缺点:
- 会造成数据倾斜的问题:
-
- 当服务器节点过少或者分布不均衡时,可能导致大量数据集中到一个服务器上

c. 哈希槽分区
有点像一致性哈希算法,但是,哈希槽调优了分配和管理的能力,改善了数据倾斜问题
概述:
- 哈希槽通过将数据映射到固定数量的槽位并主动均衡分配至节点,解决了一致性哈希算法中因哈希值分布不均可能导致的负载失衡问题,以更可控的方式实现数据分布和集群扩展。
作用:
- 均衡数据:
-
- 其实核心还是那些,但是落入哈希槽和哈希环不同,哈希槽能够均分入各个服务器
(4)注意
a. 面试--哈希槽为什么最多16384?
- 消息大小考虑:
-
- Redis 节点发送心跳数据包时需将所有槽放入。尽管 CRC16 算法能生成 65535 个值,但 16384 个哈希槽的消息占用约 2KB 内存,而 65535 个哈希槽则需 8KB。选择 16384 个哈希槽可减少网络带宽占用和内存消耗,优化系统资源。
- 集群规模设计:
-
- Redis 支持的最大分片数为 1000。16384 个哈希槽能确保在所有分片均匀分布时,每个分片拥有的槽数不会过少,避免在极端情况下退化回传统哈希算法,保证了数据分布均匀和系统扩展性。
b. Redis集群不保证数据一致性
当主机写入完成但没有传入从机而主机宕机时,数据无法被读取
3. 如何创建
(1)配置文件
redisCluster.conf:
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| bind 0.0.0.0 daemonize yes protected-mode no port 6381 logfile "/myredis/cluster/cluster6381.log" pidfile /myredis/cluster6381.pid dir /myredis/cluster dbfilename dump6381.rdb appendonly yes appendfilename "appendonly6381.aof" requirepass 111111 masterauth 111111 cluster-enabled yes cluster-config-file nodes-6381.conf cluster-node-timeout 5000 |
|---------------------------------|---------------------|
| 参数 | 说明 |
| cluster-enabled yes
| 启用集群模式 |
| cluster-config-file
| 自动生成的集群拓扑配置文件 |
| cluster-node-timeout
| 节点失联超时时间,超过此时间视为故障 |
| cluster-require-full-coverage
| 是否要求所有槽都可用才提供服务 |
| appendonly yes
| 启用 AOF 持久化(保证数据安全性) |
(2)服务器集群化启动
redis-server /配置文件路径/redisCluster1.conf
redis-server /配置文件路径/redisCluster2.conf
redis-server /配置文件路径/redisCluster3.conf
- 使用集群配置文件启动多台Redis服务
(3)启动集群关系
redis-cli -a 111111 --cluster create --cluster-replicas 1 192.168.111.175:6381 192.168.111.175:6382 192.168.111.172:6383 192.168.111.172:6384 192.168.111.174:6385 192.168.111.174:6386
- 这里连接了集群中全部的Redis数据库,包括master还有slaver,用空格隔开服务器的ip与端口
连接成功的提示:

4. 集群的读写操作
(1)读写的问题
当我们设置并启动了集群服务,我们如果仍然使用原来的方法启动cli,有的时候读写一些键时会报错。
因为,Redis集群中使用了HASH槽,如果简单使用cli,只能访问对应在本机上的key
(2)正确启动cli
为了解决前面提出的问题,我们在启动cli时,需要给启动语句后加上 -c 参数

- 作用是优化路由,使其能匹配到其他服务器
(3)查看集群信息
首先要启动一个cli服务,然后输入指令

(4)查看key对应的HASH槽
首先要启动一个cli服务,然后输入cluster keyslot 键

5. 主从容错切换迁移
(1)容错切换策略
如果主机突然宕机,那么集群会使用该策略维持服务
和Redis主从复制 + 哨兵 的效果一样:
- 主机宕机-->从机上位
- 主机回归-->主机转为从机
(2)故障调整
由于出现故障会导致集群结构异常,我们需要进行调整,使其恢复原有结构
- 注意:集群结构也无法保障数据不完全一致不丢失
常用Redis自带指令,在重连的主机cli上运行:CLUSTER FAILOVER

6. 主从扩缩容
(1)扩容
流程:
- 创建新的Redis服务和它的Cluster集群配置文件
- 通过Cluster配置文件启动Redis服务
- 将新增的6387作为master节点加入原有集群:
redis-cli -a 密码 --cluster add-node 新机P地址:6387 老机IP地址:6381
-
- 可以认为新机需要老机引入
- 此时新加入的master新机没有被分配HASH槽
- 分配HASH槽:
redis-cli -a 密码 --cluster reshardIP地址:端口号
-
- 指令分配后需要手动指定分配额度与分配给哪个数据库,集群会自动均分每个老机的部分槽位
- 指令分配后需要手动指定分配额度与分配给哪个数据库,集群会自动均分每个老机的部分槽位
-
校验结果(非必须):
redis-cli --cluster check 真实ip地址:6381- 确实得到了一个新master,并完成了槽位分配
流程图:

(2)缩容
流程:
- 删除主机下的从节点:
redis-cli -a 密码 --cluster del-node ip:从机端口 从机6388节点ID - 清空主机的槽号:
redis-cli -a 密码 -cluster reshard ip:主机端口 - 指定接收槽位的Redis服务器:(一般都是多次均分,这里图方便)
-
- 分配彻底后原主机会变成最后一次操作对应主机的新从机
- 删除原主机变成的新从机
redis-cli -a 密码 --cluster del-node ip:主机端口 从机6387节点ID
流程图:

7. 补充
(1)集群结构下批操作的限制
如果批操作的对象对应的HASH槽在不同的服务器,那么批处理会失败
(2)CRC16的底层实现方法
是Redis中的一个 .c 文件中的函数

(3)常用指令
a. 集群是否必须完整才能对外服务
默认YES,现在集群架构是3主3从的redis cluster由3个master平分16384个slot,每个master的小集群负责1/3的slot,对应一部分数据。cluster-require-full-coverage: 默认值 yes , 即需要集群完整性,方可对外提供服务 通常情况,如果这3个小集群中,任何一个(1主1从)挂了,你这个集群对外可提供的数据只有2/3了, 整个集群是不完整的, redis 默认在这种情况下,是不会对外提供服务的。

b. 查看槽位是否被占用
CLUSTER COUNTKEYSINSLOT 槽位编号
- 1:被占用
- 0:空闲中
c. 查找key 计算后应该分配的槽位编号
CLUSTER KEYSLOT 键名
九、Redis与SpringBoot
主流有Jedis、Lettuce与RedisTemplate作为Redis与SpringBoot连接的中间件。
但是因为RedisTemplate效果最好,所以主要了解RedisTemplate就差不多了
1. Redis单机的连接
(1)导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
(2)配置文件配置
# ========================redis=====================
spring.data.redis.database=0
spring.data.redis.host=127.0.0.1
spring.data.redis.port=6379
spring.data.redis.password=111111
(3)配置类
@Configuration
public class RedisConfig
{
/**
* redis序列化的工具配置类,下面这个请一定开启配置
* 127.0.0.1:6379> keys *
* 1) "ord:102" 序列化过
* 2) "\xac\xed\x00\x05t\x00\aord:102" 野生,没有序列化过
* this.redisTemplate.opsForValue(); //提供了操作string类型的所有方法
* this.redisTemplate.opsForList(); // 提供了操作list类型的所有方法
* this.redisTemplate.opsForSet(); //提供了操作set的所有方法
* this.redisTemplate.opsForHash(); //提供了操作hash表的所有方法
* this.redisTemplate.opsForZSet(); //提供了操作zset的所有方法
* @param lettuceConnectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory)
{
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
//设置key序列化方式string
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
- 这里主要是调整了Redis的keyV序列化,保证序列化和反序列化的一致性
2. Redis集群的连接
与单机基本上一样,但是由于集群工作模式不同,会有一些和单机不同的地方
(1)改动配置文件
# ========================redis??=====================
spring.data.redis.password=111111
#密码
spring.data.redis.cluster.nodes=192.168.111.175:6381,192.168.111.175:6382,192.168.111.172:6383,192.168.111.172:6384,192.168.111.174:6385,192.168.111.174:6386
# 不连接单一Redis,而是把集群中的所有服务器的ip与端口都一一说明
(2)问题:当原集群结构改变,Spring无法连接
原因:
- SpringBoot没有动态感应到Redis集群的最新状态
例:
-
Redis Cluster集群部署采用了3主3从拓扑结构,数据读写访问master节点, slave节点负责备份。当master宕机主从切换成功,redis手动OK,but 2个经典故障
由于是由语雀直接粘过来的,有些bug,详见Redis7