活动公告

系统通知
05-18 21:22
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

Redis内存释放完全指南 掌握高效清理技巧让你的数据库性能飙升解决内存溢出问题

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-9-8 19:50:01 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

Redis作为一款高性能的内存数据库,以其快速的读写能力和丰富的数据结构支持而广受欢迎。然而,正是因为所有数据都存储在内存中,内存管理成为Redis使用过程中的关键挑战。内存溢出不仅会导致性能下降,还可能使整个服务崩溃。本文将全面介绍Redis内存释放的各种策略和技巧,帮助你有效管理内存资源,提升数据库性能,避免内存溢出问题的发生。

Redis内存管理基础

Redis内存模型

Redis将所有数据存储在内存中,通过自己的内存分配器(默认为jemalloc)来管理内存。Redis的内存使用可以分为以下几个部分:

1. 数据本身:存储的键值对占用的内存
2. 缓冲区:包括客户端输入/输出缓冲区、复制积压缓冲区等
3. 内部数据结构:如数据库中的键空间、过期时间字典等
4. 碎片:内存分配和释放过程中产生的内存碎片

Redis内存分配机制

Redis使用jemalloc作为默认内存分配器,它将内存划分为多个大小不同的块(arena),并根据请求的大小选择合适的块进行分配。这种机制可以减少内存碎片,提高内存使用效率。
  1. // Redis中jemalloc的配置示例(在redis.conf中)
  2. // 启用jemalloc的后台线程进行内存整理
  3. activedefrag yes
  4. active-defrag-ignore-bytes 100mb
  5. active-defrag-threshold-lower 10
  6. active-defrag-threshold-upper 100
  7. active-defrag-cycle-min 5
  8. active-defrag-cycle-max 75
复制代码

Redis内存溢出的原因和识别

常见内存溢出原因

1. 数据量过大:存储的数据超过了可用内存
2. 内存泄漏:程序错误导致内存无法释放
3. 大键值:单个键值占用过多内存
4. 客户端缓冲区溢出:客户端连接过多或输出缓冲区配置不当
5. 持久化操作:RDB快照或AOF重写过程中占用额外内存

识别内存问题
  1. # 查看Redis内存信息
  2. redis-cli INFO memory
  3. # 示例输出
  4. # Memory
  5. used_memory:104857600
  6. used_memory_human:100.00M
  7. used_memory_rss:134217728
  8. used_memory_rss_human:128.00M
  9. used_memory_peak:105696460
  10. used_memory_peak_human:100.76M
  11. used_memory_lua:33792
  12. used_memory_lua_human:33.00K
  13. mem_fragmentation_ratio:1.28
  14. mem_allocator:jemalloc-5.1.0
  15. active_defrag_running:0
复制代码

内存碎片率(mem_fragmentation_ratio)是RSS(物理内存)与已使用内存的比值,通常大于1。如果这个值过高(如大于1.5),说明内存碎片严重。
  1. # 检查内存碎片率
  2. redis-cli INFO memory | grep mem_fragmentation_ratio
  3. # 如果碎片率过高,可以尝试手动清理碎片
  4. redis-cli MEMORY PURGE
复制代码
  1. # 使用--bigkeys选项查找大键
  2. redis-cli --bigkeys
  3. # 使用MEMORY USAGE命令查看特定键的内存使用
  4. redis-cli MEMORY USAGE mykey
复制代码
  1. # 启用慢查询日志
  2. CONFIG SET slowlog-log-slower-than 10000  # 10ms
  3. CONFIG SET slowlog-max-len 128
  4. # 查看慢查询日志
  5. SLOWLOG GET
复制代码

Redis内存释放策略

1. 过期键自动删除

Redis提供了键过期机制,可以自动删除过期的键。
  1. # 设置键的过期时间(秒)
  2. SET key value EX 60
  3. # 设置键的过期时间(毫秒)
  4. PSETEX key 60000 value
  5. # 为已存在的键设置过期时间
  6. EXPIRE key 60
  7. PEXPIRE key 60000
复制代码

Redis使用三种过期删除策略的组合:

1. 定时删除:在设置键过期时间的同时,创建定时器,到期立即删除
2. 惰性删除:键被访问时检查是否过期,过期则删除
3. 定期删除:每隔一段时间,随机检查一些键,删除过期的键
  1. // Redis配置中的过期删除相关参数
  2. // 每秒检查的次数,默认10
  3. hz 10
  4. // 过期删除的最大CPU时间比例,默认25%
  5. maxmemory-policy allkeys-lru
复制代码

2. 手动删除键
  1. # 删除单个键
  2. DEL key1 key2 key3
  3. # 删除匹配模式的键(使用Lua脚本)
  4. EVAL "return redis.call('del', unpack(redis.call('keys', ARGV[1])))" 0 prefix:*
  5. # 注意:在生产环境中使用KEYS命令要谨慎,可能阻塞Redis
  6. # 更安全的方式是使用SCAN命令
复制代码

3. 使用SCAN代替KEYS
  1. # 使用SCAN迭代删除键
  2. local cursor = '0'
  3. repeat
  4.     local reply = redis.call('SCAN', cursor, 'MATCH', ARGV[1], 'COUNT', 100)
  5.     cursor = reply[1]
  6.     local keys = reply[2]
  7.     if #keys > 0 then
  8.         redis.call('DEL', unpack(keys))
  9.     end
  10. until cursor == '0'
复制代码

4. 使用FLUSHDB或FLUSHALL
  1. # 清空当前数据库
  2. FLUSHDB
  3. # 清空所有数据库
  4. FLUSHALL
  5. # 异步清空(Redis 4.0+)
  6. FLUSHDB ASYNC
  7. FLUSHALL ASYNC
复制代码

Redis配置优化

1. 内存策略配置

Redis提供了多种内存淘汰策略,当内存达到上限时自动删除键:
  1. # 设置最大内存限制
  2. CONFIG SET maxmemory 1gb
  3. # 设置内存淘汰策略
  4. CONFIG SET maxmemory-policy allkeys-lru
复制代码

1. noeviction:不淘汰键,内存不足时返回错误
2. allkeys-lru:在所有键中使用LRU算法淘汰最少使用的键
3. volatile-lru:在设置了过期时间的键中使用LRU算法淘汰
4. allkeys-random:随机淘汰键
5. volatile-random:在设置了过期时间的键中随机淘汰
6. volatile-ttl:淘汰即将过期的键
7. allkeys-lfu(Redis 4.0+):在所有键中使用LFU算法淘汰最少使用的键
8. volatile-lfu(Redis 4.0+):在设置了过期时间的键中使用LFU算法淘汰

2. 客户端缓冲区配置
  1. # 设置客户端输出缓冲区限制
  2. CONFIG SET client-output-buffer-limit "normal 0 0 0 slave 268435456 67108864 60 pubsub 33554432 8388608 60"
  3. # 解释:
  4. # normal: 普通客户端,0 0 0 表示无限制
  5. # slave: 从客户端,硬限制256MB,软限制64MB,持续60秒
  6. # pubsub: 发布订阅客户端,硬限制32MB,软限制8MB,持续60秒
复制代码

3. 内存碎片整理
  1. # 启用主动内存碎片整理(Redis 4.0+)
  2. CONFIG SET activedefrag yes
  3. CONFIG SET active-defrag-ignore-bytes 100mb
  4. CONFIG SET active-defrag-threshold-lower 10
  5. CONFIG SET active-defrag-threshold-upper 100
  6. CONFIG SET active-defrag-cycle-min 5
  7. CONFIG SET active-defrag-cycle-max 75
复制代码

数据结构优化

1. 选择合适的数据结构

不同的数据结构在内存使用上有所差异,选择合适的数据结构可以显著减少内存占用。

对于对象存储,使用Hash通常比String更节省内存:
  1. # 使用String存储对象
  2. SET user:1:name "John"
  3. SET user:1:email "john@example.com"
  4. SET user:1:age "30"
  5. # 使用Hash存储对象(更节省内存)
  6. HSET user:1 name "John" email "john@example.com" age "30"
复制代码

