RDB 持久化是把当前进程数据生成快照保存在硬盘的过程,触发RDB持久化有两种方式,分别是 手动触发,自动触发。
进入 redis 客户端 执行 save 或 bgsave 可以生成dump.rdb文件,每一次执行命令就生成新的rdb快照文件,并覆盖原有的 rdb 快照文件。
save 命令 :执行 save 命令的时候会阻塞当前 redis 服务器,直到RDB快照成功生成,内存中的数据量过大的可能造成短时间的阻塞,线上环境不推荐使用。
bgsave 命令:Redis 主进程 fork操作创建子线程,RDB快照生成由子线程负责,完成后结束,阻塞只会在 fork 阶段且时间很短。
默认情况下,Redis 将进程内存数据保存在 dumo.rdb 的二进制文件中。
可以对Redis 进行设置,让它在 “N秒内数据集至少有M个改动” 满足这一条件,自动保存一次数据集。
# redis 默认save 策略
save 900 1 # 900秒只有一次改动,自动保存一次
save 300 10 # 300秒内有10次改动,自动保存一次
save 60 10000 # 60秒内有1万次改动,自动保存一次
#关闭RDB只需要把所有 save 保存策略注释掉即可
除了上面 save 保存策略,还有以下操作也会触发自动保存RDB快照:
1、Redis 从节点执行全量复制操作,主节点会自动执行 bgsave 生成RDB 快照文件发给 从节点.
2、执行 dubug roload 命令重新加载 redis ,也会触发生成 RDB 快照.
3、默认没开启 AOF 持久化时,执行 shutdown 命令也会自动执行 bgsave.
4、kill 服务也会执行 bgsave.
bgsave 的写时复制(COW)机制:
Redis 借助操作系统的写时复制技术(Copy-On-Write,COW),在生成快照的同时,依然可以正常处理写命令。
rdb 文件是一个二进制紧凑文件,代表着 redis 某一时段的数据快照,非常适用于备份,全量复制等场景。比如定时 bgsave 备份,把RDB文件拷贝到远程机器或者文件系统中,用于数据恢复。
加载 rdb 快照恢复数据会快于aof 方式.
1、RDB持久化数据没办法做到实时持久化/秒级持久化,且每次 bgsave 都要执行fork操作创建子进程,属于重操作,频繁执行成本过高。
2、rdb 快照文件会存在版本演变格式的不同,存在老版本 redis 无法兼容新版本redis的rdb文件。
针对RDB不适合做实时持久化的缺点,Redis 采取了AOF持久化方法解决.
AOF(append only file) 持久化:以独立日志的方式记录写的操作命令,重启 redis 的时候执行 AOF文件恢复数据来达到持久化目的。
# 开启aof 持久化
appendonly yes
# aof 文件名
appendfilename " appendonly.aof"
# 保存路径
dir ./
aof 配置开启之后, 每当 Redis 执行一个改变数据集的命令时(比如 SET), 这个命令就会被追加到 AOF 文件的末尾 (会先写入os cache,每隔一段时间fsync硬盘中(fsync策略可配置))。AOF文件名 通过appendfilename 配置 , 默认文件名 appendonly.aof。保存路径和 RDB持久化方式一致,通过dir配置指定。
例如执行 set lwc 666 , aof 文件中对应内容如下:
*2
$6
SELECT
$1
0
*3
$3
set
$3
lwc
$3
666
上面文件内容为resp协议格式数据,星号后面的数字代表命令有多少个参数,$ 号后面的数字表示有多少字符。
注意: 执行带过期时间的set命令,aof 文件里记录的并不是执行的原始命令,而是记录 key 过期的时间戳.
例如执行 set lwc 666 ex 1000 , aof文件对应内容如下 :
*3
$3
set
$3
lwc
$3
666
*3
$9
PEXPIREAT
$3
lwc
$13
1609596572778
默认 appendfsync everysec ,推荐使用,速度和安全兼得,只会丢失一秒的数据。
appendfsync always:每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全。
appendfsync everysec:每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据。
appendfsync no:从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。
随着 aof 文件越来越大,就需要对 aof 文件进行重写,到达整理压缩的目的。
会重写哪些内容?
1、进程内已经过期的数据不再写入aof文件
2、重写前的 aof 存在无用的命令,这些命令将不再保留重写后的 aof 文件,只保留最终数据执行的命令
3、多条写命令可以合并成一个,incr readcount , incr readcount , incr readcount ,如这样的命令会重写成 set readcount 3
4、为了单次写命令过大而造成客户端缓冲区溢出,将对于 list set hash zset 等类型操作,以64个元素为界分成多条命令
重写 aof 文件的好处:减少文件占用内存之外,更多是为了客户端重启执行加载 aof 文件的速度更快
手动触发:客户端执行 bgrewriteaof 命令 将会重写 aof 文件
自动触发,配置自动重写策略:
auto‐aof‐rewrite‐min‐size 64mb # aof文件至少要达到64M才会自动重写,文件太小恢复速度本来就很快,重写的意义不大
auto‐aof‐rewrite‐percentage 100 # aof文件自上一次重写后文件大小增长了100% 则再次触发重写 64mb 则 128mb
注意:aof 重写也是主线程 fork 出子线程去做(与bgsave相似),对redis正常处理其它命令不会有太大影响。
RDB 跟AOF ,用哪个?
RDB:恢复数据速度快,数据安全性不高,容易丢数据,启动优先级低
AOF: 恢复速度速度慢,数据安全性相对RDB,根据策略决定,启动优先级高
生产环境都可以启动,redis 启动是会优先选择 aof 文件恢复数据,因为 aof 的数据一般会比 rdb 的数据更安全一些。
重启Redis时,很少用RDB快照的方式来恢复内存数据,因为会丢失大量数据,我们通常使用 AOF 日志重放,但是重放 AOF日志性能比 RDB 要慢很多,这样在 redis 实例很大的情况下,重启需要一定的时间。Redis 4.0 为了解决这一问题,加强了AOF并取名叫混合持久化。
配置混合持久化(必须先开启aof) :
aof‐use‐rdb‐preamble yes
开启了混合持久化,AOF 在重写时,不再是只将内存数据转换为RESP命令写入AOF文件,而是将重写这一刻之前的内存做RDB快照处理,并且将RDB快照内容和增量的AOF修改内存数据的命令存在一起,都写入到新的AOF文件,新的文件一开始不叫 appendonly.aof。等到重写完新的AOF文件才会进行改名,覆盖原有的AOF文件,完成新旧两个AOF文件的替换。
于是在redis 重启的时候,会先加载 RDB 的内容,然后再重放增量AOF日志就可以替代之前的AOF全量文件重放,效率大大提高。
1、写 crontab 定时调度脚本,每小时copy一份rdb或aof备份到一个目录中去,仅仅保留最近48个小时的备份
2、每天都保留一份当日的数据备份到一个目录中去,可以保留最近1个月的备份
3、每次copy备份的时候,都把太旧的备份给删了
4、每天晚上将当前机器上的备份复制一份到其他机器上,防止机器损坏
只要有 appendonly.aof 和 dump.rdb 就能把数据恢复到内存中。
提示:具体还是备份策略还是要以公司的情况来定
主从复制指的是将一台 Redis 服务器的数据复制到另一台 Redis 上,前者为主节点(可读写),后者为从节点(只读),主节点可以有多个从节点,也可以没有从节点,但从节点只能有主节点。
本机多实例模拟 redis 主从架构搭建及配置:
1、复制一份redis.conf 文件,并改名为 redis_6380.conf
2、将相关配置修改为如下值:
port 6380
pidfile /var/run/redis_6380.pid # 把pid进程号写入pidfile配置的文件
logfile "6380.log"
dir ./data/6380 # 数据存在目录
3、配置主从复制(关键配置)
replicaof 127.0.0.1 6379 # 从本机6379的redis 实例复制数据,5.0 版本之前使用 slaveof
replica‐read‐only yes # 配置从节点只读
4、指定主节点的密码
masterauth 123456a
5、启动从节点
./src/redis-server redis_6380.conf
6、连接从节点客户端
./src/redis-cli -a 123456 -p 6380
7、执行 info 查看实例信息
127.0.0.1:6380> info
# Replication
role:slave
master_host:127.0.0.1 # 主节点 ip
master_port:6379 # 主节点 端口号
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:70
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:1bef69622bb1ff4051aa7ad0602b1219855149c0
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:70
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:70
8、测试在 6379 端口写数据,然后再看6380实例是否及时同步新修改数据
9、再尝试多配置几个从节点
1、slave(从节点)与master(主节点)建立连接,发送sysync同步命令
2、master会开启一个后台进程通过bgsave生成最新的rdb快照文件,同时master主进程会开始收集新的写命令并缓存,然后,master再将之前缓存在内存中的命令发送给slave。
3、slave节点将此文件保存到硬盘。
复制偏移量
主从节点都维护这一个复制偏移量(offset),它代表着当前节点接受数据的字节数,主节点表示接收客户端的字节数,从节点表示接收主节点的字节数,比如从节点接收主节点传来的N 个字节数据时,从节点的offset 会增加 N。
偏移量的作用非常大,它是用来衡量主从节点数据是否一直的唯一标准,如果主从节点的 offset 相等,表明数据一致,否则表明数据不一致。在不一致的情况下,可以根据两个节点的 offset找出从节点的缺少的那部分数据。比如,主节点的 offset 是 500,从节点的 offset是 400,那么主节点在进行数据传输时只需要将 401 ~ 500 传递给从节点即可,这就是部分复制。
复制积压缓冲区
复制积压缓冲区是一个由主节点维护的缓存队列,它具有如下几个特点:
1、由主节点维护
2、固定大小,默认为 1MB,配置参数为:repl-backlog-size
3、是一个先进先出的队列
在命令传播节点,主节点除了将写命令传递给从节点,也会将写命令写入到复制积压缓冲区中,当做一个备份,用于在部分复制流程中。由于它是先进先出的队列,且大小固定,所以他只能保存主节点最近执行的写命令,当主从节点的
offset相差较大时,超出了复制积压缓冲区的范围,则无法进行部分复制,只能进行全量复制了,所以为了能够提高网络中断引起的全量复制,我们需要认真评估复制积压缓冲区的大小,将其适当调大,比如网络中断时间是60s,主节点每秒接收的写命令为100KB,则复制积压缓冲区的平均大小应该为6MB,所以我们可以将其大小设置为6MB,甚至是10MB`,来保证绝大多数中断情况下都可以使用部分复制。
运行 ID(runid)
每个 Redis 节点在启动时都会生成一个运行 ID,即 runid,该 ID 用于唯一标识 Redis 节点,它是一个由
40 位随机的十六进制的字符组成的字符串,通过 info server 命令可以查看节点的 runid
从节点在初次建立连接进行全量复制时(从节点发送 psync ? -1),主节点会将自己的 runid 告知给从节点,从节点将其保存起来。当主从节点断开重连时,从节点会将这个runid发送给主节点,主节点会根据从节点发送的runid 来判断选择何种复制:
1、如果从节点发送的 runid 与当前主节点的
runid一致时,主节点则尝试进行部分复制,当然能不能进行部分复制还要看偏移量是否在复制积压缓冲区
2、如果从节点发送的 runid
与当前主节点的runid 不一致时,则进行全量复制
哨兵是一种特殊的模式,不提供读写服务,用来监控 redis 实例节点。
哨兵架构下 client 端第一次从哨兵找出 redis 的主节点,后续就直接访问redis的主节点,不会每次都通过sentinel 代理访问 redis 的主节点,当 redis 的主节点发生变化,哨兵会第一时间感知到,并且将新的 redis 主节点通知给 client 端。哨兵的原理就是,发送命令给 redis ,再等待 redis 响应,从而达到监听 redis 的效果。(首先 client 端要实现订阅功能,订阅 sentinel 发布的节点变动消息).
1、修改 sentinel.conf 文件
port 26379
daemonize yes
pidfile "/var/run/redis‐sentinel‐26379.pid"
logfile "26379.log"
dir "/usr/local/redis‐5.0.6/data"
# sentinel monitor
# quorum是一个数字,指明当有多少个sentinel认为一个master失效时(值一般为:sentinel总数/2 +1),master才算真正失效
sentinel monitor mymaster 192.168.0.60 6379 2 # mymaster这个名字随便取,客户端访问时会用到
3、启动sentinel哨兵实例
src/redis‐sentinel sentinel‐26379.conf
4、查看sentinel的info信息
src/redis‐cli ‐p 26379
127.0.0.1:26379>info
可以看到Sentinel的info里已经识别出了redis的主从
启动完哨兵之后,哨兵的元数据信息写入所有sentinel的配置文件里去(追加在文件的最下面),
如下:
protected-mode no
sentinel known-replica mymaster 127.0.0.1 6381
sentinel known-replica mymaster 127.0.0.1 6380
sentinel current-epoch
public static void main(String[] args) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(20);
config.setMinIdle(10);
config.setMinIdle(5);
//哨兵配置文件里面配置 redis 主节点名称
String masterName = "mymaster";
HashSet<String> sentinels = new HashSet<>();
sentinels.add(new HostAndPort("81.69.233.*",26379).toString());
sentinels.add(new HostAndPort("81.69.233.*",26380).toString());
sentinels.add(new HostAndPort("81.69.233.*",26381).toString());
//JedisSentinelPool其实本质跟JedisPool类似,都是与redis主节点建立的连接池
//JedisSentinelPool并不是说与sentinel建立的连接池,而是通过sentinel发现redis主节点并与其建立连接
JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(masterName,sentinels,config,3000,null);
Jedis jedis = null;
try {
jedis = jedisSentinelPool.getResource();
System.out.println(jedis.set("sentinel","lwc"));
jedis.set("lwc1","666");
jedis.set("lwc2","666");
jedis.set("lwc3","666");
Set<String> keys = jedis.keys("*");
Iterator<String> setKeys = keys.iterator();
while (setKeys.hasNext()) {
String str = setKeys.next();
System.out.println(str);
}
}catch (Exception e){
e.printStackTrace();
}finally {
//不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池
if (jedis != null){
jedis.close();
}
}
}