突破Java面试(24)-Redis的持久化机制

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: Redis的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?

全是干货的技术号:
本文已收录在github,欢迎 star/fork:
https://github.com/Wasabi1234/Java-Interview-Tutorial

Redis 对外提供数据访问服务时,使用的是常驻内存的数据。如果仅将数据存在内存,一旦宕机重启,数据全部丢失。

1 持久化概论

1.1 什么是持久化

redis所有数据保持在内存中,对数据的更新将异步地保存到磁盘上。持久化主要是做灾难恢复、数据恢复,可归类到高可用。

比如你的Redis宕机,你要做的事情是让Redis变得可用,尽快变得可用!

重启Redis,尽快让它对外提供服务,若你没做数据备份,即使Redis启动了,数据都没了!可用什么呢?

很可能说,大量的请求过来,缓存全部无法命中,在Redis里根本找不到数据,这个时候就造成缓存雪崩,就会去MySQL数据库去找,突然MySQL承接高并发,宕机!

MySQL宕机,你都没法去找数据恢复到Redis里面去,Redis的数据从哪儿来?就是从MySQL来的!

若你把Redis的持久化做好,备份和恢复方案也做到,那么即使你的Redis故障,也可通过备份数据,快速恢复,一旦恢复立即对外提供服务

1.2 持久化方式

Redis提供了两种持久化方式:

Redis RDB - 快照

RDB 按指定时间间隔执行数据集的时间点快照,类似于MySQL Dump。

Redis AOF - 命令日志

AOF 会记录服务器接收的每个写操作,这些操作将在服务器启动时再次执行,以重建原始数据集。使用与Redis协议本身相同的格式记录命令,并且仅采用append-only方式。当日志太大时,Redis可以在后台重写日志。类似于MySQL Binlog、Hbase HLog。在Redis重启时,通过回放日志中的写入指令来重构整个数据。

如果希望Redis仅作为纯内存的缓存来用,亦可禁用RDB和AOF。

可以在同一实例中同时使用AOF和RDB。这种情况下,当Redis重新启动时,AOF文件将用于重建原始数据集,因为它可以保证是最完整的。

最重要的是理解RDB与AOF持久性之间的不同权衡。如果同时使用RDB和AOF两种持久化机制,那么在Redis重启时,会使用AOF来重新构建数据,因为AOF中的数据更加完整!

2 RDB - 全量写入

Redis Server在有多db 中存储的K.V可理解为Redis的一个状态。当发生写操作时,Redis就会从一个状态切换到另外一个状态。
基于全量的持久化就是在某个时刻,将Redis的所有数据持久化到硬盘中,形成一个快照。当Redis 重启时,通过加载最近一个快照数据,可以将 Redis 恢复至最近一次持久化状态上。

2.1 触发方式

save 命令

save 可以由客户端显示触发,也可在redis shutdown 时触发。
save本身是单线程串行方式执行,因此当数据量大时,可能会发生Redis Server的长时间卡顿。但其备份期间不会有其他命令执行,因此备份时期 数据的状态始终是一致性 的。

若存在老的RDB文件,则新的会替换老的,时间复杂度O(N)。

bgsave

bgsave 也可由

  • 客户端显式触发
  • 配置定时任务触发
  • 主从架构下由从节点触发

bgsave命令在执行时,会fork一个子进程。子进程提交完成后,会立即给客户端返回响应,备份操作在后台异步执行,期间不会影响Redis的正常响应。

对于bgsave来说,当父进程Fork完子进程之后,异步任务会将当前的内存状态作为一个版本进行复制
在复制过程中产生的变更,不会反映在这次备份当中。

不用命令,而使用配置

在Redis的默认配置中,当满足下面任一条件,会自动触发 bgsave 的执行:

配置 seconds changes
save 900 1
save 300 10
save 60 10000

​​​​​​bgsave相对于save的优势是异步执行,不影响后续命令执行。但Fork子进程,涉及父进程的内存复制,会增加服务器内存开销。当内存开销高到使用虚拟内存时,bgsave的Fork子进程会阻塞运行,可能会造成秒级不可用。因此使用bgsave需要保证服务器空闲内存足够。

命令 save bgsave
IO类型 同步 异步
是否阻塞 阻塞 非阻塞(在fork时阻塞)
复杂度 O(N) O(N)
优点 不会消耗额外内存 不阻塞客户端命令
缺点 阻塞客户端命令 需要fork子进程,内存开销大

RDB 最佳配置

关闭自动RDB:

dbfilename dump-${port}.rdb
dir /redisDataPath
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes

需要注意的触发时机

  • 主从复制时机的全量复制,master节点会执行bgsave
  • debug reload
  • shutdown
  • flushDB 、 flushAll

