优秀的编程知识分享平台

网站首页 > 技术文章 正文

分析Redis key,value的size

nanyue 2024-11-27 18:18:06 技术文章 1 ℃

RDB文件解析

Redis的RDB文件是一个二进制文件,用于持久化内存数据,是内存数据某一时刻的快照文件,Redis启动时候通过读取该文件将键值对加载到内存中还原Redis最新的状态.

目前该RDB文件最新版本为version 9,

以下为RDB文件版本变更历史

  • Version 9 – redis 5.0
  • Version 8 – redis 4.0
  • Version 7 – redis 3.2
  • Version 6 – redis 2.8
  • Version 5 – redis 2.4

这里需要注意的是,我们可以将低版本的Redis持久化RDB文件导入到高版本的RDB文件中,即高版本兼容低版本,也可以同版本互相导入,但是不能从高版本导入到低版本的RDB文件.

例如,Redis3.2的RDB文件版本为version7,该RDB文件可以在Redis>=3.2的版本中正常加载,但是无法加载到Redis<3.2的版本中.

RDB版本号在RDB二进制文件的头部数据中, 在linux中可以通过od,xxd等命令将二进制文件以十六进制数据格式查看.

为了便于快速高效的对RDB文件进行读写,Redis采用LZF压缩算法来减少文件的大小,在Redis底层数据结构中,每个对象都会有一个相应大小的前缀用来描述该对象占用的字节数长度等,因此Redis在加载该RDB文件时,读取的每个对象Redis都会知道应该为它分配多大的内存空间.

一个Redis RDB文件典型的格式如下(以 RDB version 9为例)

----------------------------# RDB文件是二进制的,所以并不存在回车换行来分隔一行一行
52 45 44 49 53              #这里的数字两个为一字节,代表的是一个字符对应的ascii码, 这里是5个字节的字符串常量"REDIS",Redis读取该文件可以判断是否为RDB文件
30 30 30 39                   #同上,4个字节的RDB版本号, 版本 = "0007" = 7
----------------------------
FA                                  #FA表示辅助字段
$string-encoded-key      # 可能包含任意元数据信息
$string-encoded-value   # 例如Redis的版本、创建时间、已使用的内存等等...
----------------------------
FE 00                             # FE指明接下来的键值对数据所属数据库编号.  00代表数据库编号为0
----------------------------
FB                                  # 指明resizedb属性(针对hash表)
$length-encoded-int      # 相应Hash表的大小,1字节
$length-encoded-int      # 相应带失效时间的Hash表大小,1字节
----------------------------# Key-Value 对存储开始
FD $unsigned int          # FD表示秒级别的过期,时间,接下来用4字节的无符号整数表示,过期时间是用 length encoding 编码存储.
$value-type                   # 用1字节表示键的类型
$string-encoded-key     # 使用Redis字符串编码方式的键
$encoded-value             # 使用$value-type指明的编码方式的值
----------------------------
FC $unsigned long       # FCF表示毫秒级别的超时时间,紧接着是8字节的无符号整数,过期时间是用 length encoding 编码存储.
$value-type                  # 用1字节表示键的类型,如set,hash,list,zset等
$string-encoded-key    #  键,通过string encoding 编码
$encoded-value            #  值,根据不同的value type采用不同的编码方式
----------------------------
$value-type                  # 没有失效时间的键的类型
$string-encoded-key    #  使用Redis字符串编码方式的键
$encoded-value           #  使用$value-type指明的编码方式的值
----------------------------
FE $length-encoding     # 下一个库开始,库的编号用 length encoding 编码.
----------------------------
...                                    # 附加的关于该数据库的其他信息
                            
FF                                   # RDB文件结束标识
8 byte checksum             # 8字节的CRC64表示的文件校验和

首先在redis的10和11数据库写入如下测试数据.

$ redis-cli -n 10
127.0.0.1:6379[10]> hset userid0001 username zhang,quan
127.0.0.1:6379[10]> hset userid0001 gender male
127.0.0.1:6379[10]> hset userid0001 address Oregon
127.0.0.1:6379[10]> select 11
127.0.0.1:6379[11]> set userid0001 21 ex 18000
127.0.0.1:6379[11]> save

