redis——持久化

举报
兔老大 发表于 2021/04/22 01:08:35 2021/04/22
【摘要】 因为redis是内存数据库,他把数据都存在内存里,所以要想办法实现持久化功能。 RDB RDB持久化可以手动执行,也可以配置定期执行,可以把某个时间的数据状态保存到RDB文件中,反之,我们可以用RDB文件还原数据库状态。     生成 有两个命令可以生成RDB文件: SAVE 命令由服务器进程直接执行保存操作,所以该命令会阻塞服务器,服...

因为redis是内存数据库,他把数据都存在内存里,所以要想办法实现持久化功能。

RDB

RDB持久化可以手动执行,也可以配置定期执行,可以把某个时间的数据状态保存到RDB文件中,反之,我们可以用RDB文件还原数据库状态。

    生成

有两个命令可以生成RDB文件:

  • SAVE 命令由服务器进程直接执行保存操作,所以该命令会阻塞服务器,服务器不能接受其他指令。
  • BGSAVE 命令由子进程执行保存操作,所以该命令不会阻塞服务器,服务器可以接受其他指令。。

禁止BGSAVE和SAVE同时执行,也就是说执行其中一个就会拒绝另一个,这是为了避免父进程和子进程同时执行两个rdbsave,防止产生竞争条件。

    载入

    RDB载入工作是服务器启动时自动执行的。

    自动保存

用户可以通过save选项设置多个保存条件,服务器状态中会保存所有用 save 选项设置的保存条件,当任意一个保存条件被满足时,服务器会自动执行 BGSAVE 命令。

比如

save 900 1

save 300 10

满足:服务器在900秒之内被修改至少一次或者300秒内修改至少十次。就会执行BGSAVE。

 

当服务器启动时,用户可以通过指定配置文件或者传入启动参数来设置save选项,服务器会把条件放到一个结构体里,结构体有一个数组,保存了所有条件。

 

serverCron函数默认100毫秒检查一次,他会遍历数组依次检查,符合条件就会执行BGSAVE。

    RDB文件结构

一个完整 RDB 文件所包含的各个部分:

REDIS,长度5字节, 保存着 "REDIS" 五个字符。 通过这五个字符, 可以在载入文件时, 快速检查载入文件是否 RDB 文件。

db_version ,长度 4 字节, 它的值是一个字符串表示的整数, 这个整数记录了 RDB 文件的版本号

databases 部分包含着零个或任意多个数据库, 以及各个数据库中的键值对数据

EOF 常量的长度为 1 字节, 这个常量标志着 RDB 文件正文内容的结束

check_sum 是一个 8 字节长的无符号整数, 保存着一个校验和,以此来检查 RDB 文件是否出错或损坏

我并不想深入探究databases的组成。就是知道

  • RDB 文件是一个经过压缩的二进制文件,由多个部分组成。
  • 对于不同类型的键值对, RDB 文件会使用不同的方式来保存它们。

即可。

 

AOF

AOF持久化是通过保存服务器执行的命令来记录状态的。还原的时候再执行一遍即可。

功能的实现可以分为命令追加、文件写入、文件同步三个步骤。

 

当 AOF 持久化功能处于打开状态时, 服务器在执行完一个写命令之后, 会以协议格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾:


  
  1. struct redisServer {
  2. // ...
  3. // AOF 缓冲区
  4. sds aof_buf;
  5. // ...
  6. };

Redis 服务器进程就是一个事件循环

循环中的文件事件负责接收客户端的命令请求, 以及向客户端发送命令回复,

而时间事件则负责执行像 serverCron 函数这样需要定时运行的函数。

因为服务器在处理文件事件时可能会执行写命令, 使得一些内容被追加到 aof_buf 缓冲区里面, 所以在服务器每次结束一个事件循环之前, 它都会调用 flushAppendOnlyFile 函数, 考虑是否需要将 aof_buf 缓冲区中的内容写入和保存到 AOF 文件里面, 这个过程可以用伪代码表示:


  
  1. def eventLoop():
  2. while True:
  3. # 处理文件事件,接收命令请求以及发送命令回复
  4. # 处理命令请求时可能会有新内容被追加到 aof_buf 缓冲区中
  5. processFileEvents()
  6. # 处理时间事件
  7. processTimeEvents()
  8. # 考虑是否要将 aof_buf 中的内容写入和保存到 AOF 文件里面
  9. flushAppendOnlyFile()

flushAppendOnlyFile 函数的行为由服务器配置的 appendfsync 选项的值来决定

值为 always 时, 服务器在每个事件循环都要将 aof_buf 缓冲区中的所有内容写入到 AOF 文件并且同步 AOF 文件, 所以 always 的效率最慢的一个, 但从安全性来说, always 是最安全的, 因为即使出现故障停机, AOF 持久化也只会丢失一个事件循环中所产生的命令数据。

值为 everysec 时, 服务器在每个事件循环都要将 aof_buf 缓冲区中的所有内容写入到 AOF 文件, 每隔超过一秒就要在子线程中对 AOF 文件进行一次同步: 从效率上来讲, everysec 模式足够快, 并且就算出现故障停机, 数据库也只丢失一秒钟的命令数据。

值为 no 时, 服务器在每个事件循环都要将 aof_buf 缓冲区中的所有内容写入到 AOF 文件, 至于何时对 AOF 文件进行同步, 则由操作系统控制。

因为处于 no 模式下的 flushAppendOnlyFile 调用无须执行同步操作, 所以该模式下的 AOF 文件写入速度总是最快的, 不过因为这种模式会在系统缓存中积累一段时间的写入数据, 所以该模式的单次同步时长通常是三种模式中时间最长的: 从平摊操作的角度来看,no 模式和 everysec 模式的效率类似, 当出现故障停机时, 使用 no 模式的服务器将丢失上次同步 AOF 文件之后的所有写命令数据。

    重写

