RedisRemoteDictionaryServer)是一种内存数据库,具有杰出的功能体现。据测算,单机 Redis 的 TPS 可达数万,作为对比,MySQL 这样的联系型数据库只能到达数百,相差数十倍。在对有高功能需求的事务场景进行技术选型时,Redis 是作为处理用户恳求数据的中心存储节点,而联系数据库是辅助节点,它和 Redis 异步坚持数据一致性,以满意运营和办理需求。

实现 Redis 高功能的底层原理是对计算机的三大资源(CPU、内存和外存)进行最优运用,我总结为:“榨优弃弱”。

  • 榨优,对 CPU 和内存实现极致运用。
  • 弃弱,放弃外存这样的低功率 I/O。

“弃弱”不代表 Redis 不存在 I/O,相反,Redis 是 I/O 密集型应用。Redis 作为缓存中间件,自身没有事务逻辑,而是作为事务服务器的才能供给者,Redis 和应用服务器间存在极为密集的网络 I/O。I/O 等待会导致 CPU 空转,Redis 挑选了 I/O 复用这样的高效 I/O 模型,用极低的本钱做到了 CPU 的极致运用(I/O 模型的具体内容可看我的上一篇文章)。

什么是Redis内存碎片?来理解下Redis的内存分配机制,jemalloc

对于咱们用户来说,I/O 机制已绑定到 Redis 的底层实现,操作指令根本不会影响 CPU 的工作功率。内存就不相同,操作指令会直接改动内存结构。假如运用不当,如构成内/外存连续进行数据交流(Swap),缓存命中率下降等问题,会严重影响 Redis 功能。而 Redis 作为功能密集体系的中心组件,Redis 的问题或许意味着整个体系的奔溃,构成严重后果。

内存碎片就是不行忽视的 Redis 内存问题之一。

一、Redis 内存碎片的体现

内存碎片详称“内存空间碎片化”。呈现内存碎片时,内存空间被分散为巨细不一的碎片,假如把这些碎片当作逻辑上的全体是能满意新的内存分配恳求,但单一的物理上的内存碎片却没有满足的空间来满意需求,然后导致内存分配失利。

什么是Redis内存碎片?来理解下Redis的内存分配机制,jemalloc

当呈现了内存碎片问题,实际可用的内存空间就会小于物理上的闲暇空间。而Redis 是“内存密集型”程序,需求很多进行内存操作。当内存不足时,Redis 需求和外存进行数据交流(Swap),或者根据筛选策略整理老数据。前者会导致频频进行磁盘 I/O,严重降低功能。后者或许会整理掉有用数据,然后导致命中率下降。

为了发现呈现内存问题,Redis 供给了快捷的内存状态监控参数,在 Redis CLI 输入指令:

info memory

或许的输出为:

属性 描述
used_memory 184791688 已运用内存,单位:字节
used_memory_human 176.23M 人类可读的已运用内存,会主动生成单位,这里是 MB
used_memory_rss 197242880 已运用的物理内存 RSS(Resident Set Size),单位:字节
used_memory_peak 200065320 已运用的内存最大值,单位:字节
used_memory_peak_human 190.80M 人类可读的已运用内存最大值,会主动生成单位,这里是 MB
used_memory_lua 37888 Lua 脚本运用的空间,单位:字节
mem_fragmentation_ratio 1.07 内存碎片比率,mem_fragmentation_ratio = used_memory_rss / used_memory
mem_allocator jemalloc-5.2.1 内存分配器的描述信息

这些参数中和内存碎片的联系最大的是mem_fragmentation_ratio

  • 当 ratio < 1 时,已运用的物理内存小于已运用的“内存”空间,阐明发生了内存交流(Swap), Redis 需求靠外存(如硬盘)来进行部分数据的存储。这通常是未设置 max_memory 阈值,或设置得过大。在这种状况下,Redis 工作功率较低,要防止呈现。
  • ratio 略大于 1,在 1.1 左右。这是抱负状况,Redis 运用的物理内存略大是因为 Redis 自身的工作也需求空间。
  • ratio 接近 2 或为更大的整数,这时给 Redis 分配的物理空间远大于实际运用的空间,存在内存碎片问题,或许导致频频触发内存筛选(即不断整理老数据)。

二、内存碎片的应对方法

内存是影响 Redis 功能的主要因素,它在实现上就考虑到了如何进行高效的内存办理。Redis 要在做到尽量低的内存分配本钱的一起,还要有尽量高的内存运用率。为此,尽管 Redis 是基于 C 言语开发,但未运用规范的 glibc malloc,而是运用碎片率更低的 jemalloc 来处理内存恳求(jemalloc 在上一末节的参数表也有展现)。

jemalloc (Jason Evans Malloc)是一种高效的用户态内存分配器,和操作体系的内存分配器构成互补联系,jemalloc 办理的大块内存首先要从操作体系恳求得来。jemalloc 应用广泛,BSD、Firefox、Facebook、Redis 均运用它来处理在多线程环境下,内存分配的功能和碎片问题。

或许一些朋友会疑问?

内存分配碎片从何而来,程序恳求多少就分配多少,一个接一个的分配下去,空间运用率只会是 100%。

