查看: 151|回复: 0

深入学习Redis(1):Redis内存模型

[复制链接]

该用户从未签到

发表于 2019-11-4 10:38:51 | 显示全部楼层 |阅读模式
前言

Redis是现在最火爆的内存数据库之一,通过在内存中读写数据,大大提高了读写速度,可以说Redis是实现网站高并发不可或缺的一部分。
我们使用Redis时,会接触Redis的5种对象类型(字符串、哈希、列表、集合、有序集合),丰富的类型是Redis相对于Memcached等的一大上风。在了解Redis的5种对象类型的用法和特点的基础上,进一步了解Redis的内存模型,对Redis的使用有很大资助,比方:
1、估算Redis内存使用量。现在为止,内存的使用成本仍旧相对较高,使用内存不能无所顾忌;根据需求公道的评估Redis的内存使用量,选择合适的机器配置,可以在满意需求的情况下节省成本。
2、优化内存占用。了解Redis内存模型可以选择更合适的数据类型和编码,更好的利用Redis内存。
3、分析办理问题。当Redis出现阻塞、内存占用等问题时,尽快发现导致问题的缘故原由,便于分析办理问题。
这篇文章主要介绍Redis的内存模型(以3.0为例),包括Redis占用内存的情况及如何查询、不同的对象类型在内存中的编码方式、内存分配器(jemalloc)、简单动态字符串(SDS)、RedisObject等;然后在此基础上介绍几个Redis内存模型的应用。
在背面的文章中,会连续介绍关于Redis高可用的内容,包括主从复制、哨兵、集群等等,欢迎关注。
系列文章

深入学习Redis(1):Redis内存模型
深入学习Redis(2):长期化
深入学习Redis(3):主从复制
深入学习Redis(4):哨兵
深入学习Redis(5):集群
目录

一、Redis内存统计
二、Redis内存划分
  1、数据(或者称为对象)
  2、历程本身运行需要的内存
  3、缓冲内存
  4、内存碎片
三、Redis数据存储的细节
  1、概述
  2、jemalloc
  3、redisObject
  4、SDS
四、Redis的对象类型与内部编码
  1、字符串
  2、列表
  3、哈希
  4、集合
  5、有序集合
五、应用举例
  1、估算Redis内存使用量
  2、优化内存占用
  3、关注内存碎片率
六、参考文献
一、Redis内存统计

工欲善其事必先利其器,在说明Redis内存之前首先说明如何统计Redis使用内存的情况。
客户端通过redis-cli连接服务器后(背面如无特殊说明,客户端同等使用redis-cli),通过info命令可以查看内存使用情况:
  1. info memory
复制代码

此中,info命令可以表现redis服务器的许多信息,包括服务器基本信息、CPU、内存、长期化、客户端连接信息等等;memory是参数,表示只表现内存相关的信息。
返回结果中比力重要的几个说明如下:
(1)used_memoryRedis分配器分配的内存总量(单位是字节),包括使用的虚拟内存(即swap);Redis分配器背面会介绍。used_memory_human只是表现更友好。
(2)used_memory_rssRedis历程占据操作系统的内存(单位是字节),与top及ps命令看到的值是一致的;除了分配器分配的内存之外,used_memory_rss还包括历程运行本身需要的内存、内存碎片等,但是不包括虚拟内存。
因此,used_memory和used_memory_rss,前者是从Redis角度得到的量,后者是从操作系统角度得到的量。二者之所以有所不同,一方面是因为内存碎片和Redis历程运行需要占用内存,使得前者可能比后者小,另一方面虚拟内存的存在,使得前者可能比后者大。
由于在现实应用中,Redis的数据量会比力大,此时历程运行占用的内存与Redis数据量和内存碎片相比,都会小得多;因此used_memory_rss和used_memory的比例,便成了衡量Redis内存碎片率的参数;这个参数就是mem_fragmentation_ratio。
(3)mem_fragmentation_ratio内存碎片比率,该值是used_memory_rss / used_memory的比值。
mem_fragmentation_ratio一般大于1,且该值越大,内存碎片比例越大。mem_fragmentation_ratio1),称为共享对象。Redis为了节省内存,当有一些对象重复出现时,新的程序不会创建新的对象,而是仍旧使用原来的对象。这个被重复使用的对象,就是共享对象。现在共享对象仅支持整数值的字符串对象。
共享对象的具体实现
Redis的共享对象现在只支持整数值的字符串对象。之所以如此,现实上是对内存和CPU(时间)的平衡:共享对象虽然会低落内存消耗,但是判断两个对象是否相称却需要消耗额外的时间。对于整数值,判断操作复杂度为O(1);对于普通字符串,判断复杂度为O(n);而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。
虽然共享对象只能是整数值的字符串对象,但是5种类型都可能使用共享对象(如哈希、列表等的元素可以使用)。
就现在的实现来说,Redis服务器在初始化时,会创建10000个字符串对象,值分别是0~9999的整数值;当Redis需要使用值为0~9999的字符串对象时,可以直接使用这些共享对象。10000这个数字可以通过调整参数REDIS_SHARED_INTEGERS(4.0中是OBJ_SHARED_INTEGERS)的值进行改变。
共享对象的引用次数可以通过object refcount命令查看,如下图所示。命令执行的结果页佐证了只有0~9999之间的整数会作为共享对象。

