Redis是一个基于内存的数据库,所有的数据都存储在内存中。所以如何优化存储,减少内存空间占用是一个非常重要的话题。精简键名和键值是最直观的减少内存占用的方式,如将键名very.important.person:20改成VIP:20。

但有时仅凭精简键名和键值所减少的空间并不足以满足需求,这时就需要根据Redis内部编码规则来节省更多的空间。

Redis为每种数据类型都提供了两种内部编码方式,以散列类型为例,散列类型是通过散列表实现的,这样就可以实现O(1)时间复杂度的查找、赋值操作,然而当键中元素很少的时候,O(1)的操作并不会比O(n)有明显的性能提高,所以这种情况下Redis会采用一种节约内存但性能稍差(获取元素的时间复杂度为O(n))的内部编码方式。

内部编码方式的选择对于使用者来说是透明的,Redis会根据实际情况自动调整。当键中元素变多时Redis会自动将该键的内部编码方式转换成散列表。如果想查看一个键的内部编码方式可以使用”object  encoding”命令,例如:

127.0.0.1:6379> lpush list a
(integer) 2
127.0.0.1:6379> object encoding list
"ziplist"

Redis的每个value都是使用一个redisObject结构体保存的,redisObject的定义如下:

typedef struct redisObject
{
unsigned type:4;
unsigned encoding:4;
unsigned lru:24; /* lru time (relative to server.lruclock) */
int refcount;
void *ptr;
} robj;

其中type字段表示的是value的数据类型,取值可以是如下内容:

/* Object types */
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4

encoding字段表示的就是value的内部编码方式,取值可以是:

#define REDIS_ENCODING_RAW 0     /* Raw representation */
#define REDIS_ENCODING_INT 1 /* Encoded as integer */
#define REDIS_ENCODING_HT 2 /* Encoded as hash table */
#define REDIS_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8 /* Embedded sds string encoding */

各个数据类型可能采用的内部编码方式以及相应的object  encoding命令执行结果如下表所示:

一:字符串类型

Redis使用一个sdshdr类型的变量来存储字符串,而redisObject的ptr字段指向的是该变量的地址。sdshdr的定义如下:

struct sdshdr
{
unsigned int len;
unsigned int free;
char buf[];
};

注意,sizeof(struct  sdshdr) == 8,也就是说,最后的buf成员未计入。

其中len字段表示的是字符串的长度,free字段表示buf中的剩余空间,而buf字段存储的才是字符串的内容。

所以当执行”set  key foobar”时,存储键值需要占用的空间是:sizeof(redisObject) + sizeof(sdshdr) +strlen(“foobar”) = 16 + 8 + 6 = 30,如下图所示:

当键值内容可以用一个64位有符号整数表示时,Redis会将键值转换成long类型来存储。如”set  key  12345”,实际占用的空间是sizeof(redisObject)= 16字节,比存储”foobar”节省了一半的存储空间,如下图所示:

redisObject中的refcount字段存储的是该键值被引用的数量,即一个键值可以被多个键引用。Redis启动后会预先建立10000个redis0bject类型变量作为共享对象,分别存储从0到9999这些数字。如果要设置的字符串键值在这10000个数字内(如”set  key1  123”),则可以直接引用共享对象而不用再建立一个redisObject了,也就是说存储键值占用的空间是0字节,当执行了”set  key1  123”和”set 
key2 123”后,key1和key2两个键都直接引用了一个己经建立好的共享对象,节省了存储空间。如下图所示:

由此可见,使用字符串类型键存储对象ID这种小数字是非常节省存储空问的,Redis只需存储键名和一个对共享对象的引用即可。

注意,当通过配置文件参数maxmemory设置了Redis可用的最大空间大小时,Redis不会使用共享对象,因为对于每一个键值都需要使用一个RedisObject来记录其LRU信息。

二:散列类型

散列类型的内部编码方式可能是REDIS_ENCODING_HT或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都会自动判断是否满足条件来完成转换。

REDIS_ENCODING_HT编码即散列表,可以实现O(1)时间复杂度的赋值取值等操作,其字段和字段值都是使用redisObject存储的,所以前面讲到的字符串类型键值的优化方法同样适用于散列类型键的字段和字段值。

Redis的key-value存储也是通过散列表实现的,与REDIS_ENCODING_HT编码方式类似,但键名并非使用redisObject存储,所以健名”123456”并不会比”abcdef”占用更少的空间。之所以不对键名进行优化是因为绝大多数情况下键名都不会是纯数字。

REDIS_ENCODING_ZIPLIST编码类型是一种紧凑的编码格式,它牺牲了部分读取性能以换取极高的空间利用率,适合在元素较少时使用。该编码类型同样还在列表类型和有序集合类型中使用。REDIS_ENCODING_ZIPLIST编码结构如下图所示:

