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

1 回复

更多关于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")
}

性能优化技巧

  1. 并行处理:对于大型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)
	}
}
  1. 增量处理:对于非常大的RDB文件,可以考虑增量处理,避免内存占用过高

总结

rdb库提供了强大的RDB文件解析和内存分析功能,可以帮助你:

  • 了解Redis内存使用情况
  • 发现大key和热点key
  • 优化内存配置
  • 监控数据增长趋势

通过结合不同的分析方法和报告生成功能,你可以全面掌握Redis数据库的状态,为性能优化和容量规划提供数据支持。

回到顶部