1 redis安装和简介
基于ubuntu系统的安装
bash
sudo apt update
sudo apt install redis
##包安装的redis 没有默认配置文件
启动
bash
redis-server /path/to/your/redis.conf
redis-cli
Redis 默认是没有设置用户和密码的,即可以无密码访问
设置密码的方法:可以通过修改 Redis 的配置文件redis.conf来设置密码。打开配置文件,找到requirepass这一行,将其后面的值设置为你想要的密码,例如requirepass mypassword。保存配置文件后,重启 Redis 服务使设置生效。
通过命令行设置密码:也可以在 Redis 运行时通过命令行动态设置密码。先使用CONFIG SET requirepass "mypassword"命令设置密码,然后使用CONFIG REWRITE命令将配置写入配置文件,这样即使 Redis 重启,密码设置也会保留。
验证密码:设置密码后,客户端在连接 Redis 时需要提供密码进行验证。可以在redis-cli连接时使用-a参数指定密码,如redis-cli -a mypassword。如果是在代码中连接 Redis,也需要在连接配置中设置密码。
Redis 是Remote Dictionary Service 的简称;也是远程字典服务;
数据模型
- Redis:是 KV(键值对)数据库,支持多种数据结构,如字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(zset)等。数据以键值对形式存储,键是唯一标识,值可以是不同的数据结构,适合存储半结构化或非结构化数据,例如缓存用户信息、商品数据等。
- MySQL:是关系型数据库,数据以表格形式存储,由行和列组成,支持事务处理、外键约束等,适合存储结构化数据,如订单数据、用户订单详情等。
存储方式
- Redis:数据全部存储在内存中,读写速度极快,能支持高并发。但内存空间有限,通常用于存储热点数据或临时数据。为了保证数据的可靠性,Redis 提供了持久化机制,如 RDB(快照)和 AOF(追加日志),可以将内存中的数据定期或实时地保存到磁盘上。
- MySQL:数据主要存储在磁盘上,虽然也有内存缓存机制(如 InnoDB 缓冲池)来提高读写性能,但整体读写速度比 Redis 慢。不过,它能存储大量数据,适合长期存储和处理大规模数据。
性能特点
- Redis:单线程模型,基于内存操作,读写速度快,能达到每秒数万甚至数十万次的并发处理能力。适用于对读写性能要求极高、数据量相对较小、对数据实时性要求高的场景。
- MySQL:虽然性能也不错,但由于涉及磁盘 I/O 操作,在处理高并发读写时性能不如 Redis。不过,通过合理的索引设计、查询优化和数据库集群等技术,也能满足大多数企业级应用的性能需求。
Redis 6.0 及之后(网络 I/O 多线程)
多线程特性:Redis 6.0 开始支持网络 I/O 的多线程。在处理客户端的网络请求时,会使用多个线程并行处理网络读写操作,但真正的命令执行仍然是单线程的 。
多线程优势:网络 I/O 多线程能够显著提升 Redis 在高并发场景下的网络处理能力,充分利用多核 CPU 的优势,减少网络 I/O 的瓶颈,从而提高整体的吞吐量。
配置灵活:Redis 6.0 的多线程是可配置的,用户可以根据实际的硬件资源和业务需求来决定是否开启多线程以及使用多少个线程,具有较高的灵活性。
redis返回值
- 简单字符串(Simple Strings)
格式:以 + 开头,后面紧跟字符串内容,最后以 \r\n 结尾。
含义:通常用于表示命令执行成功,返回一个简单的状态信息。
示例:执行 SET key value 命令,若成功,Redis 会返回 +OK\r\n,表明键值对设置成功。 - 错误信息(Errors)
格式:以 - 开头,后面紧跟错误信息内容,最后以 \r\n 结尾。
含义:表示命令执行过程中出现错误,错误信息会提示具体的错误类型和原因。
示例:执行一个不存在的命令 NONEXISTENTCOMMAND,Redis 会返回 -ERR unknown command 'NONEXISTENTCOMMAND'\r\n,指出该命令未被识别。 - 整数(Integers)
格式:以 : 开头,后面紧跟整数值,最后以 \r\n 结尾。
含义:整数返回值可用于多种场景,如表示操作影响的元素数量、键的过期时间等。
示例:
执行 INCR key 命令,若键不存在,Redis 会先将其初始化为 0 再进行自增操作,返回 :1\r\n,表示自增后的值为 1。
执行 EXISTS key 命令,若键存在,返回 :1\r\n;若不存在,返回 :0\r\n。 - 批量字符串(Bulk Strings)
格式:以 开头,后面紧跟字符串的长度,然后是字符串内容,最后以 \\r\\n 结尾。若字符串为空,长度为 0;若键不存在,返回 -1\r\n。
含义:用于返回单个字符串值。
示例:执行 GET key 命令,若键存在且值为 value,Redis 会返回 $5\r\nvalue\r\n,其中 5 是字符串 value 的长度。 - 数组(Arrays)
格式:以 * 开头,后面紧跟数组元素的数量,然后依次列出每个元素的返回值。
含义:用于返回多个值,如执行 LRANGE key start stop 命令时,会返回列表中指定范围的元素数组。
示例:执行 LRANGE mylist 0 1 命令,若列表 mylist 包含元素 element1 和 element2,Redis 会返回 *2\r\n8\\r\\nelement1\\r\\n8\r\nelement2\r\n,表示数组有 2 个元素,分别是 element1 和 element2。
2 数据结构
在 Redis 中,键(Key)只能是字符串类型。这是 Redis 设计的基本规则,无论值(Value)是什么数据结构(如字符串、列表、哈希、集合、有序集合等 ),键都必须是字符串。
redis 命令 参考
2.1 string
字符数组,该字符串是动态字符串 raw,字符串长度小于1M 时,加倍扩容;超过 1M 每次只多扩
1M;字符串最大长度为 512M;
注意:redis 字符串是二进制安全字符串;可以存储图片,二进制协议等二进制数据
基础命令
SET role:10001 '{["name"]:"mark",["sex"]:"male",["age"]:30}'
GET role:10001
批量设置值
mset key1 value1 key2 value2 ... :
一次性设置多个键值对。例如 mset user:1:name "Bob" user:1:age "30"
,可同时设置两个键值对。这是原子操作,要么所有键值对都设置成功,要么都不成功 。
批量获取值
mget key1 key2 ... :一次性获取多个键对应的值。例如执行 mget user:1:name user:1:age ,会返回这两个键对应的值。对于不存在的键或非字符串类型值对应的键,返回 nil 。
追加值
append key value :如果键 key 存在,将 value 追加到该键原有的值后面;若键不存在,则创建一个新键,值为 value 。例如,先执行 set city "Bei" ,再执行 append city "jing" ,此时 get city 会返回 "Beijing"
。
位运算
bash
# 月签到功能 10001 用户id 202106 2021年6月份的签到 6月份的第1天
setbit sign:10001:202106 1 1
# 计算 2021年6月份 的签到情况
bitcount sign:10001:202106
# 获取 2021年6月份 第二天的签到情况 1 已签到 0 没有签到
getbit sign:10001:202106 2
setbit :命令名称,用于设置或清除键对应字符串值中特定偏移量处的位。
key :这里是 sign:10001:202106 ,表示要操作的键 。键在 Redis 中用于唯一标识数据,通过它可定位到对应的值进行操作。
offset :此例中为 1 ,指要修改的位的偏移量(位置 )。偏移量从 0 开始计数,基于位索引而非字节索引。比如要操作第 3 个位,offset 就写 2 。
value :这里是 1 ,表示要将指定偏移量处的位设置成的值 ,只能是 0 或 1 。1 代表设置该位为 1 ,0 代表设置该位为 0 。
签到功能:像 setbit sign:10001:202106 1 1 ,可以表示用户 10001 在 2021 年 6 月第 2 天(因为 offset 从 0 开始 )签到(设定位值为 1 代表签到 )。后续可结合 bitcount 命令统计该用户当月签到天数,或用 getbit 命令获取某一天签到状态。
2.1 list
双向链表实现,列表首尾操作(删除和增加)时间复杂度 O(1) ;查找中间元素时间复杂度为O(n) ;
列表中数据是否压缩的依据:
- 元素长度小于 48,不压缩;
- 元素压缩前后长度差不超过 8,不压缩;
基础命令
bash
redis 127.0.0.1:6379> RPUSH mylist "hello"
(integer) 1
redis 127.0.0.1:6379> RPUSH mylist "foo"
(integer) 2
redis 127.0.0.1:6379> RPUSH mylist "bar"
(integer) 3
redis 127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "foo"
3) "bar"
右推左出 实现了一个FIFO 先入先出
bash
redis 127.0.0.1:6379> LPUSH list1 "foo"
(integer) 1
redis 127.0.0.1:6379> LPUSH list1 "bar"
(integer) 2
redis 127.0.0.1:6379> LRANGE list1 0 -1
1) "bar"
2) "foo"
实现了栈 先进后出
2.3 hash
散列表,在很多高级语言当中包含这种数据结构;c++ unordered_map 通过 key 快速索引value;
节点数量大于 512(hash-max-ziplist-entries) 或所有字符串长度大于 64(hash-max-ziplistvalue),则使用 dict 实现;
节点数量小于等于 512 且有一个字符串长度小于 64,则使用 ziplist 实现
基础命令
bash
# 获取 key 对应 hash 中的 field 对应的值
HGET key field
# 设置 key 对应 hash 中的 field 对应的值
HSET key field value
# 设置多个hash键值对
HMSET key field1 value1 field2 value2 ... fieldn valuen
# 获取多个field的值
HMGET key field1 field2 ... fieldn
# 给 key 对应 hash 中的 field 对应的值加一个整数值
HINCRBY key field increment
# 获取 key 对应的 hash 有多少个键值对
HLEN key
# 删除 key 对应的 hash 的键值对,该键为field
HDEL key field
综合示例
bash
127.0.0.1:6379> HMSET user:1001 name "Alice" age 25 email "[email protected]"
OK
127.0.0.1:6379> HGET user:1001 name
"Alice"
127.0.0.1:6379> HMGET user:1001 name age
1) "Alice"
2) "25"
127.0.0.1:6379> HINCRBY user:1001 age 1
(integer) 26
127.0.0.1:6379> HGET user:1001 age
"26"
127.0.0.1:6379> HLEN user:1001
(integer) 3
127.0.0.1:6379> HDEL user:1001 email
(integer) 1
127.0.0.1:6379> HLEN user:1001
(integer) 2
127.0.0.1:6379> HGET user:1001 email
(nil)
2.4 set
集合;用来存储唯一性字段,不要求有序;
命令
bash
SADD key member [member ...]
# 计算集合元素个数
SCARD key
# SMEMBERS key
SMEMBERS key
# 返回成员 member 是否是存储的集合 key的成员
SISMEMBER key member
# 随机返回key集合中的一个或者多个元素,不删除这些元素
SRANDMEMBER key [count]
# 从存储在key的集合中移除并返回一个或多个随机元素
SPOP key [count]
# 返回一个集合与给定集合的差集的元素
SDIFF key [key ...]
# 返回指定所有的集合的成员的交集
SINTER key [key ...]
# 返回给定的多个集合的并集中的所有成员
SUNION key [key ...]
综合示例
bash
127.0.0.1:6379> SADD set1 apple banana cherry
(integer) 3
127.0.0.1:6379> SADD set2 banana date elderberry
(integer) 3
127.0.0.1:6379> SCARD set1
(integer) 3
127.0.0.1:6379> SISMEMBER set1 apple
(integer) 1
127.0.0.1:6379> SRANDMEMBER set1
"apple"
127.0.0.1:6379> SDIFF set1 set2
1) "apple"
2) "cherry"
127.0.0.1:6379> SINTER set1 set2
1) "banana"
127.0.0.1:6379> SUNION set1 set2
1) "cherry"
2) "date"
3) "banana"
4) "apple"
5) "elderberry"
2.5 zset
有序集合;用来实现排行榜;它是一个有序唯一;
节点数量大于 128或者有一个字符串长度大于64,则使用跳表(skiplist);
节点数量小于等于128(zset-max-ziplist-entries)且所有字符串长度小于等于64(zset-maxziplist-value),则使用 ziplist 存储;
基础命令
bash
# 添加到键为key有序集合(sorted set)里面
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
# 从键为key有序集合中删除 member 的键值对
ZREM key member [member ...]
# 返回有序集key中,成员member的score值
ZSCORE key member
# 为有序集key的成员member的score值加上增量increment
ZINCRBY key increment member
# 返回key的有序集元素个数
ZCARD key
# 返回有序集key中成员member的排名
ZRANK key member
# 返回存储在有序集合key中的指定范围的元素 order by id limit 1,100
ZRANGE key start stop [WITHSCORES]
# 返回有序集key中,指定区间内的成员(逆序)
ZREVRANGE key start stop [WITHSCORES]
综合示例
bash
127.0.0.1:6379> ZADD ranklist 100 "Alice" 200 "Bob" 150 "Charlie"
(integer) 3
127.0.0.1:6379> ZADD ranklist NX 250 "David"
(integer) 1
127.0.0.1:6379> ZINCRBY ranklist 50 "Alice"
"150"
127.0.0.1:6379> ZREVRANGE ranklist 0 -1 WITHSCORES
1) "David"
2) "250"
3) "Bob"
4) "200"
5) "Charlie"
6) "150"
7) "Alice"
8) "150"
127.0.0.1:6379> ZREVRANGE ranklist 0 -1 WITHSCORES
1) "David"
2) "250"
3) "Bob"
4) "200"
5) "Charlie"
6) "150"
7) "Alice"
8) "150"
NX:NX模式是Not eXists的缩写,代表 "仅当元素不存在时添加"。使用NX模式时,如果要添加的成员在有序集合中已经存在,那么该成员的分数不会被更新,也不会再次添加该成员
3 redis的连接
3.1 redis网络层

