您好,欢迎来到12图资源库!分享精神,快乐你我!我们只是素材的搬运工!!
  • 首 页
  • 当前位置:首页 > 开发 > WEB开发 >
    Redis中一个String类型引发的惨案
    时间:2021-08-04 08:00 来源:网络整理 作者:网络 浏览:收藏 挑错 推荐 打印

    Redis中一个String类型引发的惨案

    曾经看到这么一个案例,有一个团队需求开发一个图片存储系统,要求这个系统能快速记载图片ID和图片存储对象ID,同时还需求可以依据图片的ID快速找到图片存储对象ID。我们假定用10位数来表示图片ID和图片存储对象ID,例如图片的ID为1101021043,它所对应的图片存储对象的ID为2301010051,可以看到图片ID和图片存储ID正好是逐一对应的,是典型的key-value方式,所以首先会想到直接运用String类型来保存数据。把图片ID和图片存储ID辨别作为键值对的key和value来保存。但是随着存储的数据量越来越大,Redis的内存的运用量也快速上升,结果遇到了大内存Redis实例由于生成RDB而照应变慢的成绩。很显然String类型并不是一种好的选择,

    那有什么办法可以降低内存消耗吗?

    String类型的数据结构

    首先我们得先了解为什么String保存数据时所消耗的内存空间较大。在刚才的案例中,由于图片ID和图片存储对象ID都是10位数,我们可以用两个8字节的Long类型来表示这两个ID。所以一组图片ID及其存储对象ID的记载,实践只需求16字节就可以了。但是经过对Redis内存剖析,一组图片ID及其存储对象ID却占用了64字节,那为什么String类型会用64字节呢。其实,除了要记载实践的数据,String类型还需求额外的内存空间来记载数据的长度、空间运用信息等,这些信息也叫做元数据。当实践保存的数据较小时,元数据的空间开支就显的比较大了。我们先来看一下String类型是如何保存数据的。当你保存64位有符号的整数时,String类型会把它保存为一个8字节的Long类型整数,这种保存方式通常也叫作int编码方式。但是,当你保存的数据中包含字符时,String类型就会用复杂静态字符串结构体(SDS)来保存。如下图所示:

    Redis中一个String类型引发的惨案

    len:4个字节,表示buf的已用长度。

    alloc:4个字节,表示buf分配的长度,普通大于len。

    buf:字节数组,保存实践数据。为了表示数组的开头,Redis会自动在数组最后添加一个”\0"。

    可以看到,在SDS结构体中,除了有保存实践数据的buf,还有len和alloc的额外元数据的开支。另外关于String类型来说,除了SDS的额外开支外,还有一个叫做RedisObject结构体的开支。由于Redis的数据类型有很多,不同的数据类型都有相反的元数据要记载(例如最后一次拜访时间),所以Redis会采用一个叫做RedisObject结构体来一致记载这些元数据。一个RedisObject包含了一个8字节的元数据和一个8字节的指针,这个指针指向详细数据所在,例如String类型的SDS结构体所在的内存地址。如下图所示:

    Redis中一个String类型引发的惨案

    为了节省内存空间,Redis对Long类型整数和SDS的内存规划做了专门的设计。一方面,当保存的是 Long 类型整数时,RedisObject 中的指针就直接赋值为整数数据了,这样就不用额外的指针再指向整数了,节省了指针的空间开支。另一方面,当保存的是字符串数据,并且字符串小于等于 44 字节时,RedisObject 中的元数据、指针和 SDS 是一块延续的内存区域,这样就可以避免内存碎片。这种规划方式也被称为 embstr 编码方式。当字符串大于44字节时,SDS的数据量就末尾变多了,Redis 就不再把SDS 和

    RedisObject 规划在一同了,而是会给 SDS 分配独立的空间,并用指针指向 SDS 结构。这种规划方式被称为 raw 编码形式。如下图所示:

    Redis中一个String类型引发的惨案

    如今我们来计算一下一对图片ID和图片存储对象ID的内存的运用量。由于10位数的图片ID和图片存储对象ID是Long类型整数,所以可以直接用int编码的RedisObject保存。相对应的RedisObject元数据部分占8字节,指针部分被直接赋值为8字节的整数了。此时,每个ID会运用16字节,加起来一共是32字节。但是,另外的 32 字节去哪儿了呢?

    由于Redis是运用全局哈希表来保存一切的键值对,哈希表的每一项是一个dictEntity的结构体来指向一个键值对。dictEntity由三个8字节的指针组成,辨别来指向key、value以及下一个dictEntity。如下图所示。

    Redis中一个String类型引发的惨案

    由于Redis运用的内存分配库为jemalloc,jemalloc在分配内存时,会依据央求的字节数N,找一个比N大的,最接近N的2的幂次数作为分配的空间。

    所以央求一个24字节的dictEntity,实践会分配32个字节。

    到目前位置,你应该明白了为什么String类型来保存图片ID和图片存储对象ID会占用64个字节了。一个有效信息只要16个字节,在运用String类型保存时,却要占用64个字节内存空间,有48个字节用来保存元数据信息了,这是不是极大的糜费了内存空间。那么有没有愈加节省内存的办法呢?

    用紧缩列表节省内存

    Redis里有一种叫做紧缩列表的结构,十分节省内存。我们先回忆一下紧缩列表的构成。表头有三个字段zlbytes、zllen和zltail,辨别表示列表的长度、列表尾的偏移量以及列表中entry的个数。紧缩列表表尾有一个zlend,表示列表完毕。如下图所示。

    Redis中一个String类型引发的惨案

    由于紧缩列表采用一系列的entry保存数据,这些entry会挨个儿放置在内存中,不需求再用额外的指针停止衔接,这样就可以节省指针所占用的空间。每个entry由以下几部分组成。

    pre_len:表示前一个entry的长度。prev_len有两种取值状况:1 字节或 5 字节。当上一个 entry 长度小于 254 字节时,prev_len 取值为 1 字节,否则,就取值为 5 字节。

    len:表示本身的长度,占4个字节。

    encoding:表示编码方式,占1个字节。

    content:保存实践数据。

    (责任编辑:admin)