通过od命令将二进制文件的RDB文件以十六进制的格式展示(od -A x -t x1c -v dump.rdb)

[redis@ec2-redis-01 data]$ od -A x -t x1c -v dump.rdb
000000  52  45  44  49  53  30  30  30  39  fa  09  72  65  64  69  73
         R   E   D   I   S   0   0   0   9 372  \t   r   e   d   i   s
000010  2d  76  65  72  05  35  2e  30  2e  39  fa  0a  72  65  64  69
         -   v   e   r 005   5   .   0   .   9 372  \n   r   e   d   i
000020  73  2d  62  69  74  73  c0  40  fa  05  63  74  69  6d  65  c2
         s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e 302
000030  72  d2  3f  5f  fa  08  75  73  65  64  2d  6d  65  6d  c2  60
         r 322   ?   _ 372  \b   u   s   e   d   -   m   e   m 302   `
000040  6b  39  00  fa  0c  61  6f  66  2d  70  72  65  61  6d  62  6c
         k   9  \0 372  \f   a   o   f   -   p   r   e   a   m   b   l
000050  65  c0  00  fe  0a  fb  01  00  f8  4e  57  0d  0a  75  73  65
         e 300  \0 376  \n 373 001  \0 370   N   W  \r  \n   u   s   e
000060  72  69  64  30  30  30  31  40  40  40  00  00  00  37  00  00
         r   i   d   0   0   0   1   @   @   @  \0  \0  \0   7  \0  \0
000070  00  06  00  00  08  75  73  65  72  6e  61  6d  65  0a  0a  7a
        \0 006  \0  \0  \b   u   s   e   r   n   a   m   e  \n  \n   z
000080  68  61  6e  67  2c  71  75  61  6e  0c  06  67  65  6e  64  65
         h   a   n   g   ,   q   u   a   n  \f 006   g   e   n   d   e
000090  72  08  04  6d  61  6c  65  06  07  61  64  64  72  65  73  73
         r  \b 004   m   a   l   e 006  \a   a   d   d   r   e   s   s
0000a0  09  06  4f  72  65  67  6f  6e  ff  fe  0b  fb  01  01  fc  bd
        \t 006   O   r   e   g   o   n 377 376  \v 373 001 001 374 275
0000b0  af  60  12  74  01  00  00  f8  02  00  0a  75  73  65  72  69
       257   ` 022   t 001  \0  \0 370 002  \0  \n   u   s   e   r   i
0000c0  64  30  30  30  31  c0  15  ff  29  d9  2c  0c  22  9b  be  19
         d   0   0   0   1 300 025 377   ) 331   ,  \f   " 233 276 031
0000d0

查看ascii码表(linux命令"man ascii"可以看到)

          2 3 4 5 6 7       30 40 50 60 70 80 90 100 110 120
        -------------      ---------------------------------
       0:   0 @ P ` p     0:    (  2  <  F  P  Z  d   n   x
       1: ! 1 A Q a q     1:    )  3  =  G  Q  [  e   o   y
       2: " 2 B R b r     2:    *  4  >  H  R  \  f   p   z
       3: # 3 C S c s     3: !  +  5  ?  I  S  ]  g   q   {
       4: $ 4 D T d t     4: "  ,  6  @  J  T  ^  h   r   |
       5: % 5 E U e u     5: #  -  7  A  K  U  _  i   s   }
       6: & 6 F V f v     6: $  .  8  B  L  V  `  j   t   ~
       7: ′ 7 G W g w     7: %  /  9  C  M  W  a  k   u  DEL
       8: ( 8 H X h x     8: &  0  :  D  N  X  b  l   v
       9: ) 9 I Y i y     9: ′  1  ;  E  O  Y  c  m   w
       A: * : J Z j z
       B: + ; K [ k {
       C: , < L \ l |
       D: - = M ] m }
       E: . > N ^ n ~
       F: / ? O _ o DEL

