工具简介
这是一个可以用于二次开发和内存分析的 RDB 文件分析工具,它具备下列能力:
- 为 RDB 文件生成内存用量报告
- 将 RDB 文件中键值对数据转换为 JSON 格式
- 将 RDB 文件转换为 AOF 文件(即 Redis 序列化协议)
- 寻找 RDB 文件中大键值对
- 根据 RDB 文件绘制内存火焰图,用来分析哪类键值对占用了最多内存
- 通过 API 遍历 RDB 文件内容,自定义用途
- 生成 RDB 文件
支持 RDB 文件版本: 1 <= version <= 12(Redis 7.2)
您可以在这里阅读 RDB 文件格式的详尽介绍:Golang 实现 Redis(11): RDB 文件格式
下载安装
如果您的电脑上安装 go 语言运行环境,可以使用 go get 安装本工具:
go install github.com/hdt3213/rdb@latest
或者您可以在 releases 页面下载可执行文件,然后将它放入 PATH 变量中的目录内。
在终端中输入 rdb 命令即可获得本工具的使用手册:
$ rdb
This is a tool to parse Redis' RDB files
Options:
-c command, including: json/memory/aof/bigkey/prefix/flamegraph
-o output file path
-n number of result, using in command: bigkey/prefix
-port listen port for flame graph web service
-sep separator for flamegraph, rdb will separate key by it, default value is ":".
supporting multi separators: -sep sep1 -sep sep2
-regex using regex expression filter keys
-expire filter keys by its expiration time
1. '1751731200~1751817600' get keys with expiration time in range [1751731200, 1751817600]
2. '1751731200~now' 'now~1751731200' magic variable 'now' represents the current timestamp
3. '1751731200~inf' 'now~inf' magic variable 'inf' represents the Infinity
4. 'noexpire' get keys without expiration time
5. 'anyexpire' get all keys with expiration time
-size filter keys by size, supports B/KB/MB/GB/TB/PB/EB
1. '1KB~1MB' get keys with size in range [1KB, 1MB]
2. '10MB~inf' magic variable 'inf' represents the Infinity
3. '1024~10KB' get keys with size in range [0Bytes, 10KB]
-concurrent The number of concurrent json converters. 4 by default.
-show-global-meta Show global meta likes redis-verion/ctime/functions
-no-expired filter expired keys(deprecated, please use 'expire' option)
Examples:
parameters between '[' and ']' is optional
1. convert rdb to json
rdb -c json -o dump.json dump.rdb
2. generate memory report
rdb -c memory -o memory.csv dump.rdb
3. convert to aof file
rdb -c aof -o dump.aof dump.rdb
4. get largest keys
rdb -c bigkey [-o dump.aof] [-n 10] dump.rdb
5. get number and memory size by prefix
rdb -c prefix [-n 10] [-max-depth 3] [-o prefix-report.csv] dump.rdb
6. draw flamegraph
rdb -c flamegraph [-port 16379] [-sep :] dump.rdb
常用功能
转换为 JSON 格式
rdb -c json -o <output_path> <source_path>
示例:
rdb -c json -o intset_16.json cases/intset_16.rdb
本仓库的 cases 目录中准备了一些示例 RDB 文件,可供您进行测试。
转换出的 JSON 结果示例:
[
{"db":0,"key":"hash","size":64,"type":"hash","hash":{"ca32mbn2k3tp41iu":"ca32mbn2k3tp41iu","mddbhxnzsbklyp8c":"mddbhxnzsbklyp8c"}},
{"db":0,"key":"string","size":10,"type":"string","value":"aaaaaaa"},
{"db":0,"key":"expiration","expiration":"2022-02-18T06:15:29.18+08:00","size":8,"type":"string","value":"zxcvb"},
{"db":0,"key":"list","expiration":"2022-02-18T06:15:29.18+08:00","size":66,"type":"list","values":["7fbn7xhcnu","lmproj6c2e","e5lom29act","yy3ux925do"]},
{"db":0,"key":"zset","expiration":"2022-02-18T06:15:29.18+08:00","size":57,"type":"zset","entries":[{"member":"zn4ejjo4ths63irg","score":1},{"member":"1ik4jifkg6olxf5n","score":2}]},
{"db":0,"key":"set","expiration":"2022-02-18T06:15:29.18+08:00","size":39,"type":"set","members":["2hzm5rnmkmwb3zqd","tdje6bk22c6ddlrw"]}
]
-concurrent 选项可以修改转 JSON 的并发数,默认并发数为 CPU 数量 -1 (留一个核心给解析器)。
rdb -c json -o intset_16.json -concurrent 8 cases/intset_16.rdb
-show-global-meta 选项可以解析 RDB 文件中的元信息 (redis-ver、ctime、used-mem 等) 以及函数定义。
rdb -c json -o function.json -show-global-meta cases/function.rdb
示例:
[
{"db":0,"key":"redis-ver","size":0,"type":"aux","encoding":"","value":"7.2.5"},
{"db":0,"key":"redis-bits","size":0,"type":"aux","encoding":"","value":"64"},
{"db":0,"key":"ctime","size":0,"type":"aux","encoding":"","value":"1767107423"},
{"db":0,"key":"used-mem","size":0,"type":"aux","encoding":"","value":"1269264"},
{"db":0,"key":"aof-base","size":0,"type":"aux","encoding":"","value":"0"},
{"db":0,"key":"functions","size":0,"type":"functions","encoding":"functions","functionsLua":"#!lua name=mylib\nredis.register_function('myfunc', function(keys, args) return 'hello' end)"}
]
生成内存用量报告
本工具使用 RDB 编码后的大小来估算键值对占用的内存大小。
用法:
rdb -c memory -o <output_path> <source_path>
示例:
rdb -c memory -o mem.csv cases/memory.rdb
内存报告示例:
database,key,type,size,size_readable,element_count 0,hash,hash,64,64B,2 0,s,string,10,10B,0 0,e,string,8,8B,0 0,list,list,66,66B,4 0,zset,zset,57,57B,2 0,large,string,2056,2K,0 0,set,set,39,39B,2
前缀分析
如果您可以根据 key 的前缀区分模块,比如用户数据的 key 是 User:<uid>, Post 的模式是 Post:<postid>, 用户统计信息是 Stat:User:???, Post 的统计信息是 Stat:User:???。 那么我们可以通过前缀分析来得到各模块的情况:
database,prefix,size,size_readable,key_count 0,Post:,1170456184,1.1G,701821 0,Stat:,405483812,386.7M,3759832 0,Stat:Post:,291081520,277.6M,2775043 0,User:,241572272,230.4M,265810 0,Topic:,171146778,163.2M,694498 0,Topic:Post:,163635096,156.1M,693758 0,Stat:Post:View,133201208,127M,1387516 0,Stat:User:,114395916,109.1M,984724 0,Stat:Post:Comment:,80178504,76.5M,693758 0,Stat:Post:Like:,77701688,74.1M,693768
命令格式:
rdb -c prefix [-n <top-n>] [-max-depth <max-depth>] -o <output_path> <source_path>
- 前缀分析结果按照内存空间从大到小排列,-n 选项可以指定输出的数量。默认全部输出。
- -max-depth 可以限制前缀树的的最大深度。比如示例中 Stat: 的深度是1,Stat:User: 和 Stat:Post: 的深度是 2。
Example:
rdb -c prefix -n 10 -max-depth 2 -o prefix.csv cases/memory.rdb
火焰图
在很多时候并不是少量的大键值对占据了大部分内存,而是数量巨大的小键值对消耗了很多内存。
很多企业要求使用 Redis key 采用类似于 user:basic.info:{userid} 的命名规范,所以我们可以使用分隔符将 key 拆分并将拥有相同前缀的 key 聚合在一起。
最后我们将聚合的结果以火焰图的方式呈现可以直观地看出哪类键值对消耗内存过多,进而优化缓存和逐出策略节约内存开销。
在上图示例中,Comment:* 模式的键值对消耗了 8.463% 内存.
用法:
rdb -c flamegraph [-port <port>] [-sep <separator1>] [-sep <separator2>] <source_path>
示例:
rdb -c flamegraph -port 16379 -sep : dump.rdb
寻找最大的键值对
本工具可以用来寻找 RDB 文件中最大的 N 个键值对。用法:
rdb -c bigkey -n <result_number> <source_path>
示例:
rdb -c bigkey -n 5 cases/memory.rdb
结果示例:
database,key,type,size,size_readable,element_count 0,large,string,2056,2K,0 0,list,list,66,66B,4 0,hash,hash,64,64B,2 0,zset,zset,57,57B,2 0,set,set,39,39B,2
转换为 AOF 文件
用法:
rdb -c aof -o <output_path> <source_path>
示例:
rdb -c aof -o mem.aof cases/memory.rdb
输出的 AOF 文件示例:
*3
$3
SET
$1
s
$7
aaaaaaa
正则过滤器
支持使用正则表达式过滤自己关心的键值对:
示例:
rdb -c json -o regex.json -regex '^l.*' cases/memory.rdb
大小过滤器
-size 参数可以配置根据对象大小(字节数)进行过滤。
- 支持的单位:KB/MB/GB/TB/PB/EB。二进制和 SI 前缀都表示 2 进制单位:
- KB = K = KiB = 1024
- MB = M = MiB = 1024 × KB
- GB = G = GiB = 1024 × MB
- TB = T = TiB = 1024 × GB
- PB = P = PiB = 1024 × TB
- EB = E = EiB = 1024 × PB
- 使用范围格式 min~max,支持魔法变量 inf 表示无穷大。
示例:
# 过滤大小在 1KB 到 1MB(含)之间的键 rdb -c json -o dump.json -size 1KB~1MB cases/memory.rdb # 过滤大小大于等于 10MB 的键 rdb -c memory -o memory.csv -size 10MB~inf cases/memory.rdb
Expiration 过滤器
-expire 参数可以配置根据过期时间过滤.
过期时间在 2025-07-06 00:00:00 与 2025-07-07 00:00:00之间的 key:
# toTimestamp(2025-07-06 00:00:00) == 1751731200 # toTimestamp(2025-07-07 00:00:00) == 1751817600 rdb -c json -o dump.json -expire 1751731200~1751817600 cases/expiration.rdb
# 过期时间早于 2025-07-07 00:00:00 的 key rdb -c json -o dump.json -expire 0~1751817600 cases/expiration.rdb
魔法变量 inf 表示无穷大:
rdb -c json -o dump.json -expire 1751731200~inf cases/expiration.rdb
魔法变量 now 表示当前时间:
# 过期时间早于现在的 key rdb -c json -o dump.json -expire 0~now cases/expiration.rdb
# 过期时间晚于现在的 key rdb -c json -o dump.json -expire now~inf cases/expiration.rdb
所有设置了过期时间的key:
rdb -c json -o dump.json -expire anyexpire cases/expiration.rdb
所有未设置过期时间的key:
rdb -c json -o dump.json -expire noexpire cases/expiration.rdb
自定义用途
除了命令行工具之外,您可以在自己的项目中引入 hdt3213/rdb/parser 包,自行决定如何处理 RDB 中的数据。
示例:
package main
import (
"github.com/hdt3213/rdb/parser"
"os"
)
func main() {
rdbFile, err := os.Open("dump.rdb")
if err != nil {
panic("open dump.rdb failed")
}
defer func() {
_ = rdbFile.Close()
}()
decoder := parser.NewDecoder(rdbFile)
err = decoder.Parse(func(o parser.RedisObject) bool {
switch o.GetType() {
case parser.StringType:
str := o.(*parser.StringObject)
println(str.Key, str.Value)
case parser.ListType:
list := o.(*parser.ListObject)
println(list.Key, list.Values)
case parser.HashType:
hash := o.(*parser.HashObject)
println(hash.Key, hash.Hash)
case parser.ZSetType:
zset := o.(*parser.ZSetObject)
println(zset.Key, zset.Entries)
}
// return true to continue, return false to stop the iteration
return true
})
if err != nil {
panic(err)
}
}
生成 RDB 文件
除了解析之外,本项目也可以用于生成 RDB 文件:
package main
import (
"github.com/hdt3213/rdb/encoder"
"github.com/hdt3213/rdb/model"
"os"
"time"
)
func main() {
rdbFile, err := os.Create("dump.rdb")
if err != nil {
panic(err)
}
defer rdbFile.Close()
enc := encoder.NewEncoder(rdbFile)
err = enc.WriteHeader()
if err != nil {
panic(err)
}
auxMap := map[string]string{
"redis-ver": "4.0.6",
"redis-bits": "64",
"aof-preamble": "0",
}
for k, v := range auxMap {
err = enc.WriteAux(k, v)
if err != nil {
panic(err)
}
}
err = enc.WriteDBHeader(0, 5, 1)
if err != nil {
panic(err)
}
expirationMs := uint64(time.Now().Add(time.Hour*8).Unix() * 1000)
err = enc.WriteStringObject("hello", []byte("world"), encoder.WithTTL(expirationMs))
if err != nil {
panic(err)
}
err = enc.WriteListObject("list", [][]byte{
[]byte("123"),
[]byte("abc"),
[]byte("la la la"),
})
if err != nil {
panic(err)
}
err = enc.WriteSetObject("set", [][]byte{
[]byte("123"),
[]byte("abc"),
[]byte("la la la"),
})
if err != nil {
panic(err)
}
err = enc.WriteHashMapObject("list", map[string][]byte{
"1": []byte("123"),
"a": []byte("abc"),
"la": []byte("la la la"),
})
if err != nil {
panic(err)
}
err = enc.WriteZSetObject("list", []*model.ZSetEntry{
{
Score: 1.234,
Member: "a",
},
{
Score: 2.71828,
Member: "b",
},
})
if err != nil {
panic(err)
}
err = enc.WriteEnd()
if err != nil {
panic(err)
}
}
Benchmark
在 MacBook Air(M2,2022年)笔记本上,使用从生产环境的 Redis 5.0 上获得 1.3 GB 大小使用 v9 编码的 RDB 文件进行测试:
| usage | elapsed | speed |
|---|---|---|
| ToJson | 25s | 53.24MB/s |
| Memory | 10s | 133.12MB/s |
| AOF | 25s | 53.24MB/s |
| Top10 | 6s | 221.87MB/s |
| Prefix | 25s | 53.24MB/s |
- 本文固定链接: http://www.jiagou.cc/1786/
- 转载请注明: 摘星怪 于 架构迷 发表
