要更好地优化 Redis 使用数据使用情况,就需要先了解 Redis 数据类型的存储方式,而前面文章学习过 String 类型底层数据结构是 简单动态字符串,当你使用 set 2201000060 4402000080 插入数据时,看上去只需要 8B long + 8B long = 16B 存储空间,但实际却需要 56B 的内存空间,为什么会使用这么多内存空间呢?这篇文章就围绕这个问题,学习一下 String 类型以及如何优化这种情况的数据,从而能节省更多的存储空间。
1.笔记图
2.Redis 基本数据类型和底层数据结构关系示意图
- String 类型对应 简单动态字符串
- List 类型对应 双向链表 和 压缩列表
- Hash 类型对应 压缩列表 和 散列表
- Sorted Set 类型对应 压缩列表 和 跳表
- Set 类型对应 散列表 和 整数集合
3.String 类型保存方式
- int 编码方式:当保存 64 位有符号整数,会把它保存为一个 8 字节的 Long 类型整数
- 简单动态字符串(SDS):
- embstr 编码:字符串小于 44 字节,RedisObject 中的元数据、指针和 SDS 是一块连续的内存区域,这样就可以避免内存碎片
- raw 编码:字符串大于 44 字节,Redis 不把 SDS 和 RedisObject 布局在一起,会给 SDS 分配独立的空间,用指针指向 SDS 结构
- 每个 key 会对应全局哈希桶 dictEntry 的结构体,用来指向一个键值对
- buf:字节数组,保存实际数据。为了表示字节数组的结束,Redis 会自动在数组最后加一个\0,这就会额外占用 1 个字节的开销
- len:占 4 个字节,表示 buf 的已用长度
- alloc:也占个 4 字节,表示 buf 的实际分配长度,一般大于 len
4.压缩列表(ziplist)
压缩列表(ziplist) 实际是一个字节数组,它的设计目的是为了节约内存,和普通数组不同的是在数组头部会有三个字段,分别是 zlbytes(列表长度)、zltail(列表尾部偏移量)、zllen(entry 的个数),在 压缩列表 的尾部还会有 zlend(结束)字段,示意图如下图:
- zlbytes:列表长度
- zltail:列表尾的偏移量
- zllen:列表中的 entry 个数
- entry:
- prev_len:表示前一个 entry 的长度。prev_len 有两种取值情况:1 字节或 5 字节。取值 1 字节时,表示上一个 entry 的长度小于 254 字节。虽然 1 字节的值能表示的数值范围是 0 到 255,但是压缩列表中 zlend 的取值默认是 255,因此,就默认用 255 表示整个压缩列表的结束,其他表示长度的地方就不能再用 255 这个值了。所以,当上一个 entry 长度小于 254 字节时,prev_len 取值为 1 字节,否则,就取值为 5 字节
- len:表示自身长度,4 字节
- encoding:表示编码方式,1 字节
- content:保存实际数据
- zlend:列表结束
5.使用 Hash 类型替代 String 优化
- 插入 String 类型数据:
info memory #used_memory:867616 set 2201000060 4402000080 #used_memory:867672 #增加56B set 2201000061 4402000081 #used_memory:867728 #增加56B set 2201000062 4402000082 #used_memory:867784 #增加56B set 2201000063 4402000083 #used_memory:867840 #增加56B set 2201000064 4402000084 #used_memory:867896 #增加56B set 2201000065 4402000085 #used_memory:867952 #增加56B set 2201000066 4402000086 #used_memory:868008 #增加56B
Tips:可以看到每次新增的 INT 类型数据占用空间 56B。
- 插入Hash类型数据:
info memory #used_memory:868008 hset 2201000 060 4402000080 #used_memory:868096 #增加88B hset 2201000 061 4402000081 #used_memory:868112 #增加16B hset 2201000 062 4402000082 #used_memory:868120 #增加8B hset 2201000 063 4402000083 #used_memory:868144 #增加24B hset 2201000 064 4402000084 #used_memory:868160 #增加16B hset 2201000 065 4402000085 #used_memory:868176 #增加16B hset 2201000 066 4402000086 #used_memory:868192 #增加16B
Tips:可以看到这种方式只是在第一次增加数据的时候占用 88B,其后每次平均只增加 16B,其原因是 key 的空间是在第一次的时候创建的,后面可以共用,数据是以 ziplist 方式存储的,需要注意避免产生 bigkey。