首页 > 数据库 > 数据库开发技术 > 深入了解Redis内存碎片
首先,Redis是一个内存数据库,意味着它会尽可能使用所有可用的内存来存储数据。当你向Redis中写入数据时,它会根据数据的需要分配内存。但当你从Redis中删除数据时,Redis并不会立即释放这些内存回到操作系统。相反,它会保留这些内存,以便后续的数据写入可以快速分配内存,而不需要频繁地向操作系统请求和释放内存,这样可以提高性能。
现在,考虑您提到的情况:Redis实例原本保存了5GB的数据,然后删除了2GB的数据。即使数据只占用了3GB,Redis进程可能仍然占用大约5GB的内存,因为它保留了之前分配的内存。
maxmemory参数在Redis中用于设置Redis可以使用的最大内存量。如果达到这个限制并且没有更多的内存可以分配,Redis会根据配置的淘汰策略(如LRU、LFU等)删除某些键来释放内存。但如果没有设置maxmemory或者没有达到这个限制,Redis会继续为新数据分配内存,直到遇到操作系统的内存限制。
最后,如果Redis尝试分配内存但失败了(例如,因为操作系统没有更多的内存可以分配),Redis会返回一个错误给客户端,表示写操作失败。但这不会导致Redis宕机,因为Redis会尽力保护其内存不被耗尽,从而避免整个系统崩溃。
简而言之,即使从Redis中删除了数据,Redis进程占用的内存可能不会立即减少,因为它会保留已分配的内存以供后续使用。而maxmemory参数用于控制Redis可以使用的最大内存量,并定义了当达到这个限制时应该如何处理。在没有达到这个限制的情况下,Redis会尽量为新数据分配内存,并在失败时返回错误,但不会导致宕机。

注:设置maxmemory参数,执行命令CONFIG SET maxmemory 100mb,或在redis.conf 配置文件中设置maxmemory 100mb
使用top命令查看数据是否已经删除,为什么它仍然占用这么多内存?

释放的内存去了哪里?
当我们通过 top 命令查看系统资源使用情况时,可能会发现即使从 Redis 中删除了大量数据,系统的内存占用率依然很高,似乎 Redis 没有真正释放内存。这其实是因为 Redis 的内存管理策略导致的。Redis 在删除数据后,并不会立即将这些内存返回给操作系统,而是将这些内存标记为可用,以便后续快速分配给新的数据。这种策略可以提高 Redis 的性能,因为它避免了频繁地向操作系统申请和释放内存。
要深入了解 Redis 的内存使用情况,我们可以使用 INFO memory 命令。这个命令会提供一系列关于 Redis 内存使用的指标,比如已使用的内存量、内存碎片率、不同数据类型的内存使用情况等。通过分析这些指标,我们可以更好地理解 Redis 内存的分配和使用情况,以及为什么 Redis 没有立即释放内存给操作系统。
127.0.0.1:6379> info memoryused_memory:1132832 // Redis Amount of memory used to store dataused_memory_human:1.08M // Returns the total amount of memory in human readable formused_memory_rss:2977792 // From the perspective of the operating system, the total physical memory occupied by the processused_memory_rss_human:2.84M // used_memory_rss Readability mode displayused_memory_peak:1183808 // The maximum value of memory used, representing the peak value of used_memoryused_memory_peak_human:1.13M // Returns the value of used_memory_peak in a human readable formatused_memory_lua:37888 // Lua The amount of memory consumed by the engine。used_memory_lua_human:37.00Kmaxmemory:2147483648 // The maximum memory value that can be used, in bytes.maxmemory_human:2.00G // readable formmaxmemory_policy:noeviction // Memory Retirement Policymem_fragmentation_ratio:2.79 // The ratio of used_memory_rss & used_memory represents the memory fragmentation rate