Redis 的并发
- 原理:Redis 基于事件驱动的reactor模式。它使用一个事件循环来监听多个客户端连接的事件(如连接建立、数据到达等 )。当有多个客户端连接时,它会将这些连接产生的事件放入队列中,通过事件循环来快速切换处理不同连接的事件,实现对多个连接的数据并发处理。从概念上类似图中 "并发" 示意图,多个任务(连接请求 )看似同时进行处理 。这里说的并发,符合文字中 "活跃队列的个数大于处理器的个数",即有多个任务队列等待处理器处理 。
- 优势:能高效利用系统资源,在单线程模型下(Redis 单线程指处理命令逻辑是单线程 ),处理大量客户端的请求,提升系统的吞吐量和响应性能。例如在高并发的电商秒杀场景中,能同时处理众多用户的请求。
Redis 的串行
- 原理:对于单条连接,Redis 处理数据是串行的。就像图中 "串行" 示意图,任务按顺序依次处理 。在同一条连接上,客户端发送的多个命令会按照先后顺序依次执行,前一个命令执行完,才会执行下一个命令。这是因为 Redis 为了保证数据操作的原子性和一致性,避免出现并发访问导致的数据错乱问题。
- 好处:简化了数据处理逻辑和事务控制。比如在使用 Redis 实现分布式锁时,基于这种串行执行特性,能确保锁操作的正确性和可靠性。
3.2 Redis Pipeline
基本概念
Redis Pipeline 是客户端提供的功能,并非服务端提供 。它允许客户端将多个命令一次性打包发送到 Redis 服务端,而不用等待每个命令的响应后再发送下一个命令。服务端会按顺序依次处理这些命令,然后将所有结果一次性返回给客户端。
原理及优势
- 原理:客户端执行请求操作时,只是把数据写入到对应的写缓冲区,这个过程很快。真正耗时的是读取响应阶段。通过 Pipeline,能减少客户端与服务端之间的网络往返次数(如图片中对比常规单次请求 - 响应模式 )。常规模式下每个命令都有一次网络往返,而 Pipeline 模式下多个命令只有一次网络往返 。
- 优势:显著提升执行多个命令的性能和效率,尤其在需- 要执行大量命令且网络延迟较高的场景中。比如批量设置大量键值对时,使用 Pipeline 可大幅缩短操作时间。
c++
#include <iostream>
#include <string>
#include <vector>
#include <hiredis/hiredis.h>
// 执行 Redis Pipeline 命令
std::vector<std::string> executePipeline(redisContext* context, const std::vector<std::string>& commands) {
std::vector<std::string> results;
// 开始 Pipeline
for (const auto& command : commands) {
if (redisAppendCommand(context, "%s", command.c_str()) != REDIS_OK) {
std::cerr << "Failed to append command: " << command << std::endl;
return results;
}
}
// 执行 Pipeline 中的所有命令
for (size_t i = 0; i < commands.size(); ++i) {
redisReply* reply;
if (redisGetReply(context, reinterpret_cast<void**>(&reply)) != REDIS_OK) {
std::cerr << "Failed to get reply for command: " << commands[i] << std::endl;
continue;
}
if (reply->type == REDIS_REPLY_STATUS || reply->type == REDIS_REPLY_STRING) {
results.emplace_back(reply->str);
} else if (reply->type == REDIS_REPLY_INTEGER) {
results.emplace_back(std::to_string(reply->integer));
} else {
results.emplace_back("Unknown reply type");
}
freeReplyObject(reply);
}
return results;
}
int main() {
// 连接到 Redis 服务器
redisContext* context = redisConnect("127.0.0.1", 6379);
if (context == nullptr || context->err) {
if (context) {
std::cerr << "Error: " << context->errstr << std::endl;
redisFree(context);
} else {
std::cerr << "Can't allocate redis context" << std::endl;
}
return 1;
}
// 定义要执行的 Pipeline 命令
std::vector<std::string> commands = {
"SET key1 value1",
"SET key2 value2",
"GET key1"
};
// 执行 Pipeline
std::vector<std::string> results = executePipeline(context, commands);
// 输出结果
for (size_t i = 0; i < results.size(); ++i) {
std::cout << "Result of command " << commands[i] << ": " << results[i] << std::endl;
}
// 释放 Redis 上下文
redisFree(context);
return 0;
}
3.3 redis 的事务
Redis 事务允许将多个命令打包,然后一次性、按顺序地执行
MULTI :用于开始一个事务,调用该命令后,后续输入的所有命令不会立即执行,而是被放入一个队列,客户端会收到回复表示命令已成功入队,返回 QUEUED 。
EXEC :执行事务队列中的所有命令,并一次性将所有结果返回给客户端。即使事务中的某个命令执行失败,Redis 仍会继续执行剩余命令。
DISCARD :取消事务,会清空事务队列并退出事务状态。
WATCH key [key ...] :在事务开始前监视一个或多个键。当执行 EXEC 时,若任何被监视的键被其他客户端修改,整个事务会被取消。可理解为一种乐观锁机制,避免事务执行过程中相关数据被篡改。
UNWATCH :取消对所有键的监视。
cpp
#include <iostream>
#include <string>
#include <hiredis/hiredis.h>
int main() {
// 连接到 Redis 服务器
redisContext* context = redisConnect("127.0.0.1", 6379);
if (context == nullptr || context->err) {
if (context) {
std::cerr << "Error: " << context->errstr << std::endl;
redisFree(context);
} else {
std::cerr << "Can't allocate redis context" << std::endl;
}
return 1;
}
// 模拟初始账户余额
redisCommand(context, "SET account:1:balance 100");
redisCommand(context, "SET account:2:balance 200");
while (true) {
// 监视账户余额键
redisCommand(context, "WATCH account:1:balance account:2:balance");
redisReply* reply = static_cast<redisReply*>(redisCommand(context, "GET account:1:balance"));
int balance1 = std::stoi(reply->str);
freeReplyObject(reply);
if (balance1 < 50) {
redisCommand(context, "UNWATCH");
std::cout << "余额不足,无法转账。" << std::endl;
break;
}
// 开启事务
redisCommand(context, "MULTI");
// 账户 1 扣除 50
redisCommand(context, "DECRBY account:1:balance 50");
// 账户 2 增加 50
redisCommand(context, "INCRBY account:2:balance 50");
// 执行事务
reply = static_cast<redisReply*>(redisCommand(context, "EXEC"));
if (reply != nullptr && reply->type == REDIS_REPLY_ARRAY) {
std::cout << "转账成功。" << std::endl;
freeReplyObject(reply);
break;
}
if (reply) {
freeReplyObject(reply);
}
}
// 输出转账后的余额
redisReply* reply = static_cast<redisReply*>(redisCommand(context, "GET account:1:balance"));
std::cout << "账户 1 余额: " << reply->str << std::endl;
freeReplyObject(reply);
reply = static_cast<redisReply*>(redisCommand(context, "GET account:2:balance"));
std::cout << "账户 2 余额: " << reply->str << std::endl;
freeReplyObject(reply);
// 释放 Redis 上下文
redisFree(context);
return 0;
}
事务特性
- 原子性:事务中的命令会序列化并连续执行,一般情况下要么全部执行,要么全部不执行。不过存在例外,如运行时错误(例如对字符串类型执行集合类型命令 ),此时错误命令会被忽略,其他命令仍正常执行,无法完全保证原子性。而语法错误(如命令拼写错误 )时,整个事务都不会执行,能保证原子性(全部不执行 )。
- 隔离性:Redis 单线程执行命令,事务执行期间不会被其他客户端的命令打断,所有命令按顺序串行执行,天然保证隔离性。并且可通过 WATCH 实现乐观锁,进一步避免其他客户端修改关键数据。
- 一致性:无论事务成功或失败,数据始终保持合法状态,如语法错误时回滚(全部不执行 )、运行时错误时保留正确操作。
- 持久性:事务的持久性依赖持久化机制。若开启 AOF 且配置为 always 模式,事务完成后数据立即持久化;否则无法保证。
3.4 redis 发布订阅
Redis 的发布 - 订阅(Pub/Sub)是一种消息通信模式,它允许客户端之间进行消息的发送和接收,实现了消息的生产者和消费者之间的解耦。
基本概念
- 发布者(Publisher):负责生产消息并将其发送到指定的频道(Channel)。发布者不需要知道有哪些订阅者存在,只需要将消息发送到频道即可。
- 订阅者(Subscriber):对特定的频道感兴趣,会订阅这些频道以接收发布者发送到该频道的消息。订阅者可以同时订阅多个频道,也可以动态地添加或取消订阅。
- 频道(Channel):是消息的逻辑容器,用于区分不同类型的消息。发布者将消息发布到特定的频道,订阅者则从感兴趣的频道接收消息。
命令操作
SUBSCRIBE channel [channel ...]:订阅一个或多个频道。例如,SUBSCRIBE news sports 表示订阅 news 和 sports 两个频道。
PUBLISH channel message:向指定的频道发布一条消息。例如,PUBLISH news "New article published" 表示向 news 频道发布一条消息。
UNSUBSCRIBE [channel [channel ...]]:取消订阅一个或多个频道。如果不指定频道,则取消所有订阅。
PSUBSCRIBE pattern [pattern ...]:订阅一个或多个符合指定模式的频道。例如,PSUBSCRIBE news.* 表示订阅所有以 news. 开头的频道。
PUNSUBSCRIBE [pattern [pattern ...]]:取消订阅一个或多个符合指定模式的频道。如果不指定模式,则取消所有基于模式的订阅。
综合示例
bash
# 客户端 1:订阅 news 和 sports 频道
SUBSCRIBE news sports
# 客户端 2:订阅以 tech. 开头的所有频道
PSUBSCRIBE tech.*
# 客户端 3:向 news 频道发布一条消息
PUBLISH news "Breaking news: New law passed"
# 客户端 1 会收到这条消息
# 客户端 3:向 tech.ai 频道发布一条消息
PUBLISH tech.ai "New AI algorithm discovered"
# 客户端 2 会收到这条消息
# 客户端 1:取消订阅 news 频道
UNSUBSCRIBE news
# 客户端 2:取消订阅以 tech. 开头的所有频道
PUNSUBSCRIBE tech.*
4 redis的持久化
Redis 是内存数据库,数据都存储在内存中,一旦服务器进程退出或发生故障,内存中的数据就会丢失。为了解决这个问题,Redis 提供了持久化机制,将内存中的数据保存到磁盘上,以便在服务器重启后能够恢复数据。以下是 Redis 两种主要的持久化方式
4.1 RDB
RDB 持久化是将 Redis 在某个时间点上的数据集快照保存到磁盘上 ,生成一个二进制文件(默认名为 dump.rdb)。这个过程可以手动触发,也可以根据配置的规则自动触发。
触发方式
- 手动触发:
SAVE 命令:阻塞 Redis 服务器进程,直到 RDB 文件创建完毕。在阻塞期间,服务器不能处理任何客户端请求。
BGSAVE 命令:派生出一个子进程来创建 RDB 文件,服务器进程继续处理客户端请求。子进程创建 RDB 文件完成后会向父进程发送信号,通知完成。 - 自动触发:
通过配置文件 redis.conf 中的 save 选项来设置自动触发的条件。例如,save 900 1 表示在 900 秒(15 分钟)内,如果至少有 1 个键被修改,则自动触发 BGSAVE 操作。
4.2 AOF(Append Only File)持久化
原理
AOF 持久化是将 Redis 执行的所有写命令追加到一个文件(默认名为 appendonly.aof)的末尾。在服务器重启时,Redis 会重新执行 AOF 文件中的命令来恢复数据。
配置选项
appendonly:是否开启 AOF 持久化,默认为 no,需要将其设置为 yes 来开启。
appendfsync:控制 AOF 文件的同步频率,有以下三个可选值:
- always:每次执行写命令后都将 AOF 文件同步到磁盘,数据安全性最高,但性能开销最大。
- everysec:每秒将 AOF 文件同步到磁盘一次,是一种折中方案,兼顾了数据安全性和性能。
- no:由操作系统决定何时将 AOF 文件同步到磁盘,性能最好,但数据安全性最低。
重写机制
随着 Redis 不断执行写命令,AOF 文件会越来越大,为了避免文件过大,Redis 提供了 AOF 重写机制。AOF 重写是指将 Redis 内存中的数据以最小的命令序列重新写入到一个新的 AOF 文件中,去除冗余的命令,减小文件大小.
混合持久化(Redis 4.0 及以上)
Redis 4.0 引入了混合持久化的方式,结合了 RDB 和 AOF 的优点。在开启混合持久化后,Redis 在进行 AOF 重写时,会将当前时间点的 RDB 快照数据以二进制形式写入到新的 AOF 文件开头,然后再将重写期间产生的写命令以文本形式追加到文件末尾。这样在恢复数据时,先加载 RDB 快照部分,再执行 AOF 文件中的剩余命令,既保证了数据恢复的速度,又减少了数据丢失的风险。
5 redis的主从复制
主要用来实现 redis 数据的可靠性;防止主 redis 所在磁盘损坏,造成数据永久丢失;
主从之间采用异步复制的方式;
5.1 基础知识
全量数据同步

