写点什么

Redis 数据持久化机制 (备份恢复)、缓存淘汰策略、主从同步原理、常见规范与优化详解

作者:C++后台开发
  • 2022-12-07
    湖南
  • 本文字数:8633 字

    阅读完需:约 28 分钟

Redis数据持久化机制(备份恢复)、缓存淘汰策略、主从同步原理、常见规范与优化详解

一. 数据持久化

1. 含义

 Redis 提供了 RDB 和 AOF 两种持久化方式,默认开启的是 RDB,如果需要 AOF,需要手动修改配置文件进行开启。

 RDB:是一种对 Redis 存在内存中的数据周期性的持久化机制,将内存中的数据以快照的形式硬盘,实质上是 fork 了一个子进程在执行数据存储,采用的是二进制压缩的存储模式。

如图:

 AOF:是以文本日志的形式记录 Redis 的每一个写入、删除请求(查询请求不处理),它是以追加的方式(append-only)写入,没有磁盘寻址的开销,所以写入速度非常快(类似 mysql 的 binlog)。

如图:

​2. 优缺点对比

(1). RDB 优点

  生成多个数据文件,每个文件代表某一时刻 redis 的数据,而且 RDB 是 fork 一个子进程做持久化,所以 RDB 对 Redis 性能影响非常小,而且他在数据恢复的时候也比 AOF 快。

(2). RDB 缺点

 A. RDB 通常是每隔一段时间查看 key 变化数量从而决定是否持久化一次数据,比如 5min 分钟 1 次吧,如果这期间宕机或断电,丢失的就是这 5min 的数据了,而 AOF 最多丢失一秒。

 B. RDB 如果生成的快照文件非常大,客户端会卡顿 几毫秒或者几秒,恰逢当时是高并发期间,绝壁出问题!

(3). AOF 优点

 A. AOF 通常设置 1s 通过后台线程去追加一次,所以最多丢失 1s 数据,数据的完整性比 RDB 要高。

 B. AOF 是 appendonly 追加的方式,少了磁盘寻址的开销,所以写入性能很高,文件也不易损坏。

 C. 可以对数据误删进行紧急恢复。

(4). AOF 缺点

 A. 同样的数据,AOF 文件要比 RDB 文件大的多。

 B. 开启 AOF 存储后,整个 Redis 的 QPS 要下降,但依旧比关系型数据库要高。

3. 适用场景及如何选择

 RDB 适合做冷备,AOF 适合做热备。

在生产环境中,通常是冷备和热备一起上,RDB 中 bgsave 做全量持久化,AOF 做增量持久化,在 redis 重启的时候,使用 rdb 持久化的文件重新构建内存,再使用 aof 恢复最近操作数据,从而实现完整的服务到重启之前的状态。

(单独用 RDB 你会丢失很多数据,你单独用 AOF,你数据恢复没 RDB 来的快,所以在生产环境中,第一时间用 RDB 恢复,然后 AOF 做数据补全,冷备热备一起上,才是互联网时代一个高健壮性系统的王道。)

​4. rdb 持久化中 save 和 bgsave 的区别

 save 和 bgsave 两个命令都会调用 rdbSave 函数,但它们调用的方式各有不同:

(1). save 直接调用 rdbSave ,阻塞 Redis 主进程,直到保存完成为止,在主进程阻塞期间,服务器不能处理客户端的任何请求。

(2). bgsave 则 fork 出一个子进程,子进程负责调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成,Redis 服务器在 bgsave 执行期间仍然可以继续处理客户端的请求。

(bgsave 的本质就是 fork 和 cow,fork 是指 redis 通过创建子进程来进行 bgsave 操作,cow 指的是 copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写进的页面数据会逐渐和子进程分离开来)

总结:save 是阻塞方式的;bgsave 是非阻塞方式的。

5. 相关代码配置