其中zlbytes是uint32_t类型,表示整个结构占用的空间。zltail也是uint32_t类型。表示到最后一个元素的偏移,zltail使得程序可以直接定位到尾部元素而无需遍历整个结构,执行从尾部弹出(对列表类型而言)等操作时速度更快。zllen是uint16_t类型,存储元索的数量。zlend是一个单字节标识,标记结构的末尾,值永远是255。

在REDIS_ENCODING_ZIPLIST中每个元素由4个部分组成。第1部分用来存储前一个元素的大小以实现倒序查找;第2,3个部分分别是元素的编码类型和元素大小,第四个部分是元素的实际内容。

使用REDIS_ENCODING_ZIPLIST编码存储散列类型时元素的排列方式是:元素1存储字段1,元素2存储字段值1,依次类推,如下图所示:

例如,执行命令”hset  hkey foo  bar”命令后,下次需要执行”hset  hkey foo  anthervalue”时Redis需要从头开始找到值为foo的元素(查找时每次都会跳过一个元素以保证只查找字段名),找到后删除其下一个元素,井将新值anothervalue插入。删除和插入都需要移动后面的内存数据,并且查找操作也需要遍历才能完成,可想而知当散列键中数据多时性能将很低,所以不宜将hash-max-ziplist-entries和hash-max-ziplist-value两个参数设置得很大。

三:列表类型

列表类型的内部编码方式可能是REDIS_ENCODING_LINKEDLIST或REDIS_ENCODING_ZIPLIST。同样,也可以在配置文件中定义使用REDIS_ENCODING_ZIPLIST方式编码的时机:

list-max-ziplist-entries  512
list-max-ziplist-value 64

具体转换方式和散列类型一样,这里不再赘述。

REDIS_ENCODING_LINKEDLIST编码方式即双向链表,链表中的每个元素是用redisObject方式存储的,所以此种编码方式下元素值的优化方法与字符串类型的键值相同。

使用REDIS_ENCODING_ZIPLIST编码方式时具体的表现和散列类型一样,由于该编码方式同样支持倒序访问,所以采用此种编码方式时获取两端的数据依然较快。

四:集合类型

集合类型的内部编码方式可能是REDIS_ENCODING_HT或REDIS_ENCODING_INTSET。当集合中的所有元素都是整数且元素的个数小于配置文件中的set-max-intset-entries(默认是512)时,Redis会使用REDIS_ENCODING_INTSET编码存储该集合,否则会使用REDIS_ENCODING_HT来存储。

REDIS_ENCODING_INTSET编码存储结构体intset的定义是:

typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;

其中contents存储的是集合中的元素值,根据encoding的不同,每个元素占用的字节大小不同。默认的encoding是INTSET_ENC_INT16(即2个字节),当新增加的整数元素无法使用2个字节表示时,Redis会将该集合的encoding升级为INTSET_ENC_INT32 (即4个字节),并调整之前所有元素的位置和长度,同样集合的encoding还可升级为INTSET_ENC_INT64(即8个字节)。

REDIS_ENCODING_INTSET编码以有序的方式存储元素(所以使用smembers命令获得的结果是有序的),使得可以使用二分算法查找元素。然而无论是添加还是删除元素,Redis都需要调整后面元素的内存位置,所以当集合中的元素太多时性能较差。

当新增加的元素不是整数,或集合中的元素数量超过了set-max-intset-entries参数时,Redis会自动将该集合的存储结构转换成REDIS_ENCODING_HT。

注意当集合的存储结构转换成REDIS_ENCODING_HT后,即使将集合中的所有非整数元素删除,Redis也不会自动将存储结构转换回REDIS_ENCODING_INTSET。因为如果要支持自动回转,就意味着Redis在每次删除元素时都需要遍历集合中的健来判断是否可以转换回原来的编码,这会使得删除元素变成了时间复杂度为O(n)的操作。

五:有序集合类型

有序集合类型的内部编码方式可能是REDIS_ENCODING_SKIPLIST或REDIS_ENCODING_ZIPLIST。同样在配置文件中可以定义使用REDIS_ENCODING_ZIPLIST方式编码的时机:

zset-max-ziplist-entries  128
zset-max-ziplist-value 64

具体转换规则和散列类型及列表类型一样,不再赘述。

当编码方式是REDIS_ENCODING_SKIPLIST时,Redis使用散列表和跳跃列表(skip list)两种数据结构来存储有序集合类型键值,其中散列表用来存储元素值与元素分数的映射关系以实现O(1)时间复杂度的zscore等命令。跳跃列表用来存储元素的分数及其到元素值的映射以实现排序的功能。Redis对跳跃列表的实现进行了几点修改,其中包括允许跳跃列表中的元素(即分数)相同,还有为跳跃列表每个节点增加了指向前一个元素的指针以实现倒序查找。

采用此种编码方式时,元素值是使用redisObject存储的,所以可以使用字符串类型键值的优化方式优化元素值,而元素的分数是使用double类型存储的。