主从初次复制时,主服务器将快照文件( RDB)和缓冲的命令合并后发给从服务器,从服务器接收并加载后,完成主从数据同步。
缓冲的命令是指主服务器在执行写操作时,将这些写命令暂时存储在一个缓冲区中,以便稍后发送给从服务器
增量数据同步
全量复制后,主服务器将后续执行的写命令追加到缓冲区,从服务器定期请求缓冲区中的命令并执行,保证数据一致性。
服务器 RUN ID
无论主库还是从库都有自己的 RUN ID,RUN ID 启动时自动产生,RUN ID 由40个随机的十六进制字符组成;
当从库对主库初次复制时,主库将自身的 RUN ID 传送给从库,从库会将 RUN ID 保存;
当从库断线重连主库时,从库将向主库发送之前保存的 RUN ID;
从库 RUN ID 和主库 RUN ID 一致,说明从库断线前复制的就是当前的主库;主库尝试执行增量同步操作;
若不一致,说明从库断线前复制的主库并不时当前的主库,则主库将对从库执行全量同步操作
5.1 Redis 哨兵模式
哨兵模式是一种高可用性( High Availability)解决方案,旨在解决 Redis 主从复制架构中的单点故障问题。哨兵模式由多个哨兵( Sentinel)实例组成,它们共同监控主服务器( Master)和从服务器( Slave),在主服务器出现故障时自动进行故障转移( Failover),将其中一个从服务器提升为新的主服务器。