Redis会对小数据量的Hash、List和ZSet使用ziplist编码,对小数据量的Set使用intset编码,这些编码比原始结构更节省内存。
  1. # 设置Hash使用ziplist的阈值
  2. CONFIG SET hash-max-ziplist-entries 512
  3. CONFIG SET hash-max-ziplist-value 64
  4. # 设置List使用ziplist的阈值
  5. CONFIG SET list-max-ziplist-size -2  # -2表示8KB
  6. CONFIG SET list-compress-depth 0
  7. # 设置Set使用intset的阈值
  8. CONFIG SET set-max-intset-entries 512
  9. # 设置ZSet使用ziplist的阈值
  10. CONFIG SET zset-max-ziplist-entries 128
  11. CONFIG SET zset-max-ziplist-value 64
复制代码

2. 数据压缩

对于较大的值,可以考虑在客户端进行压缩:
  1. import redis
  2. import zlib
  3. import json
  4. r = redis.Redis()
  5. # 存储压缩数据
  6. data = {"key1": "value1", "key2": "value2", ...}
  7. compressed_data = zlib.compress(json.dumps(data).encode())
  8. r.set("compressed_data", compressed_data)
  9. # 读取并解压数据
  10. retrieved_data = r.get("compressed_data")
  11. decompressed_data = json.loads(zlib.decompress(retrieved_data).decode())
复制代码

3. 使用特殊编码

Redis提供了一些特殊编码来减少内存使用:
  1. # 使用位图存储布尔值
  2. SETBIT user:1000:notifications 0 1  # 设置第0位为1
  3. SETBIT user:1000:notifications 1 0  # 设置第1位为0
  4. GETBIT user:1000:notifications 0    # 获取第0位的值
  5. # 使用HyperLogLog进行基数统计
  6. PFADD pageviews:2023-01-01 user1 user2 user3
  7. PFCOUNT pageviews:2023-01-01
复制代码

持久化与内存管理

1. RDB持久化对内存的影响

RDB快照创建过程中会使用额外的内存,因为Redis会fork一个子进程来保存数据。
  1. # 配置RDB持久化
  2. save 900 1      # 900秒内至少有1个键改变则保存
  3. save 300 10     # 300秒内至少有10个键改变则保存
  4. save 60 10000   # 60秒内至少有10000个键改变则保存
  5. # 启用RDB压缩
  6. rdbcompression yes
  7. # 启用RDB校验和
  8. rdbchecksum yes
复制代码

2. AOF持久化对内存的影响

AOF重写过程中也会使用额外内存,但可以通过配置减少内存使用:
  1. # 启用AOF持久化
  2. appendonly yes
  3. # 配置AOF重写触发条件
  4. auto-aof-rewrite-percentage 100
  5. auto-aof-rewrite-min-size 64mb
  6. # 启用AOF压缩(Redis 4.0+)
  7. aof-use-rdb-preamble yes
复制代码

3. 持久化优化建议

1. 合理配置RDB和AOF:根据数据重要性和性能需求选择合适的持久化策略
2. 避免在高峰期进行持久化:可以手动调整持久化时间
3. 监控持久化过程中的内存使用:确保有足够内存支持持久化操作
  1. # 手动触发BGSAVE(后台保存)
  2. BGSAVE
  3. # 手动触发AOF重写
  4. BGREWRITEAOF
复制代码

监控与预警

1. 内存监控指标
  1. # 定期检查内存使用情况
  2. redis-cli INFO memory | grep -E "used_memory|mem_fragmentation_ratio"
  3. # 监控键空间信息
  4. redis-cli INFO keyspace
复制代码

2. 使用Redis慢查询日志
  1. # 配置慢查询日志
  2. CONFIG SET slowlog-log-slower-than 10000  # 10ms
  3. CONFIG SET slowlog-max-len 128
  4. # 查看慢查询日志
  5. SLOWLOG GET 10
复制代码

