Redis原理之_开源节流_小对象压缩
前言
Github:https://github.com/HealerJean
1、引入
Redis
是一个非常耗费内存的数据库,它所有的数据都放在内存里。如果我们不注意节 约使用内存,Redis
就会因为我们的无节制使用出现内存不足而崩溃。
Redis
作者为了优化数据结构的内存占用,也苦心孤诣增加了非常多的优化点,这些优化也是以牺牲代码的可读性 为代价的,当然毫无疑问这是非常值得的,尤其像Redis
这种数据库。
2、32bit
vs 64bit
Redis
如果使用32bit
进行编译,内部所有数据结构所使用的指针空间占用会少一半, 如果你对Redis
使用内存不超过4G
,可以考虑使用32bit
进行编译,可以节约大量内存。4G
的容量作为一些小型站点的缓存数据库是绰绰有余了,如果不足还可以通过增加实例的方式来解决。
3、小对象压缩存储 (ziplist
)
如果
Redis
内部管理的集合数据结构很小,它会使用紧凑存储形式压缩存储。这就好比
HashMap
本来是二维结构,但是如果内部元素比较少,使用二维结构反而浪费空间,还不如使用一维数组进行存储,需要查找时,因为元素少进行遍历也很快,甚至可 以比HashMap
本身的查找还要快。比如下面我们可以使用数组来模拟HashMap
的增删改 操作。
3.1、Hash
元素少的时候用ziplist
如果它存储的是
hash
结构,那么key
和value
会作为两个entry
相邻存在一起。
127.0.0.1:6379> hset hello a 1
(integer) 1
127.0.0.1:6379> hset hello b 2
(integer) 1
127.0.0.1:6379> hset hello c 3
(integer) 1
127.0.0.1:6379> type hello
hash
127.0.0.1:6379> object encoding hello
"ziplist"
3.2、zset
元素少的时候用ziplist
如果它存储的是
zset
,那么value
和score
会作为两个entry
相邻存在一起。
127.0.0.1:6379> zadd world 1 a
(integer) 1
127.0.0.1:6379> zadd world 2 b
(integer) 1
127.0.0.1:6379> zadd world 3 c
(integer) 1
127.0.0.1:6379> type word
zset
127.0.0.1:6379> object encoding world
"ziplist"
3.3、set
存放整数元素少的时候用intset
Redis
的intset
是一个紧凑的整数数组结构,它用于存放元素都是整数的并且元素个数 较少的set
集合如果
set
里存储的是字符串,那么sadd
立即升级为hashtable
结构。
127.0.0.1:6379> sadd hello 1
(integer) 1
127.0.0.1:6379> object encoding hello
"intset"
127.0.0.1:6379> sadd hello yes no
(integer) 2
127.0.0.1:6379> object encoding hello
"hashtable"
4、小对象存储界限
当集合对象的元素不断增加,或者某个
value
值过大,这种小对象存储也会 被升级为标准结构。Redis
规定在小对象存储结构的限制条件如下:
条件 | 长度/大小 | 说明 |
---|---|---|
hash-max-zipmap-entries |
512 | hash 的元素个数超过 512 就必须用标准结构存储 |
hash-max-zipmap-value |
64 | hash 的任意元素的 key/value 的长度超过 64 就必须用标准结构存储 |
list-max-ziplist-entries |
512 | list 的元素个数超过 512 就必须用标准结构存储 |
list-max-ziplist-value |
64 | list 的任意元素的长度超过 64 就必须用标准结构存储 |
zset-max-ziplist-entries |
128 | zset 的元素个数超过 128 就必须用标准结构存储 |
zset-max-ziplist-value |
64 | zset 的任意元素的长度超过 64 就必须用标准结构存储 |
set-max-intset-entries |
512 | set 的整数元素个数超过 512 就必须用标准结构存储 |
5、内存回收机制
Redis
并不总是可以将空闲内存立即归还给操作系统。如果当前
Redis
内存有10G
,当你删除了1GB
的key
后,再去观察内存,你会发现 内存变化不会太大。原因是操作系统回收内存是以页为单位,如果这个页上只要有一个key
还在使用,那么它就不能被回收。Redis
虽然删除了1GB
的key
,但是这些key
分散到了 很多页面中,每个页面都还有其它key
存在,这就导致了内存不会立即被回收。
问题1:有办法空闲内存清除吗?
答案:有的(但是不会使用,太危险),执行 flushdb
(将当前库数据清除),然后再观察内存会发现内存确实被回收了。原因是所有的 key
都干掉了,大部分之前使用的页面都完全干净了,会立即被操作系统回收。
问题2:尚未回收的空闲内存怎么办呢,难道一直空着吗?
答案:Redis
虽然无法保证立即回收已经删除的 key
的内存,但是它会重用那些尚未回收的空闲内存。这就好比电影院里虽然人走了,但是座位还在,下一波观众来了,直接坐就行。而 操作系统回收内存就好比把座位都给搬走了。这个比喻是不是很 6?
6、内存分配算法
内存分配是一个非常复杂的课题,需要适当的算法划分内存页,需要考虑内存碎片,需要平衡性能和效率。
Redis
为了保持自身结构的简单性,在内存分配这里直接做了甩手掌柜,将内存分配的 细节丢给了第三方内存分配库去实现。目前
Redis
可以使用jemalloc(facebook)
库来管理内 存,也可以切换到tcmalloc(google)
。因为jemalloc
相比tcmalloc
的性能要稍好一些,所以 Redis 默认使用了jemalloc
。