使用REDIS_ENCODING_ZIPLIST编码时,有序集合存储的方式按照“元素1的值,元素1的分数,元素2的值,元素2的分数”的顺序排列,并且分数是有序的。

04Redis入门指南笔记(内部编码规则简介)的更多相关文章

  1. Redis数据类型内部编码规则及优化方式

    Redis的每个键值都是使用一个redisObject结构体保存的,redisObject的定义如下: typedef struct redisObject { unsigned type:4; un ...

  2. 01Redis入门指南笔记(简介、安装、配置)

    一:简介 Redis是一个开源的高性能key-value数据库.Redis是Remote DIctionary Server(远程字典服务器)的缩写,它以字典结构存储数据,并允许其他应用通过TCP协议 ...

  3. 02Redis入门指南笔记(基本数据类型)

    一:热身 获得符合规则的健名列表:keys  pattern pattern支持glob风格的通配符,具体规则如下表: Redis命令不区分大小写.keys命令需要遍历Redis中的所有健,当键的数量 ...

  4. 08Redis入门指南笔记(集群)

    即使使用哨兵,此时的 Redis 集群的每个数据库依然存有集群中的所有数据,从而导致集群的总数据存储量受限于所有节点中,内存最小的数据库节点,形成木桶效应. 对 Redis 进行水平扩容,在旧版Red ...

  5. 03Redis入门指南笔记(事务、生存时间、排序、消息通知、管道)

    一:事务 1:概述 Redis中的事务(transaction)是一组命令的集合.事务同命令一样都是Redis的最小执行单位,一个事务中的命令要么都执行,要么都不执行. 事务的原理是是先将属于一个事务 ...

  6. 07Redis入门指南笔记(主从复制、哨兵)

    现实项目中通常需要若干台Redis服务器的支持: 结构上,单个 Redis 服务器会发生单点故障,而且一台服务器需要承受所有的请求负载.这就需要为数据生成多个副本并分配在不同的服务器上: 容量上,单个 ...

  7. 06Redis入门指南笔记(安全、通信协议、管理工具)

    一:安全 1:可信的环境 Redis以简洁为美.在安全层面Redis也没有做太多的工作.Redis的安全设计是在"Redis运行在可信环境"这个前提下做出的.在生产环境运行时不能允 ...

  8. 05Redis入门指南笔记(持久化)

    Redis的强劲性能很大程度上是由于将所有数据都存储在了内存中,然而当Redis重启后,所有存储在内存中的数据就会丢失.在一些情况下,希望Redis能将数据从内存中以某种形式同步到硬盘中,使得重启后可 ...

  9. 《Redis入门指南》笔记

    第1章 简介 1.1 历史与发展 2008年 意大利创业公司创始人因对mysql性能不满意,于是他决定开发redis. 2009年 redis初版由他一个人开发完成.redis是"remot ...

随机推荐

  1. 深入了解MVC(转)

    MVC无人不知,可很多程序员对MVC的概念的理解似乎有误,换言之他们一直在错用MVC,尽管即使如此软件也能被写出来,然而软件内部代码的组织方式却是不科学的,这会影响到软件的可维护性.可移植性,代码的可 ...

  2. String--在内存中的表现

    创建字符串的方法有两种:   Stringstr1=”直接赋值法”          Stringstr2=new String(“通过new关键字的方法来创建”); 在执行String str1=” ...

  3. scanf("%c", &ch)和scanf(" %c", &ch)和scanf("%s", str)的注意事项

    scanf("%c", &ch)和scanf(" %c", &ch): %c会读取回车和空格,所以一定要使用后者,即在%c前面加一个空格. %s ...

  4. 统计py文件或目录代码行数

    bug:当遇到3个"""时 可能会将下面的代码不计入代码总行数 import os def count_path(path,countcode): if os.path. ...

  5. Java图片高保真缩放工具类

    Java图片高保真缩放 package com.xindai.auth.service.util; import java.awt.image.BufferedImage; import java.i ...

  6. 2019-9-2-C#判断文件是否被混淆

    title author date CreateTime categories C#判断文件是否被混淆 lindexi 2019-09-02 12:57:37 +0800 2018-2-13 17:2 ...

  7. 为什么无法定义1px左右高度的容器

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xht ...

  8. kaptcha验证码的使用(转)

    使用kaptcha可以方便的配置: 验证码的字体 验证码字体的大小 验证码字体的字体颜色 验证码内容的范围(数字,字母,中文汉字!) 验证码图片的大小,边框,边框粗细,边框颜色 验证码的干扰线(可以自 ...

  9. SQLSERVER 根据传入的参数拼接sql语句字符串,反馈结果集

    ALTER PROCEDURE [dbo].[usp_visit_detail](@siteid BIGINT, @Startime VARCHAR(15), @Endtime  VARCHAR(15 ...

  10. PHP的生成图片或文字水印的类

    ImageWatermark.php <?php /*********************************************************** 类名:ImageWat ...