3. 使用Redis监控工具
  1. # 使用redis-py进行监控示例
  2. import redis
  3. import time
  4. r = redis.Redis()
  5. def monitor_memory():
  6.     while True:
  7.         info = r.info('memory')
  8.         used_memory = info['used_memory']
  9.         max_memory = info.get('maxmemory', 0)
  10.         fragmentation_ratio = info['mem_fragmentation_ratio']
  11.         
  12.         print(f"Used Memory: {used_memory / (1024*1024):.2f} MB")
  13.         print(f"Max Memory: {max_memory / (1024*1024):.2f} MB")
  14.         print(f"Fragmentation Ratio: {fragmentation_ratio:.2f}")
  15.         
  16.         # 如果内存使用超过80%或碎片率超过1.5,发出警告
  17.         if max_memory > 0 and used_memory / max_memory > 0.8:
  18.             print("WARNING: Memory usage exceeds 80%!")
  19.         
  20.         if fragmentation_ratio > 1.5:
  21.             print("WARNING: High memory fragmentation!")
  22.         
  23.         time.sleep(60)  # 每分钟检查一次
  24. monitor_memory()
复制代码

4. 设置预警机制
  1. # 使用Redis的发布订阅功能实现简单预警
  2. # 在监控脚本中
  3. redis-cli PUBLISH memory_alert "High memory usage detected"
  4. # 在预警服务中
  5. redis-cli SUBSCRIBE memory_alert
复制代码

实际案例分析

案例1:缓存雪崩导致的内存溢出

问题描述:大量缓存同时失效,导致请求直接打到数据库,同时Redis尝试重新加载大量数据,导致内存溢出。

解决方案:

1. 添加缓存过期时间随机性:
  1. import random
  2. import redis
  3. r = redis.Redis()
  4. def set_with_random_expiry(key, value, base_expiry):
  5.     # 在基础过期时间上添加随机性,避免同时过期
  6.     random_factor = random.uniform(0.8, 1.2)
  7.     expiry = int(base_expiry * random_factor)
  8.     r.setex(key, expiry, value)
复制代码

1. 实现缓存预热:
  1. def warm_up_cache():
  2.     # 在系统低峰期提前加载热点数据
  3.     hot_keys = get_hot_keys_from_database()
  4.     for key in hot_keys:
  5.         data = fetch_data_from_database(key)
  6.         set_with_random_expiry(key, data, 3600)  # 1小时基础过期时间
复制代码

1. 使用内存淘汰策略:
  1. CONFIG SET maxmemory 4gb
  2. CONFIG SET maxmemory-policy allkeys-lru
复制代码

案例2:大键值导致的内存问题

问题描述:单个键值存储了大量数据,导致内存分配不均,甚至内存溢出。

解决方案:

1. 识别大键:
  1. # 使用--bigkeys选项
  2. redis-cli --bigkeys
  3. # 使用MEMORY USAGE命令
  4. redis-cli MEMORY USAGE large_key
复制代码

1. 拆分大键:
  1. def split_large_key(r, large_key, chunk_size=1000):
  2.     # 获取大键的值
  3.     large_value = r.get(large_key)
  4.    
  5.     # 假设large_value是一个列表,将其拆分为多个小键
  6.     items = json.loads(large_value)
  7.    
  8.     # 删除原键
  9.     r.delete(large_key)
  10.    
  11.     # 创建多个小键
  12.     for i in range(0, len(items), chunk_size):
  13.         chunk = items[i:i+chunk_size]
  14.         chunk_key = f"{large_key}:{i//chunk_size}"
  15.         r.set(chunk_key, json.dumps(chunk))
  16.         
  17.         # 如果需要,设置过期时间
  18.         r.expire(chunk_key, 3600)
复制代码

1. 使用数据分片:
  1. def shard_data(r, key, data, num_shards=10):
  2.     # 使用哈希函数确定数据分片
  3.     for item in data:
  4.         shard_id = hash(str(item)) % num_shards
  5.         shard_key = f"{key}:shard:{shard_id}"
  6.         r.sadd(shard_key, item)
  7.         r.expire(shard_key, 3600)
复制代码

案例3:内存碎片过高问题

问题描述:长期运行的Redis实例内存碎片率持续升高,导致内存使用效率低下。

解决方案:

1. 启用主动碎片整理:
  1. CONFIG SET activedefrag yes
  2. CONFIG SET active-defrag-ignore-bytes 100mb
  3. CONFIG SET active-defrag-threshold-lower 10
  4. CONFIG SET active-defrag-threshold-upper 100
  5. CONFIG SET active-defrag-cycle-min 5
  6. CONFIG SET active-defrag-cycle-max 75
复制代码

1. 重启Redis实例(如果允许):
  1. # 保存当前数据
  2. SAVE
  3. # 重启Redis
  4. sudo systemctl restart redis
  5. # 或者使用SHUTDOWN命令
  6. SHUTDOWN SAVE
复制代码

1. 手动触发碎片整理:
  1. import redis
  2. import time
  3. r = redis.Redis()
  4. def manual_defrag():
  5.     # 获取当前内存信息
  6.     info = r.info('memory')
  7.     fragmentation_ratio = info['mem_fragmentation_ratio']
  8.    
  9.     print(f"Current fragmentation ratio: {fragmentation_ratio:.2f}")
  10.    
  11.     # 如果碎片率过高,尝试手动清理
  12.     if fragmentation_ratio > 1.5:
  13.         print("High fragmentation detected, attempting to defragment...")
  14.         
  15.         # 执行MEMORY PURGE命令
  16.         r.execute_command('MEMORY PURGE')
  17.         
  18.         # 等待一段时间让Redis整理内存
  19.         time.sleep(60)
  20.         
  21.         # 再次检查碎片率
  22.         info = r.info('memory')
  23.         new_fragmentation_ratio = info['mem_fragmentation_ratio']
  24.         print(f"New fragmentation ratio: {new_fragmentation_ratio:.2f}")
  25.         
  26.         if new_fragmentation_ratio > 1.5:
  27.             print("Warning: Fragmentation ratio still high after defragmentation.")
  28.             print("Consider restarting Redis instance.")
  29.     else:
  30.         print("Memory fragmentation is within acceptable range.")
  31. manual_defrag()
复制代码

最佳实践总结

1. 内存规划

• 预估内存需求:根据数据量和增长率规划足够的内存
• 设置maxmemory:始终设置maxmemory参数,避免系统级OOM
• 预留缓冲空间:为Redis预留20-30%的额外内存,应对突发情况
  1. # 设置最大内存为系统内存的70%
  2. CONFIG SET maxmemory 7gb  # 假设系统有10GB内存
复制代码

2. 数据管理

• 合理设置过期时间:为数据设置适当的过期时间,避免无限增长
• 避免大键值:拆分大键值,使用更合适的数据结构
• 使用数据压缩:对于大文本或二进制数据,考虑在客户端压缩
  1. # 设置键的过期时间示例
  2. def set_key_with_expiry(r, key, value, expiry_seconds):
  3.     r.setex(key, expiry_seconds, value)
  4. # 使用Hash代替多个String键
  5. def store_user_data(r, user_id, user_data):
  6.     r.hset(f"user:{user_id}", mapping=user_data)
  7.     r.expire(f"user:{user_id}", 86400)  # 24小时过期
复制代码

3. 监控与维护

