玩转Redis-删除了两百万key,为什么内存依旧未释放?

本文关键字:玩转Redis、Redis内存碎片、Redis内存释放;

大纲

  • 背景
  • 如何查看Redis内存数据
  • 内存为何不释放
    • 什么是内存碎片
    • Redis的内存碎片是如何形成的
  • 如何释放内存
  • 生产环境整理内存碎片的注意事项

1、背景

公司某业务使用的Redis集群是自建的,前段时间计划将自建Redis集群迁移到购买的阿里云集群。
 老集群共有 350W key,占用内存 8.8 G,DTS迁移前分析发现有近两百万的key无需迁移,于是提前删除了这两百万key。
 删除key后发现redis内存竟然几乎无变化,350W key删除了两百万,怎么也得释放几G内存吧。难道删除失败了?通过比对数据发现,计划被删除的数据确实已经删除了。
 为什么删除了两百万key,内存未释放呢?这个问题一直困扰着我,通过查阅资料终于弄明白了。

2、如何查看Redis内存数据

2.1、info memory

进入Redis命令行界面后,使用 info memory 命令(集群使用 cluster info 命令) 即可查看当前Redis相关内存信息,部分信息展示如下:

127.0.0.1:6379> info memory
# Memory

# Redis 保存数据申请的内存空间
used_memory:9469412118
used_memory_human:8.82G

# 操作系统分配给 Redis 进程的内存空间
used_memory_rss:11351138316
used_memory_rss_human:10.57G

# Redis 进程在运行过程中占用的内存峰值
used_memory_peak:12618222522
used_memory_peak_human:11.75G

# 内存碎片率,used_memory_rss / used_memory
mem_fragmentation_ratio:1.20

# Redis 最大可用内存,0表示不限制
maxmemory:0
maxmemory_human:0B

# 内存分配器
mem_allocator:jemalloc-5.1.0

我们来解释下每个属性的含义:

used_memory:Redis 保存数据申请的内存空间,包含Redis进程及数据占用内存,单位 Byte;

used_memory_rss:操作系统分配给 Redis 进程的内存空间(包含内存碎片占用的空间),是从操作系统角度看的数据;此数据结果约等于top、ps命令看到的数据结果。

used_memory_peak:Redis 进程在运行过程中占用的内存峰值,used_memory_peak >= used_memory;

maxmemory:Redis 最大可用内存,0表示不限制。可以方便的实现对一台服务器部署多个Redis进程的内存控制;防止所用内存超过服务器物理内存;便于数据超出内存限制时执行LRU等删除策略。

XXX_human:表示以可读的方式返回XXX。

mem_fragmentation_ratio:内存碎片率,used_memory_rss / used_memory。大于1的部分为redis碎片占用的大小,建议值 大于 1 但小于 1.5,大于1.5说明碎片过多需要清理了。

需要注意的是,通常情况下 used_memory_rss 是大于 used_memory 的;但也有例外,当used_memory_rss 小于 used_memory 时,说明 操作系统分配给Redis进程的数据,不足以满足实际存储数据的需求,此时Redis部分内存数据会转换到Swap中,随之引发的问题是,当Redis访问Swap中的数据时,性能会下降

2.2、memory xxx 指令

2.2.1、memory doctor

列出 Redis 服务器遇到的不同类型的内存相关问题,并提供相应的解决建议

127.0.0.1:6379> memory doctor
Hi Sam, I can't find any memory issue in your instance. I can only account for what occurs on this base.

2.2.2、memory malloc-stats

提供内存分配情况的内部统计报表,(目前只支持jemalloc内存分配器)。通过该命令可以看出jemalloc对于所有对象进行分配时,各具体分区的内存详细状况。
 memory malloc-stats 命令返回信息较多,此处暂不做详细分析,感兴趣的同学可以自行执行命令分析。

2.2.3、memory purge

手动整理内存碎片,会阻塞主进程。

127.0.0.1:6379> memory purge
OK

2.2.4、memory stats

以数组形式返回服务器的内存使用情况;

127.0.0.1:6379> memory stats
 1) "peak.allocated"
 2) (integer) 905331384
 3) "total.allocated"
 4) (integer) 905330152
 5) "startup.allocated"
 6) (integer) 791160
 7) "replication.backlog"
 8) (integer) 0
 9) "clients.slaves"
10) (integer) 0
11) "clients.normal"
12) (integer) 49694
13) "aof.buffer"
14) (integer) 0
15) "lua.caches"
16) (integer) 0
17) "db.0"
18) 1) "overhead.hashtable.main"
    2) (integer) 7888
    3) "overhead.hashtable.expires"
    4) (integer) 32
19) "db.1"
20) 1) "overhead.hashtable.main"
    2) (integer) 304
    3) "overhead.hashtable.expires"
    4) (integer) 32