#一. RDB存储#1. 下面配置为默认配置,默认就是开启的,在一定的间隔时间中,检测key的变化情况,然后持久化数据save 900 1           #900s后至少1个key发生变化则进行存储save 300 10         #300s后至少10个key发生变化则进行存储save 60 10000     #60s后至少10000个key发生变化则进行存储#2. rdb文件的存储路径(默认当前目录下,文件名为dump.rdb)dbfilename dump.rdbdir ./
#二. AOF存储#1.默认是关闭的,日志记录的方式,可以记录每一条命令的操作。可以每一次命令操作后,持久化数据,启用的话通常使用每隔一秒持久化一次的策略 appendonly no(默认no) --> appendonly yes (开启aof) # appendfsync always #每一次操作都进行持久化 appendfsync everysec #每隔一秒进行一次持久化# appendfsync no # 不进行持久化#2. aof文件路径 (默认为当前目录下,文件名为 appendonly.aof)appendfilename "appendonly.aof"dir ./ (同上)#3. 控制触发自动重写机制频率# auto-aof-rewrite-min-size 64mb //aof文件至少要达到64M才会自动重写,文件太小恢复速度本来就很快,重写的意义不大# auto-aof-rewrite-percentage 100 //aof文件自上一次重写后文件大小增长了100%则再次触发重写

#三. 混合持久化#1. 开启混合持久化配置 (5.0版本默认就是yes)aof-use-rdb-preamble yes#2. rdb和aof自身的配置也都需要开启
复制代码

6. aof 持久化文件详解

(1). aof 文件长什么样?

 通常我们配置 aof 持久化为 everysec,即为每秒写一次,这里我们先把混合持久化关掉 【aof-use-rdb-preamble no】来进行演示。

演示步骤:先清空所有数据,然后把 appendonly.aof 文件删掉(需要重启一下),多次对 name1 和 name2 设置不同的值,如下:

#先清空所有数据,然后吧flushdb
#对name1多次设置值set name1 ypf1set name1 ypf11
#对name2多次设置值set name2 ypf2set name2 ypf22set name2 ypf222
复制代码

aof 文件中的内容如下:每条 set 指令都会 aof 文件中追加记录。如下图:

剖析:

  每条指令都会追加记录,但对于同一个 key,实际上我们只需要最新值,比如 name2,我们需要的是 ypf222,前面的记录用处不大,而且很占空间,所以 redis 内部有 aof 重写机制(定期根据内存的最新数据生成 aof 文件),来处理这个问题。

更多 C++后台开发技术点知识内容包括 C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,MongoDB,ZK,流媒体,音视频开发,Linux 内核,TCP/IP,协程,DPDK 多个高级知识点。

C/C++Linux服务器开发高级架构师/C++后台开发架构师​免费学习地址

【文章福利】另外还整理一些C++后台开发架构师 相关学习资料,面试题,教学视频,以及学习路线图,免费分享有需要的可以点击领取

(2). aof 重写机制

A. 自动重写

 AOF 文件里可能有太多没用指令,所以 AOF 会定期根据内存的最新数据生成 aof 文件。

 通过下面的配置,可以控制 aof 自动重写的频率

#3. 控制触发自动重写机制频率 # auto-aof-rewrite-min-size 64mb //aof 文件至少要达到 64M 才会自动重写,文件太小恢复速度本来就很快,重写的意义不大 # auto-aof-rewrite-percentage 100 //aof 文件自上一次重写后文件大小增长了 100%则再次触发重写

B. 手动重写

 运行指令【bgrewriteaof】,后台会 fork 一个子进程去重写,对于 redis 正常进程操作影响很小,下面演示一下手动重写后 aof 文件中的内容。

​7. 混合持久化详解

(1). 概念

 重启 Redis 时,我们很少使用 RDB 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志恢复,但是使用 AOF 日志性能相对 RDB 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。

 Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。

(2). 原理

 如果开启了混合持久化,AOF 在重写时,不再是单纯将内存数据转换为 RESP 命令写入 AOF 文件,而是将重写这一刻之前的内存做 RDB 快照处理(重写期间执行的指令和之后的指令仍然是转换成 resp 指令吸入 aof 文件),并且将 RDB 快照内容和增量的 AOF 修改内存数据的命令存在一起,都写入新的 AOF 文件,新的文件一开始不叫 appendonly.aof,等到重写完新的 AOF 文件才会进行改名,会覆盖原有的 AOF 文件,完成新旧两个 AOF 文件的替换。

 于是在 Redis 重启的时候,可以先加载 RDB 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,因此重启效率大幅得到提升。

