上一篇【第29篇】RDB持久化——Redis的快照是怎么拍的下一篇【第31篇】AOF持久化明日更新敬请期待上一篇我们知道了RDB是怎么拍照的。这篇我们就来拆解这张照片——一个dump.rdb文件里每一个字节都在说什么。引言为什么要了解RDB文件格式大多数人认为RDB文件就是个黑盒——Redis写的Redis读的关我什么事但当你遇到以下场景懂RDB格式就是超能力RDB文件损坏了需要手动恢复数据需要解析RDB文件做数据迁移或分析想知道为什么RDB文件突然变大了排查RDB文件中的过期键或编码异常理解RDB格式就是拿到Redis持久化的源代码。RDB文件整体结构一个RDB文件由五大部分组成从上到下依次是┌────────────────────────────────────────────────────────┐ │ RDB 文件整体结构 │ │ │ │ ┌──────────────────────────────────────────┐ │ │ │ REDIS 魔数5字节 │ ← 文件标识 │ │ 如REDIS │ │ │ ├──────────────────────────────────────────┤ │ │ │ db_version4字节 │ ← RDB版本号 │ │ 如0011 │ │ │ ├──────────────────────────────────────────┤ │ │ │ │ │ │ │ databases 部分变长 │ ← 核心数据 │ │ ┌──────────────────────────┐ │ │ │ │ │ SELECTDB db_number │ │ │ │ │ │ key_value_pairs... │ │ │ │ │ └──────────────────────────┘ │ │ │ │ 可能包含多个数据库 │ │ │ │ │ │ │ ├──────────────────────────────────────────┤ │ │ │ EOF1字节 0xFF │ ← 结束标记 │ ├──────────────────────────────────────────┤ │ │ │ check_sum8字节 │ ← CRC64校验和 │ │ 如果开启了rdbchecksum │ │ │ └──────────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────────────────┘1. REDIS魔数5字节文件最开头的5个字节固定为REDIS这是RDB文件的身份证。Redis在载入RDB文件时首先检查这5个字节如果不是REDIS就拒绝载入。偏移量 0 1 2 3 4 内容 R E D I S ASCII 52 45 44 49 532. db_version4字节魔数之后是4字节的版本号用ASCII数字表示。比如0011代表RDB版本11。偏移量 5 6 7 8 内容 0 0 1 1 含义 RDB格式版本11版本号决定了RDB文件的编码方式不同版本支持的特性不同后文详述。3. databases部分变长这是RDB文件的核心包含所有数据库的键值对数据。我们将在下一节详细解析。4. EOF标记1字节值为0xFF255标志数据库数据部分的结束。5. check_sum8字节8字节的CRC64校验和用于验证RDB文件的完整性。如果配置了rdbchecksum yes默认开启Redis会在写入RDB时计算校验和载入时验证。完整文件字面量示例 REDIS0011 ← 魔数 版本 [database data] ← 数据库数据 FF ← EOF [A8 3B 2C ...] ← 8字节校验和databases部分的结构databases部分由零个或多个数据库块组成每个数据库块的结构如下┌───────────────────────────────────────────────┐ │ 单个数据库块结构 │ │ │ │ ┌─────────────────────────────────┐ │ │ │ SELECTDB 操作码1字节 0xFE │ ← 标识新数据库开始 │ ├─────────────────────────────────┤ │ │ │ db_number变长整数 │ ← 数据库编号0-15 │ ├─────────────────────────────────┤ │ │ │ │ │ │ │ key_value_pairs变长 │ ← 键值对数据 │ │ ┌─────────────────────────┐ │ │ │ │ │ 可选EXPIRETIME_MS │ │ │ │ │ │ 可选EXPIRETIME │ │ │ │ │ │ TYPE │ │ │ │ │ │ key │ │ │ │ │ │ value │ │ │ │ │ └─────────────────────────┘ │ │ │ │ ...重复多个键值对 │ │ │ │ │ │ │ └─────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────┘SELECTDB操作码值为0xFE254标识一个新的数据库开始db_number数据库编号用变长整数编码。0号数据库就是015号就是15key_value_pairs该数据库中的所有键值对key_value_pairs的结构每个键值对由以下部分组成┌─────────────────────────────────────────────────┐ │ 单个键值对结构 │ │ │ │ ┌───────────────────────────────┐ │ │ │ 可选EXPIRETIME_MS (0xFC) │ ← 毫秒过期时间 │ │ 毫秒时间戳8字节 │ │ │ │ │ │ │ │ 或可选EXPIRETIME (0xFD) │ ← 秒过期时间 │ │ 秒时间戳4字节 │ │ │ ├───────────────────────────────┤ │ │ │ TYPE1字节 │ ← 值类型编码 │ ├───────────────────────────────┤ │ │ │ key字符串 │ ← 键名 │ ├───────────────────────────────┤ │ │ │ value编码取决于TYPE │ ← 值 │ └───────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────┘字段说明EXPIRETIME_MS / EXPIRETIME可选字段。如果键设置了过期时间会在TYPE之前出现。0xFC表示后面跟着8字节的毫秒时间戳0xFD表示后面跟着4字节的秒时间戳。TYPE1字节表示值的类型。这是RDB中最关键的编码字段。key字符串类型表示键名。value值的编码方式取决于TYPE字段。TYPE编码值TYPE字段的值决定了value的编码方式编码值常量名类型value编码方式0REDIS_RDB_TYPE_STRINGString字符串编码1REDIS_RDB_TYPE_LISTListlist编码2REDIS_RDB_TYPE_SETSetset编码3REDIS_RDB_TYPE_ZSETSorted Setzset编码4REDIS_RDB_TYPE_HASHHashhash编码5REDIS_RDB_TYPE_ZSET_2Sorted Set (v2)zset2编码double精度6REDIS_RDB_TYPE_ZSET_ZIPLISTSorted Set (ziplist)紧凑编码7REDIS_RDB_TYPE_HASH_ZIPLISTHash (ziplist)紧凑编码8REDIS_RDB_TYPE_LIST_QUICKLISTList (quicklist)紧凑编码9REDIS_RDB_TYPE_HASH_LISTPACKHash (listpack)紧凑编码10REDIS_RDB_TYPE_ZSET_LISTPACKSorted Set (listpack)紧凑编码11REDIS_RDB_TYPE_LIST_QUICKLIST_2List (quicklist v2)紧凑编码12REDIS_RDB_TYPE_STREAM_LISTPACKSStreamstream编码13REDIS_RDB_TYPE_HASH_LISTPACK_2Hash (listpack v2)紧凑编码14REDIS_RDB_TYPE_SET_LISTPACKSet (listpack)紧凑编码注意具体编码值可能因Redis版本而异。上表基于Redis 7.x。紧凑编码ziplist/listpack等是小数据量的优化存储方式。String类型的RDB编码String类型是RDB中最基础的编码有三种子编码方式方式一int编码如果字符串可以表示为一个整数范围在LONG_MIN到LONG_MAX之间Redis会用整数编码节省空间┌─────────────────────────────────┐ │ length编码特殊值表示int类型 │ │ │ │ length 0xC0 整数长度 │ │ 0xC0 int8 (1字节整数) │ │ 0xC1 int16 (2字节整数) │ │ 0xC2 int32 (4字节整数) │ │ 0xC3 int64 (8字节整数v9) │ │ │ │ 示例值 42 │ │ 编码C0 2A │ │ │ └─ 42的int8表示 │ │ └─ int8编码标识 │ └─────────────────────────────────┘方式二len bytes编码短字符串对于不适合int编码的字符串使用length前缀字节数据的方式┌──────────────────────────────────────┐ │ length编码规则 │ │ │ │ length 64 (0x00-0x3F) │ │ → 1字节6位length 最高2位为00 │ │ 如length5 → 0x05 │ │ │ │ length 16384 (0x0000-0x3FFF) │ │ → 2字节14位length 最高2位为01 │ │ 如length1000 → 0x43E8 │ │ │ │ length 2^32 │ │ → 5字节32位length 最高1字节0x82│ │ 如length100000 → 0x82 0x000186A0│ └──────────────────────────────────────┘ 示例key hello, value world 编码 05 68 65 6C 6C 6F ← key: len5 hello 05 77 6F 72 6C 64 ← value: len5 world方式三LZF压缩编码长字符串如果开启了rdbcompression且字符串长度超过20字节Redis会用LZF算法压缩┌──────────────────────────────────────┐ │ 压缩字符串编码 │ │ │ │ 0xC3 ← 压缩标识 │ │ compressed_len (变长整数) ← 压缩后长度│ │ origin_len (变长整数) ← 原始长度 │ │ compressed_data ← 压缩数据 │ └──────────────────────────────────────┘List/Hash/Set/ZSet各类型的RDB序列化格式List类型List编码格式 ┌────────────────────────────────────┐ │ list_length (变长整数) │ ← 列表元素个数 │ item1 (字符串编码) │ │ item2 (字符串编码) │ │ ... │ │ itemN (字符串编码) │ └────────────────────────────────────┘ 示例LPUSH mylist a b c RDB编码 03 ← list_length 3 01 61 ← item1: len1, a 01 62 ← item2: len1, b 01 63 ← item3: len1, cHash类型Hash编码格式 ┌────────────────────────────────────┐ │ hash_length (变长整数) │ ← 字段个数 │ field1 (字符串编码) │ │ value1 (字符串编码) │ │ field2 (字符串编码) │ │ value2 (字符串编码) │ │ ... │ └────────────────────────────────────┘ 示例HSET myhash f1 v1 f2 v2 RDB编码 02 ← hash_length 2 02 66 31 ← field1: len2, f1 02 76 31 ← value1: len2, v1 02 66 32 ← field2: len2, f2 02 76 32 ← value2: len2, v2Set类型Set编码格式 ┌────────────────────────────────────┐ │ set_size (变长整数) │ ← 集合元素个数 │ member1 (字符串编码) │ │ member2 (字符串编码) │ │ ... │ └────────────────────────────────────┘Sorted Set类型Sorted Set编码格式 ┌────────────────────────────────────────────┐ │ zset_length (变长整数) │ ← 有序集合元素个数 │ member1 (字符串编码) │ │ score1 (double或float编码) │ │ member2 (字符串编码) │ │ score2 (double或float编码) │ │ ... │ └────────────────────────────────────────────┘ score编码 - ZSET(v1)float编码4字节 - ZSET_2(v2)double编码8字节精度更高RDB版本的演进RDB格式从版本1发展到版本12截至Redis 7.x每个版本都有重要的变化版本引入版本主要变化1Redis 1.0初始格式2Redis 1.2支持压缩字符串3Redis 2.0支持排序集合的v2编码4Redis 2.2支持编码类型字段5Redis 2.6支持checksum、64-bit时间戳6Redis 2.8支持quicklist7Redis 3.2支持ziplist编码的hash8Redis 4.0支持module类型9Redis 5.0支持int64、quicklist v210Redis 6.0支持listpack11Redis 7.0支持Stream、listpack改进12Redis 7.2更多紧凑编码改进版本演进时间线 v1 ──► v2 ──► v3 ──► v4 ──► v5 ──► v6 ──► v7 ──► v8 ──► v9 ──► v10 ──► v11 ──► v12 压缩 ZSETv2 编码类型 checksum quicklist ziplist module int64 listpack stream 改进 字符串 hash踩坑提示高版本RDB文件不能直接在低版本Redis中载入比如Redis 7.0生成的RDB文件无法在Redis 5.0中载入。做版本升级或降级时务必确认RDB版本兼容性。redis-check-rdb工具详解redis-check-rdb是Redis自带的RDB文件诊断工具可以检查和修复RDB文件# 基本检查redis-check-rdb /var/lib/redis/dump.rdb# 输出示例[offset0]Checking RDBfile/var/lib/redis/dump.rdb[offset26]AUX FIELD redis-ver7.0.12[offset40]AUX FIELD redis-bits64[offset52]AUX FIELD ctime1687700123[offset57]AUX FIELD used-mem1048576[offset62]AUX FIELD aof-preamble0[offset13443]Selecting DB ID0[offset13567]------ string key ------[offset13567]key:mykey[offset13579]value:hello[offset25987]CHECKSUM: OK ← 校验通过# 修复损坏的RDB文件redis-check-rdb--fix/var/lib/redis/dump.rdb# 会提示修复将丢弃损坏部分之后的所有数据# 选择 y 确认使用strings/xxd查看RDB原始内容用Linux命令行工具可以直接窥探RDB文件的原始内容strings命令提取可读字符串# 提取RDB文件中的可读字符串strings /var/lib/redis/dump.rdb|head-20# 输出示例REDIS 0011 redis-ver7.0.12 redis-bits64ctime used-mem mykey hello user:1 Tomstrings命令能帮你快速了解RDB中存储了哪些键和值。xxd命令查看十六进制内容# 查看RDB文件前100字节的十六进制内容xxd /var/lib/redis/dump.rdb|head-10# 输出示例00000000:5245444953003131fefa 0000 0000 0000 REDIS.11........00000010: 0c00 006d 796b65790568 656c 6c6f ff7f...mykey.hello..00000020:...从十六进制内容中可以解读52 45 44 49 53REDIS魔数00 31 31版本号0011FESELECTDB操作码000号数据库0CTYPE0String后面跟着key和value踩坑提示生产环境的RDB文件可能包含敏感数据如用户信息、token等。使用strings或xxd查看时注意安全不要在公共场合展示RDB文件内容。RDB文件的压缩Redis提供了rdbcompression配置来控制RDB文件中字符串的压缩# redis.confrdbcompressionyes# 默认开启压缩策略对于长度超过20字节的字符串使用LZF算法压缩对于List、Set、Hash等包含多个字符串元素的对象每个元素独立判断是否压缩LZF压缩的特点压缩速度快接近内存拷贝速度解压速度也快但压缩比不如gziprdbcompression 效果对比 ┌──────────────────────────────────────┐ │ 原始数据100万个10字符的随机字符串 │ │ │ │ rdbcompression yes: RDB 3.2MB │ │ rdbcompression no: RDB 8.7MB │ │ │ │ 压缩率约63% │ │ BGSAVE耗时差异5% │ └──────────────────────────────────────┘踩坑提示对于已经是压缩格式的数据如JPEG图片、Snappy压缩的数据开启rdbcompression反而可能让RDB文件变大因为LZF无法进一步压缩反而增加了压缩头部的开销。此外还有一个rdbchecksum配置rdbchecksumyes# 默认开启在RDB文件末尾添加8字节CRC64校验和关闭校验和可以让RDB文件生成快约10%但失去了文件完整性验证能力。生产环境建议保持开启。RDB vs AOF 完整对比维度RDBAOF持久化方式全量快照增量日志追加数据安全可能丢失最后一次保存后的所有数据默认每秒fsync最多丢1秒数据恢复速度快直接载入内存约10-20秒/GB慢重放所有写命令约50-100秒/GB文件大小紧凑压缩后的二进制较大记录所有写命令重写后缩小系统开销BGSAVE时fork开销COW额外内存每次写入追加AOF定期fsync可读性二进制不可直接阅读文本格式可人工阅读兼容性高版本RDB不兼容低版本RedisAOF命令向后兼容适用场景备份、灾备、冷启动数据安全要求高的在线服务大数据集表现恢复快但fork可能慢恢复慢AOF重写耗时长故障恢复从最近一次RDB恢复从AOF尾部重放推荐搭配与AOF混合使用单独使用或与RDB混合选择建议你的业务能容忍多少数据丢失 │ ├── 不容忍金融/支付──► AOF RDB 混合持久化 │ ├── 可容忍秒级 ──────────► AOFeverysec模式 │ ├── 可容忍分钟级 ─────────► RDB合理配置save参数 │ └── 仅缓存丢了无所谓 ───► 可不持久化或仅RDB备份混合持久化Redis 4.0在AOF重写时前半部分用RDB格式快后半部分用AOF格式增量兼顾恢复速度和数据安全。实战手动解析一个简单的RDB文件让我们用一个小例子来完整解析RDB文件# 在Redis中写入一些数据127.0.0.1:6379FLUSHALL127.0.0.1:6379SET mykeyhello127.0.0.1:6379SET counter42127.0.0.1:6379SAVE然后用xxd查看生成的RDB文件xxd dump.rdb解读关键字节偏移 十六进制 解读 ─────────────────────────────────────────────────── 0x0000 52 45 44 49 53 REDIS 魔数 0x0005 30 30 31 31 版本 0011 (v11) 0x0009 FA AUX操作码 (redis-ver) 0x000A ... redis-ver 7.0.12 0x00?? FE 00 SELECTDB 0 (0号数据库) 0x00?? 00 05 6D 79 6B 65 79 TYPESTRING, keymykey(5字节) 0x00?? 05 68 65 6C 6C 6F valuehello(5字节) 0x00?? 00 07 63 6F 75 6E 74 65 72 TYPESTRING, keycounter(7字节) 0x00?? C0 2A int编码, value42 0x00?? FF EOF 0x00?? XX XX XX XX XX XX XX XX CRC64校验和通过手动解析你能看到每一个键值对在RDB中的完整编码方式。这对于理解Redis内部存储和排查问题非常有帮助。总结RDB文件格式虽然看起来复杂但结构清晰、层次分明整体结构REDIS魔数 → 版本号 → 数据库数据 → EOF → 校验和数据库数据由SELECTDB 数据库编号 键值对序列组成键值对编码可选过期时间 → TYPE → key → value字符串编码int编码节省空间、lenbytes、LZF压缩长字符串版本演进从v1到v12每个版本引入新的编码方式RDB vs AOFRDB恢复快但可能丢数据AOF安全但恢复慢理解了RDB文件格式的每一个字节你就拥有了排查RDB相关问题的超能力。下一篇我们将深入AOF持久化的实现原理看看Redis的另一条持久化之路。上一篇【第29篇】RDB持久化——Redis的快照是怎么拍的下一篇【第31篇】AOF持久化明日更新敬请期待
【Redis从入门到精通】第30篇:RDB文件格式完全解析——一个快照文件里藏着什么秘密
上一篇【第29篇】RDB持久化——Redis的快照是怎么拍的下一篇【第31篇】AOF持久化明日更新敬请期待上一篇我们知道了RDB是怎么拍照的。这篇我们就来拆解这张照片——一个dump.rdb文件里每一个字节都在说什么。引言为什么要了解RDB文件格式大多数人认为RDB文件就是个黑盒——Redis写的Redis读的关我什么事但当你遇到以下场景懂RDB格式就是超能力RDB文件损坏了需要手动恢复数据需要解析RDB文件做数据迁移或分析想知道为什么RDB文件突然变大了排查RDB文件中的过期键或编码异常理解RDB格式就是拿到Redis持久化的源代码。RDB文件整体结构一个RDB文件由五大部分组成从上到下依次是┌────────────────────────────────────────────────────────┐ │ RDB 文件整体结构 │ │ │ │ ┌──────────────────────────────────────────┐ │ │ │ REDIS 魔数5字节 │ ← 文件标识 │ │ 如REDIS │ │ │ ├──────────────────────────────────────────┤ │ │ │ db_version4字节 │ ← RDB版本号 │ │ 如0011 │ │ │ ├──────────────────────────────────────────┤ │ │ │ │ │ │ │ databases 部分变长 │ ← 核心数据 │ │ ┌──────────────────────────┐ │ │ │ │ │ SELECTDB db_number │ │ │ │ │ │ key_value_pairs... │ │ │ │ │ └──────────────────────────┘ │ │ │ │ 可能包含多个数据库 │ │ │ │ │ │ │ ├──────────────────────────────────────────┤ │ │ │ EOF1字节 0xFF │ ← 结束标记 │ ├──────────────────────────────────────────┤ │ │ │ check_sum8字节 │ ← CRC64校验和 │ │ 如果开启了rdbchecksum │ │ │ └──────────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────────────────┘1. REDIS魔数5字节文件最开头的5个字节固定为REDIS这是RDB文件的身份证。Redis在载入RDB文件时首先检查这5个字节如果不是REDIS就拒绝载入。偏移量 0 1 2 3 4 内容 R E D I S ASCII 52 45 44 49 532. db_version4字节魔数之后是4字节的版本号用ASCII数字表示。比如0011代表RDB版本11。偏移量 5 6 7 8 内容 0 0 1 1 含义 RDB格式版本11版本号决定了RDB文件的编码方式不同版本支持的特性不同后文详述。3. databases部分变长这是RDB文件的核心包含所有数据库的键值对数据。我们将在下一节详细解析。4. EOF标记1字节值为0xFF255标志数据库数据部分的结束。5. check_sum8字节8字节的CRC64校验和用于验证RDB文件的完整性。如果配置了rdbchecksum yes默认开启Redis会在写入RDB时计算校验和载入时验证。完整文件字面量示例 REDIS0011 ← 魔数 版本 [database data] ← 数据库数据 FF ← EOF [A8 3B 2C ...] ← 8字节校验和databases部分的结构databases部分由零个或多个数据库块组成每个数据库块的结构如下┌───────────────────────────────────────────────┐ │ 单个数据库块结构 │ │ │ │ ┌─────────────────────────────────┐ │ │ │ SELECTDB 操作码1字节 0xFE │ ← 标识新数据库开始 │ ├─────────────────────────────────┤ │ │ │ db_number变长整数 │ ← 数据库编号0-15 │ ├─────────────────────────────────┤ │ │ │ │ │ │ │ key_value_pairs变长 │ ← 键值对数据 │ │ ┌─────────────────────────┐ │ │ │ │ │ 可选EXPIRETIME_MS │ │ │ │ │ │ 可选EXPIRETIME │ │ │ │ │ │ TYPE │ │ │ │ │ │ key │ │ │ │ │ │ value │ │ │ │ │ └─────────────────────────┘ │ │ │ │ ...重复多个键值对 │ │ │ │ │ │ │ └─────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────┘SELECTDB操作码值为0xFE254标识一个新的数据库开始db_number数据库编号用变长整数编码。0号数据库就是015号就是15key_value_pairs该数据库中的所有键值对key_value_pairs的结构每个键值对由以下部分组成┌─────────────────────────────────────────────────┐ │ 单个键值对结构 │ │ │ │ ┌───────────────────────────────┐ │ │ │ 可选EXPIRETIME_MS (0xFC) │ ← 毫秒过期时间 │ │ 毫秒时间戳8字节 │ │ │ │ │ │ │ │ 或可选EXPIRETIME (0xFD) │ ← 秒过期时间 │ │ 秒时间戳4字节 │ │ │ ├───────────────────────────────┤ │ │ │ TYPE1字节 │ ← 值类型编码 │ ├───────────────────────────────┤ │ │ │ key字符串 │ ← 键名 │ ├───────────────────────────────┤ │ │ │ value编码取决于TYPE │ ← 值 │ └───────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────┘字段说明EXPIRETIME_MS / EXPIRETIME可选字段。如果键设置了过期时间会在TYPE之前出现。0xFC表示后面跟着8字节的毫秒时间戳0xFD表示后面跟着4字节的秒时间戳。TYPE1字节表示值的类型。这是RDB中最关键的编码字段。key字符串类型表示键名。value值的编码方式取决于TYPE字段。TYPE编码值TYPE字段的值决定了value的编码方式编码值常量名类型value编码方式0REDIS_RDB_TYPE_STRINGString字符串编码1REDIS_RDB_TYPE_LISTListlist编码2REDIS_RDB_TYPE_SETSetset编码3REDIS_RDB_TYPE_ZSETSorted Setzset编码4REDIS_RDB_TYPE_HASHHashhash编码5REDIS_RDB_TYPE_ZSET_2Sorted Set (v2)zset2编码double精度6REDIS_RDB_TYPE_ZSET_ZIPLISTSorted Set (ziplist)紧凑编码7REDIS_RDB_TYPE_HASH_ZIPLISTHash (ziplist)紧凑编码8REDIS_RDB_TYPE_LIST_QUICKLISTList (quicklist)紧凑编码9REDIS_RDB_TYPE_HASH_LISTPACKHash (listpack)紧凑编码10REDIS_RDB_TYPE_ZSET_LISTPACKSorted Set (listpack)紧凑编码11REDIS_RDB_TYPE_LIST_QUICKLIST_2List (quicklist v2)紧凑编码12REDIS_RDB_TYPE_STREAM_LISTPACKSStreamstream编码13REDIS_RDB_TYPE_HASH_LISTPACK_2Hash (listpack v2)紧凑编码14REDIS_RDB_TYPE_SET_LISTPACKSet (listpack)紧凑编码注意具体编码值可能因Redis版本而异。上表基于Redis 7.x。紧凑编码ziplist/listpack等是小数据量的优化存储方式。String类型的RDB编码String类型是RDB中最基础的编码有三种子编码方式方式一int编码如果字符串可以表示为一个整数范围在LONG_MIN到LONG_MAX之间Redis会用整数编码节省空间┌─────────────────────────────────┐ │ length编码特殊值表示int类型 │ │ │ │ length 0xC0 整数长度 │ │ 0xC0 int8 (1字节整数) │ │ 0xC1 int16 (2字节整数) │ │ 0xC2 int32 (4字节整数) │ │ 0xC3 int64 (8字节整数v9) │ │ │ │ 示例值 42 │ │ 编码C0 2A │ │ │ └─ 42的int8表示 │ │ └─ int8编码标识 │ └─────────────────────────────────┘方式二len bytes编码短字符串对于不适合int编码的字符串使用length前缀字节数据的方式┌──────────────────────────────────────┐ │ length编码规则 │ │ │ │ length 64 (0x00-0x3F) │ │ → 1字节6位length 最高2位为00 │ │ 如length5 → 0x05 │ │ │ │ length 16384 (0x0000-0x3FFF) │ │ → 2字节14位length 最高2位为01 │ │ 如length1000 → 0x43E8 │ │ │ │ length 2^32 │ │ → 5字节32位length 最高1字节0x82│ │ 如length100000 → 0x82 0x000186A0│ └──────────────────────────────────────┘ 示例key hello, value world 编码 05 68 65 6C 6C 6F ← key: len5 hello 05 77 6F 72 6C 64 ← value: len5 world方式三LZF压缩编码长字符串如果开启了rdbcompression且字符串长度超过20字节Redis会用LZF算法压缩┌──────────────────────────────────────┐ │ 压缩字符串编码 │ │ │ │ 0xC3 ← 压缩标识 │ │ compressed_len (变长整数) ← 压缩后长度│ │ origin_len (变长整数) ← 原始长度 │ │ compressed_data ← 压缩数据 │ └──────────────────────────────────────┘List/Hash/Set/ZSet各类型的RDB序列化格式List类型List编码格式 ┌────────────────────────────────────┐ │ list_length (变长整数) │ ← 列表元素个数 │ item1 (字符串编码) │ │ item2 (字符串编码) │ │ ... │ │ itemN (字符串编码) │ └────────────────────────────────────┘ 示例LPUSH mylist a b c RDB编码 03 ← list_length 3 01 61 ← item1: len1, a 01 62 ← item2: len1, b 01 63 ← item3: len1, cHash类型Hash编码格式 ┌────────────────────────────────────┐ │ hash_length (变长整数) │ ← 字段个数 │ field1 (字符串编码) │ │ value1 (字符串编码) │ │ field2 (字符串编码) │ │ value2 (字符串编码) │ │ ... │ └────────────────────────────────────┘ 示例HSET myhash f1 v1 f2 v2 RDB编码 02 ← hash_length 2 02 66 31 ← field1: len2, f1 02 76 31 ← value1: len2, v1 02 66 32 ← field2: len2, f2 02 76 32 ← value2: len2, v2Set类型Set编码格式 ┌────────────────────────────────────┐ │ set_size (变长整数) │ ← 集合元素个数 │ member1 (字符串编码) │ │ member2 (字符串编码) │ │ ... │ └────────────────────────────────────┘Sorted Set类型Sorted Set编码格式 ┌────────────────────────────────────────────┐ │ zset_length (变长整数) │ ← 有序集合元素个数 │ member1 (字符串编码) │ │ score1 (double或float编码) │ │ member2 (字符串编码) │ │ score2 (double或float编码) │ │ ... │ └────────────────────────────────────────────┘ score编码 - ZSET(v1)float编码4字节 - ZSET_2(v2)double编码8字节精度更高RDB版本的演进RDB格式从版本1发展到版本12截至Redis 7.x每个版本都有重要的变化版本引入版本主要变化1Redis 1.0初始格式2Redis 1.2支持压缩字符串3Redis 2.0支持排序集合的v2编码4Redis 2.2支持编码类型字段5Redis 2.6支持checksum、64-bit时间戳6Redis 2.8支持quicklist7Redis 3.2支持ziplist编码的hash8Redis 4.0支持module类型9Redis 5.0支持int64、quicklist v210Redis 6.0支持listpack11Redis 7.0支持Stream、listpack改进12Redis 7.2更多紧凑编码改进版本演进时间线 v1 ──► v2 ──► v3 ──► v4 ──► v5 ──► v6 ──► v7 ──► v8 ──► v9 ──► v10 ──► v11 ──► v12 压缩 ZSETv2 编码类型 checksum quicklist ziplist module int64 listpack stream 改进 字符串 hash踩坑提示高版本RDB文件不能直接在低版本Redis中载入比如Redis 7.0生成的RDB文件无法在Redis 5.0中载入。做版本升级或降级时务必确认RDB版本兼容性。redis-check-rdb工具详解redis-check-rdb是Redis自带的RDB文件诊断工具可以检查和修复RDB文件# 基本检查redis-check-rdb /var/lib/redis/dump.rdb# 输出示例[offset0]Checking RDBfile/var/lib/redis/dump.rdb[offset26]AUX FIELD redis-ver7.0.12[offset40]AUX FIELD redis-bits64[offset52]AUX FIELD ctime1687700123[offset57]AUX FIELD used-mem1048576[offset62]AUX FIELD aof-preamble0[offset13443]Selecting DB ID0[offset13567]------ string key ------[offset13567]key:mykey[offset13579]value:hello[offset25987]CHECKSUM: OK ← 校验通过# 修复损坏的RDB文件redis-check-rdb--fix/var/lib/redis/dump.rdb# 会提示修复将丢弃损坏部分之后的所有数据# 选择 y 确认使用strings/xxd查看RDB原始内容用Linux命令行工具可以直接窥探RDB文件的原始内容strings命令提取可读字符串# 提取RDB文件中的可读字符串strings /var/lib/redis/dump.rdb|head-20# 输出示例REDIS 0011 redis-ver7.0.12 redis-bits64ctime used-mem mykey hello user:1 Tomstrings命令能帮你快速了解RDB中存储了哪些键和值。xxd命令查看十六进制内容# 查看RDB文件前100字节的十六进制内容xxd /var/lib/redis/dump.rdb|head-10# 输出示例00000000:5245444953003131fefa 0000 0000 0000 REDIS.11........00000010: 0c00 006d 796b65790568 656c 6c6f ff7f...mykey.hello..00000020:...从十六进制内容中可以解读52 45 44 49 53REDIS魔数00 31 31版本号0011FESELECTDB操作码000号数据库0CTYPE0String后面跟着key和value踩坑提示生产环境的RDB文件可能包含敏感数据如用户信息、token等。使用strings或xxd查看时注意安全不要在公共场合展示RDB文件内容。RDB文件的压缩Redis提供了rdbcompression配置来控制RDB文件中字符串的压缩# redis.confrdbcompressionyes# 默认开启压缩策略对于长度超过20字节的字符串使用LZF算法压缩对于List、Set、Hash等包含多个字符串元素的对象每个元素独立判断是否压缩LZF压缩的特点压缩速度快接近内存拷贝速度解压速度也快但压缩比不如gziprdbcompression 效果对比 ┌──────────────────────────────────────┐ │ 原始数据100万个10字符的随机字符串 │ │ │ │ rdbcompression yes: RDB 3.2MB │ │ rdbcompression no: RDB 8.7MB │ │ │ │ 压缩率约63% │ │ BGSAVE耗时差异5% │ └──────────────────────────────────────┘踩坑提示对于已经是压缩格式的数据如JPEG图片、Snappy压缩的数据开启rdbcompression反而可能让RDB文件变大因为LZF无法进一步压缩反而增加了压缩头部的开销。此外还有一个rdbchecksum配置rdbchecksumyes# 默认开启在RDB文件末尾添加8字节CRC64校验和关闭校验和可以让RDB文件生成快约10%但失去了文件完整性验证能力。生产环境建议保持开启。RDB vs AOF 完整对比维度RDBAOF持久化方式全量快照增量日志追加数据安全可能丢失最后一次保存后的所有数据默认每秒fsync最多丢1秒数据恢复速度快直接载入内存约10-20秒/GB慢重放所有写命令约50-100秒/GB文件大小紧凑压缩后的二进制较大记录所有写命令重写后缩小系统开销BGSAVE时fork开销COW额外内存每次写入追加AOF定期fsync可读性二进制不可直接阅读文本格式可人工阅读兼容性高版本RDB不兼容低版本RedisAOF命令向后兼容适用场景备份、灾备、冷启动数据安全要求高的在线服务大数据集表现恢复快但fork可能慢恢复慢AOF重写耗时长故障恢复从最近一次RDB恢复从AOF尾部重放推荐搭配与AOF混合使用单独使用或与RDB混合选择建议你的业务能容忍多少数据丢失 │ ├── 不容忍金融/支付──► AOF RDB 混合持久化 │ ├── 可容忍秒级 ──────────► AOFeverysec模式 │ ├── 可容忍分钟级 ─────────► RDB合理配置save参数 │ └── 仅缓存丢了无所谓 ───► 可不持久化或仅RDB备份混合持久化Redis 4.0在AOF重写时前半部分用RDB格式快后半部分用AOF格式增量兼顾恢复速度和数据安全。实战手动解析一个简单的RDB文件让我们用一个小例子来完整解析RDB文件# 在Redis中写入一些数据127.0.0.1:6379FLUSHALL127.0.0.1:6379SET mykeyhello127.0.0.1:6379SET counter42127.0.0.1:6379SAVE然后用xxd查看生成的RDB文件xxd dump.rdb解读关键字节偏移 十六进制 解读 ─────────────────────────────────────────────────── 0x0000 52 45 44 49 53 REDIS 魔数 0x0005 30 30 31 31 版本 0011 (v11) 0x0009 FA AUX操作码 (redis-ver) 0x000A ... redis-ver 7.0.12 0x00?? FE 00 SELECTDB 0 (0号数据库) 0x00?? 00 05 6D 79 6B 65 79 TYPESTRING, keymykey(5字节) 0x00?? 05 68 65 6C 6C 6F valuehello(5字节) 0x00?? 00 07 63 6F 75 6E 74 65 72 TYPESTRING, keycounter(7字节) 0x00?? C0 2A int编码, value42 0x00?? FF EOF 0x00?? XX XX XX XX XX XX XX XX CRC64校验和通过手动解析你能看到每一个键值对在RDB中的完整编码方式。这对于理解Redis内部存储和排查问题非常有帮助。总结RDB文件格式虽然看起来复杂但结构清晰、层次分明整体结构REDIS魔数 → 版本号 → 数据库数据 → EOF → 校验和数据库数据由SELECTDB 数据库编号 键值对序列组成键值对编码可选过期时间 → TYPE → key → value字符串编码int编码节省空间、lenbytes、LZF压缩长字符串版本演进从v1到v12每个版本引入新的编码方式RDB vs AOFRDB恢复快但可能丢数据AOF安全但恢复慢理解了RDB文件格式的每一个字节你就拥有了排查RDB相关问题的超能力。下一篇我们将深入AOF持久化的实现原理看看Redis的另一条持久化之路。上一篇【第29篇】RDB持久化——Redis的快照是怎么拍的下一篇【第31篇】AOF持久化明日更新敬请期待