设为首页 收藏本站
查看: 762|回复: 0

[经验分享] 深入了解一下Redis的内存模型!

[复制链接]

尚未签到

发表于 2018-11-2 11:22:13 | 显示全部楼层 |阅读模式
  跳跃表是一种有序数据结构,通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。除了跳跃表,实现有序数据结构的另一种典型实现是平衡树;大多数情况下,跳跃表的效率可以和平衡树媲美,且跳跃表实现比平衡树简单很多,因此redis中选用跳跃表代替平衡树。跳跃表支持平均O(logN)、最坏O(N)的复杂点进行节点查找,并支持顺序操作。Redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成:前者用于保存跳跃表信息(如头结点、尾节点、长度等),后者用于表示跳跃表节点。具体结构相对比较复杂,略。
  (3)编码转换
  只有同时满足下面两个条件时,才会使用压缩列表:有序集合中元素数量小于128个;有序集合中所有成员长度都不足64字节。如果有一个条件不满足,则使用跳跃表;且编码只可能由压缩列表转化为跳跃表,反方向则不可能。
  下图展示了有序集合编码转换的特点:
  五、应用举例
  了解Redis的内存模型之后,下面通过几个例子说明其应用。
  1、估算Redis内存使用量
  要估算redis中的数据占据的内存大小,需要对redis的内存模型有比较全面的了解,包括前面介绍的hashtable、sds、redisobject、各种对象类型的编码方式等。
  下面以最简单的字符串类型来进行说明。
  假设有90000个键值对,每个key的长度是7个字节,每个value的长度也是7个字节(且key和value都不是整数);下面来估算这90000个键值对所占用的空间。在估算占据空间之前,首先可以判定字符串类型使用的编码方式:embstr。
  90000个键值对占据的内存空间主要可以分为两部分:一部分是90000个dictEntry占据的空间;一部分是键值对所需要的bucket空间。
  每个dictEntry占据的空间包括:
  1)       一个dictEntry,24字节,jemalloc会分配32字节的内存块
  2)       一个key,7字节,所以SDS(key)需要7+9=16个字节,jemalloc会分配16字节的内存块
  3)       一个redisObject,16字节,jemalloc会分配16字节的内存块
  4)       一个value,7字节,所以SDS(value)需要7+9=16个字节,jemalloc会分配16字节的内存块
  5)       综上,一个dictEntry需要32+16+16+16=80个字节。
  bucket空间:bucket数组的大小为大于90000的最小的2^n,是131072;每个bucket元素为8字节(因为64位系统中指针大小为8字节)。
  因此,可以估算出这90000个键值对占据的内存大小为:9000080 + 1310728 = 8248576。
  下面写个程序在redis中验证一下:
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24

  public>  public static Jedis jedis = new Jedis("localhost", 6379);
  public static void main(String[] args) throws Exception{
  Long m1 = Long.valueOf(getMemory());
  insertData();
  Long m2 = Long.valueOf(getMemory());
  System.out.println(m2 - m1);
  }
  public static void insertData(){
  for(int i = 10000; i < 100000; i++){
  jedis.set(&quot;aa&quot; + i, &quot;aa&quot; + i); //key和value长度都是7字节,且不是整数
  }
  }
  public static String getMemory(){
  String memoryAllLine = jedis.info(&quot;memory&quot;);
  String usedMemoryLine = memoryAllLine.split(&quot;\r\n&quot;)[1];
  String memory = usedMemoryLine.substring(usedMemoryLine.indexOf(':') + 1);
  return memory;
  }
  }
  运行结果:8247552
  理论值与结果值误差在万分之1.2,对于计算需要多少内存来说,这个精度已经足够了。之所以会存在误差,是因为在我们插入90000条数据之前redis已分配了一定的bucket空间,而这些bucket空间尚未使用。
  作为对比将key和value的长度由7字节增加到8字节,则对应的SDS变为17个字节,jemalloc会分配32个字节,因此每个dictEntry占用的字节数也由80字节变为112字节。此时估算这90000个键值对占据内存大小为:90000112 + 1310728 = 11128576。
  在redis中验证代码如下(只修改插入数据的代码):
  1
  2
  3
  4
  5
  public static void insertData(){
  for(int i = 10000; i < 100000; i++){
  jedis.set(&quot;aaa&quot; + i, &quot;aaa&quot; + i); //key和value长度都是8字节,且不是整数
  }
  }
  运行结果:11128576;估算准确。
  对于字符串类型之外的其他类型,对内存占用的估算方法是类似的,需要结合具体类型的编码方式来确定。
  2、优化内存占用
  了解redis的内存模型,对优化redis内存占用有很大帮助。下面介绍几种优化场景。
  (1)利用jemalloc特性进行优化
  上一小节所讲述的90000个键值便是一个例子。由于jemalloc分配内存时数值是不连续的,因此key/value字符串变化一个字节,可能会引起占用内存很大的变动;在设计时可以利用这一点。
  例如,如果key的长度如果是8个字节,则SDS为17字节,jemalloc分配32字节;此时将key长度缩减为7个字节,则SDS为16字节,jemalloc分配16字节;则每个key所占用的空间都可以缩小一半。
  (2)使用整型/长整型
  如果是整型/长整型,Redis会使用int类型(8字节)存储来代替字符串,可以节省更多空间。因此在可以使用长整型/整型代替字符串的场景下,尽量使用长整型/整型。
  (3)共享对象
  利用共享对象,可以减少对象的创建(同时减少了redisObject的创建),节省内存空间。目前redis中的共享对象只包括10000个整数(0-9999);可以通过调整REDIS_SHARED_INTEGERS参数提高共享对象的个数;例如将REDIS_SHARED_INTEGERS调整到20000,则0-19999之间的对象都可以共享。
  考虑这样一种场景:论坛网站在redis中存储了每个帖子的浏览数,而这些浏览数绝大多数分布在0-20000之间,这时候通过适当增大REDIS_SHARED_INTEGERS参数,便可以利用共享对象节省内存空间。
  (4)避免过度设计
  然而需要注意的是,不论是哪种优化场景,都要考虑内存空间与设计复杂度的权衡;而设计复杂度会影响到代码的复杂度、可维护性。
  如果数据量较小,那么为了节省内存而使得代码的开发、维护变得更加困难并不划算;还是以前面讲到的90000个键值对为例,实际上节省的内存空间只有几MB。但是如果数据量有几千万甚至上亿,考虑内存的优化就比较必要了。
  3、关注内存碎片率
  内存碎片率是一个重要的参数,对redis 内存的优化有重要意义。
  如果内存碎片率过高(jemalloc在1.03左右比较正常),说明内存碎片多,内存浪费严重;这时便可以考虑重启redis服务,在内存中对数据进行重排,减少内存碎片。
  如果内存碎片率小于1,说明redis内存不足,部分数据使用了虚拟内存(即swap);由于虚拟内存的存取速度比物理内存差很多(2-3个数量级),此时redis的访问速度可能会变得很慢。因此必须设法增大物理内存(可以增加服务器节点数量,或提高单机内存),或减少redis中的数据。
  要减少redis中的数据,除了选用合适的数据类型、利用共享对象等,还有一点是要设置合理的数据回收策略(maxmemory-policy),当内存达到一定量后,根据不同的优先级对内存进行回收。
  欢迎大家加入Java架构开发:744677563
  本群提供免费的学习指导 架构资料 以及免费的解答
  不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导
  进群修改群备注:开发年限-地区-经验
  方便架构师解答问题


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-629771-1-1.html 上篇帖子: redis之配置 下篇帖子: redis之strings类型及操作
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

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