Redis进程的内存消耗可以细分为以下几个关键部分
基础内存消耗:这是Redis服务器本身启动时所必需的内存。它包括Redis的内部数据结构、元数据、线程栈等基本运行所需的内存。
数据存储内存:这是Redis用来存储用户数据的内存部分。当我们在Redis中设置键值对时,这些键和值(以及与之关联的任何元数据)都会存储在这里。
缓冲区内存:Redis使用多种缓冲区来暂存数据,以优化性能。这些缓冲区主要包括:
客户端输出缓冲区:每个连接到Redis的客户端通常都会有一个与之关联的输出缓冲区。这个缓冲区用来暂存Redis发送给客户端的响应数据,直到这些数据可以被发送给客户端。
复制积压缓冲区(Copy Backlog):当使用Redis的复制功能时,这个缓冲区用来保存主服务器最近发送给从服务器的数据副本。这样,如果连接丢失,从服务器可以重新同步,而不需要从头开始。
AOF缓冲区:当使用AOF(Append-Only File)持久化时,AOF缓冲区用来暂存即将写入AOF文件的数据。当缓冲区的数据达到一定的量或满足其他条件时,数据会被写入到AOF文件中。

Redis的内存占用中,其自身空进程所占用的内存相对较小,通常可以不予考虑。相比之下,对象内存是Redis内存占用的主体部分,用于存储用户设置的所有数据。然而,除了对象内存外,缓冲区内存也是Redis内存管理中的一个重要方面。在高并发或大数据流的场景下,如果缓冲区的管理不当,可能会导致内存使用迅速增长,从而使Redis的内存占用变得不稳定。
此外,随着Redis的运行和数据的增删改查,内存碎片的产生也是不可避免的。内存碎片指的是实际使用的物理内存(RSS值)与实际存储数据的内存(used_memory)之间的差异。当内存碎片过多时,即使Redis的used_memory指标显示还有可用空间,但由于物理内存(RSS值)中的碎片,Redis可能仍然无法存储更多的数据。因此,理解和管理内存碎片对于确保Redis的高效运行和稳定性至关重要。

什么是内存碎片?
存碎片问题就像是电影院座位分配的一个比喻。想象一下,你和女朋友去电影院看电影,你们希望坐在一起享受电影。电影院有8个座位,目前已经有4对情侣坐在了电影院,每对情侣之间都空着一个座位。虽然从数字上看,还剩下4个座位可供其他人购买,但实际上,由于这些座位都是分散的,并没有连续的两个座位可供一对新的情侣坐下。
这就像内存碎片问题,即使Redis显示还有可用内存,但由于这些内存碎片分散在不同的位置,无法形成连续的内存块,所以Redis可能无法存储新的数据,即使这些数据的大小远小于显示的可用内存。这就像你无法买到两个连续的座位,即使电影院显示还有足够的座位可供购买。
因此,解决内存碎片问题的关键是重新整理内存,使其形成连续的内存块,从而确保Redis可以有效地利用所有可用的内存资源。


什么导致内存碎片?
主要有两个原因:
内存分配器的分配策略
键值对的大小不同,删除操作

1. 内存分配器的分配策略
Redis的内存分配器默认采用jemalloc,但用户也有选择使用glibc或tcmalloc的灵活性。与按需分配不同,这些内存分配器使用预定义的内存块大小(如8字节、16字节、……、2KB、4KB)来分配内存。例如,如果程序只需要1.5KB的内存,jemalloc可能会分配2KB的空间,这样多出的0.5KB就形成了内存碎片。
这种分配策略旨在减少内存分配的次数,从而提高效率。例如,如果程序请求22个字节的空间,jemalloc可能会分配32个字节。当随后需要额外的10个字节空间时,jemalloc可以直接从之前分配的32个字节中满足需求,而无需再次向操作系统请求内存。
当某个键从Redis中被删除时,内存并不会立即被返还给操作系统。这是因为底层的内存分配器对内存的管理方式。很多时候,即使键被删除,它们仍可能与其他有效键共享相同的内存页。
此外,为了更有效地利用内存资源,内存分配器可能会释放部分内存块。例如,如果从一个原本占用5GB内存的Redis实例中删除了2GB的数据,当新数据被添加到实例中时,Redis的RSS(Resident Set Size,常驻集大小,即物理内存占用量)并不会显著增加。这是因为内存分配器会优先重用那些之前已经释放的2GB内存空间。通过这种方式,Redis能够更智能地管理内存,既减少了内存碎片的产生,又提高了内存的使用效率。

