Redis 从入门到精通:内存管理与淘汰策略

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。

Redis 把数据存在内存中,速度快如闪电,但内存是昂贵的硬件资源。如果不加管理,内存很快会被撑满,Redis 不是 OOM 宕机,就是拒绝所有写入。所以,学会 Redis 如何"断舍离"------过期键的清理、内存满时的淘汰策略,以及日常的内存优化------是生产级运维的必修课。

本文带你彻底搞懂这三件事:过期数据怎么删?内存满了踢谁?怎样让同样多的数据占用更少内存?全程 Python 实操,看完就能动手。

1. 过期删除策略:过期键怎么被清理的?

给键设置 TTL 后,时间一到它就应该消失。但 Redis 并非实时监控每一个键的倒计时,那样 CPU 开销太大。它用了两种策略的组合:惰性删除定期删除

1.1 惰性删除(Lazy Expiration)

当客户端尝试访问一个键时,Redis 先检查它是否过期。如果过期了,就立即删除并返回空。简单直接,CPU 开销小,但问题在于:如果键过期后从未被访问,就会一直占着内存。

1.2 定期删除(Active Expiration)

Redis 每隔 100 毫秒(默认)随机抽取一部分设置了过期时间的键,检查并删除其中过期的。这样,即使冷数据也能被慢慢清理掉。但因为是随机抽检,没法保证所有过期键都立马被清掉,可能会留一些"漏网之鱼"。

1.3 两者配合,天衣无缝

Redis 内部就是靠惰性+定期双管齐下,平衡了性能和内存。我们来用 Python 观察一下:

bash 复制代码
import redis
import time

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# 设置一个 2 秒过期的键
r.set('temp', 'I will expire', ex=2)
print(r.get('temp'))  # 存在

time.sleep(3)         # 等它过期
print(r.get('temp'))  # 惰性删除:被访问时删除,返回 None

输出:

对于定期删除,我们可以设置大量短期过期键,然后持续写入,观察内存不会无限增长。

1.4 动手观察定期删除

bash 复制代码
# 写入 10000 个 3 秒过期的键,观察内存变化
import time
for i in range(10000):
    r.setex(f'expire:{i}', 3, 'x' * 1024)  # 1KB 值

time.sleep(5)  # 所有键都过期了
info = r.info('memory')
print(f"used_memory_human: {info['used_memory_human']}")
# 大部分键已被定期删除回收,但可能还有少量残留,访问时会惰性删掉

结论:Redis 会在后台主动清理大部分过期键,冷数据也不会永远占用内存。

2. 内存淘汰策略:内存满时踢谁?

即使过期键被清理,如果写入速度远超删除速度,或者所有键都没设过期时间,内存迟早会达到上限。maxmemory 配置指定 Redis 能使用的最大内存,达到后怎么办?由 maxmemory-policy 决定。

2.1 八大淘汰策略一览

LRU vs LFU:LRU 看"多久没被碰过",LFU 看"被碰的次数有多少"。LFU 更适合保留热门数据。

2.2 配置方式

可以在 redis.conf 中永久配置,也可运行时动态修改:

bash 复制代码
127.0.0.1:6379> CONFIG SET maxmemory 10mb
OK
127.0.0.1:6379> CONFIG SET maxmemory-policy allkeys-lru
OK
127.0.0.1:6379> CONFIG GET maxmemory*
1) "maxmemory"
2) "10485760"
3) "maxmemory-policy"
4) "allkeys-lru"

2.3 Python 实战:模拟内存满触发淘汰

我们将 maxmemory 设小(比如 2MB),写入大量数据直到触发淘汰,观察不同策略下的行为。

准备工作:先恢复配置,确保安全。

bash 复制代码
import redis
import sys

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# 设置 2MB 限制,策略为 allkeys-lru
r.config_set('maxmemory', '2mb')
r.config_set('maxmemory-policy', 'allkeys-lru')
print(f"maxmemory: {r.config_get('maxmemory')['maxmemory']} bytes")
print(f"policy: {r.config_get('maxmemory-policy')['maxmemory-policy']}")

# 清除旧数据,从头开始
r.flushdb()

# 写入数据直到发生错误或大量淘汰
i = 0
try:
    while True:
        r.set(f'key:{i}', 'x' * 1024)  # 每个键约1KB
        i += 1
        if i % 100 == 0:
            info = r.info('stats')
            print(f"已写入 {i} 个键, 淘汰次数: {info['evicted_keys']}", end='\r')
except redis.exceptions.ResponseError as e:
    print(f"\n写入异常: {e}")

info = r.info('stats')
print(f"总写入键数: {i}")
print(f"最终淘汰次数: {info['evicted_keys']}")
print(f"当前内存: {r.info('memory')['used_memory_human']}")

运行(基于 allkeys-lru):

bash 复制代码
maxmemory: 2097152 bytes
policy: allkeys-lru
已写入 2600 个键, 淘汰次数: 1320
总写入键数: 3021
最终淘汰次数: 1570
当前内存: 1.98M

可以看到,写入超过 3000 个键时,内存稳定在 2MB 附近,因为有 1570 个键被淘汰了。如果换成 noeviction 策略,几 KB 后就会直接报 OOM command not allowed when used memory > 'maxmemory'

2.4 不同策略的效果对比

你可以通过修改 maxmemory-policy 重新运行脚本,观察淘汰次数和遗留键的类型。例如 volatile-lru 因为没有设过期时间的键不会被淘汰,可能导致 OOM 错误。

bash 复制代码
# 尝试 volatile-lru:不给键设过期时间,看会发生什么
r.flushdb()
r.config_set('maxmemory-policy', 'volatile-lru')
for i in range(10000):
    r.set(f'perm:{i}', 'x' * 512)  # 不设过期
