文章目录
前言
本文主要讨论Redis的持久化方式和复制特性。Redis的持久化方式有两种,一种叫RDB(Redis Database Backup),一种叫只追加文件AOF(append-only-file)。Redis的复制特性主要是用来做Redis集群时,保证各个服务器数据一致性,下面笔者分别展开详细介绍。
一、Redis持久化
1.1 RDB快照
我不知道国内是怎么翻译的,国外的相关技术书籍描述的快照是snapshotting,可能是为了和AOF保证一致的缩写?暂且不管它吧,先说说这种持久化方式的机制。
其实从Redis Database Backup这个名字就能看出来,就是Redis数据库的备份,会在指定时间点创建一个Redis数据库的完整快照,把这个快照存到硬盘。
以下是对 RDB 快照的一些详细说明:
-
定义:
RDB 快照是在某一时间点上数据库的一个完整副本
。 -
触发机制:
- 手动触发:
使用 SAVE 命令或 BGSAVE 命令
。 - 自动触发:
通过配置文件中的 save 指令设置条件,当满足这些条件时,Redis 自动执行快照
。
- 手动触发:
-
优缺点:
- 优点:
恢复速度快,占用较少的磁盘空间
。 - 缺点:
数据可能存在丢失,因为只保存了某个时间点的数据
。
- 优点:
手动触发
以下是一个简单代码的实现,演示手动触发RDB快照持久化:
package com.hl.redisdemo;
import redis.clients.jedis.Jedis;
public class RedisRDBSnapshot {
public static void main(String[] args) {
// 创建 Jedis 实例
try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {
// 设置一些键值对
jedis.set("key1", "value1");
jedis.set("key2", "value2");
// 手动触发 RDB 快照
String info = jedis.bgsave(); // 触发后台保存 RDB 文件
System.out.println("RDB 快照已触发: " + info);
// 查询 RDB 文件保存位置
String dir = jedis.configGet("dir").get(1);
String dbfilename = jedis.configGet("dbfilename").get(1);
System.out.println("RDB 文件保存目录: " + dir);
System.out.println("RDB 文件名称: " + dbfilename);
// 获取 RDB 文件的信息
String rdbInfo = jedis.info("persistence");
System.out.println("RDB 信息:\n" + rdbInfo);
} catch (Exception e) {
System.err.println("执行 RDB 持久化时发生错误: " + e.getMessage());
}
}
}
运行上述代码,控制台打印信息如下:
可以看到已经手动触发生成了一个RDB快照,并存储到磁盘了,目录为 D:\DevloperSofts\redis,名称为dump.rdb,去磁盘该路径下查看可以看到生成了一个持久化的备份文件。
自动触发
笔者是windows系统安装的Redis,找到配置文件 redis.windows-service.conf
这几个命令就是设置自动触发RDB的条件
save 900 1
save 300 10
save 60 10000
-
save 900 1:
如果在 900 秒(15 分钟)内至少有一个键被更改,则触发一次 RDB 快照。 -
save 300 10:
如果在 300 秒(5 分钟)内至少有 10 个键被更改,则触发一次 RDB 快照。 -
save 60 10000:
如果在 60 秒(1 分钟)内至少有 10000 个键被更改,则触发一次 RDB 快照。
触发机制
当 Redis 检测到任何满足上述条件之一时,它将自动触发 RDB 快照。如果多个条件都被满足,Redis 只会触发一次快照,而不是多次。
注意事项
-
如果你设置了多个 save 指令,Redis 将会检查所有条件,一旦任何一个条件满足,就会触发 RDB 快照。
-
为了防止频繁触发 RDB 快照,可以考虑增加时间间隔或更改键的数量。
-
确保目标目录存在并且 Redis 服务器有足够的权限写入文件
当然完整的配置我们可以这么写
# RDB 相关配置
dir /var/lib/redis
dbfilename dump.rdb
save 900 1
save 300 10
save 60 10000
# 其他配置
...
使用 dir 指令来指定 RDB 文件的保存目录
。使用 dbfilename 指令来指定 RDB 文件的名称
。
下面我把配置改成如下,在E盘输出备份快照,30秒内只要有一个键被修改,就会自动触发,修改 redis.windows-service.conf
为什么修改的是 redis.windows-service.conf
,而不是 redis.windows.conf
?接着往下看
笔者在Windows系统下安装的Redis,并把Redis注册成了系统服务,满足开机自动的要求,从上图可以看到其实有两个配置文件:redis.windows.conf
和redis.windows-service.conf
。
redis.windows.conf 和 redis.windows-service.conf 是 Redis 在 Windows 平台上使用的两种不同配置文件,它们的主要区别在于服务启动的方式以及某些配置项的差异。
-
redis.windows.conf
- 这个配置文件主要用于手动启动 Redis 服务器。
- 当你需要通过命令行手动启动 Redis 服务时,会使用这个配置文件。
- 通常情况下,如果你不是通过 Windows 服务管理器来启动 Redis,而是直接运行 redis-server.exe redis.windows.conf,那么就会用到这个配置文件。
-
redis.windows-service.conf
- 这个配置文件用于将 Redis 作为一个 Windows 服务来自动启动。
- 当你希望 Redis 随操作系统启动而自动启动时,会使用这个配置文件。
- 通常情况下,如果通过 Windows 服务管理器注册 Redis 为一个服务,那么会用到这个配置文件。
这两种配置文件在默认情况下可能有相同的设置,但是根据实际需求,你可以对它们进行不同的配置。例如,在作为服务运行时,你可能需要不同的日志记录设置或者资源限制等。
当你安装 Redis 并希望将其设置为 Windows 服务时,通常会使用 redis.windows-service.conf 文件,并且可以通过命令行工具 redis-server.exe --service-install redis.windows-service.conf 来完成服务的安装。同样地,如果你想卸载服务,可以使用 redis-server.exe --service-uninstall 命令。
通过win+R键,输入 services.msc
,找到Redis服务,重启Redis服务,再使用Jedis或者原生Redis命令随便修改一个键值,等待30秒观察结果,笔者的E盘根目录看到如下结果:
通过上图可以看到,已经自动生成了RDB的快照文件了。
save和bgsave的区别
Redis 中的 SAVE 和 BGSAVE 命令都是用于创建当前数据库的备份,具体来说,它们是在 RDB(Redis Database Backup)快照模式下进行数据持久化的两种方式。下面是它们之间的主要区别:
SAVE
-
SAVE 命令执行一个同步保存操作,将当前 Redis 实例的所有数据快照(snapshot)以 RDB 文件的形式保存到硬盘。
-
在执行过程中,Redis 主线程会被阻塞,直到保存操作完成。
-
在阻塞期间,服务器不能处理客户端的任何请求。
-
由于阻塞主线程,可能会导致性能问题,尤其是在数据量大或硬件较慢的情况下。
BGSAVE
-
BGSAVE 命令执行后立即返回 OK,然后 Redis 会 fork 出一个新子进程。
-
原来的 Redis 进程(父进程)继续处理客户端请求,而子进程则负责将数据保存到磁盘,然后退出。
-
Redis 服务器在 BGSAVE 执行期间仍然可以继续处理客户端的请求。
使用 BGSAVE 可以避免阻塞主线程,提高系统的可用性。
总结
-
SAVE 命令简单直接,但在数据量较大时可能导致服务不可用。
-
BGSAVE 命令更适用于生产环境,因为它不会阻塞主线程,可以保证服务的连续性。
使用场景
-
如果需要快速备份数据并且不关心短暂的服务中断,可以选择使用 SAVE。
-
如果需要在不影响服务的情况下备份数据,则应使用 BGSAVE。
注意事项
-
在使用 BGSAVE 时,如果 Redis 数据集非常大,fork 操作可能会消耗大量的 CPU 资源,因此在高负载情况下,频繁的 BGSAVE 可能会导致性能下降。
-
如果 BGSAVE 在执行过程中 Redis 重启或崩溃,可以通过 LASTSAVE 命令查看最后一次成功保存的时间戳,以评估数据丢失的可能性。
1.2 AOF持久化
简单来讲,AOF持久化是把被执行的写命令追加到AOF文件的末尾,以此来记录数据发生的变化。所以Redis只要从头到尾执行一次AOF文件中的所有写命令,就可以恢复AOF文件记录的所有数据集。
下面是AOF持久化的几个关键点:
-
命令追加:Redis服务器在执行写操作命令时,会将命令以追加的方式写入到AOF文件的末尾。这样做的好处是可以详细记录下导致数据变更的每一步操作,便于故障恢复时重放这些操作来重建数据状态。
-
AOF文件的写入策略:通过配置appendfsync参数,用户可以选择AOF日志同步到磁盘的策略。有三种策略可选:
always
:每次写操作后立即同步到磁盘,最安全,但性能开销最大。everysec
:每秒同步一次,折衷方案,性能和安全性相对平衡。no
:由操作系统决定何时同步,性能最好,但数据安全性最低。
-
AOF重写:随着Redis的操作增多,AOF文件可能会变得非常大,这会影响到Redis的重启时间和数据恢复速度。AOF重写机制允许Redis创建一个新的AOF文件,这个新文件只包含重建当前数据集所需的最小命令集合。重写可以通过BGREWRITEAOF命令手动触发,或通过配置自动触发。
-
AOF文件载入和数据还原:当Redis服务器重启时,如果有AOF文件存在,Redis会优先使用AOF文件来恢复数据,而不是RDB文件。这是因为AOF记录了更完整的历史操作,可以恢复到服务器关闭前的最后状态。
-
异常处理:在AOF重写或正常同步过程中如果遇到错误,Redis提供了若干机制来确保数据的完整性和一致性,比如部分重写失败时回滚到之前的AOF文件等。
-
使用 AOF 的场景
当数据安全性非常重要时,通常会选择 AOF
。如果应用程序能够容忍少量的数据丢失,并且更关心性能,可以选择 RDB 或者同时使用 RDB 和 AOF
。
AOF日志同步策略中的always策略,这种策略会造成大量的磁盘写入操作,而且是同步的,性能会受到磁盘性能限制。固态硬盘SSD就比轮转式硬盘性能高很多,但是我们的硬盘如果是SSD,SSD每秒可以支持写入几万条命令。若我们使用aways策略时,我们需要注意这种策略会让Redis每次只写入一个命令,命令特别多时,大量的写入会严重降低SSD硬盘的寿命。为了兼顾数据安全和性能通常我们会选择折中的策略,即everysec
。
appendonly配置
**redis.windows-service.conf
**配置文件中修改appendonly为true。
下图所示配置本来是no,改为yes
然后重启Redis服务即可。笔者提供了一个演示AOF的示例代码
package com.hl.redisdemo;
import redis.clients.jedis.Jedis;
public class AofDemo {
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
// 设置键值对
String key = "testKey";
String value = "你好,AOF!";
String result = jedis.set(key, value);
System.out.println("设置结果: " + result);
// 获取键值对
String getValue = jedis.get(key);
System.out.println("获取结果: " + getValue);
// 检查AOF是否启用
String aofStatus = jedis.configGet("appendonly").get(1);
System.out.println("AOF 状态: " + aofStatus);
// 查询AOF文件的保存路径
String dirPath = jedis.configGet("dir").get(1);
System.out.println("AOF 文件保存路径: " + dirPath);
// AOF文件名默认为 appendonly.aof
String aofFileName = "appendonly.aof";
System.out.println("AOF 文件完整路径: " + dirPath + "/" + aofFileName);
// 如果没有启用,可以尝试通过命令行工具启动AOF
// 注意:这里只是示例,实际部署中应该在配置文件中设置
if (!"yes".equals(aofStatus)) {
System.out.println("正在启用 AOF...");
// 在生产环境中,这一步应该在部署前完成
// jedis.configSet("appendonly", "yes");
}
}
}
}
运行结果如下
也是在E盘,和之前RDB设置的dir路径配置是一样的,两种方式共用一个日志存储路径
实际开发中应该根据需求决定使用哪种方式,二者各自有适用场景,当然也可以综合使用。
二、Redis复制
在面对高负载时,关系数据库通常会使用一个主服务器向多个从服务器发送数据更新,主数据库负责写,从数据库负责读请求,二者之间使用一些同步机制实现数据的同步。Redis也使用了类似的方法来实现复制特性,作为一种支持性能扩展的手段。
Redis性能尽管十分优秀,但是总会有瓶颈,在对集合Set和有序集合Zset处理时,元素可能有几万甚至上百万个,这时执行操作的时间要以秒来计算了。即使一个命令10毫秒,单个Redis实例1秒也只能执行100个命令。
在我们需要扩展读请求时,或者在需要写入临时数据时,我们可以设置另外的Redis从服务器来保存数据收集的副本。在接收到主服务器发送的数据初始副本时,客户端每次向主服务器进行写入时,从服务器都会实时得到更新。部署好Redis主从服务器后,就可以向任意一个从服务器发送读请求了,不必再像之前一样,读请求都发给主服务器。客户端会随机选择使用哪个从服务器,从而把负载平均分配到各个从服务器上。
2.1 开启Redis主从复制
-
slaveof host port
slaveof host port 命令用于配置从服务器连接到主服务器,从而实现数据复制。当你运行这条命令时,从服务器会开始监听主服务器的更新,并将这些更新应用于自己的数据库。
slaveof <master-ip> <master-port>
master-ip和master-port分别对应主服务器节点的ip地址和端口号
slaveof no one
slaveof no one 命令用于解除从服务器与主服务器之间的复制关系。当你运行这条命令时,从服务器将不再复制主服务器的数据,并且可以独立地作为主服务器运行。
只需要使用命令在从服务器执行如下
slaveof no one
或者直接把从服务器配置文件的slaveof配置成如上结果即可。
下面笔者通过命令演示如何把Redis服务设置成从服务器和变成主服务器,实际开发中一般不这么干,都是通过修改配置文件操作的,不能这么随意的修改主从服务器。
-
查看初始服务器角色
可以看到初始角色role为master为主节点 -
设置为从服务器
执行命令SLAVEOF localhost 6378
,设置主服务器节点端口号为6378,再执行INFO replication
命令看到已经变成从节点了
- 恢复成主节点
执行SLAVEOF no one
,再次查看结果如下恢复成了主节点
2.2 Redis复制的启动过程
从服务器在连接一个主服务器时,主服务器会创建一个快照文件并将其发送到从服务器,这只是主从复制执行过程中的一步,下面提供一个表格完整说明主从复制时,主从服务器执行的所有操作。
步骤 | 主服务器操作 | 从服务器操作 |
---|---|---|
1 | 等待命令进入 | 连接主服务器,发送sync同步命令 |
2 | 开始执行BGSAVE,并使用缓冲区记录BGSAVE之后执行的所有写记录 | 根据配置选项来决定是继续使用现有数据(如果有),还是向发送请求的客户端返回错误 |
3 | BGSAVE执行完,向从服务器发送快照文件,并在发送期间继续使用缓冲区记录被执行的写命令 | 丢弃所有旧数据(如果有的话),开始载入主服务器发来的快照 |
4 | 快照文件发送完毕,开始向从服务器发送存储在缓冲区里的写命令 | 完成快照文件的解释操作后,开始正常接收命令请求 |
5 | 缓冲区存储的写命令发送完毕后;从现在开始,每接受一个写命令,就向从服务器发送相同的写命令 | 执行主服务器发来的缓冲区中的写命令;从现在开始,接收并执行主服务器发来的每个写命令 |
实际当中,Redis在复制过程中会尽可能的处理接收到的请求,这就要求我们一定要预留一定的内存,一般主服务器占内存在50%~65%左右,剩下的内存用来执行BGSAVE命令和创建记录写命令的缓冲区。