Redis是一种面向"key-value"类型数据的分布式NoSQL数据库系统,具有高性能、持久存储和适应高并发应用场景等诸多优势。尽管Redis的发展起步较晚,但它的发展速度迅猛。
探究Redis持久化的工作原理以及实现方式。通过深入理解Redis持久化,用户可以更好地了解Redis数据的持久化和可靠性,进而更加有效地使用和管理Redis数据库。
本篇文章对于解决关于Redis持久化的误解非常有帮助,可以为用户提供更为清晰和准确的理解。
Redis持久化是指将数据存储到在断电后数据不会丢失的设备中,通常指的是硬盘。简单来说,持久化就是将数据持久保存的过程。
现在我们来详细了解一下数据库在进行写操作时的具体过程,主要包括以下五个步骤:
以上就是数据库进行写操作时的基本流程。通过持久化机制,数据可以在断电情况下得到有效保护,从而保证数据的可靠性和持久性。
让我们来对故障进行分析。写操作经过了上述提到的五个步骤,下面我们结合这些步骤,分析不同级别的故障可能导致的影响:
当数据库系统发生故障时,系统内核仍然正常运行。在这种情况下,只要执行完第三步,数据就是安全的,因为后续的操作系统会完成后面的步骤,确保数据最终存储在磁盘上。
当系统断电时,上述五个步骤中提到的所有缓存都会失效,数据库和操作系统也会停止工作。因此,只有在完成第五步之后,才能确保在断电后数据不会丢失。
通过以上分析,我们可以看出,在数据库的写操作中,如果完成了第三步和第五步,数据就会得到保证。在故障发生时,正确的执行和处理这些步骤是确保数据安全和避免数据丢失的关键。
通过上述的五个步骤,我们可能会产生以下几个问题的疑惑:
对于这个问题,数据库通常会对此进行完全的控制。
操作系统会有默认的策略,但是我们也可以通过POSIX API提供的fsync系列命令来强制操作系统将数据从内核区写入磁盘控制器。
尽管看起来数据库无法直接触及,但实际上,在大多数情况下磁盘缓存会被关闭,或者仅用于读取缓存,也就是说,写操作直接写入磁盘而不进行缓存。建议的做法是,只有在磁盘设备具备备用电池的情况下才开启写缓存。
数据损坏是指数据无法恢复。在上面我们讨论了如何确保数据被写入磁盘,但是写入磁盘并不意味着数据不会损坏。举个例子,当发生意外时,可能只有一次写操作成功完成,而另一次写操作尚未完成。此时,如果数据库的数据文件结构组织不合理,可能导致数据无法完全恢复。
第一种策略是最基本的方法,即通过配置数据同步备份来保证数据的可恢复性。即使数据文件损坏,也可以通过数据备份来进行恢复。实际上,MongoDB在不开启操作日志的情况下,通过配置 Replica Sets 就是采用了这种策略。
另一种策略是在前面的基础上增加一个操作日志,每次操作都会记录操作的行为。通过操作日志,我们可以进行数据恢复。由于操作日志是按顺序追加的方式写入的,因此不会发生操作日志也无法恢复数据的情况。这也类似于MongoDB开启了操作日志的情况。
更加安全的做法是数据库不直接修改旧数据,而是以追加的方式进行写操作。这样数据本身就是一种日志,从而永远不会出现无法恢复数据的情况。实际上,CouchDB就是这种做法的一个优秀例子。
Redis有两种持久化策略:RDB快照和AOF日志。
RDB快照:Redis可以将当前数据生成一个数据文件作为持久化机制。为了生成快照,Redis使用了fork命令的copy on write机制。生成快照时,Redis会fork出一个子进程,并在子进程中循环遍历所有数据,然后将数据写入RDB文件。
AOF日志:AOF日志的全称是Append Only File,它是一个追加写入的日志文件。与一般数据库不同,AOF文件是一个可识别的纯文本文件,其中包含了一系列的Redis标准命令。通过启用AOF功能,Redis会将每个写操作都追加到AOF文件末尾,从而记录了数据库的完整操作历史。
RDB持久化是指将内存中的数据集以快照的方式写入磁盘,它是Redis的默认持久化方式。持久化文件的默认名称是"dump.rdb"。
在RDB机制中,可以通过配置来设置自动执行快照持久化的方式。我们可以配置Redis,在一定的时间间隔内,当有一定数量的键被修改时,自动执行快照持久化。以下是默认的快照保存配置示例:
save 900 1 # 在900秒内,如果有1个或更多键被修改,则执行快照保存
save 300 10 # 在300秒内,如果有10个或更多键被修改,则执行快照保存
save 60 10000 # 在60秒内,如果有10000个或更多键被修改,则执行快照保存
性能:RDB持久化采用了快照的方式,将整个数据集写入磁盘。相比于AOF(Append-Only File)持久化方式,RDB在恢复数据时的速度更快,因为只需加载一次文件即可。
占用空间较小:RDB持久化生成的快照文件是二进制文件,相比于AOF持久化生成的文本文件,占用的空间更小。
简单和灵活:RDB机制配置简单,适用于定期备份数据或将数据迁移到其他环境。
配置合理的保存策略:根据实际需求,可以调整保存策略的时间间隔和键的修改数量,以平衡数据可靠性和性能效率。
定期监测和验证RDB文件的完整性:定期检查RDB文件是否完整,以确保在恢复数据时不会出现问题。
定期备份RDB文件:为了防止意外情况下的数据丢失,定期将RDB文件备份到其他位置。
Redis调用fork命令后,会生成一个子进程和一个父进程。
父进程继续处理客户端请求,而子进程负责将内存中的数据写入临时文件。
由于操作系统的写时复制机制(copy on write),父进程和子进程会共享相同的物理数据页。当父进程要修改数据页时,操作系统会为其创建一个副本,而不是直接写入共享的页面。因此,子进程的地址空间中的数据是fork时刻整个数据库的一个快照。
客户端也可以使用save或者bgsave命令通知Redis执行快照持久化操作。
每次快照持久化都会将内存数据完整地写入磁盘,并非增量同步脏数据。如果数据量较大且有大量写操作,将会引发大量的磁盘IO操作,可能严重影响性能。因此,在性能敏感的情况下,需要考虑其他持久化方式或配置适当的快照保存策略,以减少对性能的影响。
注意,save操作是在主线程中执行的,而Redis使用单线程来处理所有客户端请求。这种方式会阻塞所有客户端请求,因此不推荐使用。
方便备份:采用RDB方式后,整个Redis数据库只包含一个文件,非常方便进行备份。例如,可以每天归档一些数据,只需移动一个RDB文件到其他存储介质即可。
快速恢复:RDB在恢复大数据集时的速度比AOF恢复速度更快,能够迅速还原数据库状态。
最大化性能:RDB可以最大化Redis的性能。父进程在保存RDB文件时只需fork出一个子进程,然后子进程负责处理接下来的保存工作,父进程无需执行磁盘IO操作,减少了性能开销。
数据丢失风险:如果需要尽量避免在服务器故障时丢失数据,RDB并不适合。由于RDB文件需要保存整个数据集的状态,保存频率较低,可能导致在故障停机时丢失几分钟的数据。
高耗时:每次保存RDB时,Redis都会fork出一个子进程来进行实际的持久化工作。当数据集较大时,fork操作可能耗时较长,导致服务器在某段时间内停止处理客户端请求。如果数据集非常巨大且CPU资源紧张,这种停止时间甚至可能长达整整一秒。相比之下,AOF重写也需要fork操作,但不论执行间隔长短,数据的耐久性都不会受到任何损失。
Redis会将每个收到的写命令通过write函数追加到文件中(默认为appendonly.aof)。当Redis重启时,它会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
./redis-server --appendonly yes
注意,由于操作系统会在内核中缓存write所做的修改,因此并不是立即写入磁盘。
AOF方式的持久化可能会丢失部分修改。但是,我们可以通过配置文件告诉Redis在何时通过fsync函数强制将数据写入磁盘。有三种可选方式如下(默认为每秒fsync一次):
appendonly yes //启用aof持久化方式
# appendfsync always //每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用
appendfsync everysec //每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐
# appendfsync no //完全依赖os,性能最好,持久化没保证
redis 127.0.0.1:6379> set key1 Hello
OK
redis 127.0.0.1:6379> append key1 " World!"
(integer) 12
redis 127.0.0.1:6379> del key1
(integer) 1
redis 127.0.0.1:6379> del non_existing_key
(integer) 0
$ cat appendonly.aof
*2
$6
SELECT
$1
0
*3
$3
set
$4
key1
$5
Hello
*3
$6
append
$4
key1
$7
World!
*2
$3
del
$4
key1
AOF方式也带来了另一个问题,即持久化文件的大小逐渐增大。例如,我们调用了100次incr test命令,文件中将保存全部的100条命令,而实际上有99条是多余的。因为为了恢复数据库的状态,只需要保存一条set test 100命令就足够了。
为了压缩AOF持久化文件,Redis提供了bgrewriteaof
命令。当收到该命令时,Redis将以与快照相似的方式将内存中的数据以命令的形式保存到临时文件中,最后替换原来的文件。
1. Redis调用fork创建父子两个进程。
2. 子进程根据内存中的数据库快照,将重建数据库状态所需的命令写入临时文件。
3. 父进程继续处理客户端请求,将写命令写入原始的AOF文件,并将接收到的写命令缓存起来。这样即使子进程的重写操作失败,也不会出现问题。
4. 当子进程将快照内容以命令的方式写入临时文件后,子进程发送信号通知父进程。然后父进程将缓存的写命令也写入临时文件。
5. 现在父进程可以用临时文件替换旧的AOF文件,并将其重命名。之后,后续接收到的写命令将追加到新的AOF文件中。
注意,重写AOF文件的操作并不涉及读取旧的AOF文件,而是通过将整个内存中的数据库内容用命令的方式重写一个新的AOF文件。这一点类似于快照操作。
AOF持久化可以让Redis变得非常耐久:可以设置不同的fsync策略来控制写入频率,保证数据的持久性。默认的每秒fsync一次的策略可以保持良好的性能,即使发生故障停机,最多只会丢失一秒钟的数据。
AOF文件是一个只进行追加操作的日志文件,不需要进行seek操作,即使AOF文件中包含未完整写入的命令,使用redis-check-aof工具可以轻松修复。
Redis可以在AOF文件体积过大时,在后台自动进行AOF重写,生成包含恢复当前数据集所需的最小命令集合的新AOF文件。重写操作是安全的,在创建新AOF文件的过程中,仍然会将命令追加到旧的AOF文件中,即使重写过程中发生停机,旧的AOF文件也不会丢失。
AOF文件有序地保存了所有写入操作,格式简单易懂,方便人工阅读和文件分析。导出AOF文件也很简单,例如当执行了FLUSHALL命令后,只要AOF文件未被重写,可以通过去除AOF文件末尾的FLUSHALL命令,然后重新启动Redis来恢复数据集到执行FLUSHALL之前的状态。
对于相同的数据集来说,AOF文件通常比RDB文件更大。
根据所使用的fsync策略,AOF的速度可能比RDB慢。一般情况下,每秒fsync的性能依然非常高,而关闭fsync可以让AOF的速度和RDB一样快,即使在高负载下也是如此。然而,处理大量的写入负载时,RDB可以提供更可靠的最大延迟时间。
AOF在过去曾遇到过一些bug,特定命令导致AOF文件在重新载入时无法恢复数据集到原始状态。虽然这种bug较为罕见,但与RDB相比,几乎不会出现这种情况。测试套件已经添加了针对这种情况的检测,通过重新载入随机、复杂的数据集来确保恢复正常。
写操作都会生成相应的命令作为AOF日志,但需要注意的是,最后一个del命令不会被记录在AOF日志中,因为Redis判断该命令不会修改当前数据集,所以无需记录无用的写命令。
此外,AOF日志的生成并不完全按照客户端请求,例如命令INCRBYFLOAT会被记录为一条SET记录,因为浮点数操作在不同系统上可能会有不同结果。为了避免在不同系统上生成不同的数据集,这里只记录操作后的结果通过SET命令来记录。
如何将源实例的AOF数据文件导入到目标实例中。首先在目标实例上清空数据,然后在源实例上开启AOF功能并生成AOF数据文件。接下来,通过使用--pipe
参数和输入重定向符号将AOF数据文件导入到目标实例。最后,关闭源实例的AOF功能。这个过程可以用于在不同的Redis实例之间迁移数据或复制数据。
redis-cli
命令,连接到目标实例(xxx.xxx.xxx.xxx),并使用密码进行身份验证。然后,使用flushall
命令清空目标实例中的所有数据。[root~]# redis-cli -h xxx.xxx.xxx.xxx -a password flushall
OK
redis-cli
命令,连接到源实例(xxx.xxx.xxx.xxx),并使用密码进行身份验证。然后,使用config set appendonly yes
命令将AOF功能设置为开启状态。[root~]# redis-cli -h xxx.xxx.xxx.xxx -a password config set appendonly yes
生成AOF文件:开启AOF功能后,Redis会将所有写入操作追加到AOF文件(名为appendonly.aof
)中。该文件会存储源实例的数据变更操作。
导入AOF数据到目标实例:假设appendonly.aof
文件与当前路径相同。使用redis-cli
命令,连接到目标实例(xxx.xxx.xxx.xxx),并使用密码进行身份验证。然后,使用--pipe
参数和输入重定向(<
)符号将appendonly.aof
文件的内容导入到目标实例。
[root ~]# redis-cli -h xxx.xxx.xxx.xxx -a password --pipe < appendonly.aof
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 5
检查导入结果:在导入期间,redis-cli将显示传输进度。当最后一个回复接收到时,显示发送错误数量和回复数量。如果没有错误并且回复数量正确,那么导入操作成功。
关闭源实例的AOF功能:通过执行redis-cli
命令,连接到源实例(xxx.xxx.xxx.xxx),并使用密码进行身份验证。然后,使用config set appendonly no
命令将AOF功能设置为关闭状态。
[root~]# redis-cli -h xxx.xxx.xxx.xxx -a password config set appendonly no
OK
复制当前Redis的RDB文件:将要迁移的Redis的RDB文件名设置为与当前Redis的RDB文件名相同(在本例中,要迁移的Redis的文件名为/var/rdb/dump6380.rdb),确保在继续操作之前,杀掉当前Redis进程,并关闭要迁移的服务器的AOF功能(如果未关闭AOF功能,默认使用AOF文件来恢复数据)。
启动Redis实例6380:启动Redis实例6380后,您会注意到6380中多了数据。这个数据就是从6379固化到RDB文件中的数据。
redis-dump是一个用于导出Redis数据的工具。它可以帮助你将Redis数据库中的数据导出为文本文件,以便进行备份、迁移或分析。以下是redis-dump工具的使用步骤:
安装redis-dump工具:你可以使用npm包管理器全局安装redis-dump,运行如下命令:
npm install -g redis-dump
运行redis-dump命令:运行下面的命令导出Redis数据库中的数据:
redis-dump -u <redis服务器地址> -p <redis服务器端口> -o <输出文件路径>
替换
、
和 <输出文件路径>
分别为你的Redis服务器的地址、端口和导出数据的输出文件路径。
例如,如果Redis服务器的地址是 127.0.0.1
,端口是 6379
,输出文件路径是 /path/to/output.txt
,则命令如下:
redis-dump -u 127.0.0.1 -p 6379 -o /path/to/output.txt
导入导出的数据:得到导出的数据文件后,你可以使用Redis的redis-cli
命令行工具,通过RESTORE
命令将数据导入到另一个Redis实例中。例如:
cat /path/to/output.txt | redis-cli -h <目标redis地址> -p <目标redis端口> --pipe
替换 <目标redis地址>
和 <目标redis端口>
为你要导入数据的Redis实例的地址和端口。
这样就完成了使用redis-dump工具导出和导入Redis数据的操作。请确保你已经在操作前备份好数据,以免误操作导致数据丢失。
[root ~]# cat xxx.xxx.xxx.xx.txt | redis-load -u :[email protected]:6379