(5)ptr

ptr指针指向具体的数据,如前面的例子中,set hello world,ptr指向包含字符串world的SDS。
(6)总结

综上所述,redisObject的布局与对象类型、编码、内存回收、共享对象都有关系;一个redisObject对象的大小为16字节:
4bit+4bit+24bit+4Byte+8Byte=16Byte。
4、SDS

Redis没有直接使用C字符串(即以空字符’\0’结尾的字符数组)作为默认的字符串表示,而是使用了SDS。SDS是简单动态字符串(Simple Dynamic String)的缩写。
(1)SDS布局

sds的布局如下:
  1. struct sdshdr {     int len;    int free;    char buf[];};
复制代码
此中,buf表示字节数组,用来存储字符串;len表示buf已使用的长度,free表示buf未使用的长度。下面是两个例子。


图片泉源:《Redis设计与实现》

通过SDS的布局可以看出,buf数组的长度=free+len+1(此中1表示字符串结尾的空字符);所以,一个SDS布局占据的空间为:free所占长度+len所占长度+ buf数组的长度=4+4+free+len+1=free+len+9。
(2)SDS与C字符串的比力

SDS在C字符串的基础上加入了free和len字段,带来了很多好处:

  • 获取字符串长度:SDS是O(1),C字符串是O(n)
  • 缓冲区溢出:使用C字符串的API时,如果字符串长度增加(如strcat操作)而忘记重新分配内存,很轻易造成缓冲区的溢出;而SDS由于记录了长度,相应的API在可能造成缓冲区溢出时会自动重新分配内存,杜绝了缓冲区溢出。
  • 修改字符串时内存的重分配:对于C字符串,如果要修改字符串,必须要重新分配内存(先开释再申请),因为如果没有重新分配,字符串长度增大时会造成内存缓冲区溢出,字符串长度减小时会造成内存泄露。而对于SDS,由于可以记录len和free,因此解除了字符串长度和空间数组长度之间的关联,可以在此基础上进行优化:空间预分配策略(即分配内存时比现实需要的多)使得字符串长度增大时重新分配内存的概率大大减小;惰性空间开释策略使得字符串长度减小时重新分配内存的概率大大减小。
  • 存取二进制数据:SDS可以,C字符串不可以。因为C字符串以空字符作为字符串结束的标识,而对于一些二进制文件(如图片等),内容可能包括空字符串,因此C字符串无法正确存取;而SDS以字符串长度len来作为字符串结束标识,因此没有这个问题。
此外,由于SDS中的buf仍旧使用了C字符串(即以’\0’结尾),因此SDS可以使用C字符串库中的部分函数;但是需要留意的是,只有当SDS用来存储文本数据时才可以这样使用,在存储二进制数据时则不行(’\0’不一定是结尾)。
(3)SDS与C字符串的应用

Redis在存储对象时,同等使用SDS取代C字符串。比方set hello world命令,hello和world都是以SDS的形式存储的。而sadd myset member1 member2 member3命令,不论是键(”myset”),还是集合中的元素(”member1”、 ”member2”和”member3”),都是以SDS的形式存储。除了存储对象,SDS还用于存储各种缓冲区。
只有在字符串不会改变的情况下,如打印日记时,才会使用C字符串。
四、Redis的对象类型与内部编码

前面已经说过,Redis支持5种对象类型,而每种布局都有至少两种编码;这样做的好处在于:一方面接口与实现分离,当需要增加或改变内部编码时,用户使用不受影响,另一方面可以根据不同的应用场景切换内部编码,提高效率。
Redis各种对象类型支持的内部编码如下图所示(图中版本是Redis3.0,Redis背面版本中又增加了内部编码,略过不提;本章所介绍的内部编码都是基于3.0的):

图片泉源:《Redis设计与实现》

关于Redis内部编码的转换,都符合以下规律:编码转换在Redis写入数据时完成,且转换过程不可逆,只能从小内存编码向大内存编码转换。
1、字符串

(1)概况

字符串是最基础的类型,因为所有的键都是字符串类型,且字符串之外的其他几种复杂类型的元素也是字符串。
字符串长度不能超过512MB。
(2)内部编码

字符串类型的内部编码有3种,它们的应用场景如下:
<ul>int:8个字节的长整型。字符串值是整型时,这个值使用long整型表示。
embstr:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?用户注册

x

相关技术服务需求,请联系管理员和客服QQ:2753533861或QQ:619920289
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

帖子推荐:
客服咨询

QQ:2753533861

服务时间 9:00-22:00

快速回复 返回顶部 返回列表