小对象压缩
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 - max - ziplist- value 64 超过 64 就必须用标准结构存储
list - max - ziplist- entries 512 结构存储
list - max - z 工plist - value 64 用标准结构存储
zset- max - ziplist- entries 128 结构存储
zset- max - ziplist- value 64 用标准结构存储
set - max - intset- entries 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高可用基石–主从同步