魔术字符串REDIS

Redis RDB文件以"REDIS"五个字符开头,占用5个字节,如上述十六进制的"52 45 44 49 53",分别代表" R E D I S"

000000  52  45  44  49  53
              R   E   D   I   S   0   0   0   9 372  \t   r   e   d   i   s

RDB版本

接下来的4个字节记录了RDB的版本号

000000  52  45  44  49  53  30  30  30  39
                                            0    0    0    9

操作码

在以上REDIS和RDB版本头部信息加载完成后,后续的每个部分均由一个指定的操作码引入

十六进制          名称                         代表含义
0xFA                 AUX                         辅助字段
0xFE                 SELECTDB             数据库选择器
0xFB                 RESIZEDB              数据库中哈希表和带有TTL哈希表的数量
0xFD                EXPIRETIME          键的失效时间(秒级别)
0xFC                EXPIRETIMEMS    键的失效时间(毫秒级别)
0xFF	              EOF	                          RDB文件的结尾

辅助字段

辅助字段以0xFA开始,用来描述Redis实例的一些信息,每个信息以键值对的形式出现,例如

  • redis-ver: redis版本号
  • redis-bits: redis架构
  • ctime: RDB创建时间
  • used-mem: 使用内存
  • repl-stream-db: 在server.master客户端中选择的数据库
  • repl-id: 当前实例的复制ID
  • repl-offset: 当前实例复制的偏移量
  • aof-preamble: aof混合持久化
000010  2d  76  65  72  05  35  2e  30  2e  39  fa  0a  72  65  64  69
         -   v   e   r 005   5   .   0   .   9 372  \n   r   e   d   i
000020  73  2d  62  69  74  73  c0  40  fa  05  63  74  69  6d  65  c2
         s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e 302
000030  27  c4  3f  5f  fa  08  75  73  65  64  2d  6d  65  6d  c2  80
         ' 304   ?   _ 372  \b   u   s   e   d   -   m   e   m 302 200
000040  6b  39  00  fa  0c  61  6f  66  2d  70  72  65  61  6d  62  6c
         k   9  \0 372  \f   a   o   f   -   p   r   e   a   m   b   l
000050  65  c0  00  fe  0a  fb  01  00  f8  0c  0d  0a  75  73  65  72
         e 300  \0 376  \n 373 001  \0 370  \f  \r  \n   u   s   e   r

SELECTDB(数据库选择器)

一个Redis实例可能有多个数据库,RDB文件以一个字节的0xFE标识数据库选择器部分的开始,在该字节后,一个变长的字段表示数据库的索引值。

000050  65  c0  00  fe  0a  fb  01  00  f8  0c  0d  0a  75  73  65  72
                                     10
......
0000a0  09  06  4f  72  65  67  6f  6e  ff  fe  0b  fb  01  01  fc  bd
                                                                      11

RESIZEDB(哈希表信息)

RDB在version 7中引入resizedb操作码概念,RDB文件以一字节的0xFB标识RESIZEDB,通过调整哈希表大小的操作码RESIZEDB,redis可以更快的读RDB文件,在RESIZEDB操作码之后的两个值是:

  1. 数据库的哈希表大小
  2. 失效哈希表的大小
000050  65  c0  00  fe  0a  fb  01  00  f8  4e  57  0d  0a  75  73  65
                                                1    0
......
0000a0  09  06  4f  72  65  67  6f  6e  ff  fe  0b  fb  01  01  fc  bd
                                                                                  1   1

key-value键值对

在RESIZEDB之后,包含是数据库选择器一系列的KV键值对序列,每个key-value由四部分组成

  1. key expiry time: 键失效时间戳,这个字段是可选的.
  2. value type: 一个字节的标识符,指明Value的类型.
  3. key值: Key被当作一个Redis字符串进行编码.
  4. value值: 值的编码方式根据Value Type字段决定.
000050  65  c0  00  fe  0a  fb  01  00  f8  45  c9  0d  0a  75  73  65
                                                           370   E 311  \r  \n   u   s   e
