我们知道Redis是一个基于内存的数据库,所有的数据都存储在内存中,所以我们需要学习如何优化存储,用最小的代价得到最好的性能。
在使用Redis的过程中,我们可以使用下面的方式来节省空间:
一.精简键名和键值
这是一种很直观的减少内存占用的方式,这种方式的目标是:用最简洁的名称表达最好的效果。切忌为了单纯的节省空间而使用难以理解的键名。
二.内部编码优化
1.概念了解
这种方式是根据Redis内部编码规则来节省空间,Redis为每种数据类型都提供了两种内部编码方式,我们以散列类型为例:
散列类型是通过散列表实现的,它的优势是在数据很多的时候以优越的时间复杂度查找指定数据。
但是当键中的元素很少的时候,它的性能并不比普通的查找方式高所以这种情况下Redis会采用一种更为紧凑但性能较差的普通内部编码方式。
内部编码方式的选择对于开发者来说是透明的,Redis会根据实际情况自动调整,当键中元素变多时Redis会自动将该键的内部编码方式转换为散列表。
如果想查看一个键的内部编码方式我们可以使用OBJECT ENCODING命令来执行:
2.基本知识了解
Redis的每个键值都是使用一个redisObject结构体保存的,redisObject的定义如下:
type struct redisObject{
unsigned type:4; /表示键值的数据类型
unsigned notused:2; /
unsigned encoding:4; /表示Redis键值的内部编码方式
unsigned lru:22;/
int refcount;/该键被引用的数量
void *ptr;
}robj;
其中type字段表示的键值的数据类型取值可以是如下内容:
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
encoding字段表示的就是Redis键值的内部编码方式。
3.下面我们针对每种数据类型分别学习其内部编码规则及优化方式:
1)字符串类型
Redis使用一个sdshdr类型的变量来存储字符串,而redisObject的ptr子段指向的是改变量的地址,
sdshdr定义如下:
struct sdshdr{
int len;
int free;
char buf[];
}
其中len表示字符串长度,free表示buf中的剩余空间,buf存储字符串内容。
所以当执行SET key foobar时,存储键值需要的空间是:
sizeof(redisObject)+sizeof(sdshdr)+strlen(“foobar”)=30字节
而当键值内容可以用一个64位有符号整数表示时,Redis会将键值转换成long类型来存储,例如:SET key 123456, 它所占用的空间比前者要小。
由此可见,使用字符串类型存储数字比存储字符串要省空间。 int 比 raw编码方式省空间。
在Redis 3.0中新加了一中编码方式,embstr方式,这种方式与raw类似,都是基于sdshdr方式实现,只不过sdshdr的结构体与其对应的分配在同一块连续的内存空间中。这种方式便于分配和释放内存,当键值内容不超过39字节时,Redis会采用这种方式编码,但当对这种编码方式的键值进行任何修改操作时,Redis会将其转换为raw编码方式。
2)散列类型
散列类型的内部编码方式可能是 :
REDIS_ENCODING_HT(基于hash存储,适合元素量较多的集合)
REDIS_ENCODING_ZIPLIST(基于链表存储,适合元素较少的集合)
我们可以在配置文件中定义使用REDIS_ENCODING_ZIPLIST方式编码散列类型的时机,在配置文件中配置下面属性:
hash-max-ziplist-entries 512
hash-max-ziplist-value 64 时,
当散列类型键的字段数少于hash-max-ziplist-entries参数值且每个字段名和字段值的长度都小于 hash-max-ziplist-value参数值(单位为字节)时,Redis就会使用REDIS_ENCODING_ZIPLIST来存储该键,否则使用REDIS_ENCODING_HT. 每当键值变更后Redis都会自动判断是否满足条件来完成转换。
3)列表类型
列表类型的内部编码方式:
REDIS_ENCODING_LINKEDLIST(基于双向链表)
REDIS_ENCODING_ZIPLIST(和散列类型的一样)
它们之间的转换方式和散列类型的一样,同理。
REDIS_ENCODING_QUICKLIST:它是前两者的结合,其原理是将一个长列表分成若干个以链表形式组织的ziplist,从而达到减少空间占用的同时提升了REDIS_ENCODING_ZIPLIST编码的性能。(最新的Redis默认使用它)
4)集合类型
集合类型的内部编码方式为:
REDIS_ENCODING_HT(无序)
REDIS_ENCODING_INTSET(有序)
当集合中的所有元素都是整数且元素的个数小于配置文件中的set-max-intset-entries(默认512)时Redis会使用REDIS_ENCODING_INTSET编码方式存储该集合,否则使用REDIS_ENCODING_HT。
REDIS_ENCODING_INTSET编码存储结构体inset的定义如下:
typedef struct intset{
uint32_t encoding;
uint32_t length;
int8_t contents[];
}intset;
其中contents存储的就是集合中的元素值,根据encooding的不同,每个元素占用的字节数不同,默认的encoding是INTSET_ENC_INT16(两个字节),当新增加的整数元素无法用两个字节表示时,Redis会用INTSET_ENC_INT32(4个字节),再者就用INTSET_ENC_INT64(8个字节)
这种编码方式以有序的的方式存储元素。
5)有序集合类型
有序集合类型的内部编码方式可能是:
REDIS_ENCODING_SKIPLIST
REDIS_ENCODING_ZIPLIST
同样我们可以在配置文件中定义使用:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
具体是转换和散列类型一样,不赘述。
当编码方式是REDIS_ENCODING_SKIPLIST时,Redis使用散列表和跳跃列表两种数据结构来存储有序集合类型的键值。
其中散列表用来存储元素值于元素分数的映射关系以实现O(1)时间复杂度的ZSCORE等命令。
跳跃表用来存储元素的分数及其到元素值的映射以实现排序的功能,其中Redis中对跳跃列表的实现进行了几点修改,包括:允许跳跃表中的元素(分数)相同,为跳跃链表每个节点增加了指向前一个元素的指针以实现倒序查找。
总结:我们除了优化键值的应用格式外,还可以通过编码方式对存储空间进行优化。