• 定期监控内存使用:建立内存监控机制,及时发现异常
• 定期检查碎片率:监控内存碎片率,必要时进行整理
• 定期审查数据:检查无用数据,及时清理
  1. # 定期检查脚本示例
  2. def regular_health_check(r):
  3.     # 检查内存使用
  4.     memory_info = r.info('memory')
  5.     used_memory = memory_info['used_memory']
  6.     max_memory = memory_info.get('maxmemory', 0)
  7.     fragmentation_ratio = memory_info['mem_fragmentation_ratio']
  8.    
  9.     print(f"Memory Usage: {used_memory / (1024*1024):.2f} MB")
  10.     print(f"Fragmentation Ratio: {fragmentation_ratio:.2f}")
  11.    
  12.     # 检查大键
  13.     big_keys = []
  14.     for key in r.scan_iter(match="*", count=1000):
  15.         try:
  16.             size = r.memory_usage(key)
  17.             if size > 1024 * 1024:  # 大于1MB的键
  18.                 big_keys.append((key, size))
  19.         except:
  20.             pass
  21.    
  22.     if big_keys:
  23.         print("Big keys detected:")
  24.         for key, size in big_keys:
  25.             print(f"  {key}: {size / (1024*1024):.2f} MB")
  26.    
  27.     # 检查过期键
  28.     total_keys = 0
  29.     expired_keys = 0
  30.     for key in r.scan_iter(match="*", count=1000):
  31.         total_keys += 1
  32.         ttl = r.ttl(key)
  33.         if ttl == -1:  # 没有过期时间
  34.             expired_keys += 1
  35.    
  36.     print(f"Keys without expiration: {expired_keys}/{total_keys} ({expired_keys/total_keys*100:.1f}%)")
  37.    
  38.     # 返回健康状态
  39.     health_status = {
  40.         'memory_usage_ok': max_memory == 0 or used_memory / max_memory < 0.8,
  41.         'fragmentation_ok': fragmentation_ratio < 1.5,
  42.         'big_keys_count': len(big_keys),
  43.         'keys_without_expiry_ratio': expired_keys / total_keys if total_keys > 0 else 0
  44.     }
  45.    
  46.     return health_status
复制代码

4. 应急处理

• 制定应急方案:为内存溢出等紧急情况制定处理流程
• 准备回滚策略:在执行可能影响内存的操作前,准备回滚方案
• 定期备份:定期备份数据,以防意外情况
  1. # 应急处理脚本示例
  2. def emergency_memory_management(r):
  3.     # 获取当前内存状态
  4.     memory_info = r.info('memory')
  5.     used_memory = memory_info['used_memory']
  6.     max_memory = memory_info.get('maxmemory', 0)
  7.    
  8.     # 如果内存使用超过90%,执行紧急清理
  9.     if max_memory > 0 and used_memory / max_memory > 0.9:
  10.         print("CRITICAL: Memory usage exceeds 90%, executing emergency cleanup...")
  11.         
  12.         # 1. 清理过期键
  13.         print("Cleaning expired keys...")
  14.         for key in r.scan_iter(match="*", count=1000):
  15.             ttl = r.ttl(key)
  16.             if ttl > 0 and ttl < 3600:  # 1小时内过期的键
  17.                 r.delete(key)
  18.         
  19.         # 2. 清理特定模式的键
  20.         print("Cleaning temporary keys...")
  21.         for key in r.scan_iter(match="temp:*", count=1000):
  22.             r.delete(key)
  23.         
  24.         # 3. 如果仍然内存不足,考虑清理LRU键
  25.         memory_info = r.info('memory')
  26.         used_memory = memory_info['used_memory']
  27.         if max_memory > 0 and used_memory / max_memory > 0.85:
  28.             print("Still high memory usage, considering LRU eviction...")
  29.             # 这里的策略取决于你的业务需求
  30.             # 可能需要手动删除一些不那么重要的键
  31.             
  32.         # 4. 最后手段:保存数据并重启
  33.         memory_info = r.info('memory')
  34.         used_memory = memory_info['used_memory']
  35.         if max_memory > 0 and used_memory / max_memory > 0.95:
  36.             print("CRITICAL: Memory still too high, saving data and restarting...")
  37.             r.save()
  38.             # 这里需要外部机制重启Redis
  39.             # 例如:os.system("sudo systemctl restart redis")
复制代码

结语

Redis内存管理是一个系统性工程,需要从规划、配置、监控、优化等多个维度进行综合考虑。通过本文介绍的各种技巧和策略,你可以有效地管理Redis内存,避免内存溢出问题,提升数据库性能。记住,预防胜于治疗,建立完善的监控机制和预警系统,定期进行内存健康检查,是确保Redis稳定运行的关键。

随着Redis版本的更新,新的内存管理功能不断被引入,如Redis 6.0的多线程I/O、Redis 7.0的函数功能等,都为内存管理提供了更多可能性。持续关注Redis的最新发展,不断优化你的内存管理策略,将帮助你充分发挥Redis的高性能优势。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则