一、持久化
1、RDB
save命令会阻塞当前redis服务,已经废弃。bgsave命令只有在fork子线程的时候阻塞。
触发RDB的操作:
手动执行bgsave命令;
使用save相关配置,如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave;
如果从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件并发送给从节点;
默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则自动执行bgsave;
配置
#设置目录
config set dir {newDir}
#设置RDB文件名
config set dbfilename {newFileName}
#采用LZF算法对生成的RDB文件做压缩处理,压缩后的文件远远小于内存大小,默认开启
config set rdbcompression{yes|no}
RDB特点:
RDB压缩的二进制文件,适用于备份,全量复制等场景。
Redis加载RDB恢复数据远远快于AOF的方式。
RDB方式数据没办法做到实时持久化/秒级持久化。
2、AOF
AOF以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的,类似数据库binlog形式。
开启AOF功能需要设置配置:appendonly yes,默认不开启。
AOF文件名通过appendfilename配置设置,默认文件名是appendonly.aof。
1)所有的写入命令会追加到aof_buf(缓冲区)中。
Redis使用单线程响应命令,如果每次写AOF文件命令都直接追加到硬盘,那么性能完全取决于当前硬盘负载。先写入缓冲区aof_buf中,还有另一个好处,Redis可以提供多种缓冲区同步硬盘的策略,在性能和安全性方面做出平衡。
2)AOF缓冲区根据对应的策略向硬盘做同步操作。
•配置为always时,每次写入都要同步AOF磁盘文件,影响性能,不建议配置。
•配置为no,由于操作系统每次同步AOF文件的周期不可控,而且会加大每次同步硬盘的数据量,虽然提升了性能,但数据安全性无法保证。
•配置为everysec,是建议的同步策略,也是默认配置,做到兼顾性能和数据安全性。
系统调用write和fsync说明:
•write操作会触发延迟写(delayed write)机制。Linux在内核提供页缓冲区用来提高硬盘IO性能。
write操作在写入系统缓冲区后直接返回。同步硬盘操作依赖于系统调度机制,
例如:缓冲区页空间写满或达到特定时间周期。同步文件之前,如果此时系统故障宕机,缓冲区内数据将丢失。
•fsync针对单个文件操作(比如AOF文件),做强制硬盘同步,fsync将阻塞直到写入硬盘完成后返回,保证了数据持久化。
使用everysec策略进行AOF的sync时,当系统硬盘资源繁忙时可能会阻塞主线程。
1)everysec配置最多可能丢失2秒数据,不是1秒。
2)如果系统fsync缓慢,将会导致Redis主线程阻塞影响效率。
3)随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。
AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程。
上图3.2)由于fork操作运用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然响应命令,Redis使用“AOF重写缓冲区”保存这部分新数据,防止新AOF文件生成期间丢失这部分数据。
重写达到压缩的原因:
1)进程内已经超时的数据不再写入文件。
2)旧的AOF文件含有无效命令,如del key1等。重写使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令。
3)多条写命令可以合并为一个,如:lpush list a、lpushlist b、lpush list c可以转化为:lpush list a b c。
为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash、zset等类型操作,以64个元素为界拆分为多条。
触发时机:
手动触发:直接调用bgrewriteaof命令。
自动触发:根据auto-aof-rewrite-min-size(默认64M)和auto-aof-rewrite-percentage参数确定自动触发时机。
自动触发时机=[(aof_current_size>auto-aof-rewrite-min-size)
&& ((aof_current_size-aof_base_size)/aof_base_size>=auto-aof-rewrite-percentage)]
4)当Redis服务器重启时,可以加载AOF文件进行数据恢复。
3、重启加载
注意:
虽然fork创建的子进程不需要拷贝父进程的物理内存空间,但是会复制父进程的空间内存页表。
例如对于10GB的Redis进程,需要复制大约20MB的内存页表,因此fork操作耗时跟进程总内存量息息相关,
线上建议每个Redis实例内存控制在10GB以内。
或者降低fork操作的频率,如适度放宽AOF自动触发时机,避免不必要的全量复制等。
二、复制
1、配置
建立主从关系
1)在配置文件中加入slaveof{masterHost}{masterPort}随Redis启动生效。
2)在redis-server启动命令后加入--slaveof{masterHost}{masterPort}生效。
3)直接使用命令:slaveof{masterHost}{masterPort}生效。
断开主从:从节点执行slaveof no one命令断开与主节点的复制关系。
#主节点6379复制状态信息:
127.0.0.1:6379>info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6379,state=online,offset=43,lag=0
....
#从节点6380复制状态信息:
127.0.0.1:6380>info replication
#Replication
role:slave
master_host:127.0.0.1
master_port:6380
master_link_status:up
master_last_io_seconds_ago:4
master_sync_in_progress:0
...
切主操作流程如下:
1)断开与旧主节点复制关系。2)与新主节点建立复制关系。3)删除从节点当前所有数据。4)对新主节点进行复制操作。切主后从节点会清空之前所有的数据,线上人工操作时小心slaveof在错误的节点上执行或者指向错误的主节点。
默认情况下,从节点使用slave-read-only=yes配置为只读模式。
repl-disable-tcp-node-lay参数
•当关闭时,主节点产生的命令数据无论大小都会及时地发送给从节点,这样主从之间延迟会变小,但增加了网络带宽的消耗。适用于主从之间的网络环境良好的场景,如同机架或同机房部署。 默认关闭
•当开启时,主节点会合并较小的TCP数据包从而节省带宽。默认发送时间间隔取决于Linux的内核,一般默认为40毫秒。这种配置节省了带宽但增大主从之间的延迟。适用于主从网络环境复杂或带宽紧张的场景,如跨机房部署。
主从之间通过长链接彼此发送心跳命令。
主节点默认每隔10秒对从节点发送ping命令,判断从节点的存活性和连接状态。可通过参数repl-ping-slave-period控制发送频率。
从节点在主线程中每隔1秒发送replconf ack{offset}命令,给主节点上报自身当前的复制偏移量。
主节点根据replconf命令判断从节点超时时间,体现在info replication统计中的lag信息中,lag表示与从节点最后一次通信延迟的秒数,正常延迟应该在0和1之间。如果超过repl-timeout配置的值(默认60秒),则判定从节点下线并断开复制客户端连接。
2、常见拓扑
用于主节点出现宕机时从节点提供故障转移支持。当应用写命令并发量较高且需要持久化时,可以只在从节点上开启AOF,这样既保证数据安全性同时也避免了持久化对主节点的性能干扰。
注意主节点宕机后,先断开主从复制关系,再重启主节点。
一主多从结构(又称为星形拓扑结构)使得应用端可以利用多个从节点实现读写分离(见图6-5)。对于读占比较大的场景,可以把读命令发送到从节点来分担主节点压力。
但是对于写并发量较高的场景,多个从节点会导致主节点写命令的多次发送从而过度消耗网络带宽,同时也加重了主节点的负载影响服务稳定性。可下面采用树形结构
当主节点需要挂载多个从节点时为了避免对主节点的性能干扰,可以采用树状主从结构降低主节点压力。
3、原理
复制分为全量复制和部分复制。
全量复制:一般用于初次复制场景,把主节点全部数据一次性发送给从节点,当数据量较大时,会对主从节点和网络造成很大的开销。Redis早期支持的复制功能只有全量复制。
部分复制:用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连上主节点后,如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销。
主从复制组件介绍
•主从节点各自复制偏移量。
参与复制的主从节点都会维护自身复制偏移量,用于确定主从之间延迟,进行部分复制
#主
127.0.0.1:6379> info replication
# Replication
role:master
...
master_repl_offset:1055130
#从
127.0.0.1:6379> info replication
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=1055214,lag=1
...
•主节点复制积压缓冲区。
复制积压缓冲区是保存在主节点上的一个固定长度的队列,默认大小为1MB,当主节点有连接的从节点(slave)时被创建,这时主节点(master)响应写命令时,不但会把命令发送给从节点,还会写入复制积压缓冲区。
127.0.0.1:6379> info replication
# Replication
role:master...repl_backlog_active:1 // 开启复制缓冲区
repl_backlog_size:1048576 // 缓冲区最大长度
repl_backlog_first_byte_offset:7479 // 起始偏移量,计算当前缓冲区可用范围
repl_backlog_histlen:1048576 // 已保存数据的有效长度。
•主节点运行id。
每个Redis节点启动后都会动态分配一个40位的十六进制字符串作为运行ID。运行ID的主要作用是用来唯一识别Redis节点,比如从节点保存主节点的运行ID识别自己正在复制的是哪个主节点。需要注意的是Redis关闭再启动后,运行ID会随之改变。
如果只使用ip+port的方式识别主节点,那么主节点重启变更了整体数据集(如替换RDB/AOF文件),从节点再基于偏移量复制数据将是不安全的。
# redis-cli -p 6379 info server | grep run_id
run_id:545f7c76183d0798a327591395b030000ee6def9
•psync命令
•如果回复+FULLRESYNC{runId}{offset},那么从节点将触发全量复制流程。
•如果回复+CONTINUE,从节点将触发部分复制流程。
•如果回复+ERR,说明主节点版本低于Redis2.8,无法识别psync命令,从节点将发送旧版的sync命令触发全量复制流程。
全量复制
对于从节点开始接收RDB快照到接收完成期间,主节点仍然响应读写命令,因此主节点会把这期间写命令数据保存在复制客户端缓冲区内,当从节点加载完RDB文件后,主节点再把缓冲区内的数据发送给从节点,保证主从之间数据一致性。(上图6步骤)
RDB文件从创建到传输完毕消耗的总时间,超过repl-timeout所配置的值(默认60秒),从节点将放弃接受RDB文件并清理已经下载的临时文件,导致全量复制失败。
如果主节点创建和传输RDB的时间过长,对于高流量写入场景非常容易造成主节点复制客户端缓冲区溢出。默认配置为client-output-buffer-limit slave256MB64MB60,如果60秒内缓冲区消耗持续大于64MB或者直接超过256MB时,主节点将直接关闭复制客户端连接,造成全量同步失败。因此,运维人员需要根据主节点数据量和写命令并发量调整client-output-buffer-limit slave配置,避免全量复制期间客户端缓冲区溢出。
全量复制非常耗时费力,它的时间开销主要包括:
•主节点bgsave时间。
•RDB文件网络传输时间。
•从节点清空数据时间。
•从节点加载RDB的时间。
•可能的AOF重写时间。
部分复制
部分复制利用psync{runId}{offset}命令实现。
当从节点(slave)正在复制主节点(master)时,如果出现网络闪断或者命令丢失等异常情况时,从节点会向主节点要求补发丢失的命令数据,如果主节点的复制积压缓冲区(默认1M)内存在这部分数据则直接发送给从节点,这样就可以保持主从节点复制的一致性。补发的这部分数据一般远远小于全量数据,所以开销很小
4、主从复制注意的问题
主从数据延迟:可以通过监控主从偏移量,超过一定阀值报警,并迁移到其他延迟小的从节点。
读到过期数据:
过期的key删除策略主要有两种:惰性删除(读取key之前检查是否过期)和定时删除(内部定时任务会循环采样一定数量的键,当发现采样的键过期时执行del命令)。
由于从节点不能主动删除key,过期key由主节点删除后同步到从节点,因此从节点可能读到过期数据。
redis3.2之后版本支持从节点读取数据之前会检查键的过期时间来决定是否返回数据。
避免全量复制:
第一次建立复制(无法避免)
节点运行ID不匹配(手动提升从节点为主节点或者采用支持自动故障转移的哨兵或集群方案)
复制积压缓冲区不足(保证repl_backlog_size>net_break_time*write_size_per_minute,从而避免因复制积压缓冲区不足造成的全量复制)
规避复制风暴: