Redis内部编码

Redis内部编码
我们常说的String,list.,hash ,set,sorted set只是对外的编码,实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现,这样redis可以在合适的场景选择更合适的内部编码。

如图所示:(intset编码,不是inset编码),可以看到每种数据结构都有2种以上的内部编码实现,例如String数据结构就博阿寒了raw 、int、emstr三种内部编码。同时,有些内部编码可以作为多种外部数据结构的内部实现,例如ziplist就是hash、list、zset共有的内部编码,而set的内部编码可能是hashtable或者是intset;

在这里插入图片描述
redis这样的设计有两个好处:
1.可以偷偷的改进内部编码,而对外的数据机构和命令没有影响,这样一旦开发出更优秀的内部编码,无需改动对外数据结构和命令。
2.多种内部编码的实现可以在不同的场景下发挥各自的又是。例如ziplist比较节省内存,但是列表元素比较多的情况下,性能会有所下降。这个是狗redis会根据配置选项将列表类型的内部事项转换为linkedlist;

String的三种内部编码:
string 的三种内部编码分别是:int 、embstr、raw。int类型很好理解,当一个key的value是整型时,redis就将其编码为int类型(另外还有一个条件;把这个value当成字符串来看,他的长度不能超过20); 这种编码类型时位列节省内存。redis会缓存10000个整型值,这就意味着,如果有10个不同的key,其value都是10000以内的值,试试上全部都是共享同一个对象;

127.0.0.1:6379> set number “7890”
OK
127.0.0.1:6379> object encoding number
“int”

接下来就是ebmstr和raw两种内部编码的长度界限,请看下面的源码

#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}

也就是说,ebmstr和raw编码的长度界限时44,我们可以做如下验证。长度超过44以后,就是raw编码类型,不会有任何优化,是多长,就要消耗多少内存:


```bash

```bash
127.0.0.1:6379> set name "a1234567890123456789012345678901234567890123"
OK
127.0.0.1:6379> object encoding name
"embstr"
127.0.0.1:6379> set name "a12345678901234567890123456789012345678901234"
OK
127.0.0.1:6379> object encoding name
"raw"

为什么要有embstr编码呢?他比raw的优势在哪里?embstr编码将创建字符串对象所需的空间分配的次数从raw编码的两次降低为一次。因为emstr编码字符串的素有对象保持在一块连续的内存里面,所以这猴子那个编码的字符串对象比起raw编码的字符串对象能更好的利用缓存带来的又是。并且释放embstr编码的字符串对象只需要调用一次内存释放函数,而释放raw编码对象的字符串对象需要调用两次内存释放函数,如图所示,左边emstr编码,右边是raw编码:

在这里插入图片描述
ziplist

由前面的图可知,LIst,Hash,Sorted set 三种对外结构,在特殊的情况下的内部编码都是ziplist,那么隔着ziplist有什么神奇之处呢?

以hash为例,首先看一下什么条件下他的内部编码是ziplist:

  1. 当hash类型元素个数小于hash-max-ziplist-entries配置(默认512个)
  2. 所有的值都小于hash-max-ziplist-value配置(默认64个字节)

如果是sorted set 的话,同样需要满足两个条件:

1.元素个数小于zset-max-ziplist-entries配置,默认128;
2.所有值都小于zset-max-ziplist-value配置,默认64

实际上,ziplist充分体现了redis对于存储效率的追求,一个普通的双向链表,链表中每一项都占用独立的一块内存,各项之间用地址指针链接起来。这种方式会带来大量的内存碎片,而且地址指针也会占用额外的内存。而ziplist确实将表中每一项存放在前后连续的地址空间内,一个ziplist整体占用一大块内存。他是一个表list,但是其实不是一个链表。

  1. zlbytes:表示这个ziplist占用了多少个空间,或者说占了多少个自己二,这其中包括了zlbytes本身占用的四个字节。
  2. zltail:表示到ziplist最后一个元素的偏移量,有了这个值,pop操作的时间复杂度就是O(1)了,即不需要遍历整个ziplist;
  3. zllen:表示ziplist种有多少个entry,及保存了多少个元素。由于这个字段占用16个字节,所以最大值是2的16次幂-1,也就意味着,如果entry的数量超过这个数量时,需要遍历整个ziplist才知道entry的数量;
  4. entryL:真正保存的数据,有他自己的编码
  5. zlend:专门用来表示ziplist尾部的特殊字符,占用8个字节,固定值为255,及8个字节每一位都是1.
    如下就是一个真正的ziplist编码,包含了2和5两个元素;
 [0f 00 00 00] [0c 00 00 00] [02 00] [00 f3] [02 f6] [ff]
       |             |          |       |       |     |
    zlbytes        zltail    entries   "2"     "5"   end

linkedlist:
这是list的一种编码数据机构非常简单,就是我们非常熟悉的双向链表,对应Java的linkedlist。

skiplist
经典的跳表数据结构。
hashtable
对应Java的hashmap

inset
set内部特殊编码,当满足下面的条件时set内部编码就是intset而不是hashtable;

  1. set集合中必须是64位有符号的十进制整型;
  2. 元素个数不能超过set-max-inset-entries配置,默认512
127.0.0.1:6379> sadd scores 135
(integer) 0
127.0.0.1:6379> sadd scores 128
(integer) 1
127.0.0.1:6379> object encoding scores
"intset"

那么intset编码到底是个什么东西呢?看它的源码定义如下,很明显,就是整型数组,并且是一个有序的整型数组。它在内存分配上与ziplist有些类似,是连续的一整块内存空间,而且对于大整数和小整数采取了不同的编码,尽量对内存的使用进行了优化。这样的数据结构,如果执行SISMEMBER命令,即查看某个元素是否在集合中时,事实上使用的是二分查找法:

bitmap
这个就是Redis实现的BloomFilter,BloomFilter非常简单,如下图所示,假设已经有3个元素a、b和c,分别通过3个hash算法h1()、h2()和h2()计算然后对一个bit进行赋值,接下来假设需要判断d是否已经存在,那么也需要使用3个hash算法h1()、h2()和h2()对d进行计算,然后得到3个bit的值,恰好这3个bit的值为1,这就能够说明:d可能存在集合中。再判断e,由于h1(e)算出来的bit之前的值是0,那么说明:e一定不存在集合中:

在这里插入图片描述
需要说明的是,bitmap并不是一种真实的数据结构,它本质上是String数据结构,只不过操作的粒度变成了位,即bit。因为String类型最大长度为512MB,所以bitmap最多可以存储2^32个bit。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值