Redis所有的数据都存在内存中,相对于廉价的硬盘,内存资源还是比较昂贵的,因此如何高效利用redis内存变得非常重要。

  • 内存消耗分析
  • 管理内存的原理和方法
  • 内存优化技巧

一、内存消耗

理解redis内存,首先要掌握redis内存消耗在哪些方面。有些内存消耗是必不可少的,而有些可以通过参数调整和合理使用来规避内存浪费。

1.1 内存使用统计

首先需要了解redis自身使用内存的统计数据,可通过执行info memory命令来获取内存相关指标

  • used_memory                              redis分配器分配的内存总量,也就是内部存储的所有数据内存占用量
  • used_memory_human                 以可读的格式返回used_memory
  • used_memory_rss                       从操作系统的角度显示redis进程占用的物理内存总量
  • used_memory_peak                    内存使用的最大值,表示used_memory的峰值
  • used_memory_peak_human       以可读的格式返回used_memory_peak
  • used_memory_lua                       Lua引擎所消耗的内存大小
  • mem_fragmentation_ratio            used_memory_rss/used_memory比值。表示内存碎片率
  • mem_allocator                             redis 所使用的内存分配器,默认为jemalloc

需要重点关注的指标有:used_memory_rss和used_memory 以及它们的比值mem_fragmentation_ratio。

当mem_fragmentation_ratio>1时,说明used_memory_rss - used_memory 多出的部分内存并没有用于数据存储,而是被内存所消耗,如果两者相差很大,说明碎片率严重。

当mem_fragmentation_ratio<1时,这种情况一般出现在操作系统把redis内存交换(swap)到磁盘导致,出现这种情况要格外关注,由于硬盘速度远远慢于内存,redis性能会变得很差,甚至僵死。

1.2 内存消耗划分

redis进程内消耗主要包括:

  • 自身内存
  • 对象内存
  • 缓冲内存
  • 内存碎片

redis自身内存消耗非常少,通常空进程中used_memory_rss在3MB左右,used_memory在800KB左右,一个空的redis进程消耗的内存可以忽略不计。

1.2.1 对象内存

对象内存是redis内存占用最大的一块,存储着用户所有的数据,对象内存可以简单理解为sizeof(key)+ sizeof(values)。键对象都是字符串,在使用redis时很容易忽略键对内存消耗的影响,应当避免使用过长的键。value对象更复杂些,主要包含五种基本数据类型:字符串、列表、哈希、集合、有序集合。其它数据类型都是建立在这5种数据结构之上实现的,如:Bitmaps和HyperLogLog是使用字符串实现的,GEO使用有序集合实现等。每种value对象类型根据使用规模不同,占用内存不同。在使用时一定要合理预估并监控value对象占用情况,避免内存溢出。

1.2.2 缓冲内存

缓冲内存主要包括:客户端缓冲、复制积压缓冲区、AOF缓冲区。

客户端缓冲指的是所有接入到redis服务器tcp连接的输入输出缓冲。输入缓冲无法控制,最大空间为1G,如果超过将断开连接。对于 Redis 的输出(也就是命令的返回值)来说,其大小经常是不可控的,可能是一个简单的命令,能够产生体积庞大的返回数据。另外也有可能因为执行命令太多,产生的返 回数据的速率超过了往客户端发送的速率,这时也会产生消息堆积,从而造成输出缓冲区越来越大,占用过多内存,甚至导致系统崩溃。所以 Redis 设置了一些保护机制来避免这种情况的出现,这些机制作用于不同种类的客户端,有不同的输出缓冲区大小限制,限制方式有两种:

  • 一种是大小限制,当某一个客户端的缓冲区超过某一大小时,直接关闭掉这个客户端连接
  • 另一种是当某一个客户端的缓冲区持续一段时间占用空间过大时,也直接关闭掉客户端连接

输出缓冲区通过client-output-buffer-limit控制:

  • 对普通客户端来说,默认配置是client-output-buffer-limit normal 0 0 0,也就是不限制,因为普通客户端通常采用阻塞式的消息应答模式,如:发送请求,等待返回,再发请求,再等待返回。这种模式通常不会导致输出缓冲区的堆积膨胀。但是当有大量慢连接客户端接入时,这部分内存消耗就不能忽略不计了,可以设置maxclients做限制。特别是当使用大量数据输出的命令且数据无法及时推送给客户端时,如monitor命令,容易造成redis服务器内存突然飙升。
  • 对于 Pub/Sub 客户端来说,大小限制是32m,当输出缓冲区超过32m时,会关闭连接。持续性限制是,当客户端缓冲区大小持续60秒超过8m,也会导致连接关闭。当订阅服务的消息生产快于消费速度时,输出缓冲区会产生积压造成输出缓冲区空间溢出。
  • 而对于 Slave 客户端来说,大小限制是256m,持续性限制是当客户端缓冲区大小持续60秒超过64m时,关闭连接。当从节点之间网络延迟较高或主节点挂在大量从节点时这部分内存消耗占用很大一部分,建议主节点挂在的从节点不要多于2个,主节点不要部署在网络较差的环境下,如异地机房环境,防止复制客户端连接缓慢造成溢出。

