第十一章 “万金油”的String,为什么不好用了 ?

为什么 String 类型内存开销大 ?

  • 除了记录实际数据,String 类型还需要额外的内存空间记录数据长度、空间使用等信息,这些信息也叫作元数据。
  • 所谓元数据信息,就是描述信息的信息。
  • 当实际保存的数据较小时,元数据的空间开销就显得比较大了。

String 类型具体是怎么保存数据的呢 ?

  • String中保存整数会用 int编码 方式,
  • 而保存的数据是字符时会用 SDS(简单动态字符串) 结构体 来保存。

  • buf:字节数组,保存实际数据。为了表示字节数组的结束,Redis 会自动在数组最后加一个 '\0',这就会额外占用 1 个字节的开销。
  • len:占 4 个字节,表示 buf 的已用长度。
  • alloc:也占个 4 字节,表示 buf 的实际分配长度,一般大于 len。

对于 String 类型来说,除了 SDS 的额外开销,还有一个来自于 RedisObject 结构体的开销。

  • RedisObject 结构体主要记录最后一次访问的时间、被引用的次数等信息,这些被称为元数据信息。
  • 一个 RedisObject 包含了 8 字节的元数据和一个 8 字节指针,这个指针再进一步指向具体数据类型的实际数据所在,例如指向 String 类型的 SDS 结构所在的内存地址。

Redis针对Long类型、SDS结构体 的内存布局的优化

  • 当保存的是Long类型整数时,RedisObject(元数据、指针)中的指针位置直接赋值为整数数据,节省指针空间。
  • 当保存的字符串 <= 44字节时,RedisObject (元数据、指针)和 SDS 结构体是一块连续的内存区域,可以避免内存碎片(embstr编码) 。
  • 当保存的字符串 > 44字节时,给 SDS 分配独立空间存储(raw编码)。

int、embstr 和 raw 三种编码方式:

Redis 会使用一个全局哈希表保存所有键值对,哈希表的每一项是一个 dictEntry 的结构体,用来指向一个键值对。

  • 一个 dictEntry 需要24字节, 分别存储三个8字节的指针, 指向Key Value 对应的 RedisObject 和 下一个dictEntry。
  • redis采用 jemalloc 分配库为这三个指针分配内存,jemalloc 分配的内存会比申请的字节数大,且小于临近的2的次幂(比如 24 —> 32 保存)

用什么数据结构可以节省内存 ?

  • Redis 有一种底层数据结构,叫压缩列表(ziplist),这是一种非常节省内存的结构。
  • 表头有三个字段 zlbytes(列表长度)、zltail(列表尾的偏移量) 和 zllen(列表中的entry个数)。压缩列表尾还有一个 zlend,表示列表结束。
  • 压缩列表之所以能节省内存,就在于它是用一系列连续的 entry 保存数据。

每个 entry 的元数据包括下面几部分:

  • prev_len:表示前一个 entry 的长度。

    • prev_len 有两种取值情况:1 字节或 5 字节。
    • 取值 1 字节时,表示上一个 entry 的长度小于 254 字节。
    • 虽然 1 字节的值能表示的数值范围是 0 到 255,但是压缩列表中 zlend 的取值默认是 255,因此,就默认用 255 表示整个压缩列表的结束其他表示长度的地方就不能再用 255 这个值了
    • 所以,当上一个 entry 长度小于 254 字节时,prev_len 取值为 1 字节,否则,就取值为 5 字节。
  • len:表示自身长度,4 字节。
  • encoding:表示编码方式,1 字节。
  • content:保存实际数据。

这些 entry 会挨个放置在内存中,不需要再用额外的指针进行连接,这样就可以节省指针所占用的空间。

如何用集合类型保存单值的键值对 ?

  • 在保存单值的键值对时,可以采用基于 Hash 类型的二级编码方法。
  • 以图片 ID 1101000060 和图片存储对象 ID 3302000080 为例,
  • 我们可以把图片 ID 的前 7 位(1101000)作为 Hash 类型的键,
  • 把图片 ID 的最后 3 位(060)和 图片存储对象 ID 分别作为 Hash 类型值中的 key 和 value。

Redis Hash 类型的两种底层实现结构,分别是压缩列表和哈希表。那么,Hash 类型底层结构什么时候使用压缩列表,什么时候使用哈希表呢 ?

  • Hash 类型设置了用压缩列表保存数据时的两个阈值,一旦超过了阈值,Hash 类型就会用哈希表来保存数据了。
  • 这两个阈值分别对应以下两个配置项:
    • hash-max-ziplist-entries:表示用压缩列表保存时哈希集合中的最大元素个数。
    • hash-max-ziplist-value:表示用压缩列表保存时哈希集合中单个元素的最大长度。
  • 如果我们往 Hash 集合中写入的元素个数超过了 hash-max-ziplist-entries,或者写入的单个元素大小超过了 hash-max-ziplist-value,Redis 就会自动把 Hash 类型的实现结构由压缩列表转为哈希表。
  • 一旦从压缩列表转为了哈希表,Hash 类型就会一直用哈希表进行保存,而不会再转回压缩列表了。
  • 为了能充分使用压缩列表的精简内存布局,我们一般要控制保存在 Hash 集合中的元素个数。

Redis 开销测试工具(推荐)

Redis容量预估-极数云舟

压缩列表有什么缺点吗 ?

  • Hash和Sorted Set 底层都是使用压缩列表和哈希表进行存储的。
  • 当采用ziplist方式存储时,虽然可以节省内存空间,但是在查询指定元素时,都要遍历整个ziplist,找到指定的元素。
  • 所以使用ziplist方式存储时,虽然可以利用CPU高速缓存,但也不适合存储过多的数据(hash-max-ziplist-entries和zset-max-ziplist-entries不宜设置过大),否则查询性能就会下降比较厉害。
  • 整体来说,这样的方案就是时间换空间,我们需要权衡使用。
  • 当使用ziplist存储时,我们尽量存储int数据,ziplist在设计时每个entry都进行了优化,针对要存储的数据,会尽量选择占用内存小的方式存储(整数比字符串在存储时占用内存更小),这也有利于我们节省Redis的内存。
  • 还有,因为ziplist是每个元素紧凑排列,而且每个元素存储了上一个元素的长度,所以当修改其中一个元素超过一定大小时,会引发多个元素的级联调整(前面一个元素发生大的变动,后面的元素都要重新排列位置,重新分配内存),这也会引发性能问题,需要注意。
  • 另外,使用Hash和Sorted Set存储时,虽然节省了内存空间,但是设置过期变得困难(无法控制每个元素的过期,只能整个key设置过期,或者业务层单独维护每个元素过期删除的逻辑,但比较复杂)。

redis 中什么数据类型适合当做数据库使用,什么数据类型适合当做缓存使用 ?

  • 在选用Hash和Sorted Set存储时,意味着把Redis当做数据库使用,这样就需要务必保证Redis的可靠性(做好备份、主从副本),防止实例宕机引发数据丢失的风险。
  • 而采用String存储时,可以把Redis当做缓存使用,每个key设置过期时间,同时设置maxmemory和淘汰策略,控制整个实例的内存上限,这种方案需要在数据库层(例如MySQL)也存储一份映射关系,当Redis中的缓存过期或被淘汰时,需要从数据库中重新查询重建缓存,同时需要保证数据库和缓存的一致性,这些逻辑也需要编写业务代码实现。

第十一章 “万金油”的String,为什么不好用了相关推荐

  1. “万金油”的String,为什么不好用了?

    从今天开始,我们就要进入"实践篇"了.接下来,我们会用5节课的时间学习"数据结构".我会介绍节省内存开销以及保存和统计海量数据的数据类型及其底层数据结构,还会围 ...

  2. Redis中“万金油“的string,为什么不好用了

    分享一个曾经使用Redis时遇到的需求以及解决方案 当时,要开发一个图片存储系统,要求这个系统能快速地记录图片ID,和图片在存储系统中保存时的ID(可以直接叫作图片存储对象ID).同时,还要能够根据图 ...

  3. 《Deep Learning With Python second edition》英文版读书笔记:第十一章DL for text: NLP、Transformer、Seq2Seq

    文章目录 第十一章:Deep learning for text 11.1 Natural language processing: The bird's eye view 11.2 Preparin ...

  4. 程序员编程艺术第三十 三十一章 字符串转换成整数,通配符字符串匹配

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 第三十~ ...

  5. 程序员编程艺术第三十~三十一章:字符串转换成整数,通配符字符串匹配

    第三十~三十一章:字符串转换成整数,带通配符的字符串匹配 前言 之前本一直想写写神经网络算法和EM算法,但写这两个算法实在需要大段大段的时间,而平时上班,周末则跑去北大教室自习看书(顺便以时间为序,说 ...

  6. 羊皮卷的实践-第二十一章

    第二十一章 高山滑雪是人与环境以及时间的竞赛.每当我看到输赢之间只差极短的时间时,我就不禁摇头同情那些输家. 第一名的时间是一分三十七秒二二. 第二名的时间是一分二十七秒二五. 也就是说,冠军与平庸之 ...

  7. 鸟哥的Linux私房菜(基础篇)- 第十一章、认识与学习 BASH

    第十一章.认识与学习 BASH 最近升级日期:2009/08/25 在 Linux 的环境下,如果你不懂 bash 是什么,那么其他的东西就不用学了!因为前面几章我们使用终端机下达命令的方式,就是透过 ...

  8. 第十一章 异常,日志,断言和调试

    2019独角兽企业重金招聘Python工程师标准>>> 第十一章 异常,日志,断言,调试 由于程序的错误或一些外部环境的影响造成用户数据的丢失,用户就有可能不再使用这个程序了.为了避 ...

  9. 从零开始的linux 第十一章

    从零开始的linux 第十一章 同学们早上好~~已经要步入秋天了~~同学们要记得看天气的变化,适当的增加衣服哦~~没错~~适当的增加(害羞~) 这次小编的博客不出意外的话是周六更新一次~~这周小编推迟 ...

最新文章

  1. 利用OpenCV+ConvNets检测几何图形
  2. 矢量合成和分解的法则_高考复习整理力的合成和分解
  3. 进阶学习(3.14) Strategy Pattern 策略模式
  4. CentOS-6.4无线上网命令行配置
  5. 关于IFRAME的一些小应用
  6. 前端请求接口出现的跨域问题
  7. boost::io模块ios相关的测试程序
  8. linux 如何创建内核进程,Linux内核的进程创建和执行.pdf
  9. 双指针解决力扣两/三数之和问题
  10. python-迭代器
  11. (转)十分钟搞定你自己的多图片/文件服务器
  12. cpc卡内计费信息异常包括_抖音信息流广告投放收费标准是什么?抖音发一个广告多少钱?...
  13. 【计算机网络笔记】计算机网络五层体系结构
  14. Windows下安装hadoop2.7.1
  15. RCP程序中集成其他插件的配置方法
  16. html5 3D地球转动动画js特效
  17. 在直流电源(Vcc)和地之间并接电容的作用
  18. RS232_RS422_RS485简介
  19. 没有基础学习大数据难吗?
  20. CENTOS上的时间/时区设定

热门文章

  1. 工作中使用到的单词(软件开发)_2021-12-26_备份
  2. Android客户端学习-jdk安装
  3. 谷歌浏览器chrom兼容问题
  4. 008-阈下意识|识别商场上的圈套
  5. kubectl k8s 复制文件从宿主机到pod内或从pod到宿主机内
  6. 探索推荐引擎内部的秘密系列
  7. Linux内核如何替换内核函数并调用原始函数
  8. postgresql 杀不死的会话
  9. Centos7 配置阿里云的 base 和 epel 源
  10. Ubuntu18.04安装wps office