000060  72  69  64  30  30  30  31  40  40  40  00  00  00  37  00  00
         r   i   d   0   0   0   1   @   @   @  \0  \0  \0   7  \0  \0
000070  00  06  00  00  08  75  73  65  72  6e  61  6d  65  0a  0a  7a
        \0 006  \0  \0  \b   u   s   e   r   n   a   m   e  \n  \n   z
000080  68  61  6e  67  2c  71  75  61  6e  0c  06  67  65  6e  64  65
         h   a   n   g   ,   q   u   a   n  \f 006   g   e   n   d   e
000090  72  08  04  6d  61  6c  65  06  07  61  64  64  72  65  73  73
         r  \b 004   m   a   l   e 006  \a   a   d   d   r   e   s   s
0000a0  09  06  4f  72  65  67  6f  6e  ff  fe  0b  fb  02  01  f8  02
        \t 006   O   r   e   g   o   n 377 376  \v 373 002 001 370 002

key expiry time

该部分由一个字节标识开始,该表示可以是以下两种之一:

  • 0xFD: 该标识表示,失效时间以秒为单位,接下来的4个字节组成一个无符号的整型,表示Unix时间戳.
  • 0xFC: 该标识表示,失效时间以毫秒为单位,接下来的8个字节组成一个无符号的长整型,表示Unix时间戳.

在导入过程中,如果key失效了,会须被忽略掉.

value type

一个字节来表示value使用的编码方式。

  • 0 = String编码
  • 1 = List编码
  • 2 = Set编码
  • 3 = SortedSet编码
  • 4 = Hash编码
  • 9 = ZipMap编码
  • 10 = ZipList编码
  • 11 = IntSet编码
  • 12 = Sorted Set in ZipList编码
  • 13 = Hashmap in ZipList编码(RDB版本4引入)
  • 14 = List in QuickList编码 (RDB版本7引入)

Key

Key被当作一个Redis字符串进行编码.

value

值的编码方式根据Value Type字段决定.

  • 当value type为0,值是一个简单的字符串.
  • 当value type为1,2,3或4,值是字符串序列,这一系列的字符串用于构建list,set,hash和zset 结构.
  • 当value type为9,10,11或12,值被封装在字符串中,在读取出来后需要进一步解析.

Length Encoding(长度编码)

上面提到FD(秒级别)和FC(毫秒级别)分别占用4字节和8字节的长度来表示键的过期时间,采用Length Encoding编码来存储,这里所说的Length Encoding用来存储数据流中下一个对象的长度,是一种可变字节编码,旨在减少字节开销,将不同大小的数字编码成不同的长度.

那么Length encoding是如何工作的呢?

  1. 首先在读取长度时,会读取一个字节的数据,其中前两位用来变长编码判断.
  2. 如果前两位是 0 0,那么下面剩下的 6位就表示具体长度.
  3. 如果前两位是 0 1,那么会再读取一个字节的数据,加上前面剩下的6位,共14位用于表示具体长度.
  4. 如果前两位是 1 0,那么剩下的 6位就被废弃了,取而代之的是再读取后面的4 个字节用于表示具体长度.
  5. 如果前两位是 1 1,那么下面的应该是一个特殊编码,剩下的 6位用于标识特殊编码的种类.特殊编码主要用于将数字存成字符串,或者编码后的字符串,具体见 “String Encoding”.

通过可变长编码带来如下好处.

  1. 0 – 63的数字只需要一个字节进行存储.
  2. 而64 – 16383 的数字只需要两个字节进行存储.
  3. 16383 - 2^32 -1 的数字只需要用5个字节(1个字节的标识加4个字节的值)进行存储.

String Encoding,List Encoding,Set Encoding,Sorted Set Encoding等不同数据库类型的底层编码方案可参考Redis官方网站.

RDB文件分析工具

Rdbtools是一个解析dump.rdb文件,并生成内存报告的分析工具,对我们更好地使用Redis非常有帮助,是一个不可多得的利器,除此之外,该工具还提供额外的功能.

  1. 生成所有数据库所有类型的键的内存报告.
  2. 将RDB文件转储为json格式.
  3. 通过diff工具比较两个rdb文件的差异等.