哨兵实例( Sentinel Instances):图中有三个哨
兵实例( sentinel1、 sentinel2、 sentinel3)。这
些哨兵实例共同监控主服务器和从服务器的状
态。
主服务器( Master Redis):负责处理写操作和
读操作,从服务器会从主服务器复制数据。
从服务器( Slave Redis):负责处理读操作,
从主服务器复制数据以保持数据一致性。
客户端( Client):与 Redis 服务器进行交互的
应用程序。
流程
sentinel 会以每秒一次的频率向所有节点(其他sentinel、主节点、以及从节点)发送 ping 消
息,然后通过接收返回判断该节点是否下线;如果在配置指定 down-after-milliseconds 时间内则被判断为主观下线
当一个 sentinel 节点将一个主节点判断为主观下线之后,为了确认这个主节点是否真的下线,它
会向其他sentinel 节点进行询问,如果收到一定数量的已下线回复,sentinel 会将主节点判定为客观下线,并通过领头 sentinel 节点对主节点执行故障转移
节点被判定为客观下线后,开始领头 sentinel 选举,需要一半以上的 sentinel 支持,选举领头sentinel后,开始执行对主节点故障转移;从从节点中选举一个从节点作为新的主节点通知其他从节点复制连接新的主节点
若故障主节点重新连接,将作为新的主节点的从节点