2.键值对大小不同,删除操作
由于内存分配器遵循固定大小的分配策略,它为数据分配的内存空间通常会比实际所需的空间要大。这种分配方式不可避免地会产生内存碎片,因为分配的内存块往往不能完全匹配数据实际占用的空间大小,进而影响了内存的存储效率。
此外,键值对的频繁变动,包括修改和删除操作,会进一步加剧内存碎片问题。比如,如果一个原本占用32字节的字符串被修改为仅占用20字节,那么剩下的12字节就会成为未被利用的内存碎片。当新的数据存储请求到来,需要分配一个13字节的空间时,这12字节的碎片就无法满足需求,因为它们的空间并不连续。
内存碎片化的核心问题是,虽然总的空闲内存空间看似充足,但由于这些空间分布零散,无法形成连续的内存块,导致无法满足一些数据结构的内存分配需求。这就像是拼图游戏,单个碎片可能足够大,但如果没有合适的邻接碎片,它们就无法组合成满足需求的大块内存空间。
mem_fragmentation_ratio = used_memory_rss/ used_memory

如何解决?
要评估Redis是否存在内存碎片问题,关键在于查看info memory命令输出的mem_fragmentation_ratio指标。这个比率表示内存碎片的程度。
当mem_fragmentation_ratio的值介于1到1.5之间时,通常认为内存碎片处于可接受的范围。然而,如果这个值超过1.5,那就意味着内存碎片已经超过了50%,这时就需要采取措施来减少碎片。
解决内存碎片问题的方法有几种:
重启Redis实例:这是最简单直接的方法。但需要注意的是,如果Redis没有启用持久化,那么重启会导致数据丢失。如果启用了持久化(如RDB或AOF),虽然可以通过这些持久化文件来恢复数据,但在恢复过程中,如果数据量很大,会导致Redis在一段时间内无法提供服务,这会影响到系统的高可用性。
利用Redis的内存碎片自动清理机制:从Redis 4.0版本开始,Redis引入了内存碎片自动清理功能。当Redis发现内存空间被不连续地分割时,它会尝试将这些分散的数据块移动并拼接在一起,从而释放出连续的内存空间。这个过程涉及到操作系统级别的内存操作,因此可能会对Redis的性能产生一定影响。

虽然自动清理内存碎片有其好处,但也不能滥用。因为操作系统在执行数据移动和释放原空间的过程中需要消耗一定的资源。
考虑到Redis处理数据指令是单线程的,这意味着在数据复制和移动的过程中,系统必须等待碎片清理完成后才能继续处理其他请求。这种等待会导致性能下降,尤其是在高负载的情况下,可能会显著影响Redis的响应时间和吞吐量。因此,在使用自动清理功能时,需要权衡其带来的好处和可能导致的性能损失。

那么如何减少对性能的影响来实现自动清理碎片呢?
用下面两个参数来控制内存碎片清理和结束的时机,避免占用过多CPU,减少清理碎片对Redis处理请求的性能影响。
启用自动内存碎片整理:
CONFIG SET activedefrag yes

这只是为了启用自动清洁。当清理需要同时满足以下两个条件时,就会出发清理操作。
清理条件:
active-defrag-ignore-bytes 200mb:内存碎片占用内存达到200MB,开始清理
active-defrag-threshold-lower 20: 内存碎片空间超过系统分配给Redis空间的20%,开始清理
避免性能影响
清理时间是有的,需要控制清理对性能的影响。一二设置先分配清理碎片占用的CPU资源,保证碎片可以正常清理,避免对Redis处理请求造成性能影响。
active-defrag-cycle-min 20:自动碎片整理过程中占用CPU时间比例不低于20%,以保证清理任务正常进行。
active-defrag-cycle-max 50:自动清理进程占用CPU时间比例不能高于50%。如果超过,会立即停止清理,避免阻塞 Redis 造成高延迟。
总结
当发现Redis实际使用的内存远小于操作系统为其分配的内存,但数据仍然无法被保存时,这通常暗示着内存碎片问题可能很严重。为了验证这一点,我们可以使用info memory命令来检查mem_fragmentation_ratio指标。如果这个值异常高,那么内存碎片可能就是问题的根源。
为了解决内存碎片问题,我们可以启用Redis的内存自动清理功能,并根据实际情况调整清理时间和CPU资源占用的配置。然而,需要注意的是,自动清理机制涉及到内存复制操作,这可能会对Redis的性能产生一定的影响。如果在启用自动清理后,发现Redis的性能有所下降,那么我们可以考虑减小active-defrag-cycle-max的值,以减轻对性能的影响。
END

添加博主
微信号|yfq940-
点个关注!!!
原文作者:步步运维步步坑;如有侵权,请联系管理员删除。