21) "overhead.total"
22) (integer) 849110
23) "keys.count"
24) (integer) 152
25) "keys.bytes-per-key"
26) (integer) 5950914
27) "dataset.bytes"
28) (integer) 904481042
29) "dataset.percentage"
30) "99.99359130859375"
31) "peak.percentage"
32) "99.999870300292969"
33) "allocator.allocated"
34) (integer) 905598528
35) "allocator.active"
36) (integer) 905961472
37) "allocator.resident"
38) (integer) 910348288
39) "allocator-fragmentation.ratio"
40) "1.0004007816314697"
41) "allocator-fragmentation.bytes"
42) (integer) 362944
43) "allocator-rss.ratio"
44) "1.0048421621322632"
45) "allocator-rss.bytes"
46) (integer) 4386816
47) "rss-overhead.ratio"
48) "0.0086568007245659828"
49) "rss-overhead.bytes"
50) (integer) -902467584
51) "fragmentation"
52) "0.0087051792070269585"
53) "fragmentation.bytes"
54) (integer) -897408432

2.2.5、memory usage

返回一个key和它值在内存中占用的字节数(包含redis管理该key所占用的内存);

127.0.0.1:6379> memory usage key1
(integer) 46

2.2.6、memory help

memory相关命令的帮助信息;

127.0.0.1:6379> memory  help
1) MEMORY <subcommand> arg arg ... arg. Subcommands are:
2) DOCTOR - Return memory problems reports.
3) MALLOC-STATS -- Return internal statistics report from the memory allocator.
4) PURGE -- Attempt to purge dirty pages for reclamation by the allocator.
5) STATS -- Return information about the memory usage of the server.
6) USAGE <key> [SAMPLES <count>] -- Return memory in bytes used by <key> and its value. Nested values are sampled up to <count> times (default: 5).

3、内存为何不释放

Redis有自己的内存分配器,当数据删除后,释放的内存空间由Redis自己的内存分配器管理,并没有立即将内存返回给操作系统,所以对于操作系统而言,仍然认为Redis占用了内存。

这样的好处是,减少Redis向系统申请内存分配的次数,提升Redis自身性能。

3.1、什么是内存碎片

不少同学应该听说过磁盘碎片,使用过Smart Defrag等软件清理过磁盘碎片,清理磁盘碎片能够优化文件系统,将零散的磁盘空间移动合并,将频繁使用的文件和目录放置到磁盘的速度最快的区域,使计算机能以最高速度稳定运行。

内存碎片是由于计划申请的空间比空闲的连续空间小,导致这部分小内存空间无法被使用,无法被使用的内存空间则可称为内存碎片。

image.png

如图,9字节的内存空间示意图,序号为1、2、3、5、6、9的内存空间已使用,如果现在计划申请3字节的连续内存空间,按照现有的内存使用情况是无法申请的,此时序号4、7、8就是“内存碎片”了。

3.2、Redis的内存碎片是如何形成的

Redis产生内存碎片主要由以下2点原因导致;

  • 内存分配器机制;
  • Redis数据的修改和删除引发空间的扩容和释放;

3.2.1、内存分配器机制

Redis有几种内存分配器 jemalloc、libc、tcmalloc,默认使用 jemalloc。
 jemalloc 内存分配方式为 按照一系列固定大小分配内存空间,jemalloc 按照申请的内存大小分配最接近的内存空间;
 比如申请220字节,jemalloc 会分配256字节,如果还要继续写入20字节,Redis则不会继续向系统申请内存空间,因为先前申请的256字节还剩余36字节可用;但如果此时需要继续写入60字节,则已分配空间不够用了,需要再次向系统申请分配内存空间。

默认64位系统jemalloc的划分方式如下:
Small: [8], [16, 32, 48, ..., 128], [192, 256, 320, ..., 512], [768, 1024, 1280, ..., 3840]
Large: [4 KiB, 8 KiB, 12 KiB, ..., 4072 KiB]
Huge: [4 MiB, 8 MiB, 12 MiB, ...]

3.2.2、Redis数据的修改和删除引发空间的扩容和释放

如下图所示,key1扩容时需新增2字节,为保证内存空间连续性,key2发生迁移;当key3释放空间后,序号为7、8、14、15的空间均未使用。
 如果此时有key希望申请3字节的空间,虽然总共剩余了4字节,但是没有连续的3字节空间,所以无法直接使用。

了解了原理后,如果我们想测试内存碎片清理,则可以插入大量key,再删除大量key(或者插入key时设置过期时间),以此来模拟高内存碎片率场景。

image.png

4、如何释放内存

4.1、重启Redis释放内存

