前言
上周刚来了个应届小师弟,组长说让我带着,周二问了我这样一个问题:师兄啊,我用 top
命令看了下服务器的内存占用状况,发现 Redis
内存占用重大,于是我就删除了大部分不必的keys
,为什么内存占用还是很重大,并没有开释呢?
嗯?为什么呢?明天就带着这个问题来介绍一下如何正确开释 Redis
的内存。
什么是内存碎片?
内存碎片这个概念应该不是第一据说了,相熟 JVM 或者操作系统的应该都相熟,以火车卖票为例,一个车厢 128
个车位,因为高峰期,只残余 两个
地位了,然而此时 三个
人想要坐在一起,可能吹吹牛批,喝喝酒的,那么这三个人必定不会买这节车厢的两个地位了,此时这两个地位能够称之为座位碎片。
操作系统中对于内存调配也是一样的,比方利用须要申请一块间断 N 个字节的空间,尽管残余内存总量大于 N
个字节,然而没有一块间断的内存空间是 N
个字节,那么残余的空间就是内存碎片。如下图:
上图中的闲暇 3 个字节和闲暇 2 个字节都是内存碎片。
那么什么起因会造成内存碎片呢?这个其实大抵分为两个起因,一个是操作系统的内存调配策略,一个是 Redis 本身起因,上面就这两个起因详细分析。
内存分配器的调配策略
内存分配器的调配策略个别是依照固定大小来分配内存,而不是依照应用程序申请的内存空间按需分配。比方 8 字节、16 字节、32 字节 ……
Redis
提供了多种的内存调配策略,比方libc
、jemalloc
、tcmalloc
,默认应用jemalloc
。
jemalloc
这种调配策略,是依照固定的空间调配,比方 8 字节、32 字节 ….2KB、4KB 等。当应用程序申请的内存靠近某个固定值的时候,jemalloc
则会调配固定的大小。比方申请了 6 字节,则会调配 8 字节的空间。
这种调配的形式的益处很显著,则会缩小内存调配的次数,比方申请了 20 字节
的内存,理论调配的是 32 字节
的内存空间,当利用再写入 10 字节
的数据时,则不会再次调配,残余的 12 字节
足够用了。这样就防止了一次的内存调配。如下图:
然而害处也很显著,申请的和调配的空间不一样,则残余的空间很可能造成内存碎片,一旦内存碎片多了,内存利用率也会随之升高,这是很可怕的。
Redis 本身的起因
Redis 作为键值对存储的数据库,自身键值对的大小就是不确定的,正如下面的例子中,Redis 申请了 20 字节的空间,但理论调配却是 32 字节,那么残余的 12 字节则会被闲置成为内存碎片。如下图:
上图中残余 12 个字节空间则是闲置的,很有可能成为内存碎片,因而键值对大小不同则会造成肯定的内存碎片,这是第一个起因。
第二个起因其实了解起来很简略,键值对的批改或者删除必定会造成空间的扩容或者开释;
一方面,如果批改后的键值对变大或者变小了,势必会将占用的空间扩充或者开释不必的空间,如下图:
上图中键值对批改后变小了,从原来的 10 个字节变成了 7 个字节,从而开释了 3 个字节,此时残余了 5 个字节的闲暇空间。
另一方面,如果键值对删除了,则会开释掉占用的空间,造成闲暇空间。
如何判断存在内存碎片?
这个对于运维人员来说很重要,一旦呈现 Redis 运行迟缓或者阻塞了,肯定须要先判断内存的占用状况,而不是说胡乱的重启 Redis。
Redis 本身提供了 INFO
命令,能够用来查问内存的应用状况,命令如下:
INFO memory
# Memory
used_memory:1073741736
used_memory_human:1024.00M
used_memory_rss:1997159792
used_memory_rss_human:1.86G
…
mem_fragmentation_ratio:1.86
下面的各种属性含意如下:
mem_fragmentation_ratio
这个指标很分明的展现了以后内存的碎片率,比方 Redis 申请了 1000 字节,然而操作系统理论调配的内存 1800 个字节,则mem_fragmentation_ratio=1800/1000=1.8
从上文也晓得了,因为内存分配器的局限性,理论调配的内存绝大部分都是大于理论申请的内存,则如何通过 mem_fragmentation_ratio
这个值来掂量呢?这个值的范畴在多少是失常的呢?
作者这里参照了许多开发人员的倡议,列出了以下教训阀值:
>1&&<1.5
:在这个范畴内是正当的,毕竟大部分状况下操作系统调配的内存总是总是大于理论申请的空间。>1.5
:这表明内存碎片率曾经超过50%
,此时须要采取一些措施来升高碎片率了。<1
:what?表明理论调配的内存小于申请的内存了,很显然内存不足了,这样会导致局部数据写入到Swap
中,之后 Redis 拜访 Swap 中的数据时,提早会变大,性能会升高。
如何清理内存碎片?
既然存在内存碎片,那么的肯定有办法革除内存碎片,最简略的办法则是重启 Redis
然而这也存在一些危险,如下;
- 如果 Redis 未长久化,则数据会失落(疏忽从后端复原)
- 即便长久化了,然而复原数据时长不定,这个要依据 AOF 和 RDB 文件大小决定,在复原阶段则无奈提供服务。
好在 Redis 4.0-RC3 版本之后,Redis 本身提供了一种革除内存碎片的办法
革除的原理很简略,通过复制拷贝将不间断的寄存的数据搬到一起造成一块间断的内存空间,如下图:
如上图,革除之前 A
和B
不是间断的,中距离着两个字节 闲暇 1
,然而在执行革除内存碎片操作之后,Redis 拷贝了 B
到闲暇 1
,开释掉之前 B
的空间,此时 闲暇 1
和 闲暇 2
则变成了间断的闲暇空间了。
那么问题来了,这种形式诚然好,然而对于单线程的 Redis 来说,通过这种拷贝复制的形式显然是一种耗时的操作,性能大大降低,那么有什么好的办法呢?
Redis 提供了参数配置,能够管制革除内存碎片的机会,命令如下:
config set activedefrag yes
以上命令启动主动清理,然而具体什么时候清理,还要受以下两个参数的影响:
active-defrag-ignore-bytes 400mb
:如果内存碎片达到了400mb
,开始清理(自定义)active-defrag-threshold-lower 20
:内存碎片空间占操作系统调配给 Redis 的总空间比例达到20%
时,开始清理(自定义)
以上两个参数只有全副满足才会开始清理
除了以上触发清理内存碎片的参数,Redis 还提供了两个参数来保障在清理过程中不影响解决失常的申请,如下:
active-defrag-cycle-min 25
:示意主动清理过程所用CPU
工夫的比例不低于25%
,保障清理能失常发展active-defrag-cycle-max 75
:示意主动清理过程所用CPU
工夫的比例不高于75%
,一旦超过,就进行清理,从而防止在清理时,大量的内存拷贝阻塞 Redis,导致响应提早升高。
以上两个参数管制了清理过程中的 CPU 工夫占比,保障了失常解决申请不受影响
总结
本文以师弟的一个疑难结尾介绍了删除数据导致内存占用还是很高的起因是存在内存碎片,导致内存碎片大抵分为两个起因,如下:
- 内存调配策略局限性,个别都会调配固定的空间大小,导致理论调配的内存空间大于理论申请的,从而多出了许多不间断的闲暇内存块。
- 键值对的批改、删除导致了内存的扩容或者开释,导致多余的不间断的闲暇内存块。
介绍了如何通过 INFO memory
命令查看内存的碎片率,通过 mem_fragmentation_ratio
的教训阀值来判断异样。
介绍了 Redis 清理内存碎片的形式以、主动清理的两个触发条件、保障失常解决申请的两个管制 CPU 工夫的参数。
举荐浏览:
深入浅出 Redis,十年阿里架构师分享:有了它还怕不懂 Redis 原理?
字节跳动总结的设计模式 PDF 火了,完整版凋谢分享
刷 Github 时发现了一本阿里大神的算法笔记!标星 70.5K
如果能听懂这个网约车实战,哪怕接私活你都能够月入 40K
为什么阿里巴巴的程序员成长速度这么快,看完他们的内部资料我懂了
程序员达到 50W 年薪所须要具备的常识体系。
对于【暴力递归算法】你所不晓得的思路
看完三件事❤️
如果你感觉这篇内容对你还蛮有帮忙,我想邀请你帮我三个小忙:
点赞,转发,有你们的『点赞和评论』,才是我发明的能源。
关注公众号『Java 斗帝』,不定期分享原创常识。
同时能够期待后续文章 ing????