输入输出缓冲区在大流量的场景中容易失控,造成redis内存不稳定,需要重点监控。

复制积压缓冲区:redis2.8版本之后提供了一个可重用的固定大小缓冲区用于实现部分复制功能,根据repl-backlog-size参数控制,默认是1M。对于复制积压缓冲区整个主节点只有一个,所有的从节点共享此缓冲区,因此可以设置较大的缓冲区空间,如100M,这部分内存投入是有价值的,可以有效避免全量复制

AOF缓冲区:这部分空间用于在redis重写期间保存最近写入命令,此缓冲区空间消耗用户无法控制,消耗的内存取决于AOF重写时间和写入命令量,这部分空间占用通常很小。

1.2.3 内存碎片

redis默认的内存分配器采用jemalloc,可选的分配器还有:glibc、tcmalloc。内存分配器为了更好的管理和重复利用内存,分配内存策略一般采用固定范围的内存块进行分配。例如jemalloc在64位系统中将内存空间分为:小、大、巨大三个范围。每个范围内又划分为多个小的内存块单位。比如当保存5KB对象时,jemalloc可能会采用8KB的块存储,而剩下的3KB空间变为了内存碎片不能再分配给其他对象存储。内存碎片虽然是所有内存服务的通病,但是jemalloc针对碎片化问题专门做了优化,一般不会存在过度碎片化的问题,正常的碎片率(mem_fragmentation_tatio)在1.03左右。但是当存储的数据长短差异较大时,以下场景容易出现高内存碎片问题:

  • 频繁做更新操作,例如频繁对已存在的键执行append、setrange等更新操作。
  • 大量过期键删除,键对象过期删除后,释放的空间无法得到充分利用,导致碎片率上升

出现高内存碎片问题时,常见的解决方式如下:

  • 数据对齐:在条件允许的情况下尽量做数据对齐,比如数据尽量采用数字类型或者固定长度字符串等,但是这要视具体的业务而定,有些场景无法做到。
  • 安全重启:重启节点可以做到内存碎片重新整理,因此可以利用高可用架构,如sentinel或cluster,将碎片率过高的主节点转换为从节点,进行安全重启。

1.3 子进程内存消耗

子进程内存消耗主要指执行AOF/RDB重写时redis创建的子进程内存消耗。redis执行fork操作产生的子进程内存占用量对外表现与父进程相同,理论上需要一倍的物理内存来完成重写操作。但linux具有写时复制技术(copy-on-write),父子进程会共享相同的物理内存页,当父进程处理写请求时会对需要修改的页复制出一份副本来完成写操作,而子进程依然读取fork时整个父进程的内存快照。

linux kernel在2.6.38内核增加了Transparent Huge Page(THP)机制,而有些linux发行版即使内核达不到2.6.38也会默认加入并开启这个功能,如Redhat Enterprise Linux在6.0以上版本默认会引入THP。虽然开启THP可以降低fork子进程的速度,但之后cory-on-write期间复制内存页的单位从4KB变为2MB,如果父进程有大量写命令,会加重内存拷贝量,从而造成过度内存消耗。例如,以下两个执行AOF重写时的内存消耗日志:

#开启THP:

C * AOF rewrite: 1039 MB of memory used by copy-on-write

#关闭THP:

C * AOF rewrite: 9 MB of memory used by copy-on-write

这两个日志出自同一redis进程,used_memory总量为1.5GB,子进程执行期间每秒写命令量都在200左右。当分别开启和关闭THP时,子进程的内存消耗有天壤之别。如果在高并发写的场景下开启THP,子进程内存消耗可能是父进程的数倍,极易造成机器物理内存溢出,从而触发SWAP或者OOM,子进程消耗总结如下:

  • redis产生的子进程并不需要消耗一倍的父进程内存,实际消耗根据期间写入命令量决定,但是依然要预留出一些内存防止溢出。
  • 需要设置sysctl vm.overcommit_memory=1 允许内核可以分配所有的物理内存,防止redis进程执行fork时因系统剩余内存不足而失败。
  • 排查当前系统是否支持THP,如果开启建议关闭,防止copy-on-write期间内存过度消耗。

