新手必踩 Redis 10 个低级坑:过期时间、KEYS 命令、持久化误区

新手必踩 Redis 10 个低级坑:过期时间、KEYS 命令、持久化误区

Redis 用起来简单,踩起坑来要命。很多人以为自己会用 Redis,其实只是在"会用"和"会坑自己"之间反复横跳。以下这 10 个坑,每一个都是生产环境用真金白银换来的教训。


一、过期时间:看似简单,处处是陷阱

坑1:SET/DEL 会清除过期时间,INCR 不会

这是最容易混淆的点。

bash 复制代码
bash
set mykey hello ex 300    # 过期时间 300s
ttl mykey                 # 294
set mykey world           # 覆盖了!过期时间被清除
ttl mykey                 # -1(永不过期)

但用 INCRLPUSHHSET 修改值时,过期时间不会被清除:

bash 复制代码
bash
set counter 1 ex 300
incr counter              # 值变了,过期时间还在
ttl counter               # 依然在倒计时

记住这个规则:覆盖整个 value 的命令(SET/DEL/GETSET/PERSIST)会清过期时间;局部修改的命令(INCR/LPUSH/HSET)不会。


坑2:EXPIRE 设置负数 = 直接删除

bash 复制代码
bash
expire mykey -1           # key 立即被删除
expireat mykey 10000      # 时间戳是过去的,key 也被删除

这不是 bug,是特性。但很多人在代码里算错了时间戳,结果 key 莫名消失,排查半天找不到原因。


坑3:RENAME 会继承过期时间

bash 复制代码
bash
set key_a "a" ex 300
set key_b "b" ex 600
rename key_a key_b        # key_b 现在继承 key_a 的 300s 过期时间

老 key 的过期时间会"搬家"到新 key 上,不管新 key 原本有没有过期时间。这个特性很少有人注意,但在数据迁移场景下可能引发意外。


坑4:所有 key 同时过期 → 缓存雪崩

系统初始化时批量写入缓存,所有 key 的 TTL 都设成 3600 秒。到点集体失效,所有请求瞬间穿透到数据库,连接池直接打满。

解法:加随机偏移。

ini 复制代码
python
import random
ttl = 3600 + random.randint(-300, 300)  # ±5分钟随机
r.set(key, value, ex=ttl)

别小看这几分钟的随机值,它能把"集中爆破"变成"均匀释放"。


坑5:Key 忘设 TTL → 内存黑洞

代码里 set user:1001 json 写得飞起,就是不带 EX。半年后 Redis 内存从 2G 涨到 16G,INFO keyspace 显示 key 数量不断增加,但业务说数据量没变------全是没过期的僵尸 key。

写缓存时养成习惯:SET 必须带 EX。

sql 复制代码
bash
SET user:1001 '{"name":"Alice"}' EX 3600

批量排查无 TTL 的 key:

bash 复制代码
bash
redis-cli --scan --pattern "*" | while read key; do
  ttl=$(redis-cli TTL "$key")
  [ "$ttl" = "-1" ] && echo "永不过期: $key"
done

二、KEYS 命令:生产环境的定时炸弹

坑6:KEYS * 能把生产打挂

Redis 是单线程的。KEYS * 是 O(N) 阻塞命令,会从头到尾扫描所有 key,期间所有其他请求全部排队。

数据量小时感觉不到,百万级 key 时,一次 KEYS * 可能阻塞 Redis 数秒。这数秒内,支付超时、订单失败、监控报警,一连串雪崩。

有真实案例:运营同学执行 KEYS coupon:*,800 万 key,阻塞 680ms,直接导致支付接口全线超时,活动被迫暂停。

替代方案:SCAN。

sql 复制代码
bash
SCAN 0 MATCH user:* COUNT 100
# 返回游标 + 一批 key,下次用游标继续,非阻塞

Python 完整遍历:

ini 复制代码
python
import redis
r = redis.Redis(host='127.0.0.1', password='yourpassword', decode_responses=True)
cursor = 0
while True:
    cursor, keys = r.scan(cursor=cursor, match='user:*', count=100)
    for key in keys:
        print(key)
    if cursor == 0:
        break
危险命令 危险原因 安全替代
KEYS * O(N) 阻塞全量遍历 SCAN
HGETALL 大 Hash 全量读取 HSCAN
SMEMBERS 大 Set 全量读取 SSCAN
LRANGE key 0 -1 大 List 全量读取 分页 LRANGE
FLUSHALL 阻塞删除所有 key FLUSHALL ASYNC(Redis 4.0+)