(3). 演示

 A. 开启混合持久化机制 【aof-use-rdb-preamble yes】

 B. 运行指令【bgrewriteaof】手动进行 aof 重写(前提内存中有内容),此时查看 aof 文件中的内容,类似乱码的东西,就是 rdb 快照处理

​C. 再任意执行几个 set 指令,然后查看 aof 文件中内容。

​8. 数据备份恢复

(前言: 上面的代码配置 RDB 和 AOF 都是自动进行持久化存储的,下面我们进行介绍手动进行备份恢复)

(1). RDB 模式

 比如当没有达到 RDB 持久化的条件,而此时我又想把内存中的数据持久化到硬盘上,这个时候可以运行 bgsave 指令进行后台持久化。(save 命令也可以,但会阻塞主线程)

 (下面步骤在关闭 aof 存储的模式下进行,即 appendonly no,rdb 使用默认配置即可,为了演示效果,可以临时删除 dump.rdb,实际是不需要的,数据会覆盖的)

A. 运行【 bgsave】 指令 ,默认配置下会在 redis 的根目录下生成一个名为 dump.rdb 的数据文件。

​B. 可以通过指令 【config get dir】获取数据存放的路径,【config get dbfilename】获取生成的 rdb 文件名称。

​C. 关闭服务【./redis-cli -a 123456 shutdown】,重新启动 redis 服务【./redis-server redis.conf】,将自动初始化 rdb 中的内容到内存(因为默认数据存储目录和启动目录是一致的)。

(2). AOF 模式

(下面步骤在开启 aof 存储的模式下进行,即 appendonly yes,关闭 rdb 存储,即 3 个 save 全部注释掉,然后 sava "" )

 save ""
#save 900 1#save 300 10#save 60 10000
复制代码

A. 运行【 bgrewriteaof】 指令 ,默认配置下会在 redis 的根目录下生成一个名为 appendonly.aof 的数据文件。

​B. 关闭服务【./redis-cli -a 123456 shutdown】,重新启动 redis 服务【./redis-server redis.conf】,将自动初始化 aof 中的内容到内存(因为默认数据存储目录和启动目录是一致的)。

(3). RDB 和 AOF 模式同时开启

(默认 redis 里没有任何数据)

A. 先写入 name1 和 name2,然后运行【bgsave】,然后写入 name3,运行【bgrewriteaof】指令,然后写入 name4

​B. 关闭服务【./redis-cli -a 123456 shutdown】,重新启动 redis 服务【./redis-server redis.conf】,发现内存中的数据为 name1-4 都有,说明同时开启的时候,初始化内存数据的时候,使用的是 AOF 模式

PS:

1. 只开启 rdb, 重启的时候加载 rdb 文件进行恢复数据。

2. 只开启 aof,重启的时候加载 aof 文件进行恢复数据。

3. 同时开始 rdb 和 aof,重启的时候依旧是使用 aof 文件进行恢复数据。

二. 缓存淘汰策略

1. 背景

Redis 对于过期键有三种清除策略:

 (1). 被动删除:设置一个 key 过期时间后,当该 key 过期了,不会马上从内存中删除,当对其 读/写一个已经过期的 key 时,会触发惰性删除策略,直接删除掉这个过期 key .

 (2). 主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以 Redis 会定期主动淘汰一批已过期的 key .

 (3). 缓存淘汰策略:当前已用内存超过 maxmemory 限定时,触发主动清理策略当 REDIS 运行在主从模式时,只有主结点才会执行被动和主动这两种过期删除策略,然后把删除操作”del key”同步到从结点。(大量的 key 定期没有删除,而且也没惰性删除(查询的时候删除),这个时候要适用缓存淘汰策略了。)

2. 淘汰策略种类

 当前已用内存超过 redis.cnf 配置文件中 # maxmemory <bytes> 限定时,会触发主动清理策略根据自身业务类型,选好 redis.cnf 配置文件中 maxmemory-policy (最大内存淘汰策略),设置好过期时间。如果不设置最大内存, 当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap),会让 Redis 的性能急剧下降。

redis5.0 相关配置如下:

############################## MEMORY MANAGEMENT ################################
# Set a memory usage limit to the specified amount of bytes.# When the memory limit is reached Redis will try to remove keys# according to the eviction policy selected (see maxmemory-policy).## If Redis can't remove keys according to the policy, or if the policy is# set to 'noeviction', Redis will start to reply with errors to commands# that would use more memory, like SET, LPUSH, and so on, and will continue# to reply to read-only commands like GET.## This option is usually useful when using Redis as an LRU or LFU cache, or to# set a hard memory limit for an instance (using the 'noeviction' policy).## WARNING: If you have replicas attached to an instance with maxmemory on,# the size of the output buffers needed to feed the replicas are subtracted# from the used memory count, so that network problems / resyncs will# not trigger a loop where keys are evicted, and in turn the output# buffer of replicas is full with DELs of keys evicted triggering the deletion# of more keys, and so forth until the database is completely emptied.## In short... if you have replicas attached it is suggested that you set a lower# limit for maxmemory so that there is some free RAM on the system for replica# output buffers (but this is not needed if the policy is 'noeviction').## maxmemory <bytes>
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory# is reached. You can select among five behaviors:## volatile-lru -> Evict using approximated LRU among the keys with an expire set.# allkeys-lru -> Evict any key using approximated LRU.# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.# allkeys-lfu -> Evict any key using approximated LFU.# volatile-random -> Remove a random key among the ones with an expire set.# allkeys-random -> Remove a random key, any key.# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)# noeviction -> Don't evict anything, just return an error on write operations.## LRU means Least Recently Used# LFU means Least Frequently Used## Both LRU, LFU and volatile-ttl are implemented using approximated# randomized algorithms.## Note: with any of the above policies, Redis will return an error on write# operations, when there are no suitable keys for eviction.## At the date of writing these commands are: set setnx setex append# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby# getset mset msetnx exec sort## The default is:## maxmemory-policy noeviction
# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated# algorithms (in order to save memory), so you can tune it for speed or# accuracy. For default Redis will check five keys and pick the one that was# used less recently, you can change the sample size using the following# configuration directive.## The default of 5 produces good enough results. 10 Approximates very closely# true LRU but costs more CPU. 3 is faster but not very accurate.## maxmemory-samples 5
# Starting from Redis 5, by default a replica will ignore its maxmemory setting# (unless it is promoted to master after a failover or manually). It means# that the eviction of keys will be just handled by the master, sending the# DEL commands to the replica as keys evict in the master side.## This behavior ensures that masters and replicas stay consistent, and is usually# what you want, however if your replica is writable, or you want the replica to have# a different memory setting, and you are sure all the writes performed to the# replica are idempotent, then you may change this default (but be sure to understand# what you are doing).## Note that since the replica by default does not evict, it may end using more# memory than the one set via maxmemory (there are certain buffers that may# be larger on the replica, or data structures may sometimes take more memory and so# forth). So make sure you monitor your replicas and make sure they have enough# memory to never hit a real out-of-memory condition before the master hits# the configured maxmemory setting.## replica-ignore-maxmemory yes
复制代码

配置说明:

(1). LRU means Least Recently Used: 最近最少使用

(2). LFU means Least Frequently Used: 最不经常使用(表示按最近的访问频率进行淘汰,它比 LRU 更加精准地表示了一个 key 被访问的热度)

volatile-lru:超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。allkeys-lru:超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
volatile-lfu: 超过最大内存后,在过期键中使用lfu算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。allkeys-lfu : 超过最大内存后,在过期键中使用lfu算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
allkeys-random:随机删除所有键,直到腾出足够空间为止。volatile-random: 随机删除过期键,直到腾出足够空间为止。
volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据,如果没有,回退到noeviction策略。noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error)OOM command not allowed when used memory",此时Redis只响应读操作。
复制代码

PS: 位于上述配置文件的位置,默认配置的是 noeviction

# maxmemory-policy noeviction
复制代码

三. 主从同步原理

1. 主从的背景

 这里的主从架构是 redis 的第一代架构模式,即一个 master 对应多个 slave,它的出现主要是为了实现读写分离,让主节点之负责写,从节点负责读的请求,从而分摊压力。

 后续的架构,无论是哨兵还是 cluster,都是在主从架构的基础上进行改进和升级的。

