定期全备redis

  • 2020 年 3 月 18 日
  • 筆記

首先申明我的观点,redis本身只是缓存,不适合作为数据库使用,有说微博就是拿redis当DB用的,自己去证实吧。如果非要拿redis当数据库,就不得不考虑数据丢失问题,这里讨论两种常见的可能造成数据丢失的情况。

第一种情况是redis实例或所在主机宕机,这可以通过复制来解决,再配以redis哨兵机制,实现自动failover。应用通过哨兵访问redis,当master出现问题,redis自动切换到slave继续提供服务,整个过程对应用完全透明。这种方式与MySQL router的工作原理非常相似。

第二种情况是用户错误,比如有人误操作执行了一个flushdb命令。这种情况复制无能为力,因为slave上的数据也同时被删除了。redis也没有延迟复制的概念,那么能想到的就是在持久化上想办法,比如同时开启RDB和AOF两种持久化。还是类比MySQL,RDB相当于dump全备,AOF则像是statement格式的binlog,保存所有redis命令。AOF能保证不丢失数据,当有误删除发生,用AOF中保存的命令去重放以恢复数据。但是,AOF本身的实现可能对线上系统产生影响。例如appendfsync或no-appendfsync-on-rewrite参数配置不当,aof-rewrite时就可能发生redis卡顿,我们的生产系统就是因为此原因而放弃了AOF。退一步说,就算AOF可行,真到了恢复数据那一步,重放命令也要执行很长时间。

两害相权取其轻,既然不开AOF就没法保证误操作时的数据丢失,那就用RDB尽量减少损失。参照我们生产redis实际的部署方式,假设有三台物理服务器,IP为192.168.210.39、192.168.210.40、192.168.210.41。redis采用一主两从复制,主和从分别部署到不同主机,同时每个主机上通过不同端口开启多个redis实例。三个实例上再分别启动一个哨兵实例,同时监控多组redis master。哨兵的监控信息如下:

# Sentinel  sentinel_masters:14  sentinel_tilt:0  sentinel_running_scripts:0  sentinel_scripts_queue_length:0  sentinel_simulate_failure_flags:0  master0:name=redis9,status=ok,address=192.168.210.39:20009,slaves=2,sentinels=3  master1:name=redis5,status=ok,address=192.168.210.39:20005,slaves=2,sentinels=3  master2:name=redis3,status=ok,address=192.168.210.41:20003,slaves=2,sentinels=3  master3:name=redis13,status=ok,address=192.168.210.39:20013,slaves=2,sentinels=3  master4:name=redis7,status=ok,address=192.168.210.40:20007,slaves=2,sentinels=3  master5:name=redis11,status=ok,address=192.168.210.40:20011,slaves=2,sentinels=3  master6:name=redis14,status=ok,address=192.168.210.39:20014,slaves=2,sentinels=3  master7:name=redis6,status=ok,address=192.168.210.39:20006,slaves=2,sentinels=3  master8:name=redis12,status=ok,address=192.168.210.41:20012,slaves=2,sentinels=3  master9:name=redis8,status=ok,address=192.168.210.41:20008,slaves=2,sentinels=3  master10:name=redis2,status=ok,address=192.168.210.40:20002,slaves=2,sentinels=3  master11:name=redis4,status=ok,address=192.168.210.39:20004,slaves=2,sentinels=3  master12:name=redis1,status=ok,address=192.168.210.39:20001,slaves=2,sentinels=3  master13:name=redis10,status=ok,address=192.168.210.41:20010,slaves=2,sentinels=3

可以看到当前总共运行了14组redis主从,端口从20001-20014,每组的主从实例使用相同端口。为分散负载,14个redis master实例分布到不同主机。针对以上部署方式编写了一个定时自动备份脚本redis_backup.sh,内容如下:

#!/bin/bash    # 192.168.210.39、192.168.210.40、192.168.210.41  ~/redis-5.0.3/src/redis-cli -h 192.168.210.39 -p 30001 info | grep address=192.168.210. | while read line  do      port=`echo $line | awk -F, '{print $3}' | awk -F: '{print $2}'`      master_ip=`echo $line | awk -F, '{print $3}' | awk -F: '{print $1}' | awk -F= '{print $2}'`      master_ip_last_part=`echo $master_ip | awk -F. '{print $4}'`      let slave_ip_last_part=($master_ip_last_part-39+1)%3+39      slave_ip=192.168.210.$slave_ip_last_part      password="123456"        # echo $master_ip $slave_ip $port $password        if [ ! -d "/data/redis/192.168.210.39/$port" ]; then         mkdir "/data/redis/192.168.210.39/$port"      fi        ~/redis-5.0.3/src/redis-cli -p $port -a $password -h $slave_ip --rdb /data/redis/192.168.210.39/$port/dump_`date +%H`.rdb  done

说明:

  1. 所有redis实例使用相同口令。
  2. 遍历哨兵返回的监控信息,取得每组redis复制的master IP、端口。
  3. 为避免对master造成影响,连接redis slave实例进行备份,用对3取模获取slave的IP。
  4. 使用redis-cli –rdb执行备份。
  5. 备份文件名中带有精确到小时的时间。

以上脚本在另外的备份机器上每小时定时执行一次:

0 * * * * /data/redis/redis_backup.sh 1>/data/redis/redis_backup.log 2>&1

这个备份方案具有以下特点:

  • redis实例自动感知。比如增加了第15套redis主从,只要还是按上述部署方式,备份脚本无需做任何更改,自动生成备份目录和文件。
#ll /data/redis/192.168.210.39/  total 56  drwxr-xr-x 2 root root 4096 Jan 14 18:22 20001  drwxr-xr-x 2 root root 4096 Jan 14 18:22 20002  drwxr-xr-x 2 root root 4096 Jan 14 18:22 20003  drwxr-xr-x 2 root root 4096 Mar 12 04:11 20004  drwxr-xr-x 2 root root 4096 Jan 14 18:17 20005  drwxr-xr-x 2 root root 4096 Jan 14 18:22 20006  drwxr-xr-x 2 root root 4096 Jan 14 18:18 20007  drwxr-xr-x 2 root root 4096 Jan 14 18:22 20008  drwxr-xr-x 2 root root 4096 Jan 14 18:16 20009  drwxr-xr-x 2 root root 4096 Jan 14 18:23 20010  drwxr-xr-x 2 root root 4096 Jan 14 18:21 20011  drwxr-xr-x 2 root root 4096 Jan 14 18:22 20012  drwxr-xr-x 2 root root 4096 Jan 14 18:18 20013  drwxr-xr-x 2 root root 4096 Jan 14 18:22 20014
  • 对于每组redis实例,保留一天的24个备份文件,每小时一个。 #ll /data/redis/192.168.210.39/ total 96 total 3329112 -rw-r–r– 1 root root 142460399 Mar 12 00:18 dump_00.rdb -rw-r–r– 1 root root 142456166 Mar 12 01:18 dump_01.rdb -rw-r–r– 1 root root 141816942 Mar 12 02:18 dump_02.rdb -rw-r–r– 1 root root 141215064 Mar 12 03:20 dump_03.rdb -rw-r–r– 1 root root 140840412 Mar 12 04:17 dump_04.rdb -rw-r–r– 1 root root 140768868 Mar 12 05:17 dump_05.rdb -rw-r–r– 1 root root 141169526 Mar 12 06:17 dump_06.rdb -rw-r–r– 1 root root 141918869 Mar 11 07:17 dump_07.rdb -rw-r–r– 1 root root 142697847 Mar 11 08:18 dump_08.rdb -rw-r–r– 1 root root 142923196 Mar 11 09:18 dump_09.rdb -rw-r–r– 1 root root 142881297 Mar 11 10:19 dump_10.rdb -rw-r–r– 1 root root 142753570 Mar 11 11:23 dump_11.rdb -rw-r–r– 1 root root 142550684 Mar 11 12:24 dump_12.rdb -rw-r–r– 1 root root 142459276 Mar 11 13:25 dump_13.rdb -rw-r–r– 1 root root 142360821 Mar 11 14:20 dump_14.rdb -rw-r–r– 1 root root 142215182 Mar 11 15:22 dump_15.rdb -rw-r–r– 1 root root 142013490 Mar 11 16:20 dump_16.rdb -rw-r–r– 1 root root 141865563 Mar 11 17:22 dump_17.rdb -rw-r–r– 1 root root 141740614 Mar 11 18:21 dump_18.rdb -rw-r–r– 1 root root 141718646 Mar 11 19:23 dump_19.rdb -rw-r–r– 1 root root 141929286 Mar 11 20:24 dump_20.rdb -rw-r–r– 1 root root 142080608 Mar 11 21:23 dump_21.rdb -rw-r–r– 1 root root 142115053 Mar 11 22:23 dump_22.rdb -rw-r–r– 1 root root 141918890 Mar 11 23:18 dump_23.rdb #
  • 当有误操作发生时,最多丢失一小时的数据。

如果绝对不能丢失数据,建议还是用MySQL之类的数据库吧。再次强调,最好别拿redis当DB!