这应该是最直接有效的方法。但是生产环境不是你想重启就能重启的。因为重启Redis需要考虑很多问题,比如:

  • 重启时加载恢复数据需要时间,在此期间Redis将不可用;
  • 确保所有的配置项变更都更新到redis.conf,否则重启后在线修改的配置将还原;

4.2、使用备用Redis实例同步迁移

内存碎片的产生很大程度上是因为数据的修改和删除,所以计划清理 实例A的内存碎片时,我们可以引入实例B,将实例A的数据全量同步到实例B中,再使用实例B替换原有的实例A对外提供服务。

此方法思路虽然是通的,但操作流程复杂且风险较高,生产环境几乎不会采用。但如果本就计划迁移实例,那就刚好可以采用此种思路。

4.3、memory purge手动碎片整理

手动整理内存碎片,会阻塞主进程,生产环境慎用。

memory purge 和 activedefrag回收的并不是同一块区域的内存,它简单粗暴的尝试清除脏页以便内存分配器回收。可以根据实际情况和activedefrag配合使用,memory purge在极端情况下效果较好,activedefrag则更彻底。

4.4、开启activedefrag自动碎片整理

在Redis 4.0 版本后新增配置项activedefrag(active:主动的,defrag:整理碎片),activedefrag默认关闭,计划清理碎片时需手动开启,命令如下:

127.0.0.1:6379> config set activedefrag yes

让我们看看相关的配置文件:

# 以下内容节选至 redis.conf

########################### ACTIVE DEFRAGMENTATION #######################

# 3. Once you experience fragmentation, you can enable this feature when
#    needed with the command "CONFIG SET activedefrag yes".
#

# Enabled active defragmentation
# activedefrag yes

# Minimum amount of fragmentation waste to start active defrag
# active-defrag-ignore-bytes 100mb

# Minimum percentage of fragmentation to start active defrag
# active-defrag-threshold-lower 10

# Maximum percentage of fragmentation at which we use maximum effort
# active-defrag-threshold-upper 100

# Minimal effort for defrag in CPU percentage
# active-defrag-cycle-min 5

# Maximal effort for defrag in CPU percentage
# active-defrag-cycle-max 75

# Maximum number of set/hash/zset/list fields that will be processed from
# the main dictionary scan
# active-defrag-max-scan-fields 1000

内存碎片整理开关(需同时满足才执行):

  • activedefrag:内存碎片整理总开关,开启后才有可能执行碎片整理;
  • active-defrag-ignore-bytes:内存碎片的字节数达到此阀值(默认100MB)时,允许整理;
  • active-defrag-threshold-lower:内存碎片空间占操作系统分配给 Redis 的总空间比例达到此阀值(默认10%)时,允许整理;

此外,还有几个参数用于控制内存碎片整理的力度

  • active-defrag-cycle-min:清理内存碎片占用 CPU 时间的比例不低于此阀值(默认5%),保证清理能正常开展;
  • active-defrag-cycle-max:清理内存碎片占用CPU 时间的比例不高于此阀值(默认75%),一旦超过则停止清理,从而避免在清理时,大量的内存拷贝阻塞 Redis,导致其他请求延迟。

内存碎片整理其他参数

  • active-defrag-threshold-upper:内存碎片空间占操作系统分配给 Redis 的总空间比例达到此阀值(默认100%)时,则尽最大努力整理;
  • active-defrag-max-scan-fields:碎片整理 扫描set/hash/zset/list时,仅当 set/hash/zset/list 的长度小于此阀值时,才会将此key加入碎片整理;

5、生产环境整理内存碎片的注意事项

5.1、清理内存碎片的时机

通过“info memory”命令查看mem_fragmentation_ratio(内存碎片率),当mem_fragmentation_ratio > 1.5 时,建议开始清理内存碎片。当然,也可以通过分析调整activedefrag的参数配置从而达到自动清理效果。

5.2、清理内存碎片前你必须了解的事

memory purge:手动整理内存碎片,会阻塞主进程,生产环境慎用,清理效果和activedefrag并不相同。

activedefrag:自动整理内存碎片,其原理是通过scan迭代整个Redis数据,通过一系列的内存复制、转移操作完成内存碎片整理,由于此操作使用的是主线程,故会影响Redis对其他请求的响应。

在Redis日志中,可以查看activedefrag耗时及资源占用记录:Active defrag done in 79214ms 表示 耗时,但此耗时并不是阻塞了主线程的时间,而是从内存碎片整理的第一次scan到最后一次scan的时间差,在此期间,主线程是可以处理其他请求的。

12:M 21 May 12:31:11.210 - Starting active defrag, frag=12%, frag_bytes=381401201, cpu=75%
12:M 21 May 12:32:30.424 - Active defrag done in 79214ms, reallocated=50, frag=12
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342