2. 主从同步原理

(1). 全量复制

 A. slave 第一次启动时,连接 master,发送 PSYNC 命令,

 B. master 收到 psync 命令后,会执行 bgsave 命令进行全量复制生成 rdb 快照文件,持久化期间,所有写命令都被缓存在内存中

 C. master bgsave 执行完毕,向 slave 发送 rdb 文件

 D. slave 收到 rdb 文件,删掉之前所有旧数据,开始载入 rdb 文件

 E. rdb 文件同步结束之后,master 将之前缓冲区中命令发送给 slave。

 F. 此后 master 每执行一个写命令,就向 slave 发送相同的写命令。

注:

 (1). 当 master 与 slave 之间的连接由于某些原因而断开时,slave 能够自动重连 Master,如果 master 收到了多个 slave 并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送给多个并发连接的 slave。

 (2). 当 master 和 slave 断开重连后,一般都会对整份数据进行复制。但从 redis2.8 版本开始,master 和 slave 断开重连后支持部分复制。

流程图如下:

(2). 增量复制(部分复制)

 如果出现网络闪断或者命令丢失等异常情况,从节点之前保存了自身已复制的偏移量和主节点的运行 ID,主节点会根据偏移量把复制积压缓冲区里的数据发送给从节点,保证主从复制进入正常状态。

详细说明:

从 2.8 版本开始,slave 与 master 能够在网络连接断开重连后只进行部分数据复制。

 master 会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master 和它所有的 slave 都维护了复制的数据下标 offset(偏移量)和 master 的进程 id,因此,当网络连接断开后,slave 会请求 master 继续进行未完成的复制,从所记录的数据下标开始。如果 master 进程 id 变化了,或者从节点数据下标 offset 太旧,已经不在 master 的缓存队列里了,那么将会进行一次全量数据的复制。

流程图如下:

3. 同步过程中宕机怎么办?

 (1). 会自动重连的,自动补充缺少的数据。

 (2). 如同上面主从同步原理所说的,master 生成 rdb 文件期间,所有的写命令都被缓存在内存中,这些命令并不会丢失。

四. 常见规范和优化

1. key 的命名设计

(1). 可读性和可管理性

 以业务名(或数据库名)为前缀(防止 key 冲突),用冒号分隔,比如业务名:表名:id

trade:order:1

(2). 简洁性

 保证语义的前提下,控制 key 的长度,当 key 较多时,内存占用也不容忽视,例如:

user:{uid}:friends:messages:{mid} 简化为 u:{uid}:fr:m:{mid}

(3). 不要包含特殊字符

 反例:包含空格、换行、单双引号以及其他转义字符

2. 命令的使用

(1).O(N)命令关注 N 的数量

 例如 hgetall、lrange、smembers、zrange、sinter 等并非不能使用,但是需要明确 N 的值。有遍历的需求可以使用 hscan、sscan、zscan 代替。

(2).禁用命令

 禁止线上使用 keys、flushall、flushdb 等,通过 redis 的 rename 机制禁掉命令,或者使用 scan 的方式渐进式处理。

(3).合理使用 select

 redis 的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。

(4).使用批量操作提高效率

 原生命令:例如 mget、mset。

 非原生命令:可以使用 pipeline 提高效率。

注:

 1. 原生是原子操作,pipeline 是非原子操作。

 2. pipeline 可以打包不同的命令,原生做不到

 3. pipeline 需要客户端和服务端同时支持。

(5). Redis 事务功能较弱,不建议过多使用,可以用 lua 替代

3. 线程池最大连接数设计

原文链接:第四节:Redis数据持久化机制(备份恢复)、缓存淘汰策略、主从同步原理、常见规范与优化详解 - Yaopengfei - 博客园

用户头像

C/C++后台开发技术交流qun:720209036 2022-05-06 加入

还未添加个人简介

评论

发布
暂无评论
Redis数据持久化机制(备份恢复)、缓存淘汰策略、主从同步原理、常见规范与优化详解_数据库_C++后台开发_InfoQ写作社区