AOF持久化是保存了一堆命令来恢复数据库,随着时间流逝,存的会越来越多,如果不加以控制,文件过大可能影响服务器甚至计算机。而且文件过大,恢复时需要时间也太长。

所以redis提供了重写功能,写出的新文件不会包含任何浪费时间的冗余命令。

接下来,我们就介绍重写的原理。

其实重写不会对现有的AOF文件进行读取分析等操作,而是通过当前服务器的状态来实现。


  
  1. # 假设服务器对键list执行了以下命令s;
  2. 127.0.0.1:6379> RPUSH list "A" "B"
  3. (integer) 2
  4. 127.0.0.1:6379> RPUSH list "C"
  5. (integer) 3
  6. 127.0.0.1:6379> RPUSH list "D" "E"
  7. (integer) 5
  8. 127.0.0.1:6379> LPOP list
  9. "A"
  10. 127.0.0.1:6379> LPOP list
  11. "B"
  12. 127.0.0.1:6379> RPUSH list "F" "G"
  13. (integer) 5
  14. 127.0.0.1:6379> LRANGE list 0 -1
  15. 1) "C"
  16. 2) "D"
  17. 3) "E"
  18. 4) "F"
  19. 5) "G"
  20. 127.0.0.1:6379>

当前列表键list在数据库中的值就为["C", "D", "E", "F", "G"]。要使用尽量少的命令来记录list键的状态,最简单的方式不是去读取和分析现有AOF文件的内容,,而是直接读取list键在数据库中的当前值,然后用一条RPUSH list "C" "D" "E" "F" "G"代替前面的6条命令。

  • 伪代码表示如下

  
  1. def AOF_REWRITE(tmp_tile_name):
  2. f = create(tmp_tile_name)
  3. # 遍历所有数据库
  4. for db in redisServer.db:
  5. # 如果数据库为空,那么跳过这个数据库
  6. if db.is_empty(): continue
  7. # 写入 SELECT 命令,用于切换数据库
  8. f.write_command("SELECT " + db.number)
  9. # 遍历所有键
  10. for key in db:
  11. # 如果键带有过期时间,并且已经过期,那么跳过这个键
  12. if key.have_expire_time() and key.is_expired(): continue
  13. if key.type == String:
  14. # 用 SET key value 命令来保存字符串键
  15. value = get_value_from_string(key)
  16. f.write_command("SET " + key + value)
  17. elif key.type == List:
  18. # 用 RPUSH key item1 item2 ... itemN 命令来保存列表键
  19. item1, item2, ..., itemN = get_item_from_list(key)
  20. f.write_command("RPUSH " + key + item1 + item2 + ... + itemN)
  21. elif key.type == Set:
  22. # 用 SADD key member1 member2 ... memberN 命令来保存集合键
  23. member1, member2, ..., memberN = get_member_from_set(key)
  24. f.write_command("SADD " + key + member1 + member2 + ... + memberN)
  25. elif key.type == Hash:
  26. # 用 HMSET key field1 value1 field2 value2 ... fieldN valueN 命令来保存哈希键
  27. field1, value1, field2, value2, ..., fieldN, valueN =\
  28. get_field_and_value_from_hash(key)
  29. f.write_command("HMSET " + key + field1 + value1 + field2 + value2 +\
  30. ... + fieldN + valueN)
  31. elif key.type == SortedSet:
  32. # 用 ZADD key score1 member1 score2 member2 ... scoreN memberN
  33. # 命令来保存有序集键
  34. score1, member1, score2, member2, ..., scoreN, memberN = \
  35. get_score_and_member_from_sorted_set(key)
  36. f.write_command("ZADD " + key + score1 + member1 + score2 + member2 +\
  37. ... + scoreN + memberN)
  38. else:
  39. raise_type_error()
  40. # 如果键带有过期时间,那么用 EXPIREAT key time 命令来保存键的过期时间
  41. if key.have_expire_time():
  42. f.write_command("EXPIREAT " + key + key.expire_time_in_unix_timestamp())
  43. # 关闭文件
  44. f.close()

    AOF后台重写


aof_rewrite函数可以创建新的AOF文件,但是这个函数会进行大量的写入操作,所以调用这个函数的线程被长时间的阻塞,因为服务器使用单线程来处理命令请求;所以如果直接是服务器进程调用AOF_REWRITE函数的话,那么重写AOF期间,服务器将无法处理客户端发送来的命令请求;


Redis不希望AOF重写会造成服务器无法处理请求,所以将AOF重写程序放到子进程(后台)里执行。这样处理的好处是: 
1)子进程进行AOF重写期间,主进程可以继续处理命令请求;
2)子进程带有主进程的数据副本,使用子进程而不是线程,可以避免在锁的情况下,保证数据的安全性。

还有一个问题,可能重写的时候又有新的命令过来,造成信息不对等,所以redis设置了一个缓冲区,重写期间把命令放到重写缓冲区。
 

  总结


AOF重写的目的是为了解决AOF文件体积膨胀的问题,使用更小的体积来保存数据库状态,整个重写过程基本上不影响Redis主进程处理命令请求;
AOF重写其实是一个有歧义的名字,实际上重写工作是针对数据库的当前状态来进行的,重写过程中不会读写、也不适用原来的AOF文件;
AOF可以由用户手动触发,也可以由服务器自动触发。

 

文章来源: fantianzuo.blog.csdn.net,作者:兔老大RabbitMQ,版权归原作者所有,如需转载,请联系作者。

原文链接:fantianzuo.blog.csdn.net/article/details/89385311

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200