持久化之RDB
持久化简介
Redis 持久化是确保内存数据安全的关键机制,通过将数据写入磁盘避免因宕机导致数据丢失。其核心包括 RDB(快照)、AOF(日志追加) 和 混合持久化 三种模式,各有适用场景和优劣势。
RDB(Redis Database) 简介
原理:
- 内存快照: 定期将内存数据以二进制压缩格式保存到
dump.rdb
文件中 - 写时复制(Copy-On-Write): 主进程通过
fork()
创建子进程处理快照生成, 子进程与主进程共享内存页, 仅当数据修改时才复制内存页, 减少内存占用。
优点:
- 特别适合数据容灾, 可以在灾难发生时轻松恢复不同版本的数据集。
- 因为是一个压缩文件,所以可以进行远程传输提供远程救灾功能
- 使用
Linux fork
子进程工作, 最大限度地提高了Redis
的性能 - 允许使用大数据集更快的重启
- 支持重启和故障转移后的部分重新同步
- 对数据完整性和一致性要求不高
缺点:
- 在突发情况下发生服务中断(如:断电), 则容易丢失数据
RDB
需要常使用Linux fork
子进程在磁盘中进行持久化, 若数据集很大且CPU性能优先, 则可能导致服务阻塞, 影响服务器性能。
触发机制:
自动触发:
修改 redis.conf
文件中的 SNAPSHOTTING
板块下的 save <seconds> <changes>
配置, 即规定了 多少秒内,修改了多少次数据激动进行 RDB
模式下持久化。
示例:
################################ SNAPSHOTTING ################################
# Save the DB to disk.
#
# save <seconds> <changes> [<seconds> <changes> ...]
#
# Redis will save the DB if the given number of seconds elapsed and it
# surpassed the given number of write operations against the DB.
#
# Snapshotting can be completely disabled with a single empty string argument
# as in following example:
#
# save ""
#
# Unless specified otherwise, by default Redis will save the DB:
# * After 3600 seconds (an hour) if at least 1 change was performed
# * After 300 seconds (5 minutes) if at least 100 changes were performed
# * After 60 seconds if at least 10000 changes were performed
#
# You can set these explicitly by uncommenting the following line.
#
# save 3600 1 300 100 60 10000
save 600 100 1800 10000 # 此处设置了Redis 每10分钟若进行了100次修改 或 每30分钟进行了1000次修改. 则将其自动持久化一次
修改持久化文件路径
Redis
默认的持久化文件是存储在 ./
(即 Redis 安装目录
) 中的, 为了后期方便维护, 可对持久化文件存储路径进行修改: 将
dir ./
# 改为
dir /指定路径
修改持久化文件名
为了保证 Redis
的高可用, 通常在生产环境中需要以集群的方式同时部署几台 Redis服务器
, 若每个服务器的持久化文件名相同, 则可能会降低维护性, 所以建议修改 Redis 持久化文件
, 修改方法如下:
# 将下行注释
dbfilename dump.rdb
# 设置此主机的 Redis 持久化文件名
dbfilename dump-6379.rdb
通过以下命令可查看是否修改成功:
127.0.0.1:6379> config get port
1) "port"
2) "6379"
127.0.0.1:6379> config get dir
1) "dir"
2) "/var/lib/redis/dumpfiles/"
使用程序触发自动持久化
# 查看备份目录
[root@redis-master ~]# ll /var/lib/redis/dumpfiles/
total 0 # 此时可以看见备份目录里没有持久化文件
编写代码使用程序触发 redis自动持久化
机制, 本文以 Python
和 Go
语言插入100条数据为例:
Python示例:
import redis
def test_redis(db: redis.Redis):
for i in range(100):
key = f"string-py{i}"
value = f"abc-py{i}"
try:
db.setex(key, 48 * 3600, value)
except redis.RedisError as e:
print(f"Error: {e}")
if __name__ == '__main__':
r_db = redis.Redis(host='redis_ip地址', port=6379, db=0, password='passwd')
test_redis(r_db)
Go语言示例:
import (
"context"
f "fmt"
"github.com/redis/go-redis/v9"
"sync"
"sync/atomic"
"time"
)
var ctx context.Context = context.Background() // 声明 根上下文
var wg sync.WaitGroup // 声明并发锁
type AtomicCounter struct { // 声明原子计数器类
counter int64
}
func (a *AtomicCounter) Inc() { // 声明一个原子计数器的自增方法
atomic.AddInt64(&a.counter, 1)
}
// 声明插入数据函数
func TestRedis(db *redis.Client, a *AtomicCounter, key, value string) {
a.Inc()
err := db.SetEx(ctx, key, value, 48*time.Hour).Err()
if err != nil {
f.Println(err)
}
defer wg.Done()
}
func main() {
var a AtomicCounter // 原子计数器对象
opt, err := redis.ParseURL("redis://default:passwd@redis_ip地址:6379/0")
if err != nil {
f.Println(err)
}
redisDB := redis.NewClient(opt)
defer redisDB.Close()
for i := 0; i < 100; i++ {
key := f.Sprintf("string-go%d", i)
value := f.Sprintf("abc-go%d", i)
wg.Add(1)
go TestRedis(redisDB, &a, key, value)
}
wg.Wait()
}
执行上述任意代码后可以触发 RDB自动持久化
机制, 得到以下结果:
[root@redis-master ~]# ls -lhs /var/lib/redis/dumpfiles/
total 4.0K
4.0K -rw-r--r-- 1 root root 3.2K Feb 25 18:42 dump-6379.rdb # 备份文件大小为 3.2k
注意:
- 若写入数据时太快, 例如写入100次数据只耗时1秒, 虽然次数已经达到自动持久化设定的次数, 但未等到后续时间窗口, 可能会导致自动持久化无法触发。
- 使用
flushall
/flushdb
清空数据库命令也会触发自动持久化, 但存储的.rdb
文件数据为空, 无意义- 在生产环境中, 若产生了有数据的
.rdb
文件, 应尽快进行迁移, 使服务和备份分机隔离, 以防生产环境和备份文件一同损坏
手动触发
1. save命令
save
命令直接由 Redis
主线程执行, 会阻塞所有客户端请求, 直到 .rdb
文件生成完毕。(生产环境禁用!!!)
适用场景:
- 程序调试或关闭服务时最后一次持久化
语法:
127.0.0.1:6379> save
OK
数据一致性风险
由于是同步操作,快照生成期间的数据修改会被包含在 .rdb
文件中,但若持久化过程中服务崩溃,可能导致最后一次快照后的数据丢失。
2. bgsave 命令
bgsave
通过 fork 子进程
异步生成 .rdb
文件, 主线程仅会产生短暂阻塞。(通过内存分片、系统调优并搭配 MQ(消息队列) 使用时,可以显著降低阻塞影响,在低谷期甚至能达到几乎无阻塞的效果!!)
注意:
- 写时复制(Copy-On-Write)机制:子进程共享父进程内存页,仅当父进程修改数据时,系统会复制被修改的内存页,确保子进程的快照数据是执行
fork
时的状态。- 内存开销:极端情况下(所有共享内存被修改),内存占用可能达到原数据的 2 倍。
语法:
127.0.0.1:6379> bgsave
Background saving started
数据一致性风险
快照仅包含 fork
时刻的数据,后续修改不会被持久化,因此两次快照间的数据可能丢失。
获取最后一次生成 RDB 快照时间
通过 lastsave
命令可以查看 Redis
最后一次生成 RDB 快照的时间。
示例:
127.0.0.1:6379> lastsave
(integer) 1740485907
127.0.0.1:6379> exit
[root@redis-master ~]# date -d @1740485907
Tue Feb 25 20:18:27 CST 2025
恢复数据
当数据因意外丢失后, 可以使用持久化机制产生 .rdb
文件恢复数据, 操作如下:
1. 停止 Redis 服务
为确保恢复过程中, Redis
未运行影响恢复, 应登录 redis-cli
命令行关闭 Redis
服务, 命令如下:
127.0.0.1:6379> shutdown
not connected> exit
2.将 .rdb 文件进行迁移
将备份机中的备份 .rdb
文件迁移到 Redis
服务机上的工作目录:
[root@Bak dumpfiles]# scp dump-6379.rdb root@redis-master:/var/lib/redis/dumpfiles/
3.启动Redis服务
使用以下命令启动Redis服务
, 便可恢复数据
[root@redis-master ~]# redis-server /etc/redis/redis.conf
注意:
启动 Redis服务
前请确保 Redis
服务机启动内存过度分配, 否则会出现以下警告:
[root@redis-master ~]# redis-server /etc/redis/redis.conf
3812:C 25 Feb 2025 19:29:18.139 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
启动内存过度分配:
启用方法:
[root@redis-master ~]# echo "vm.overcommit_memory = 1" >> /etc/sysctl.conf
[root@redis-master ~]# sysctl -p
# 若输出下列内容则表示设置成功
vm.overcommit_memory = 1
值得注意的是:
启用内存过度分配后, 系统可能分配超过物理内存的空间, 需定期检查内存或对
Redis
配置进行优化。
Redis 配置优化:
此处仅针对最大内存策略进行一定延伸, 若有进一步优化需求可参考Redis官方文档。
编辑 redis.conf
, 设置最大内存策略:
[root@redis-master ~]# vim /etc/redis/redis.conf
# 取消下面 maxmemory <size> 的注释, 并将 size 设定为指定值
# 单机预留大约 50% 物理内存为持久化等操作预留空间, 集群主节点额外预留 10% 用于数据同步
maxmemory 4gb
配置完成重启Redis服务
[root@redis-master ~]# systemctl restart redis.service
.rdb
文件修复
若因特殊原因导致 .rdb
文件损坏, 则可使用 Redis
提供的redis-check-rdb
工具进行修复。
示例(仅对文件做出基本的检查):
[root@redis-master ~]# redis-check-rdb /var/lib/redis/dumpfiles/dump-6379.rdb
[offset 0] Checking RDB file /var/lib/redis/dumpfiles/dump-6379.rdb
[offset 26] AUX FIELD redis-ver = '7.4.2'
[offset 40] AUX FIELD redis-bits = '64'
[offset 52] AUX FIELD ctime = '1740485907'
[offset 67] AUX FIELD used-mem = '1056720'
[offset 79] AUX FIELD aof-base = '0'
[offset 81] Selecting DB ID 0
[offset 3175] Checksum OK
[offset 3175] \o/ RDB looks OK! \o/
[info] 100 keys read
[info] 100 expires
[info] 0 already expired
[info] 0 subexpires
[root@redis-master ~]#
修复模式
使用 --fix
参数尝试修复文件, 修复前应对文件进行备份
redis-check-rdb --fix /var/lib/redis/dump.rdb
输出详细信息
使用 --verbose
参数显示详细的检查过程:
redis-check-rdb --verbose /var/lib/redis/dump.rdb
禁用RDB
在某些业务中, 只使用 AOF
进行持久化, 需对 RDB快照
禁用, 下面进行操作演示:
# 编辑 redis.conf 文件
[root@redis-master ~]# vim /etc/redis/redis.conf
################################ SNAPSHOTTING ################################
# Save the DB to disk.
#
# save <seconds> <changes> [<seconds> <changes> ...]
#
# Redis will save the DB if the given number of seconds elapsed and it
# surpassed the given number of write operations against the DB.
#
# Snapshotting can be completely disabled with a single empty string argument
# as in following example:
#
save "" # 找到这一行, 取消注释
# 保存退出后, 重启 Redis 服务, 使配置生效
[root@redis-master ~]# systemctl restart redis.service