Redis 的两种持久化操作以及如何保障数据安全
RDB 持久性以指定的时间间隔执行数据集的时间点快照。整体备份
AOF 持久性记录服务器接收的每个写入操作,将在服务器启动时再次播放,重建原始数据集。使用与 Redis 协议本身相同的格式以仅追加方式记录命令。当 Redis 太大时,Redis 能够在后台重写日志。单操作备份
如果您愿意,只要服务器正在运行,您就可以根据需要禁用持久化。可以动态禁用持久化
可以在同一实例中组合 AOF 和 RDB。请注意,在这种情况下,当 Redis 重新启动时,AOF 文件将用于重建原始数据集,因为它保证是最完整的。AOF+REB是最好的
RDB | AOF | |
advantage | RDB 是 Redis 数据的一个非常紧凑的单文件时间点表示。RDB 文件非常适合备份。例如,您可能希望在最近的24小时内每小时归档 RDB 文件,并在30天内每天保存 RDB 快照。这允许您在发生灾难时轻松恢复数据集的不同版本。保存整体快照增量备份 RDB 对于灾难恢复非常有用,因为单个压缩文件可以传输到远程数据中心,或者 Amazon S3(可能是加密的)上。单文件传送到数据中心或云端 RDB 最大限度地提高了 Redis 的性能,因为 Redis 父进程需要做的唯一工作就是创建一个子进程来执行其余的所有工作。父实例永远不会执行磁盘I/O或类似的操作。不占用父进程 与 AOF 相比,RDB 允许使用大数据集更快地重新启动。不需要 play again | Redis 可以自动重写背景中的 AOF,当它变得太大。重写是完全安全,复述,继续追加到旧文件,产生一个全新的最小集合操作需要创建当前数据集,而一旦准备复述,开关的两个和第二个文件附加到新的一个开始。将 file1 最后一个步骤的结果作为 file2 的开始 AOF 以一种易于理解和解析的格式,一个接一个地记录所有操作的日志。您甚至可以轻松导出AOF文件。例如,即使您使用 FLUSHALL 命令刷新了所有错误,如果在此期间没有执行日志重写,您仍然可以保存数据集,只需停止服务器,删除最新的命令,并再次启动 Redis。备份文件具有可读性 |
disadvantage | 如果需要在 Redis 停止工作(例如停电之后)时最小化数据丢失的机会,那么 RDB 就不是很好。可以在生成 RDB 的地方配置不同的保存点(例如,对数据集进行至少5分钟和100次写操作之后,但是可以有多个保存点)。但是,通常每5分钟或更长时间创建一个 RDB 快照,因此,如果 Redis 停止工作而没有正确地关闭,您应该准备好丢失最新的几分钟数据。按照预先设定的时间间隔备份,两次备份中间的数据有可能在断电时丢失 RDB 通常需要 fork(),以便使用子进程将其持久化到磁盘上。如果数据集很大,fork()可能会很耗时,如果数据集很大,CPU 性能不是很好,可能会导致Redis在几毫秒甚至一秒内停止为客户机提供服务。AOF 还需要 fork(),但是您可以调优重写日志的频率,而不必牺牲持久性。创建子进程可能会暂时停止响应 | 对于相同的数据集,AOF 文件通常比等效的 RDB文件大。因为要存操作过程 根据确切的 fsync 策略,AOF 可能比RDB 慢。一般来说,fsync 设置为每秒的性能仍然很高,如果禁用 fsync,即使在高负载下,它的速度也应该和 RDB 一样快。尽管如此,RDB 仍然能够提供关于最大延迟的更多保证,即使在写入负载很大的情况下也是如此。从某些角度看来比较慢 在过去,我们在特定的命令中遇到过罕见的错误(例如,有一个错误涉及像 BRPOPLPUSH 这样的阻塞命令),导致在重新加载时生成的 AOF 不能完全复制相同的数据集。这种 bug 很少见,我们在测试套件中进行了测试,自动创建随机的复杂数据集并重新加载它们,以检查一切是否正常,但是使用RDB持久性,这种 bug 几乎是不可能的。为了更清楚地说明这一点: Redis AOF 会像 MySQL 或 MongoDB 那样增量地更新现有状态,而 RDB 快照会一次又一次地从头创建一切,这在概念上更健壮。然而- 1)需要注意的是,每次 AOF 是重写的复述,从头重新创建从实际数据中包含的数据集,使错误更强而总是附加阻力AOF文件(或一个重写阅读旧 AOF 代替阅读数据在内存中)。2)我们从来没有收到过来自用户的关于在现实世界中检测到的 AOF 出错的报告。官方辩解自己又快又安全 |
step | • Redis forks. We now have a child and a parent process.创建子进程 • The child starts to write the dataset to a temporary RDB file.子进程写数据到临时 RDB 文件 • When the child is done writing the new RDB file, it replaces the old one.当子进程完成写操作后替换旧 RDB | • Redis forks, so now we have a child and a parent process.创建子进程 • The child starts writing the new AOF in a temporary file.子进程写新 AOF 到临时文件 • The parent accumulates all the new changes in an in-memory buffer (but at the same time it writes the new changes in the old append-only file, so if the rewriting fails, we are safe).父类将所有新更改累积到内存缓冲区中(但同时它将新更改写入仅用于追加的旧文件中,因此如果重写失败,这是安全的)。 • When the child is done rewriting the file, the parent gets a signal, and appends the in-memory buffer at the end of the file generated by the child.当子节点重写文件完成时,父节点得到一个信号,并在子节点生成的文件的末尾追加内存缓冲区。 |
example | save 60 1000 | appendonly yes |
Redis 如何实现数据分片
- 客户端分区意味着客户端直接选择正确的节点在哪里写或读取给定的 key。许多 Redis 客户端实现客户端分区。知道自己去哪
- 代理辅助分区意味着我们的客户端向能够确定 Redis 协议的代理发送请求,而不是直接向正确的 Redis 实例发送请求。代理将确保将我们的请求相应地转发到正确的 Redis 实例到已配置的分区模式,并将回复发送回客户端。Redis 和 Memcached代理 Twemproxy 实现代理辅助分区。询问代理自己该去哪
- 查询路由意味着您可以将查询发送到随机实例,并且实例将确保将查询转发到正确的节点。Redis Cluster 在客户端的帮助下实现了一种混合形式的查询路由(请求不是直接从 Redis 实例转发到另一个实例,而是将客户端重定向到正确的节点)。发给你之后带我去哪我去哪
通常不支持涉及多个 key 的操作。例如,如果它们存储在映射到不同 Redis 实例的 key 中,则无法执行两个集合之间的交集(实际上有方法可以执行此操作,但不能直接执行此操作) |
涉及多个 key 的 Redis 事务不能使用。 |
分区粒度是关键,因此不可能使用单个巨大的密钥(如非常大的有序集)对数据集进行分片。 |
使用分区时,数据处理更复杂,例如,您必须处理多个 RDB / AOF 文件,并且需要从多个实例和主机聚合持久性文件来备份数据。 |
添加和删除容量可能很复杂。例如,Redis Cluster 支持大多数透明的数据重新平衡,能够在运行时添加和删除节点,但客户端分区和代理等其他系统不支持此功能。然而,一种称为预分片的技术在这方面有所帮助。 |
Redis 的主从复制策略是怎样的
- 当主实例和从属实例连接良好时,主设备通过向从设备发送命令流来保持从设备更新,以便复制对主设备端发生的数据集的影响,原因是:客户端写入,key已过期,更改主数据集的任何其他操作。
- 当主设备和从设备之间的链路中断时,对于网络问题或者由于主设备或从设备中检测到超时,从设备重新连接并尝试继续部分重新同步:这意味着它将尝试仅获取部件它在断开连接时错过的命令流。
- 当无法进行部分重新同步时,从站将要求完全重新同步。这将涉及一个更复杂的过程,其中主机需要创建其所有数据的快照,将其发送到从机,然后在数据集更改时继续发送命令流。
- Redis 默认使用异步复制,即低延迟和高性能,是绝大多数 Redis 用例的自然复制模式。但是,Redis 从站异步确认它们与主站定期收到的数据量。因此主设备不会每次等待从设备处理命令,但是如果需要,它知道哪个从设备已经处理了什么命令。这允许具有可选的同步复制。
Redis是如何使用 Redis Sentinel 来保证高可用的
- 监控:Sentinel 会不断检查主实例和从属实例是否按预期工作。
- 通知:Sentinel 可以通过 API 通知系统管理员,另一台计算机程序,其中一个受监控的 Redis 实例出现问题。
- 自动故障转移:如果主服务器未按预期工作,Sentinel 可以启动故障转移过程,其中从服务器被提升为主服务器,其他其他服务器将重新配置为使用新主服务器,并且使用 Redis 服务器的应用程序会通知有关新服务器的地址。连接。
- 配置提供商:Sentinel充当客户端服务发现的权限来源:客户端连接到 Sentinels,以便询问负责给定服务的当前 Redis 主服务器的地址。如果发生故障转移,Sentinels 将报告新地址。
Redis是内存数据库,对于要求数据持久化的场景如何实现
Redis 是一个支持持久化的内存数据库,也就是说Redis需要经常将内存中的数据同步到磁盘来保证持久化。redis支持四种持久化方式,一是 Snapshotting(快照)(RDB)也是默认方式;二是 Append-only file(缩写aof)的方式;三是虚拟内存方式;四是diskstore方式。下面分别介绍。
Snapshotting(RDB)
快照是默认的持久化方式。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。可以通过配置设置自动做快照持久化的方式。我们可以配置redis在n秒内如果超过m个key被修改就自动做快照,下面是默认的快照保存配置:
save 900 1 #900秒内如果超过1个 key 被修改,则发起快照保存
save 300 10 #300秒内容如超过10个 key 被修改,则发起快照保存
save 60 10000
快照保存过程:
- Redis调用fork函数,有了子进程和父进程。
fork() 创建一个新进程,并为它创建新的地址空间 - 父进程继续处理 clien t请求,子进程负责将内存内容写入到临时文件。由于 os 的写时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时os会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程的地址空间内的数据是 fork 时刻整个数据库的一个快照。
- 当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出(fork 一个进程入内在也被复制了,即内存会是原来的两倍)。
client 也可以使用 save 或者 bgsave 命令通知 Redis 做一次快照持久化。save 操作是在主线程中保存快照的,由于 Redis 是用一个主线程来处理所有 client 的请求,这种方式会阻塞所有 client 请求。所以不推荐使用。另一点需要注意的是,每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据。如果数据量大或者频繁修改的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能。
另外由于快照方式是在一定间隔时间做一次的,所以如果 Redis 意外 down 掉的话,就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话,可以采用 aof 持久化方式。
Append-only file(AOF)
aof 比快照方式有更好的持久化性,是由于在使用 aof 持久化方式时,Redis 会将每一个收到的写命令都通过 write 函数追加到文件中(默认是appendonly.aof)。当 Redis 重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当然由于 os 会在内核中缓存 write 做的修改,所以可能不是立即写到磁盘上。这样 aof 方式的持久化也还是有可能会丢失部分修改。不过我们可以通过配置文件告诉 Redis 我们想要通过 fsync 函数强制 os 写入到磁盘的时机。有三种方式如下(默认是:每秒 fsync 一次):
appendonly yes #启用 aof 持久化方式
# appendfsync always #每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用
appendfsync everysec #每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐
# appendfsync no #完全依赖 os,性能最好,持久化没保证
aof 的方式也同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用 incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条 set test 100就够了。为了压缩 aof 的持久化文件。Redis提供了 bgrewriteaof 命令。收到此命令 Redis将使用与快照类似的方式将内存中的数据以命令的方式保存到临时文件中,最后替换原来的文件。具体过程如下:
- Redis 调用 fork ,现在有父子两个进程
- 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
- 父进程继续处理 client 请求,除了把写命令写入到原来的 aof 文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
- 当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
- 现在父进程可以使用临时文件替换老的 aof 文件,并重命名,后面收到的写命令也开始往新的 aof 文件中追加。
需要注意到是重写 aof 文件的操作,并没有读取旧的 aof 文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的 aof 文件,这点和快照有点类似。
虚拟内存方式(desprecated)
首先说明:在 Redis-2.4 后虚拟内存功能已经被废弃了,原因如下:
- slow restart 重启太慢
- slow saving 保存数据太慢
- slow replication 上面两条导致 replication 太慢
- complex code 代码过于复杂
Redis 的虚拟内存与 os 的虚拟内存不是一码事,但是思路和目的都是相同的。就是暂时把不经常访问的数据从内存交换到磁盘中,从而腾出宝贵的内存空间用于其他需要访问的数据。尤其是对于 Redis 这样的内存数据库,内存总是不够用的。除了可以将数据分割到多个 Redis server外。另外的能够提高数据库容量的办法就是使用 vm(virtual memory) 把那些不经常访问的数据交换的磁盘上。如果我们的存储的数据总是有少部分数据被经常访问,大部分数据很少被访问,对于网站来说确实总是只有少量用户经常活跃。当少量数据被经常访问时,使用 vm 不但能提高单台 Redis server数据库的容量,而且也不会对性能造成太多影响。
Redis 没有使用 os 提供的虚拟内存机制而是自己在用户态实现了自己的虚拟内存机制,主要的理由有两点:
- os 的虚拟内存是已4k页面为最小单位进行交换的。而 Redis 的大多数对象都远小于4k,所以一个 os 页面上可能有多个Redis 对象。另外 Redis 的集合对象类型如 list,set 可能存在与多个 os 页面上。最终可能造成只有10%key被经常访问,但是所有 os 页面都会被 os 认为是活跃的,这样只有内存真正耗尽时os才会交换页面。
- 相比于 os 的交换方式。Redis 可以将被交换到磁盘的对象进行压缩,保存到磁盘的对象可以去除指针和对象元数据信息。一般压缩后的对象会比内存中的对象小10倍。这样 Redis的 vm 会比 os vm 能少做很多 io 操作。
下面是vm相关配置:
slaveof 192.168.1.1 6379 #指定 master 的 ip 和端口
vm-enabled yes #开启 vm 功能
vm-swap-file /tmp/redis.swap #交换出来的 value 保存的文件路径 /tmp/redis.swap
vm-max-memory 1000000 #Redis 使用的最大内存上限,超过上限后 Redis 开始交换 value 到磁盘文件中
vm-page-size 32 #每个页面的大小32个字节
vm-pages 134217728 #最多使用在文件中使用多少页面,交换文件的大小 = vm-page-size * vm-pages
vm-max-threads 4 #用于执行 value 对象换入换出的工作线程数量,0表示不使用工作线程
Redis 的 vm 在设计上为了保证 key 的查找速度,只会将 value 交换到 swap 文件中。所以如果是内存问题是由于太多 value 很小的 key 造成的,那么 vm 并不能解决。和 os 一样 Redis也是按页面来交换对象的。Redis规定同一个页面只能保存一个对象。但是一个对象可以保存在多个页面中。
在 Redis 使用的内存没超过 vm-max-memory 之前是不会交换任何 value 的。当超过最大内存限制后,Redis 会选择较老的对象。如果两个对象一样老会优先交换比较大的对象,精确的公式 swappability = age*log(size_in_memory)。对于 vm-page-size的设置应该根据自己的应用将页面的大小设置为可以容纳大多数对象的大小。太大了会浪费磁盘空间,太小了会造成交换文件出现碎片。对于交换文件中的每个页面,Redis 会在内存中对应一个 1bit 值来记录页面的空闲状态。所以像上面配置中页面数量(vm-pages 134217728 )会占用 16M 内存用来记录页面空闲状态。vm-max-threads 表示用做交换任务的线程数量。如果大于0推荐设为服务器的 cpu core 的数量。如果是0则交换过程在主线程进行。
diskstore方式
diskstore 方式是废弃了虚拟内存方式后选择的一种新的实现方式,也就是传统的 B-tree 的方式。具体细节是:
- 读操作,使用 read through 以及 LRU( LRU是Least Recently Used的缩写,即最近最少使用,常用于页面置换算法,是为虚拟页式存储管理服务的。) 方式。内存中不存在的数据从磁盘拉取并放入内存,内存中放不下的数据采用 LRU淘汰。
- 写操作,采用另外 spawn 一个线程单独处理,写线程通常是异步的,当然也可以把 cache-flush-delay 配置设成0,Redis尽量保证即时写入。但是在很多场合延迟写会有更好的性能,比如一些计数器用Redis存储, 在短时间如果某个计数反复被修改,Redis 只需要将最终的结果写入磁盘。这种做法叫 per key persistence。由于写入会按 key 合并,因此和 snapshot 还是有差异,diskstore 并不能保证时间一致性。