生产建议:在 redis.conf 里直接禁用危险命令。

lua 复制代码
rename-command KEYS ""
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command CONFIG ""

三、持久化:RDB 和 AOF 的经典误区

坑7:只用 RDB,重启丢几分钟数据

RDB 是定时快照,默认触发规则是:

yaml 复制代码
save 3600 1     # 1小时内1次写操作
save 300 100    # 5分钟内100次写操作
save 60 10000   # 1分钟内10000次写操作

崩溃发生在两次快照之间,这段时间的数据直接丢失。而且 RDB 文件可能因磁盘故障损坏,没有备份就彻底完蛋。

解法:必须开 AOF,或者用混合持久化(Redis 4.0+ 推荐)。

bash 复制代码
bash
appendonly yes
appendfsync everysec     # 推荐:每秒同步,最多丢1秒
aof-use-rdb-preamble yes # 混合持久化,重启更快

坑8:AOF 文件无限膨胀,磁盘写满

AOF 记录每一条写命令,同一个 key 改 1000 次就记 1000 条。时间一长文件可能几十 GB,但实际有效数据只有几百 MB。

解法:配置自动 rewrite。

arduino 复制代码
bash
auto-aof-rewrite-percentage 100   # 比上次大100%时触发
auto-aof-rewrite-min-size 64mb    # 至少64MB才触发

紧急手动触发:redis-cli BGREWRITEAOF


坑9:BGSAVE 失败,RDB 静默停止

INFO persistence 显示:

vbnet 复制代码
rdb_last_bgsave_status:err
rdb_last_bgsave_error:Can't save in background: fork: Cannot allocate memory

Redis 执行 BGSAVE 时需要 fork 子进程,而 Linux 默认 overcommit_memory=0,内存高时拒绝 fork,导致快照失败、持久化静默停止------你以为数据安全,其实早就不存了。

解法:Redis 官方推荐配置。

bash 复制代码
bash
echo 1 > /proc/sys/vm/overcommit_memory           # 临时生效
echo "vm.overcommit_memory = 1" >> /etc/sysctl.conf  # 永久生效
sysctl -p

坑10:不设置密码 + 暴露公网 = 等着被清库

没有密码的 Redis 一旦被扫描到,轻则被清空数据库,重则被植入挖矿程序。这不是假设,是每天都在发生的事。

最低配置

bash 复制代码
bash
requirepass yourStrongPassword
bind 127.0.0.1 ::1          # 只允许本地访问

生产环境还要考虑 SSL/TLS 加密、防火墙白名单、定期更新补丁。


总结:一张表记住所有坑

编号 一句话解法
1 SET 清过期时间,INCR 不清 区分"覆盖"和"局部修改"
2 EXPIRE 负数 = 删 key 算准时间戳
3 RENAME 继承过期时间 迁移时注意
4 统一 TTL → 雪崩 加随机偏移
5 忘设 TTL → 内存爆炸 SET 必带 EX
6 KEYS * 打挂生产 用 SCAN 替代
7 只用 RDB 丢数据 开 AOF 或混合持久化
8 AOF 无限膨胀 配置 auto-rewrite
9 BGSAVE 静默失败 设置 overcommit_memory=1
10 无密码 + 公网暴露 必须设密码 + 绑定内网

Redis 不难,难的是知道哪些"简单操作"背后藏着炸弹。把这 10 个坑刻进肌肉记忆里,比多背 100 个命令有用得多。

相关推荐
Csvn3 小时前
Python 两大经典坑点 —— 可变默认参数 & 闭包延迟绑定
后端·python
Csvn3 小时前
定时任务 — Crontab 从入门到生产实战
后端
ServBay4 小时前
Laravel Herd MCP 的替代,多语言与跨平台的 AI 本地开发选择
后端·ai编程·mcp
GoGeekBaird5 小时前
Prompt、Context、Harness 工程全景图
后端
SimonKing5 小时前
艹,维护AI写的代码,我心态崩了......
java·后端·程序员
AskHarries5 小时前
MCP 基础:Server、Tool、Resource 和 Prompt
后端·程序员
长栎5 小时前
你写的 DCL 单例,在反序列化面前就是个弟弟——单例模式的破局与重建
后端