内存优化
咱们都晓得 Redis 的数据都存储在内存中,而内存又是十分贵重的资源,本文将解说如何进行内存优化。
redisObject 对象
首先须要理解什么是 redisObject,在 Redis 中存储的所有值对象在外部都被定义为redisObject,构造如下。
-
type
:示意以后对象应用的数据类型,次要就是 string、hash、list、set、zset 五种。4 示意占 4 个 bit 位。📢:应用 type [key] 命令能够查看对象的所属类型,返回的是值对象的类型,键都为 string 类型。
encoding
:示意外部编码的类型,代表以后对象应用哪种数据结构实现。了解外部编码类型对于内存优化十分重要。-
lru
:记录对象最初一次被拜访的工夫,当配置了 maxmemory 和 maxmemory-policy=volatile-lru 或者 allkeys-lru 时,用于辅助 LRU 算法删除键数据。📢:应用 object idletime [key] 在不更新 lru 字段时间的状况下查看以后键的闲暇工夫。
应用 scan + object idletime 命令能够批量查问出那些键长时间未被拜访并进行清理,升高内存占用。
-
refcount
:记录以后对象被援用的次数,当 refcount=0 的时候能够平安的进行对象空间回收。📢:应用 object recount [key] 获取以后对象援用。当对象值在 [0-9999] 的时候,redis 会应用共享对象池来节俭内存。
* ptr
:如果存的是整数,则间接存储数据,否则存储指向数据的指针。当值对象为字符串并且 <=44 字节时候,外部编码为 embstr 类型,当 >44 字节时候,应用 raw 类型。
缩减键值对象
缩减 key 和 value 的长度能够无效缩小 Redis 内存应用状况,比方将 key 进行缩写等。
value 比较复杂,如果是将业务对象进行序列化为二进制数组,能够去掉不必要的属性,其次在序列化工具上能够抉择更高效的如:protostuff、kryo 等。除了二进制数组外,咱们也会存入 json、xml 等字符串,在内存缓和状况下,咱们能够应用压缩算法来压缩 json、xml 再存入 redis。
共享对象池
共享对象池指的是在 Redis 中保护了 [0-9999] 的整数对象池。因为创立大量的整数类型的 redisObject 存在内存开销,一个 redisObject 外部至多占用 16 字节,所以 Redis 保护了整数对象池来节约内存。另外,list、hash、set、zset 中也是能够应用共享对象池的。
如上图,能够看到当值为 100 时,refcount 是 2147483647,其实就是 INT_MAX, 这是一个共享对象。而 12000 的援用计数是 1,是一个新创建的对象。
此时的 redisObject 如下:
📢:须要留神的是,在设置了 maxmemory 和 LRU 相干淘汰策略入:volatile-lru,allkeys-lru 时,Redis 此时会禁用共享对象池。
LRU 算法须要获取对象最近一次拜访的工夫,但共享对象池可能存在多个援用同时指向同一个 redisObject,这时 lru 字段也会被共享,导致无奈获取每个对象的最初一次拜访工夫。但如果没有设置 maxmemory 的话,直到内存用完之前都不会触发回收机制,所以共享对象池能够始终应用。
📢:另外须要留神的是,如果外部编码应用的是 ziplist 的值对象,即便所有数据为整数也无奈应用共享对象池。因为 ziplist 应用压缩且内存间断的构造,对象判断老本过高。
字符串优化
在 Redis 中最常见的就是字符串,所有的键都是字符串类型,值对象数据类型除了整数就是字符串类型。所以如何进行字符串优化也是重点之一。
首先咱们先来理解字符串构造。
字符串构造
redis 并没有应用 C 语言中的字符串,而是本人实现了字符串构造。
简略动静字符串(simple dynamic string)SDS
struct sdshdr{
// 字节数组
char buf[];
//buf 数组中已应用字节数量
int len;
//buf 数组中未应用字节数量
int free;
}
SDS 的长处:
- O(1)的工夫复杂度获取字符串长度,已应用长度,未应用长度。
- 能够保留字节数组,反对平安的二进制数据存储。
- 外部实现空间预分配机制,升高内存再调配次数。
- 惰性删除机制,在字符串缩减后空间不立刻开释,作为预调配空间保留。
PS:无关字符串 SDS 的相干内容能够看之前的文章。
📢:须要留神的是,字符串应用预分配机制是为了避免频繁批改字符串内容导致频繁地进行重分配内存和字符串拷贝。所以须要尽可能减少批改,比方 append,setrange。能够改为间接应用 set 批改字符串,升高调配带来的内存节约和内存碎片化。
字符串重构
如果保留的是 json 数据,能够应用 hash 构造来进行存储,应用 hmget,hmset 进行批量获取和批改。
如果存在长字符串的状况下进行测试性能:
须要留神的是如果值对象字符串长度大于 65 则 Redis 会应用 hashtable 编码方式,反而会耗费更多内存。
通过调整
hash-max-ziplist-value=xx
设置一个适合的值,则会应用 ziplist 编码方式,会更节俭内存。
编码优化
理解编码
Redis 提供了 string、list、hash、set、zet 等类型。但对每种类型存在不同编码的概念,其实就是具体底层应用了哪种数据结构。不同的编码会间接影响内存占用和读写效率。
应用 object encoding [key]命令来获取编码类型。
> object encoding jack
int
> hset hello student jack
1
> object encoding hello
ziplist
...
类型 | 编码方式 | 数据结构 |
---|---|---|
string | raw | 动静字符串 |
string | embstr | 优化内存调配的字符串 |
string | int | 整数 |
hash | hashtable | 散列表 |
hash | ziplist | 压缩列表 |
list | linkedlist | 双向链表 |
list | ziplist | 压缩列表 |
list | quicklist | 疾速列表 |
set | hashtable | 散列表 |
set | intset | 整数汇合 |
zset | skiplist | 跳跃表 |
zset | ziplist | 压缩列表 |
📢:编码类型在 Redis 写入数据时主动实现,只能从小内存编码转换为大内存编码,过程是不可逆的。
接下来咱们看一下转换条件是什么样的。
类型 | 编码 | 转换条件 |
---|---|---|
string | embstr | value 字节长度 <= 44 |
string | raw | value 字节长度 > 44 |
string | int | 整数 |
hash | ziplist | value 字节长度 <=hash-max-ziplist-value 并且 field 个数 <=hash-max-ziplist-entries |
hash | hashtable | value 字节长度 >hash-max-ziplist-value 或者 field 个数 >hash-max-ziplist-entries |
list | ziplist | value 字节长度 <=list-max-ziplist-value 并且链表长度 <=list-max-ziplist-entries |
list | linkedlist | value 字节长度 >list-max-ziplist-value 或者链表长度 >list-max-ziplist-entries |
list | quicklist | 废除上述 list-max-ziplist-value、list-max-ziplist-entries 配置。应用:list-max-ziplist-size 示意最大压缩空间或长度。最大空间应用 [-5~1] 范畴配置,默认 -2 示意 8KB。正整数示意最大压缩长度。list-compress-depth:示意最大压缩深度,默认 0 不压缩 |
set | intset | 元素均为整数并且汇合长度 <=hash-max-ziplist-entries |
set | hashtable | 元素非整数或者汇合长度 >hash-max-ziplist-entries |
zset | ziplist | value 字节长度 <=zset-max-ziplist-value 并且汇合长度 <=zset-max-ziplist-entries |
zset | skiplist | value 字节长度 >zset-max-ziplist-value 或者汇合长度 >zset-max-ziplist-entries |
ziplist 编码
本文着重介绍一下 ziplist,ziplist 中所有数据都是采纳线性间断存储的内存构造,能够作为 list、hash、zset 底层数据结构实现。
zlbytes
:记录整个压缩列表所占用的字节长度。类型是 int-32,长度为 4 字节。zltail
:记录间隔尾结点的偏移量,不便尾节点弹出操作。类型是 int-32,长度为 4 字节。zllen
:记录压缩链表节点数量。entry
:记录具体的节点。prev_entry_bytes_length
:记录前一个节点所占空间,用于疾速定位上一个节点,也能够实现列表反向迭代。encoding
:标示以后节点编码和长度,前两位标示编码类型:字符串 / 整数,其余位示意数据长度。contents
:保留节点的值。zlend
:记录列表结尾,占用一个字符。
特点:
- 外部为数据紧凑排列的 一块间断内存数组。
- 能够模仿双向链表构造,O(1)工夫复杂度入队和出队。
- ziplist 在空间利用率上极高,每个 entry 最多只有 6 字节的节约。
- ziplist 底层构造无链表,通过内存偏移量获取 next 或 last 节点地位
- ziplist 在插入和删除的时候有很大的概率呈现连锁更新,因而在应用时尽量保障所存储的 value 位数雷同,否则最坏会呈现 O(n^2)的工夫复杂度。
总结
- Redis 内存耗费次要在于:键值对象、缓冲区内存。
- 通过
maxmemory
来管制 Redis 最大可用内存,当超出设置的内存大小后,依据maxmemory-policy
管制内存回收策略。 - 应用共享对象池优化小整数对象。
- 优先应用整数,比字符串更节俭空间。
- 优化字符串应用,防止预调配造成的内存节约。
- 应用 ziplist 压缩编码优化 hash、list、zset 构造。
- 应用 intset 编码优化整数汇合。