小对象压缩
- Redis是一种内存数据库,内存是计算机中一种比较宝贵的资源,如果我们不注意节约,Redis很可能出现内存不足,最终导致崩溃。Redis为了优化数据结构的内存占用,增加了非常多的优化点,这些优化也是牺牲代码的可读性为代价,但是对于解决的内存问题来说是非常值得的,尤其是对于Redis这种内存数据库
32bit VS 64bit
- Redis如果使用32bit版本,内部存储所有数据结构使用的指针空间占用会少一半,如果Redis使用内存不会超过2^32 = 4GB,可以考虑32bit进行编译,能节约大量内存。4GB容量对小站点来说戳戳由于。不足可以增加节点
- 指针是寻址,存储的是地址信息,32bit系统能表示的总大小是2^32 位,所以指针能表示的寻址最大值也就是 2 ^32,同理64bit 系统占用2 ^ 64
小对象压缩
- 如果Redis内部管理的集合数据结构很小,他会使用紧凑型存储形式压缩存储,只有当集合数据达到一定量级的时候,Redis才会将数据存储称为正常我们熟知的状态。
- 例如HashMap本来是二维结构,如果内部元素少,使用数组+链表的形式反而浪费空间,因为指针占用空间比数据还要多,还不如使用一维数组进行存储,查找时候元素少,遍历也快,只不过代码略复杂而已。以下案例用一维数组存储HashMap的CUD
public class ArrayMap<K, V> {private List<K> keys = new ArrayList<>();private List<V> values = new ArrayList<>();public V put(K k, V v){for (int i = 0; i < keys.size(); i++) {if(keys.get(i).equals(k)){V oldv = values.get(i);values.set(i, v);return oldv;}}keys.add(k);values.add(v);return null;}public V get(K k){for (int i = 0; i < keys.size(); i++) {if(keys.get(i).equals(k)){return values.get(i);}}return null;}public V delete(K k){for (int i = 0; i < keys.size(); i++) {if(keys.get(i).equals(k)){keys.remove(k);return values.remove(i);}}return null;}
}
- 同样Redis的ziplist是一个紧凑型的字节数组结构,如下图中所示,每个元素都是紧密相连,中间没有指针用来寻址操作,通过三个全局指针来完成逻辑处理,zlbytes, zltail和zlend。如下:
- 如果如上结果存储的是hash结构,那么key和value作为两个entry,被相邻的两个entry存储,通过如下方式验证,输出类型是ziplist:
新docker-redis:0>hset hello a 1
"1"
新docker-redis:0>hset hello b 2
"1"
新docker-redis:0>object encoding hello
"ziplist"
- 如果存储的类型是zset,那么value和score会作为两个entry被相邻存储
新docker-redis:0>zadd world 1 a
"1"
新docker-redis:0>zadd world 2 b
"1"
新docker-redis:0>object encoding world
"ziplist"
- 整数的存储Redis有一个叫intset的整数数组结构,用于存放元素都是整数且元素个数较少的set集合。
- 如果整数可以用uint16标识,那么intset的元素就是16位的数组,如果新加入的整数超过uint16,那么久用uint32,还不够则用uint64,Redis支持set集合动态从unit16升级到unit32,在到unit64
- 如下代码,我们在set集合中存储的数字,他的类型就是intset
新docker-redis:0>sadd hello 1 2
"2"
新docker-redis:0>object encoding hello
"intset"
- 如果set存储的字符串,那么sadd会升级为hashtable结构,
新docker-redis:0>sadd hello yes no
"2"
新docker-redis:0>object encoding hello
"hashtable"
总结
hash - max-z 工pl 工 st entries 512 结构存储 # hash 的元素个数超过 512 就必须用标准
hash- max-ziplist-value 64 超过 64 就必须用标准结构存储 # hash 的任意元素的 key/value 的长度超过64 就必须用标准结构存储
list-max-ziplist-entries 512 结构存储 # list 的元素个数超过 512 就必须用标准结构存储
list-max-z 工plist - 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 就必须用标准结构存储
- 如下测试代码:
public static void main(String[] args) {Jedis jedis = JedisPoolTools.getJedis();jedis.del("books");for (int i = 0; i < 512; i++) {jedis.hset("books", String.valueOf(i), String.valueOf(i));}System.out.println(jedis.objectEncoding("books"));jedis.hset("books", "hello", "512");System.out.println(jedis.objectEncoding("books"));}
//输出如下:
ziplist
hashtable
- 当Hash结构元素超过512 ,存储结构发生变化其他的也都同理,我们通过如上总结数据看出,当hash结构的任意entry的value超过64 ,存储结构就升级成标准结构。
内存回收机制
- Redis并不总是将空闲内存理解归还操作系统
- 例如Redis内存10GB,当删除1GBkey后,观察内存,并不会太大变化,因为操作系统是以页为单位回收内存,这个页内只要还有一个key,就不会被回收。Redis虽然删除了1GBkey但是这些key是分散到各个page中,每个page都还有其他key,这就导致内不会被立刻回收。
- 我们执行flushdb,观察内存,他就会重新使用那些尚未回收的空闲内存。就比如一个page中只有一个key,次数set另一个key,他会存储在这个page的空闲内存中。
内存分配算法
- 内存分配是比较复杂的,需要是的划分内存页,考虑内存碎片,需要平衡性能与效率
- Redis为保证自身结构简单,目前没有自己做内存分配策略,由第三方库完成此部分内容,
- Redis可以用jemalloc,facebook的库来管理内存
- Redis也可以切换到tcmalloc,google的库来管理
- jemalloc性能优于tcmalloc,Redis默认使用jemalloc
新docker-redis:0>info memory
"# Memory
used_memory:5110711968
.....
mem_fragmentation_ratio:1.33
mem_allocator:jemalloc-4.0.3
active_defrag_running:0
lazyfree_pending_objects:0
- 通过info memory指令可以看到Redis的 mem_allocator:jemalloc-4.0.3使用的jemalloc-4.0.3
上一篇:Redis–事务理解
下一篇:Redis高可用基石–主从同步