Redis作为高性能的内存数据库,其内存管理机制决定了性能表现和资源利用率。本文将深入解析Redis的内存管理,包括内存模型、对象内存、缓冲内存、内存碎片、内存回收机制、内存优化及参数优化。
Redis的内存模型是其高性能运行的基础,它主要由以下几个部分组成:
Redis的核心功能是将数据存储在内存中,以支持高效读写操作。不同的数据类型采用不同的数据结构进行存储,常见的有:
字符串(String):底层使用SDS(Simple Dynamic String)结构,支持动态扩容。可以存储普通字符串、整数或浮点数。
列表(List):基于quicklist数据结构,结合了ziplist和linked list的优点。用于存储多个有序字符串值。
哈希(Hash):对于小哈希表使用ziplist编码,大哈希表采用hashtable存储。用于存储键值对集合。
集合(Set):小集合使用intset存储,大集合采用hashtable存储。用于存储无序、不重复的字符串值。
有序集合(Zset):由skiplist和ziplist组成,适用于高效范围查询。用于存储带分数的有序元素集合。
Redis使用内存分配器管理内存,默认采用jemalloc,优势包括:
减少内存碎片化:相比glibc malloc,jemalloc能更好地管理小对象,提高内存利用率。
线程安全:适用于多线程环境,减少锁竞争。
优化性能:针对不同的对象大小采用不同的分配策略,减少内存占用。
Redis提供RDB(Redis Database File)和AOF(Append Only File)两种持久化方式:
RDB快照:定期将内存数据保存到磁盘,适用于灾难恢复。
AOF日志:记录每条写入命令,适用于数据完整性要求高的场景。
混合持久化:结合RDB和AOF的优点,提高数据恢复速度。
影响Redis内存占用的主要因素包括:
键值对数量:存储的数据越多,内存占用越高。
数据类型:不同的数据结构的内存占用差异较大,如ziplist比hashtable更节省内存。
编码方式:小数据采用紧凑编码(如intset、ziplist),大数据使用更灵活的结构(如hashtable、skiplist)。
过期数据:未及时清理的过期数据可能造成额外的内存消耗。
Redis根据数据规模自动选择最佳编码,但也可以通过参数调整优化:
合理选择数据类型:避免大规模使用hashtable和skiplist,使用紧凑型编码(如ziplist、intset)减少内存占用。
优化字符串存储:小整数可直接用int存储,而非字符串。
控制数据增长:限制列表、哈希和集合的大小,避免存储过大的单个对象。
使用过期策略:自动清理不必要的数据,减少内存占用。
Redis使用多个缓冲区来优化性能并减少I/O等待,主要包括:
每个客户端连接到Redis时,都会分配一个输入缓冲区。
存储客户端发送的命令,在命令解析后释放。
若客户端发送过长命令,可能导致内存膨胀。
用于存储Redis返回给客户端的响应数据。
大量订阅的客户端可能导致输出缓冲区过大,引发内存问题。
主从同步时,Redis会使用replication backlog存储数据。
允许从节点断开后快速恢复,而不需要完整同步。
AOF日志文件重写时,Redis使用缓冲区暂存新命令。
避免数据丢失,提高AOF日志写入性能。
内存碎片是指由于内存分配和释放不均衡,导致系统中的可用内存无法被有效利用的现象。在 Redis 中,由于数据结构的动态变化、不同数据类型的存储方式以及底层内存分配器的特性,都会导致内存碎片的产生。
内存碎片通常可以分为以下几种:
外部碎片(External Fragmentation):Redis 释放的内存块大小不一,导致无法高效地重新分配。
内部碎片(Internal Fragmentation):Redis 分配的内存块大于实际数据所需的空间,造成浪费。
Redis 主要使用 jemalloc 作为默认的内存分配器,尽管 jemalloc 具备较强的内存管理能力,但仍然可能产生碎片。内存碎片的主要来源包括:
Redis 中的数据可能随时被删除,或者由于 TTL 过期后被自动清理。
当大量键值对被删除时,Redis 释放的内存可能不会立即归还给操作系统,而是继续留在 jemalloc 管理的内存池中。
由于 jemalloc 的内存分配策略,释放的内存可能无法被其他数据结构高效复用,从而导致外部碎片。
Redis 采用多种数据结构来优化性能,但这些数据结构会根据数据量的变化进行扩容或转换,导致碎片化现象:
字符串(String):
短字符串(≤44 字节)使用 embstr 编码,存储在单一内存块中。
长字符串(>44 字节)使用 raw 编码,分配更大的内存块。
当字符串长度增长或缩短时,可能导致新分配的内存块与旧内存块大小不匹配,产生碎片。
列表(List):
小列表使用 quicklist,可能会因扩展或收缩导致内存重新分配。
哈希(Hash)、集合(Set)、有序集合(Zset):
这些数据结构会在元素数量超过阈值时,从紧凑存储(如 ziplist)升级为更复杂的结构(如 hashtable、skiplist)。
结构升级后,原来的小块内存可能无法被有效复用,导致内存碎片。
Redis 在 AOF 持久化过程中,会定期进行 AOF 重写(rewrite),减少日志大小。
AOF 重写需要分配新的缓冲区,并在完成后释放旧缓冲区,可能导致短时间内的内存占用峰值。
RDB 快照时,Redis 采用 fork() 进行子进程写入,如果写入过程中数据变化较大,会导致写时复制(Copy-On-Write,COW)机制带来额外的内存碎片。
主从同步(Replication)时,Redis 需要使用 复制积压缓冲区(Replication Backlog) 来存储主从同步数据。
当从节点断开后,复制缓冲区的大小可能会发生变化,从而导致内存碎片。
Redis 提供了 INFO MEMORY 命令来查看内存使用情况,其中 mem_fragmentation_ratio 反映了当前的内存碎片率。
redis-cli INFO MEMORY
示例输出:
used_memory:1024000000
used_memory_rss:1400000000
mem_fragmentation_ratio:1.37
used_memory:Redis 实际使用的内存。
used_memory_rss:Redis 进程从操作系统获取的物理内存。
mem_fragmentation_ratio = used_memory_rss / used_memory
<1.0:可能是 Redis 进程正在交换内存(不正常)。
≈1.0:几乎没有碎片,内存利用率较高。
>1.2:内存碎片较严重,可能需要进行优化。
Redis 默认使用 jemalloc,可以通过 jemalloc 提供的 jeprof 工具分析内存分配情况,并调整参数:
echo "prof:true" > /etc/redis.conf
使用 jeprof 进行分析:
jeprof --dot /usr/bin/redis-server /tmp/jeprof.out > output.dot
从 Redis 4.0 开始,Redis 提供了 active defrag 机制,可以主动整理内存碎片。
在 redis.conf 中启用:
activedefrag yes
或运行命令:
CONFIG SET activedefrag yes
Redis 提供多种内存淘汰策略,合理设置 maxmemory-policy 可以降低碎片化问题。
CONFIG SET maxmemory-policy allkeys-lru
推荐策略:
allkeys-lru:优先淘汰最少使用的数据,减少碎片。
volatile-lru:仅淘汰设置了过期时间的键,适用于缓存场景。
volatile-ttl:优先淘汰即将过期的数据。
当 Redis 内存碎片率长期保持在高水平(mem_fragmentation_ratio > 1.5)时,可以通过定期重启 Redis 来释放无用的内存:
service redis restart
选择适合的数据结构,例如使用 ziplist 代替 hashtable 以减少内存占用。
合理设置 hash-max-ziplist-entries、list-max-ziplist-size 等参数。
使用 auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size 控制 AOF 重写的频率,避免频繁的内存分配。
Redis 作为一个基于内存的数据库,需要有效地管理和回收内存,以保证高效运行。内存回收机制的核心目标是:
释放不再使用的内存,防止内存泄漏。
避免内存碎片化,提高内存利用率。
在达到内存上限时,合理淘汰数据,以维持服务可用性。
Redis 的内存回收主要包括 过期键清理、惰性删除、定期删除、内存淘汰策略、垃圾回收(针对 Lua 脚本和 jemalloc) 等方式。
Redis 允许为键设置过期时间(TTL),当键过期后,系统会回收对应的内存。Redis 采用三种策略来删除过期键:
当客户端访问某个键时,Redis 会检查其是否已经过期。
如果该键过期,则在访问时立即删除。
该策略的优点是减少 CPU 资源消耗,但如果过期键较多且长期不被访问,会导致内存占用持续增长。
示例:
SET key1 "value1" EX 10 # 设置 10 秒后过期
GET key1 # 10 秒后访问会触发惰性删除
Redis 每隔一段时间(默认 100 毫秒)会主动扫描数据库中的键,删除部分过期键。
采用 渐进式删除算法(Random Sampling):
在每个数据库(DB)中随机抽取一些键进行检查。
如果键过期,则删除。
如果发现过期比例较高,会增加扫描频率。
该策略可以避免内存无限增长,但也会增加 CPU 开销。
示例:
INFO MEMORY # 查看 used_memory 降低,说明定期删除生效
方式 |
优点 |
缺点 |
---|---|---|
惰性删除 |
CPU 资源占用低 |
可能导致大量过期键滞留内存 |
定期删除 |
主动清理内存,防止内存膨胀 |
可能增加 CPU 负载 |
当 Redis 达到 maxmemory 限制时,需要主动回收内存,以便存储新数据。Redis 提供多种淘汰策略,用户可以根据业务需求选择合适的策略。
Redis 通过 maxmemory-policy 选项控制数据淘汰方式,可用的策略如下:
策略 |
说明 |
适用场景 |
noeviction | 禁止驱逐数据(返回错误) |
适用于持久化存储,防止数据丢失 |
allkeys-lru | 淘汰最近最少使用(LRU)的键 |
适用于通用缓存,保证热点数据存活 |
volatile-lru | 在设置了过期时间的键中,淘汰最近最少使用的键 |
适用于临时缓存 |
allkeys-random | 在所有键中随机淘汰 |
适用于非关键数据的缓存 |
volatile-random | 在过期键中随机淘汰 |
适用于部分临时数据存储 |
volatile-ttl | 淘汰即将过期的键 |
适用于基于 TTL 管理的缓存 |
示例:
CONFIG SET maxmemory 100mb
CONFIG SET maxmemory-policy allkeys-lru
Redis 4.0+ 支持 LFU(Least Frequently Used) 淘汰策略,可以基于访问频率淘汰键。
LRU 适用于访问频次随时间衰减的缓存。
LFU 适用于需要长期存储热点数据的缓存。
配置 LFU
CONFIG SET maxmemory-policy allkeys-lfu
Redis 4.0+ 提供了 activedefrag 选项,可以自动整理内存碎片。
适用于 jemalloc 造成的碎片问题。
启用方法:
CONFIG SET activedefrag yes
AOF 机制可能会导致内存膨胀,需要定期触发重写(rewrite)。
适当调整 auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size,减少内存压力。
优化示例:
CONFIG SET auto-aof-rewrite-percentage 50
CONFIG SET auto-aof-rewrite-min-size 64mb
大键(如包含大量元素的 List、Hash、Set)会导致内存分配不均,影响回收。
解决方案:
使用 SCAN 命令查找大键。
拆分大键,减少单个键的负载。
MEMORY USAGE mybigkey # 查看单个键的内存使用量
长时间未消费的 Pub/Sub 订阅者会导致内存堆积。
可通过 client-output-buffer-limit 限制其内存占用。
CONFIG SET client-output-buffer-limit pubsub 64mb 16mb 60
Redis 提供多种数据结构,不同结构的存储方式不同,选择合适的数据结构可以减少内存占用。
小型字符串(≤44 字节) 使用 embstr 存储,减少额外的内存开销。
哈希(Hash)、列表(List)、集合(Set) 等结构在元素较少时使用 ziplist 存储,节省空间。
优化方法: 调整 hash-max-ziplist-entries 和 hash-max-ziplist-value。
CONFIG SET hash-max-ziplist-entries 512
CONFIG SET hash-max-ziplist-value 64
大列表(List)、大集合(Set)、大哈希(Hash) 会占用大量内存。
使用 MEMORY USAGE
避免存储 JSON,拆分大键,使用多个小键代替。
MEMORY USAGE my_large_list
适用场景: 统计 UV(独立访问用户数)、去重计数。
占用空间固定 12 KB,比 Set 节省大量内存。
PFADD unique_visitors user1 user2 user3
PFCOUNT unique_visitors
避免 key 长期不删除,占用内存。
合理使用 TTL,使 key 过期后自动删除。
使用 Lazy Deletion + Active Expiration。
EXPIRE mykey 3600 # 设置 1 小时后过期
禁用 AOF 或优化 AOF 重写。
适当调整 RDB 触发条件,避免频繁 dump。
CONFIG SET save "900 1 300 10 60 10000"
CONFIG SET auto-aof-rewrite-percentage 50
开启 activedefrag
进行碎片整理。
CONFIG SET activedefrag yes
参数 |
作用 |
推荐值 |
---|---|---|
maxmemory |
限制最大内存 |
视硬件情况而定 |
maxmemory-policy |
淘汰策略 |
allkeys-lru 或 volatile-lru |
activedefrag |
自动碎片整理 |
yes |
示例:
CONFIG SET maxmemory 2gb
CONFIG SET maxmemory-policy allkeys-lru
参数 |
作用 |
推荐值 |
timeout |
客户端超时 |
300 |
tcp-keepalive |
TCP 长连接检测 |
60 |
lazyfree-lazy-eviction |
惰性删除 |
yes |
示例:
CONFIG SET timeout 300
CONFIG SET lazyfree-lazy-eviction yes
关闭 appendfsync always,改为 appendfsync everysec 降低 IO。
CONFIG SET appendfsync everysec
CONFIG SET logfile /var/log/redis.log
CONFIG SET slowlog-log-slower-than 10000
通过合理的内存管理,Redis能够在高并发环境下保持稳定运行,提高系统的吞吐量和响应速度。
开源一个纯AI生成的健身记录App,如需源码,可关注公众号:小健学Java,回复“健身”即可获得!
如需Java面试题资料,可关注公众号:小健学Java,回复“面试”即可获得!