抱负状况下确实是这样,但已分配的空间不会一直运用,当旧内存被收回后,就会在内存空间留下“空泛”,构成内存碎片。特别是当小“空泛”(小空泛指无法满意大部分内存分配恳求的的闲暇内存)累计得过多时,内存运用率极低,或许不足 50%。这些在已分配内存外的碎片称为 外部内存碎片

什么是Redis内存碎片?来理解下Redis的内存分配机制,jemalloc

外部碎片能够转化为内部碎片。假如提早把整个空间划分红固定巨细的、整整齐齐的几块,有内存恳求就分配其间一块。这样在已分配空间的外部就不存在碎片,而是留在了内部,这样内存运用率依然不高。但由于多余的空间提早分配给了恳求者,尽管它暂时用不上,但今后需求就能直接运用,而不用再恳求,提升了功率。

什么是Redis内存碎片?来理解下Redis的内存分配机制,jemalloc

内存碎片仍没处理,因为按固定巨细分红几块不能匹配大多数的内存恳求,或许还大大超越了恳求的巨细,构成空间糟蹋。咱们需求个性化,但又不能太随意,随意代表着更高的办理本钱。所以是在本钱可控下的恰当个性化,这就能够参阅服装行业的做法,把一个款式分为:XS、S、M、L 等多个尺寸,咱们尽量挑选能穿下的最小的尺寸,这也是 Buddy(伙伴)算法的根本思路。

什么是Redis内存碎片?来理解下Redis的内存分配机制,jemalloc

Buddy 算法把空间分为多个层次的内存块,相邻层次间的内存块巨细存在两倍联系。恳求者首先去向巨细最匹配的内存标准恳求,若无剩余空间,晋级到更大的一级恳求,直到获取成功。晋级后获取到的空间会超越需求量数倍,为了防止空间糟蹋,要对其进行切分,以得到略大于需求的规范内存标准。切分剩余的内存块会加入到当时内存标准的闲暇列表。

什么是Redis内存碎片?来理解下Redis的内存分配机制,jemalloc

Buddy 算法的恳求过程有变得零星的趋势,大内存块会不断被切分红小块。这要如何防止大内存块都消失,而无法给大对象分配内存?现在进入到了 Buddy 算法的中心:兼并。Buddy 算法把相邻的相同标准的闲暇内存块称为:Buddy(伙伴)。当某个内存块的空间被释放时,会检查是否存在相邻的 Buddy,如有,两者兼并晋级为更大的标准。若还有闲暇空间,就持续晋级,直到成为最大的标准。Buddy 的兼并和分化同为一体,两者组合才能确保整个流程循环不息的工作下去。

Buddy 算法分化的最小粒度是 Page 内存页,一般为 4KB 内存。小于 4KB 的对象需求不少,但分配的仍是 4KB Page,这样在 Page 中就会有闲暇,构成内部碎片。Slab 算法着重优化了小对象分配,它引进 slab_partial 来维护未彻底运用的内存块,新的分配需求能复用这些已分配的空间,把能用上的价值都尽量收集起来。

三、Redis 内置的内存碎片应对方法,jemalloc

内存办理进化到 Slab 算法,碎片问题根本已经处理,内存恳求尽量按需分配,不多余,没用完的内存块鄙人一次尽量复用。但随着处理器朝多核方向发展,内存分配呈现了新的功能问题。当多个处理器一起建议内存分配恳求时,需求用排他锁来坚持有序竞赛,锁争用带来的额外开销导致内存分配功能下降。

新一代内存分配器应运而生,jemalloc 就是其间一种。多核锁争用的原因是锁的范围过大,jemalloc 就缩小锁粒度,为每个线程引进私有的内存分配区域 Arena,当自有的 Arena 有满足空间时,线程间就不会交互。jemalloc 不仅对多核环境做了针对性优化,还归纳了之前的 Slab 等算法在内存碎片优化上的经历。实现了多核分配功能在跟随处理器核数增加外,还有优秀的内存碎片整合才能。

Redis 挑选了 jemalloc 作为自己的内存分配器,这意味着它天生带有高功能且低碎片的内存办理才能。那当呈现内存碎片,即 mem_fragmentation_ratio 远大于 1时,对咱们有什么实际意义?

四、Redis 内存碎片需求处理吗?

对内存碎片优化,是忧虑占用的物理内存不能用于处理新的内存分配恳求,导致 Redis 服务器无法响应客户端指令。但 Redis 内置了 jemalloc 这样的高效内存分配器,它能实现对已占用的闲暇内存做到极致复用。Redis 官方也建议,咱们能够采纳“鸵鸟算法”,啥都不用做。假如存在满足的可用物理内存并且设置了适宜的 maxmemory 参数,即便 mem_fragmentation_ratio 偏大,后续的内存分配也是安全的。

在外部碎片的发生过程中,咱们了解到,碎片发生来自频频进行内存的分配和收回。也就是说,假如咱们防止频频的对 Redis 键值对进行改变,就能在用户侧缩小碎片空间比例。但考虑到 Redis 的内存分配机制,这不必定意味着在功能和安全上就愈加优秀。

五、参阅资料

  1. Try Redis, redis.io
  2. How to Monitor Redis Performance Metrics, Datadog
  3. A Scalable Concurrent malloc(3) Implementation for FreeBSD, Jason Evans
  4. Memory optimization, redis.io