RDB性质

  1. RDB是Redis内存到硬盘的快照,用于持久化
  2. save通常会阻塞Redis
  3. bgsave不会阻塞Redis,但会fork新进程
  4. save自动配置满足任一就会被执行

RDB 优点

  • RDB会生成多个数据文件,每个文件都代表了某时刻Redis中的所有数据,这种方式非常适合做冷备,可将这种完整数据文件发送到云服务器存储,比如ODPS分布式存储,以预定好的备份策略来定期备份Redis中的数据
  • RDB对Redis对外提供的读写服务,影响非常小,可让Redis保持高性能,因为Redis主进程只要fork一个子进程,让子进程执行RDB
  • 相对于AOF,直接基于RDB文件重启和恢复Redis进程,更加快速

RDB缺点

  • 耗时,O(n)
  • fork():耗内存,copy-on-write策略
    RDB每次在fork子进程来执行RDB快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒
  • 不可控,容易丢失数据
    一般RDB每隔5分钟,或者更长时间生成一次,若过程中Redis宕机,就会丢失最近未持久化的数据

2.2 恢复流程

当Redis重新启动时,会从本地磁盘加载之前持久化的文件。当恢复完成之后,再受理后续的请求操作。

3 AOF(append only file)- 增量模式

RDB记录的是每个状态的全量数据,AOF记录的则是每条写命令的记录,通过所有写命令的执行,最后恢复出最终的数据状态。

  • 其文件的生成如下:

3.1 写入流程

AOF的三种策略

always

  • 每次刷新缓冲区,都会同步触发同步操作。因为每次的写操作都会触发同步,所以该策略会降低Redis的吞吐量,但该 模式会拥有最高的容错能力。

every second

  • 每秒异步的触发同步操作,为Redis的默认配置

no

  • 由操作系统决定何时同步,该方式下Redis无法决定何时落地,因此不可控。

对比

命令 always everysec no
优点 不丢失数据 每秒1次fsync,丢1秒数据 无需设置
缺点 IO开销大,一般的STAT盘只有几百TPS 丢1秒数据 不可控

3.2 回放流程

AOF的回放时机也是在机器启动时一旦存在AOF,Redis就会选择增量回放

因为增量持久化是持续的写盘,相比于全量持久化,数据更加完整。回放过程就是将AOF中存放的命令,重新执行一遍。完成后再继续接收客户端新命令。

AOF模式的优化重写

随着Redis 持续的运行,会有大量的增量数据append 到AOF 文件中。为了减小硬盘存储和加快恢复速度,Redis 通过rewrite 机制合并历史AOF 记录。如下所示:

原生 AOF

set hello world
set hello java
set hello hehe
incr counter
incr counter
rpush mylist a
rpush mylist b
rpush mylist c
过期数据

AOF 重写

set hello hehe
set counter 2 
rpush mylist a b c

AOF重写的作用

  • 减少硬盘占用量
  • 加速恢复速度

3.3 AOF重写实现两种方式

bgrewriteaof

AOF 重写配置

配置项

  • AOF文件增长率 / AOF文件重写需要的大小
  • AOF当前尺寸(单位:字节)
  • aof_base_size AOF 上次启动和重写的大小(单位:字节)

自动触发配置

aof_current_size > auto-aof-rewrite-min-size
aof_current_size - aof_base_size/aof_base_size > auto-aof-rewrite-percentage

3.4 AOF 重写流程

AOF 重写配置

修改配置文件

appendonly yes
appendfilename "appendonly-$(port).aof"
appendfsync everysec
dir /opt/soft/redis/data
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
no-appendfsync-on-rewrite yes

AOF的优点

  • 更好避免数据丢失
    一般AOF每隔1s,通过子进程执行一次fsync,最多丢1s数据
  • append-only模式追加写
    所以没有任何磁盘寻址的开销,写入性能高,且文件不易破损,即使文件尾部破损,也易修复
  • 日志文件即使过大,出现后台重写操作,也不会影响客户端的读写
    因为在rewrite log时,会压缩其中的指令,创建出一份需要恢复数据的最小日志。在创建新日志时,旧日志文件还是照常写入。当新的merge后的日志文件准备好时,再交换新旧日志文件即可!
  • 命令通过非常可读的方式记录
    该特性非常适合做灾难性误删除操作的紧急恢复

比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,可立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可通过恢复机制,自动恢复所有数据

2.2.2 AOF的缺点

  • 对于同一份数据,AOF日志一般比RDB快照更大
  • AOF开启后,写QPS会比RDB的低,因为AOF一般会配置成每s fsync一次日志文件,当然,每s一次fsync,性能也还是很高的
  • 以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来
    类似AOF这种较为复杂的基于命令日志/merge/回放的方式,比基于RDB的每次持久化一份完整的数据快照方式相比更加脆弱一些,易产生bug

不过AOF就是为了避免rewrite过程导致的bug,因此每次rewrite并不是基于旧的指令日志进行merge的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会更好

4 选型及最佳实践

命令 RDB AOF
启动优先级
体积
恢复速度
数据安全性 丢数据 根据策略决定
量级 重量级 轻量级

4.1 RDB最佳策略

  • 关闭
  • 集中手动管理RDB操作
  • 在从节点打开自动执行配置,但是不宜频繁执行RDB

4.2 AOF最佳策略

  • 建议打开,但是如果只是纯作为缓存使用可不开
  • AOF重写集中管理
  • everysec

4.3 抉择RDB & AOF

  1. 不要仅使用RDB,因为那样会导致你丢失很多数据
  2. 也不要仅使用AOF,因为那样有两个问题

    • 你通过AOF做冷备,没有RDB做冷备,来的恢复速度更快
    • RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug
  3. 综合使用AOF和RDB

    • 用AOF保证数据不丢失,作为数据恢复的第一选择
    • 用RDB做不同程度的冷备,在AOF文件都丢失或损坏不可用时,还可使用RDB快速实现数据恢复

4.4 一些最佳实践

  • 小分片
    例如设置maxmemory参数设置每个redis只存储4个G的空间,这样各种操作都不会太慢
  • 监控(硬盘、内存、负载、网络)
  • 足够的内存

参考

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
19天前
|
NoSQL Java 关系型数据库
Liunx部署java项目Tomcat、Redis、Mysql教程
本文详细介绍了如何在 Linux 服务器上安装和配置 Tomcat、MySQL 和 Redis,并部署 Java 项目。通过这些步骤,您可以搭建一个高效稳定的 Java 应用运行环境。希望本文能为您在实际操作中提供有价值的参考。
98 26
|
28天前
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
67 14
|
23天前
|
Java 程序员
深入理解Java异常处理机制
Java的异常处理是编程中的一块基石,它不仅保障了代码的健壮性,还提升了程序的可读性和可维护性。本文将深入浅出地探讨Java异常处理的核心概念、分类、处理策略以及最佳实践,旨在帮助读者建立正确的异常处理观念,提升编程效率和质量。
111 1
|
24天前
|
Java 开发者 UED
深入探索Java中的异常处理机制##
本文将带你深入了解Java语言中的异常处理机制,包括异常的分类、异常的捕获与处理、自定义异常的创建以及最佳实践。通过具体实例和代码演示,帮助你更好地理解和运用Java中的异常处理,提高程序的健壮性和可维护性。 ##
47 2
|
24天前
|
Java 开发者
Java中的异常处理机制深度剖析####
本文深入探讨了Java语言中异常处理的重要性、核心机制及其在实际编程中的应用策略,旨在帮助开发者更有效地编写健壮的代码。通过实例分析,揭示了try-catch-finally结构的最佳实践,以及如何利用自定义异常提升程序的可读性和维护性。此外,还简要介绍了Java 7引入的多异常捕获特性,为读者提供了一个全面而实用的异常处理指南。 ####
48 2
|
28天前
|
运维 Java 编译器
Java 异常处理:机制、策略与最佳实践
Java异常处理是确保程序稳定运行的关键。本文介绍Java异常处理的机制,包括异常类层次结构、try-catch-finally语句的使用,并探讨常见策略及最佳实践,帮助开发者有效管理错误和异常情况。
91 5
|
27天前
|
Java 程序员 UED
深入理解Java中的异常处理机制
本文旨在揭示Java异常处理的奥秘,从基础概念到高级应用,逐步引导读者掌握如何优雅地管理程序中的错误。我们将探讨异常类型、捕获流程,以及如何在代码中有效利用try-catch语句。通过实例分析,我们将展示异常处理在提升代码质量方面的关键作用。
37 3
|
27天前
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
|
27天前
|
开发框架 安全 Java
Java 反射机制:动态编程的强大利器
Java反射机制允许程序在运行时检查类、接口、字段和方法的信息,并能操作对象。它提供了一种动态编程的方式,使得代码更加灵活,能够适应未知的或变化的需求,是开发框架和库的重要工具。
41 2
|
1月前
|
Java
深入探讨Java中的中断机制:INTERRUPTED和ISINTERRUPTED方法详解
在Java多线程编程中,中断机制是协调线程行为的重要手段。了解和正确使用中断机制对于编写高效、可靠的并发程序至关重要。本文将深入探讨Java中的`Thread.interrupted()`和`Thread.isInterrupted()`方法的区别及其应用场景。
38 4