# 最终会 OOM,因为没有任何键符合淘汰条件

结论:生产环境几乎不用 noevictionvolatile-*,最常用的是 allkeys-lruallkeys-lfu

3. 内存优化技巧

3.1 使用 Hash 替代 String 存储对象

我们在第 3 篇学过,String 存 JSON 对象,每个键都有大量元数据开销。而 Hash 利用 ziplist 编码,多个字段紧凑存储在一个键里,内存占用大幅降低。

对比实验

bash 复制代码
# 方案A:10000 个 String 键,每个存用户信息
r.flushdb()
for i in range(10000):
    r.set(f'user:{i}', 'Alice,30,Beijing')
mem_a = r.info('memory')['used_memory_human']

r.flushdb()
# 方案B:1 个 Hash,10000 个字段
for i in range(10000):
    r.hset('users', f'user:{i}', 'Alice,30,Beijing')
mem_b = r.info('memory')['used_memory_human']

print(f"10000 String 键内存: {mem_a}")
print(f"1 个 Hash 内存: {mem_b}")

输出示例:

bash 复制代码
10000 String 键内存: 2.50M
1 个 Hash 内存: 856.00K

内存节省了约 3 倍!前提是字段数量和值大小在 ziplist 阈值内(默认 512 个字段,64 字节值)。如果超过阈值,Hash 会转为 hashtable,优势减弱。

3.2 短键名

键名本身也占内存。user:1001:profile:nameu:1001:pf:n 效果一样,但后者省一半键名内存。当然,可读性更重要,需要权衡。推荐使用 : 分隔的短命名空间,如 u:1001:name

3.3 合理设置过期时间

不要让永远不会再用的数据留在内存里。结合业务特点,为缓存、临时会话、验证码等设置准确的 TTL。

3.4 监控内存并报警

INFO memory 定期采集指标:

bash 复制代码
def memory_report(r):
    info = r.info('memory')
    print(f"已用内存: {info['used_memory_human']}")
    print(f"内存峰值: {info['used_memory_peak_human']}")
    print(f"内存碎片率: {info['mem_fragmentation_ratio']}")
    print(f"淘汰键数: {r.info('stats')['evicted_keys']}")

memory_report(r)

输出示例:

bash 复制代码
已用内存: 1.98M
内存峰值: 3.21M
内存碎片率: 1.05
淘汰键数: 1570

mem_fragmentation_ratio 远大于 1 时,说明内存碎片严重,可以考虑重启或使用 MEMORY PURGE(需 Redis 4.0+)。

3.5 用 MEMORY USAGE 精确衡量键大小

bash 复制代码
127.0.0.1:6379> SET short "hello"
OK
127.0.0.1:6379> MEMORY USAGE short
(integer) 72
127.0.0.1:6379> HSET user:1 name "Alice"
(integer) 1
127.0.0.1:6379> MEMORY USAGE user:1
(integer) 88

在 Python 中:

bash 复制代码
print(r.memory_usage('short'))  # 72

4. 动手试试

  1. 过期删除实验 :循环写入 10000 个 5 秒过期的键,观察 info memoryused_memory 的变化:5秒后是否会明显回落?不回落说明定期删除了但有些残留,再随机 GET 几个键触发惰性删除。

  2. 淘汰策略对比 :分别用 allkeys-lruallkeys-randomvolatile-ttl 运行 2MB 限制脚本,观察 evicted_keys 和留存键分布,理解每种策略的"踢人逻辑"。

  3. Hash 内存优化 :用 10000 个 String 和 10 个 Hash(每个 1000 字段)存储同样数据,对比 used_memory,验证内存节省效果。

  4. 监控脚本 :写一个定时任务,每隔 30 秒打印 Redis 内存使用情况和淘汰数量,当内存使用率超过 80% 时发出警告(打印红色 WARNING)。

预期效果:过期键过期后内存回落;LRU 策略保留最近写入的键;Hash 比 String 省 2~3 倍内存;监控脚本及时发现内存压力。

5. 总结

本文深入 Redis 内存管理的三个层面:

  • 过期删除:惰性删除保性能,定期删除清冷数据,双剑合璧无泄漏。

  • 淘汰策略 :8 种策略应对不同场景,allkeys-lru 是万能选,LFU 适合热点缓存。

  • 内存优化:Hash 替代 String 省内存,短键名、合理 TTL、碎片监控等工程技巧。

理解这些,你就能在有限的内存里装下更多数据,并让 Redis 在达到上限时优雅降级而不是直接崩掉。下一章,我们将挑战分布式锁,从 SETNX 到 Redlock 算法,实现跨进程的协同控制。

想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !

相关推荐
承渊政道1 小时前
【MySQL数据库学习】(MySQL内置函数)
数据库·学习·mysql·ubuntu·bash·数据库开发·数据库系统
weixin_307779131 小时前
在 Azure 上构建数据库路由与异构整合层:原理、方案与最佳实践
数据库·人工智能·后端·云计算·azure
爱基百客1 小时前
植物单细胞配受体数据库:PlantCellChatDB详解
数据库·单细胞·单细胞分析
A.说学逗唱的Coke10 小时前
【大模型专题】向量数据库深度解析:从原理到实战,构建企业级 AI 知识检索底座
数据库·人工智能
果丁智能10 小时前
智能锁赋能网约房民宿数字化管控:身份核验+远程授权,筑牢安全防线、降本增效
网络·数据库·人工智能·安全·智能家居
无敌的牛11 小时前
redis学习过程
数据库·redis·学习
IT北辰11 小时前
神通数据库管理系统V7.0.251210 for Windows(x86 64bit)安装部署
数据库·神通
北顾笙98011 小时前
MySQL-day2
数据库·mysql