上周刚来了个应届小师弟,组长说让我带着,周二问了我这样一个问题:师兄啊,我用
top
命令看了下服务器的内存占用情况,发现
Redis
内存占用严重,于是我就删除了大部分不用的
keys
,为什么内存占用还是很严重,并没有释放呢?
嗯?为什么呢?今天就带着这个问题来介绍一下如何正确释放
Redis
的内存。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
-
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
-
视频教程:https://doc.iocoder.cn/video/
内存碎片这个概念应该不是第一听说了,熟悉JVM或者操作系统的应该都熟悉,以火车卖票为例,一个车厢
128
个车位,由于高峰期,只剩余
两个
位置了,但是此时
三个
人想要坐在一起,能够吹吹牛批,喝喝酒的,那么这三个人肯定不会买这节车厢的两个位置了,此时这两个位置可以称之为座位碎片 。
操作系统中对于内存分配也是一样的,比如应用需要申请一块连续N个字节的空间,虽然剩余内存总量大于
N
个字节,但是没有一块连续的内存空间是
N
个字节,那么剩余的空间就是内存碎片。如下图:
上图中的空闲3个字节和空闲2个字节都是内存碎片。
那么什么原因会造成内存碎片呢?这个其实大致分为两个原因,一个是操作系统的内存分配策略,一个是Redis自身原因,下面就这两个原因详细分析。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
-
项目地址:https://github.com/YunaiV/yudao-cloud
-
视频教程:https://doc.iocoder.cn/video/
内存分配器的分配策略一般是按照固定大小来分配内存,而不是按照应用程序申请的内存空间按需分配。比如8字节、16字节、32字节......
Redis
提供了多种的内存分配策略,比如
libc
、
jemalloc
、
tcmalloc
,默认使用
jemalloc
。
jemalloc
这种分配策略,是按照固定的空间分配,比如8字节、32字节....2KB、4KB等。当应用程序申请的内存接近某个固定值的时候,
jemalloc
则会分配固定的大小。比如申请了6字节,则会分配8字节的空间。
这种分配的方式的好处很明显,则会减少内存分配的次数,比如申请了
20字节
的内存,实际分配的是
32字节
的内存空间,当应用再写入
10字节
的数据时,则不会再次分配,剩余的
12字节
足够用了。这样就避免了一次的内存分配。如下图:
但是坏处也很明显,申请的和分配的空间不一样,则剩余的空间很可能形成内存碎片,一旦内存碎片多了,内存利用率也会随之降低,这是很可怕的。
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
则变成了连续的空闲空间了。