redis内存消耗详解的更多相关文章

  1. 深度历险:Redis 内存模型详解

    https://mp.weixin.qq.com/s/Gp6Ur7omGY6ZqDWygU2meQ Redis 是目前最火爆的内存数据库之一,通过在内存中读写数据,大大提高了读写速度,可以说 Redi ...

  2. Redis 配置文件 redis.conf 项目详解

    Redis.conf 配置文件详解 # [Redis](http://yijiebuyi.com/category/redis.html) 配置文件 # 当配置中需要配置内存大小时,可以使用 1k, ...

  3. Redis配置参数详解

    Redis配置参数详解 /********************************* GENERAL *********************************/ // 是否作为守护进 ...

  4. Redis 复制过程详解

    Redis 的复制功能分为同步( sync )和命令传播( command propagate )两个步骤: 同步用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态. 命令传播则用于在主服务 ...

  5. [转]Reids配置文件redis.conf中文详解

    转自: Reids配置文件redis.conf中文详解 redis的各种配置都是在redis.conf文件中进行配置的. 有关其每项配置的中文详细解释如下: 对应的中文版解释redis.conf # ...

  6. 探索Redis设计与实现2:Redis内部数据结构详解——dict

    本文转自互联网 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial ...

  7. Redis底层函数详解

    Redis底层函数详解 serverCron 函数 它负责管理服务器的资源,并维持服务器的正常运行.在执行 serverCron 函数的过程中会调用相关的子函数,如 trackOperationsPe ...

  8. Tomcat内存设置详解

    Java内存溢出详解 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError: Java heap space ----JVM Heap(堆)溢出 JVM在 ...

  9. Tomcat内存溢出详解【转载】

    本文转载自 http://elf8848.iteye.com/blog/378805 Java内存溢出详解 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryEr ...

随机推荐

  1. 二叉树遍历 C#

    二叉树遍历 C# 什么是二叉树 二叉树是每个节点最多有两个子树的树结构 (1)完全二叉树——若设二叉树的高度为h,除第 h 层外,其它各层 (1-h-1) 的结点数都达到最大个数,第h层有叶子结点,并 ...

  2. R语言快速深度学习进行回归预测(转)

    深度学习在过去几年,由于卷积神经网络的特征提取能力让这个算法又火了一下,其实在很多年以前早就有所出现,但是由于深度学习的计算复杂度问题,一直没有被广泛应用. 一般的,卷积层的计算形式为: 其中.x分别 ...

  3. 在vue-cli搭建的项目中增加后台mock接口

    用vue-cli搭建一个前端开发环境确实是极其方便,在写前端代码肯定也是少不了需要调用后台提供的业务接口进行前后端交互,特别在敏捷开发中,前后端都要提前确定业务接口并进行打桩,在开发过程中基本是没有现 ...

  4. kafka 0.8.2 消息生产者 producer

    package com.hashleaf.kafka; import java.util.Properties; import kafka.javaapi.producer.Producer; imp ...

  5. NopCommerce(3.9)作业调度插件

    NopCommerce(3.9)作业调度插件视频教程录制完成,下面是插件源码下载地址和插件视频教程下载地址:插件下载地址: http://www.nopcommerce.com/p/2752/jobs ...

  6. superagent和request结果转换区别

    superagent和request结果转换区别 使用superagent和request抓取页面内容时,两个抓取内容都可以被cheerio进行处理.但处理时有个细微差别. 1. 使用superage ...

  7. sqlserver删除重复的数据

    分享链接: http://blog.csdn.net/s630730701/article/details/52033018 http://blog.csdn.net/anya/article/det ...

  8. jquery中html、text、val回调函数

    先扫盲: 摘自菜鸟教程:jQuery 方法:text().html() 以及 val()拥有回调函数. 回调函数有两个参数:被选元素列表中当前元素的下标,以及原始(旧的)值.然后以函数新值返回您希望使 ...

  9. 机器学习:Python实现聚类算法(三)之总结

    考虑到学习知识的顺序及效率问题,所以后续的几种聚类方法不再详细讲解原理,也不再写python实现的源代码,只介绍下算法的基本思路,使大家对每种算法有个直观的印象,从而可以更好的理解函数中参数的意义及作 ...

  10. Elasticsearch VS Solr

    最近公司用到了ES搜索引擎,调研发现大公司常用的搜索引擎还有Solr. 鉴于 Lucene 强大的特性和稳定性,有很多种基于 Lucene 封装的企业级搜索平台.其中最流行有两个:Apache Sol ...