如何安装Rdbtools

Rdbtools工具使用Python开发,解析RDB文件需要使用以下库文件.

  1. python-lzf: RDB文件使用lzf压缩算法
  2. redis-py

通过pip安装

pip install rdbtools python-lzf

安装完成后查看Rdbtools的命令帮助

$ rdb --help
usage: usage: rdb [options] /path/to/dump.rdb

Example : rdb --command json -k "user.*" /var/redis/6379/dump.rdb

positional arguments:
  dump_file             RDB Dump file to process

optional arguments:
  -h, --help            show this help message and exit
  -c CMD, --command CMD
                        Command to execute. Valid commands are json, diff,
                        justkeys, justkeyvals, memory and protocol
  -f FILE, --file FILE  Output file
  -n DBS, --db DBS      Database Number. Multiple databases can be provided.
                        If not specified, all databases will be included.
  -k KEYS, --key KEYS   Keys to export. This can be a regular expression
  -o NOT_KEYS, --not-key NOT_KEYS
                        Keys Not to export. This can be a regular expression
  -t TYPES, --type TYPES
                        Data types to include. Possible values are string,
                        hash, set, sortedset, list. Multiple typees can be
                        provided. If not specified, all data types will be
                        returned
  -b BYTES, --bytes BYTES
                        Limit memory output to keys greater to or equal to
                        this value (in bytes)
  -l LARGEST, --largest LARGEST
                        Limit memory output to only the top N keys (by size)
  -e {raw,print,utf8,base64}, --escape {raw,print,utf8,base64}
                        Escape strings to encoding: raw (default), print,
                        utf8, or base64.
  -x, --no-expire       With protocol command, remove expiry from all keys
  -a N, --amend-expire N
                        With protocol command, add N seconds to key expiry
                        time

生成内存报告

$ rdb --command memory dump.rdb -f dump.csv
$ head -n 10 dump.csv 
database,type,key,size_in_bytes,encoding,num_elements,len_largest_element,expiry
0,string,ElastiCacheMasterReplicationTimestamp,120,string,24,24,
1,hash,2A9IADAECJ3GV95OF0YJTNHJW,213,ziplist,4,31,
1,hash,3557RNEUH63KXCLL3D8YJOKEH,820,hashtable,6,165,
1,hash,4JYVF4Q8182YAK4K1CAXX0DJH,120,ziplist,1,31,
1,hash,1PZY9WS8GSCXVA7JY8KWC5RRW,120,ziplist,1,31,
1,hash,A3JIPZHUZ8WA1TH6K8JO09FC1,120,ziplist,1,31,
1,hash,180FDGPWBGLG6K8L5T32A14Y8,120,ziplist,1,31,
1,hash,3MCC09SRQSG2SA8I46TOIOD4M,120,ziplist,1,31,
1,hash,3682794E83385JHZJUD5BLQF3,120,ziplist,1,31,

以上各列依次分别代表数据库,键类型,键名,键大小,编码,元素数量,最大元素长度,过期时间.

需要注意的是,键的大小是一个估值,实际占用的内存大小略大于该报告.

将该csv文件导入到关系型数据库中,就可以针对某个特定的数据库或者某个特定类型的键做进一步分析,当然Rdbtools工具也支持针对单个库或者指定类型的键操作.

下面将上述csv文件导入到PostgreSQL数据库.

# create table redis_rdb (
    database smallint,
    type varchar(12),
    key varchar(64),
    size_in_bytes integer,
    encoding varchar(24),
    num_elements smallint,
    len_largest_element smallint,
    expiry integer
);
postgres=# create index on redis_rdb (key);
postgres=# copy redis_rdb(database, type, key, size_in_bytes, encoding, num_elements, len_largest_element,expiry) from '/usr/local/pgsql/dba/dump.csv' delimiter ',' csv header;

Tags:

最近发表
标签列表