golang Redis RDB文件解析与内存分析插件库rdb的使用
Golang Redis RDB文件解析与内存分析插件库rdb的使用
这是一个用Golang实现的Redis RDB解析器,可用于二次开发和内存分析。
功能特性
- 为RDB文件生成内存报告
- 将RDB文件转换为JSON格式
- 将RDB文件转换为Redis序列化协议(AOF文件)
- 查找RDB文件中最大的N个键
- 绘制火焰图分析占用内存最多的键类型
- 自定义数据使用
- 生成RDB文件
支持的RDB版本:1 <= version <= 12(Redis 7.2)
安装
如果已安装Go,只需运行:
go install github.com/hdt3213/rdb@latest
包管理器
Homebrew用户可以使用:
$ brew install rdb
或者从发布页面下载可执行二进制文件,并将其路径添加到PATH环境变量中。
在终端使用rdb
命令,可以查看手册:
This is a tool to parse Redis' RDB files
Options:
-c command, including: json/memory/aof/bigkey/prefix/flamegraph
-o output file path, if there is no `-o` option, output to stdout
-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' '>now' get keys with expiration time greater than given time
2. '<1751731200' '<now' get keys with expiration time less than given time
3. '1751731200~1751817600' '1751731200~now' get keys with expiration time in range
4. 'noexpire' get keys without expiration time
5. 'anyexpire' get all keys with expiration time
-no-expired filter expired keys(deprecated, please use 'expire' option)
-concurrent The number of concurrent json converters. (CpuNum -1 by default, reserve a core for decoder)
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 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
更改并发转换器的数量,默认值为4。
rdb -c json -o intset_16.json -concurrent 8 cases/intset_16.rdb
JSON格式详情
字符串类型
{
"db": 0,
"key": "string",
"size": 10, // 估计的内存大小
"type": "string",
"expiration":"2022-02-18T06:15:29.18+08:00",
"value": "aaaaaaa"
}
列表类型
{
"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": "set",
"expiration": "2022-02-18T06:15:29.18+08:00",
"size": 39,
"type": "set",
"members": [
"2hzm5rnmkmwb3zqd",
"tdje6bk22c6ddlrw"
]
}
哈希类型
{
"db": 0,
"key": "hash",
"size": 64,
"type": "hash",
"expiration": "2022-02-18T06:15:29.18+08:00",
"hash": {
"ca32mbn2k3tp41iu": "ca32mbn2k3tp41iu",
"mddbhxnzsbklyp8c": "mddbhxnzsbklyp8c"
}
}
有序集合类型
{
"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": "mystream",
"size": 1776,
"type": "stream",
"encoding": "",
"version": 3, // 版本2表示RDB_TYPE_STREAM_LISTPACKS_2,3表示RDB_TYPE_STREAM_LISTPACKS_3
// StreamEntry是Redis流底层基数树中的一个节点,类型为listpacks,包含多条消息。使用时无需关心消息属于哪个entry。
"entries": [
{
"firstMsgId": "1704557973866-0", // listpack头部主条目的ID
"fields": [ // 主字段,用于压缩大小
"name",
"surname"
],
"msgs": [ // entry中的消息
{
"id": "1704557973866-0",
"fields": {
"name": "Sara",
"surname": "OConnor"
},
"deleted": false
}
]
}
],
"groups": [ // 消费者组
{
"name": "consumer-group-name",
"lastId": "1704557973866-0",
"pending": [ // 待处理消息
{
"id": "1704557973866-0",
"deliveryTime": 1704557998397,
"deliveryCount": 1
}
],
"consumers": [ // 组中的消费者
{
"name": "consumer-name",
"seenTime": 1704557998397,
"pending": [
"1704557973866-0"
],
"activeTime": 1704557998397
}
],
"entriesRead": 1
}
],
"len": 1, // 当前流中的消息数量
"lastId": "1704557973866-0",
"firstId": "1704557973866-0",
"maxDeletedId": "0-0",
"addedEntriesCount": 1
}
生成内存报告
RDB使用RDB编码大小来估计Redis内存使用情况。
rdb -c memory -o <output_path> <source_path>
示例:
rdb -c memory -o mem.csv cases/memory.rdb
CSV结果示例:
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
按前缀分析
如果可以通过键的前缀区分模块,例如用户数据的键是User:<uid>
,帖子的键是Post:<postid>
,用户统计是Stat:User:???
,帖子统计是Stat:Post:???
。那么我们可以通过前缀分析获取每个模块的状态:
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。
示例:
rdb -c prefix -n 10 -max-depth 2 -o prefix.csv cases/memory.rdb
火焰图
在许多情况下,不是少数非常大的键占用了大部分内存,而是大量的小键占用了大部分内存。
RDB工具可以通过给定的分隔符分隔键,然后聚合具有相同前缀的键。
最后,RDB工具将结果呈现为火焰图,通过它可以找出哪种类型的键消耗了最多的内存。
在这个例子中,模式为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
CSV结果示例:
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工具支持使用正则表达式过滤键。
示例:
rdb -c json -o regex.json -regex '^l.*' cases/memory.rdb
过期时间过滤
-expire
参数可以配置为基于过期时间进行过滤。
过期时间在2025-07-06 00:00:00和2025-07-07 00:00:00之间的键:
# 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的键:
# Keys with expiration times earlier than 2025-07-07 00:00:00
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
表示当前时间:
# Keys with expiration times earlier than now
rdb -c json -o dump.json -expire 0~now cases/expiration.rdb
# Keys with expiration times later than now
rdb -c json -o dump.json -expire now~inf cases/expiration.rdb
所有设置了过期时间的键:
rdb -c json -o dump.json -expire anyexpire cases/expiration.rdb
所有没有设置过期时间的键:
rdb -c json -o dump.json -expire noexpire cases/expiration.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)
case parser.StreamType:
stream := o.(*parser.StreamObject)
println(stream.Entries, stream.Groups)
}
// 返回true继续,返回false停止迭代
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)
}
aux
更多关于golang Redis RDB文件解析与内存分析插件库rdb的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang Redis RDB文件解析与内存分析插件库rdb的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang Redis RDB文件解析与内存分析 - rdb库使用指南
Redis RDB文件是Redis数据库的持久化快照文件,包含了某个时间点的数据状态。在Golang中,我们可以使用rdb
库来解析和分析这些文件。
rdb库简介
rdb
是一个用于解析Redis RDB文件的Golang库,它可以帮助你:
- 读取RDB文件内容
- 分析内存使用情况
- 统计不同类型key的数量和大小
- 生成内存分析报告
安装
go get github.com/HDT3213/rdb
基本用法
1. 解析RDB文件
package main
import (
"fmt"
"github.com/HDT3213/rdb/core"
"os"
)
func main() {
rdbFile, err := os.Open("dump.rdb")
if err != nil {
panic(err)
}
defer rdbFile.Close()
decoder := core.NewDecoder(rdbFile)
err = decoder.Parse(func(o core.RedisObject) bool {
fmt.Printf("Key: %s, Type: %s, Size: %d\n",
o.GetKey(),
o.GetType(),
o.GetSize())
return true // 返回true继续解析,false停止
})
if err != nil {
panic(err)
}
}
2. 内存分析
package main
import (
"fmt"
"github.com/HDT3213/rdb/core"
"github.com/HDT3213/rdb/memory"
"os"
)
func main() {
rdbFile, err := os.Open("dump.rdb")
if err != nil {
panic(err)
}
defer rdbFile.Close()
// 创建内存分析器
mem := memory.NewAnalyzer()
decoder := core.NewDecoder(rdbFile)
// 解析并统计内存
err = decoder.Parse(func(o core.RedisObject) bool {
mem.Analyze(o)
return true
})
if err != nil {
panic(err)
}
// 输出统计结果
fmt.Printf("Total keys: %d\n", mem.Total)
fmt.Printf("Total memory: %d bytes\n", mem.TotalBytes)
for t, stat := range mem.TypeStats {
fmt.Printf("Type %s: %d keys, %d bytes\n",
t, stat.Count, stat.Bytes)
}
}
3. 高级分析 - 查找大key
package main
import (
"fmt"
"github.com/HDT3213/rdb/core"
"github.com/HDT3213/rdb/memory"
"os"
"sort"
)
type KeySize struct {
Key string
Size uint64
Type string
}
func main() {
rdbFile, err := os.Open("dump.rdb")
if err != nil {
panic(err)
}
defer rdbFile.Close()
var bigKeys []KeySize
decoder := core.NewDecoder(rdbFile)
err = decoder.Parse(func(o core.RedisObject) bool {
size := o.GetSize()
if size > 1024 { // 大于1KB的key
bigKeys = append(bigKeys, KeySize{
Key: o.GetKey(),
Size: size,
Type: o.GetType(),
})
}
return true
})
if err != nil {
panic(err)
}
// 按大小排序
sort.Slice(bigKeys, func(i, j int) bool {
return bigKeys[i].Size > bigKeys[j].Size
})
// 输出前10大key
fmt.Println("Top 10 largest keys:")
for i := 0; i < 10 && i < len(bigKeys); i++ {
fmt.Printf("%d. Key: %s, Type: %s, Size: %d bytes\n",
i+1, bigKeys[i].Key, bigKeys[i].Type, bigKeys[i].Size)
}
}
4. 生成HTML报告
package main
import (
"github.com/HDT3213/rdb/core"
"github.com/HDT3213/rdb/memory"
"github.com/HDT3213/rdb/reporter"
"os"
)
func main() {
rdbFile, err := os.Open("dump.rdb")
if err != nil {
panic(err)
}
defer rdbFile.Close()
mem := memory.NewAnalyzer()
decoder := core.NewDecoder(rdbFile)
err = decoer.Parse(func(o core.RedisObject) bool {
mem.Analyze(o)
return true
})
if err != nil {
panic(err)
}
// 生成HTML报告
htmlReporter := reporter.NewHTMLReporter(mem)
reportFile, err := os.Create("report.html")
if err != nil {
panic(err)
}
defer reportFile.Close()
err = htmlReporter.Report(reportFile)
if err != nil {
panic(err)
}
fmt.Println("HTML report generated: report.html")
}
性能优化技巧
- 并行处理:对于大型RDB文件,可以考虑将解析和处理逻辑分离,使用goroutine并行处理
package main
import (
"github.com/HDT3213/rdb/core"
"os"
"sync"
)
func main() {
rdbFile, err := os.Open("dump.rdb")
if err != nil {
panic(err)
}
defer rdbFile.Close()
// 创建带缓冲的channel
objChan := make(chan core.RedisObject, 1000)
var wg sync.WaitGroup
// 启动多个worker处理对象
for i := 0; i < 4; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for obj := range objChan {
// 处理对象
_ = obj
}
}()
}
// 解析RDB文件并发送到channel
decoder := core.NewDecoder(rdbFile)
err = decoder.Parse(func(o core.RedisObject) bool {
objChan <- o
return true
})
close(objChan)
wg.Wait()
if err != nil {
panic(err)
}
}
- 增量处理:对于非常大的RDB文件,可以考虑增量处理,避免内存占用过高
总结
rdb
库提供了强大的RDB文件解析和内存分析功能,可以帮助你:
- 了解Redis内存使用情况
- 发现大key和热点key
- 优化内存配置
- 监控数据增长趋势
通过结合不同的分析方法和报告生成功能,你可以全面掌握Redis数据库的状态,为性能优化和容量规划提供数据支持。