首页 / 资讯中心 / 文章详情

Redis 宕机数据会丢吗?6.2 实测 kill -9 和断电:RDB、AOF everysec、always 各丢多少,以及有 dump.rdb 重启后却没数据

Redis 宕机数据会丢吗?6.2 实测 kill -9 和断电:RDB、AOF everysec、always 各丢多少,以及有 dump.rdb 重启后却没数据 ★ FEATURED ARTICLE
这一篇讲什么「Redis 宕机会不会丢数据」的标准答案是:RDB 丢上次快照以后的、AOF everysec 最多丢 1 秒、always 不丢。这一篇把这几句话放到真机上验一遍,并且把进程被杀和机器断电分开测 —— 这两种情况的结果差别很大。另外几个经常一起被问的问题,也都实测了:明明有dump.rdb,重启后数据却没了,是怎么回事?bgsave执行的这几秒里写进来的数据,会不会被保存?Redis 挂了的那段时间,Java 应用会不会卡住?卡多久?实测环境:CentOS 7.9(VMware 虚拟机,xfs),Redis 6.2.4源码编译。实验实例端口 6380,和正式实例分开。写入方放在另一台机器上(宿主机),这样断电时它不会跟着一起停。它做的事很简单:循环执行INCR seq,每条都等到回复再发下一条,记下最后一次收到的值(下面叫「已确认」)。宕机后用同一份配置重启,GET seq,和「已确认」比:少了就是丢了,多了也有,后面说。每种情况写 75 秒后宕机。每种模式、每种宕机方式都只跑了一次,下面的数字是这一次的结果,不是固定值;能稳定说明的是「丢不丢、大概丢多少的量级」。1. 进程被杀(kill -9)持久化已确认重启后结果只开 RDB(默认 save 规则)257,351171,657丢 85,694 次写入AOF,appendfsync everysec252,018252,018一条不丢AOF,appendfsync always140,428140,429多了 1 条都不开(save )258,543(nil)全丢1.1 只开 RDB:丢的是「上次快照以后的全部」不写save时,6.2.4 的默认规则是:127.0.0.1:6380 config get save 1) save 2) 3600 1 300 100 60 10000意思是 3600 秒内有 1 次修改、300 秒内有 100 次、或 60 秒内有 1 万次修改,就做一次快照。kill 之前查了一下:rdb_changes_since_last_save:85692最后丢的是 85,694 条,和这个数对得上(查完到 kill 之间又写了 2 条)。重启日志:* Loading RDB produced by version 6.2.4 * RDB age 25 seconds * DB loaded from disk: 0.000 secondsINFO persistence里的rdb_changes_since_last_save,就是「如果现在宕机、会丢多少次写入」。平时监控它,比背规则有用。1.2 AOF everysec:kill -9 一条不丢kill -9 杀掉的只是 Redis 进程。AOF 的内容已经write进了操作系统的页缓存,只是还没fsync到磁盘;进程死了,页缓存还在,操作系统会照常把它写盘。所以重启后:* DB loaded from append only file: 0.061 seconds 重启后 GET seq 252018 ← 和已确认的一样1.3 AOF always:重启后比「已确认」还多 1最后一个INCR已经执行、也写进了 AOF,但回复还没送到客户端进程就没了。客户端看到的是连接断开,它会认为这次写入失败了,而数据其实已经在里面。这和数据库里「超时的写入其实提交了」是一回事:宕机那一刻正在进行的写入,客户端拿到报错,不代表没写进去。需要严格对账的业务,重试时要按「可能已经成功」处理(做成幂等)。顺带:同一个逐条等回复的客户端,75 秒里 always 只写了 14 万次,everysec 写了 25 万次。2. 真断电kill -9 不丢,是因为操作系统还活着。断电时页缓存里还没落盘的东西就没了。断电用的是虚拟机的硬重置:宿主机执行vmrun reset hard,效果等于直接拔电源。持久化已确认断电重启后结果AOF,appendfsync everysec233,692231,184丢 2,508 次写入AOF,appendfsync always138,010138,010一条不丢只开 RDB238,257197,216丢 41,041 次写入everysec:这一轮的写入速度大约每秒 3,100 次(233,692 ÷ 75),丢了 2,508 次,不到 1 秒的量。重启日志DB loaded from append only file: 0.056 seconds,没有任何截断警告。同样的配置,kill -9 时一条不丢,断电时丢了将近 1 秒。always:断电也一条不丢。RDB:最后一次快照的日志时间是 22:15:10,断电在 22:15:26,丢的就是这 16 秒的写入。重启后 RDB 的日志写的是:* RDB age 60 seconds这个 60 秒是「快照生成」到「重启加载」之间的时间,包含了机器重启的那段停机时间,不是丢了 60 秒的数据。看日志判断丢了多少时,别直接拿这个数。断电时,写入方(另一台机器上)看到的是TimeoutError: timed out—— 它不会收到「连接被重置」,只会一直等到超时。3. 明明有 dump.rdb,重启后数据却没了3.1 把配置改成appendonly yes再重启这是最容易踩、后果也最重的一个。一个只开 RDB 的实例,有 1001 个 key,dump.rdb9,003 字节。想开 AOF,于是在配置文件里把appendonly no改成appendonly yes,然后重启:dbsize0 x -rw-r--r--. 1 root root 0 10月 15 21:59 appendonly.aof -rw-r--r--. 1 root root 9003 10月 15 21:59 dump.rdb库是空的。AOF 开着时,Redis 只从 AOF 加载;AOF 不存在,它就新建一个 0 字节的,从空库开始 ——dump.rdb就在旁边,但不会去读它。日志里没有任何警告,只有正常的Ready to accept connections。更糟的是接下来:空库状态下随便写一条,下一次快照就把dump.rdb覆盖了。我写了一条SET y 1再BGSAVE:-rw-r--r--. 1 root root 50 10月 15 21:59 appendonly.aof -rw-r--r--. 1 root root 102 10月 15 21:59 dump.rdb ← 9,003 字节变成了 102 字节原来的数据到这里就彻底没了。发现重启后是空库,第一件事是先把dump.rdb拷走,别让它被覆盖。正确的开法是运行中开,让 Redis 先把当前数据写进 AOF:127.0.0.1:6380 config set appendonly yes OK 127.0.0.1:6380 info persistence aof_enabled:1 aof_rewrite_in_progress:0 aof_last_bgrewrite_status:ok 127.0.0.1:6380 config rewrite OKCONFIG SET appendonly yes会触发一次 AOF 重写,把现有数据写成appendonly.aof(这次是 9,003 字节,开头就是 RDB 格式的内容);CONFIG REWRITE把appendonly yes写回配置文件。之后重启:重启后 dbsize1001 xfrom-rdb * DB loaded from append only file: 0.000 seconds3.2dir是相对路径,换个目录启动配置里不写dir,默认是./,即启动时所在的目录。在数据目录里启动,数据存在数据目录;换个目录启动,它就去别处找了:在 /works/redis-lab/p2/reldir 下启动:dir/works/redis-lab/p2/reldir ← 写了 1000 个 key,SAVE 换到 / 下启动(配置文件用绝对路径):dir/ dbsize0配置文件路径写对了也没用,dir跟的是启动时所在的目录 —— 只要哪次启动不是在数据目录里执行的,就会碰上。dir一定写绝对路径。3.3 启动时没带配置文件cd /root redis-server --port 6380 --daemonize yes dir/root dbsize0 appendonlyno maxmemory0没带配置文件,所有配置都是默认值:dir是当前目录、不开 AOF、不限内存。数据文件在别处,自然是空库。这三种情况,都可以先用一条命令确认:127.0.0.1:6379 config get dir 127.0.0.1:6379 config get appendonly 127.0.0.1:6379 info server ← 看 config_file 那一行,是不是你以为的那个配置文件4. bgsave 期间写进来的数据,会不会被保存灌了 200 万个 key,执行BGSAVE,在rdb_bgsave_in_progress为 1 的这段时间里不停地写during:*:bgsave 期间写入 during:* 401 个 rdb_last_bgsave_status:ok rdb_last_bgsave_time_sec:2 * DB saved on disk * RDB: 9 MB of memory used by copy-on-write 现在内存里 during:* 401然后 kill -9,重启:kill -9 后重启:dbsize2000000 during:* 0不会。BGSAVE是 fork 出一个子进程来写快照,快照内容就是 fork 那一刻的数据;之后主进程收到的写入只在内存里,要等下一次快照。日志里那句9 MB of memory used by copy-on-write,就是 bgsave 期间被改动、需要复制一份的内存。5. Redis 挂了,Java 应用会卡多久Spring Boot 4.1.1(Lettuce),spring.data.redis.timeout: 5s。应用每 0.5 秒调一次set,中途把 Redis kill -9,15 秒后再启动:#4 22:01:14.975 耗时 1ms OK #5 22:01:15.492 耗时 5008ms QueryTimeoutException / io.lettuce.core.RedisCommandTimeoutException: Command timed out after 5 second(s) #6 22:01:21.016 耗时 5005ms QueryTimeoutException / io.lettuce.core.RedisCommandTimeoutException: Command timed out after 5 second(s) #7 22:01:26.537 耗时 4200ms OK ← Redis 22:01:30 启动 #8 22:01:31.250 耗时 2ms OK ……之后全部 1~2ms不是立刻报错,是每次调用卡满timeout才失败。报的是超时,不是「连接被拒绝」—— Lettuce 在断线时会把命令先攒着、自己在后台重连,命令等不到结果就超时。所以 Redis 一挂,用到它的接口每个请求都会慢 5 秒;timeout设得越长,卡得越久。#7是在 Redis 还没起来时发出的,等了 4.2 秒,正好赶上 Redis 启动、重连成功,这条成功了。超时的#5、#6后来有没有被执行?去 AOF 里看lab:down被写入的值的顺序:1 2 3 4 7 8 9 10 11 …这次实验里,超时的那两条没有被执行。Redis 回来以后,应用不用重启就自动恢复了。6. 速查想知道看哪里现在宕机会丢多少写入(RDB)INFO persistence的rdb_changes_since_last_save现在用的是什么持久化CONFIG GET appendonly、CONFIG GET appendfsync、CONFIG GET save重启后从哪个文件加载的启动日志:DB loaded from disk(RDB)还是DB loaded from append only file(AOF)重启后数据没了config get dir、config get appendonly、info server的config_file;先把dump.rdb拷走想给已有数据的实例开 AOF运行中CONFIG SET appendonly yes→ 看aof_last_bgrewrite_status:ok→CONFIG REWRITE;别直接改配置重启持久化kill -9(这次实测)断电(这次实测)只开 RDB丢上次快照之后的全部丢上次快照之后的全部AOF everysec不丢丢不到 1 秒AOF always不丢(还可能多出「客户端以为失败」的那一条)不丢都不开全丢全丢下一篇:持久化本身出错 ——bgsave失败(fork: Cannot allocate memory)、MISCONF让所有写入被拒、磁盘满了、AOF 文件太大和损坏。
阅读完成 · 觉得有帮助?
咨询建站