一、结论先给
Redis 用得好不好,看三件事:有没有设过期时间、有没有用 KEYS、有没有给危险命令改名。这三个占了线上 Redis 事故的绝大多数。
| 铁律 | 原因 |
|---|---|
| 所有缓存 key 必须设 TTL | 不设就是内存泄漏,迟早 OOM |
生产禁用 KEYS * |
单线程阻塞,几百万 key 能卡死十几秒 |
FLUSHALL/FLUSHDB/KEYS/CONFIG 必须改名或禁用 |
误执行就是灾难 |
| value 不要超过 10KB | 大 key 是网络和内存的双重负担 |
| 批量操作用 pipeline / MGET | 一条一条发,网络往返是主要耗时 |
👉 命令记不全查 Linux 命令速查;Redis 日志报错分析用 日志分析工具。
二、连接与基础
redis-cli -h 127.0.0.1 -p 6379 -a 密码
redis-cli -h 127.0.0.1 -p 6379 -a 密码 --no-auth-warning # 去掉明文密码警告
redis-cli -n 1 # 选 1 号库(默认 16 个库,编号 0-15)
redis-cli --scan --pattern 'user:*' # 安全遍历
redis-cli --bigkeys # 找大 key(运维必用)
redis-cli --hotkeys # 找热 key(需 maxmemory-policy 为 LFU)
redis-cli --latency # 延迟采样
redis-cli --stat # 实时状态
redis-cli -a pwd INFO | grep -E "used_memory_human|connected_clients|instantaneous_ops_per_sec"
PING -- PONG
SELECT 1 -- 切库(集群模式不支持)
DBSIZE -- 当前库 key 数
INFO server / memory / stats / replication / keyspace
CONFIG GET maxmemory
CONFIG GET maxmemory-policy
SLOWLOG GET 10 -- 慢命令(默认超过 10 毫秒记录)
SLOWLOG LEN
CLIENT LIST -- 客户端连接
MONITOR -- ⚠️ 实时打印所有命令,线上别用,会打满输出且严重降性能
三、五种数据类型与适用场景
3.1 String(最常用)
SET k v -- 设值
SET k v EX 3600 -- 60 分钟后过期 ⭐常用
SET k v PX 5000 -- 毫秒
SETNX k v -- 不存在才设(分布式锁基础)
SET k v NX EX 30 -- 原子:不存在才设 + 30 秒过期 ⭐分布式锁标准写法
SETEX k 3600 v -- 设值并指定秒数
GETSET k v2 -- 设新值返回旧值
GET k
MGET k1 k2 k3 -- 批量取 ⭐比三次 GET 快得多
MSET k1 v1 k2 v2
INCR counter -- +1(原子,计数器/限流)
INCRBY counter 10
DECR counter
APPEND k "more"
STRLEN k
EXPIRE k 3600 -- 设过期(秒)
PEXPIRE k 60000 -- 毫秒
TTL k -- 剩余秒数;-1 永不过期,-2 已不存在
PERSIST k -- 去掉过期
典型场景:缓存对象 JSON、计数器、分布式锁、限流、Token。
3.2 Hash(对象字段,省内存)
HSET user:1 name afei age 30 city beijing
HGET user:1 name
HMGET user:1 name age
HGETALL user:1 -- ⚠️ 字段多时很慢,用 HSCAN
HKEYS user:1 / HVALS user:1
HLEN user:1
HINCRBY user:1 age 1
HEXISTS user:1 email
HDEL user:1 city
HSCAN user:1 0 COUNT 100 -- 安全遍历大 hash
典型场景:购物车(cart:userId → 商品id 数量)、用户属性、配置项。
Hash vs String 存对象:
| 方式 | 优点 | 缺点 |
|---|---|---|
| String 存 JSON | 一次取完、简单 | 改一个字段要整体覆盖 |
| Hash 存字段 | 可单字段读写、更省内存(ziplist 编码) | 字段上千时 HGETALL 要小心 |
3.3 List(队列、栈、时间线)
LPUSH queue a b c -- 左边插入
RPUSH queue x -- 右边插入
LPOP queue -- 左边弹出
RPOP queue
BLPOP queue 30 -- 阻塞弹出,30 秒超时 ⭐消息队列常用
BRPOP queue 30
LRANGE queue 0 -1 -- ⚠️ 全量取,大列表会卡
LRANGE queue 0 9 -- 取前 10
LLEN queue
LTRIM queue 0 99 -- 只保留最近 100 条 ⭐做时间线
LINDEX queue 0
LREM queue 1 "a" -- 删 1 个值为 a 的元素
典型场景:消息队列(LPUSH + BRPOP)、最新动态列表、限流滑动窗口。
⚠️ Redis 5.0 起有了 Stream,做消息队列比 List 更合适(支持消费组、ACK、持久化)。
3.4 Set(去重、集合运算)
SADD tags:1 java redis mysql
SMEMBERS tags:1 -- ⚠️ 大集合慢,用 SSCAN
SISMEMBER tags:1 java -- 是否存在 ⭐
SCARD tags:1 -- 元素数
SREM tags:1 mysql
SPOP tags:1 -- 随机弹出
SRANDMEMBER tags:1 3 -- 随机取 3 个(不删)
SINTER set1 set2 -- 交集(共同好友)
SUNION set1 set2 -- 并集
SDIFF set1 set2 -- 差集
SINTERSTORE result set1 set2
SSCAN tags:1 0 COUNT 100
典型场景:标签、去重、共同好友、抽奖、签到(BitMap 更省)。
3.5 ZSet(有序集合,排行榜)
ZADD rank 100 "player1" 200 "player2"
ZRANGE rank 0 9 WITHSCORES -- 分数升序前 10
ZREVRANGE rank 0 9 WITHSCORES -- 降序前 10 ⭐排行榜
ZRANGEBYSCORE rank 100 200 WITHSCORES -- 分数区间
ZSCORE rank player1 -- 查分数
ZRANK rank player1 -- 升序排名(从 0)
ZREVRANK rank player1 -- 降序排名 ⭐
ZINCRBY rank 10 player1 -- 加分
ZCARD rank -- 成员数
ZCOUNT rank 100 200 -- 区间内数量
ZREM rank player1
ZREMRANGEBYRANK rank 0 99 -- 删排名前 100(保留榜单长度)
ZREMRANGEBYSCORE rank 0 50 -- 删分数区间的
典型场景:排行榜、延迟队列(score 存执行时间)、优先级队列、时间窗口限流。
四、通用 key 操作(含高危)
KEYS user:* -- ❌ 生产禁用!单线程全量遍历,数据量大时卡死
SCAN 0 MATCH user:* COUNT 100 -- ✅ 游标方式分批遍历
TYPE k -- key 类型
EXISTS k
DEL k1 k2
UNLINK k1 k2 -- ⭐异步删除,大 key 用这个不阻塞
RENAME k k2
RENAMENX k k2
DUMP k / RESTORE k 0 "..." -- 迁移用
MOVE k 1
OBJECT ENCODING k -- 看内部编码(ziplist/quicklist 等)
MEMORY USAGE k -- key 占多少内存 ⭐排查大 key
SCAN 的正确用法(游标迭代,注意可能返回重复,要去重):
#!/usr/bin/env bash
cursor=0
while : ; do
read -r cursor keys < <(redis-cli -a pwd SCAN $cursor MATCH 'user:*' COUNT 500 | head -1 >/dev/null; echo)
# 实际用 redis-cli --scan 更简单
done
# 推荐直接用:
redis-cli -a pwd --scan --pattern 'user:*' | head -1000
五、批量操作与 pipeline
MGET k1 k2 k3 ... -- 批量读
MSET k1 v1 k2 v2 -- 批量写
pipeline(把多条命令打包一次发,减少网络 RTT):
# 用 redis-cli 的 pipe 模式批量导入
cat <<'EOF' | redis-cli -a pwd --pipe
SET a 1
SET b 2
SET c 3
EOF
# 从文件批量删除
redis-cli -a pwd --scan --pattern 'tmp:*' | xargs -L 1000 redis-cli -a pwd UNLINK
各语言客户端都支持 pipeline,比如 Java:
List<Object> res = redisTemplate.executePipelined((RedisCallback<Object>) conn -> {
for (String k : keys) conn.get(k.getBytes(StandardCharsets.UTF_8));
return null;
});
⚠️ pipeline 不是原子操作,要原子请用 MULTI/EXEC 或 Lua 脚本。
六、Lua 脚本(原子复合操作)
EVAL "return redis.call('GET', KEYS[1])" 1 mykey
-- 原子扣减库存(不存在则返回 -1,不足返回 -2)
EVAL "
local stock = tonumber(redis.call('GET', KEYS[1]) or '-1')
if stock < 0 then return -1 end
if stock < tonumber(ARGV[1]) then return -2 end
redis.call('DECRBY', KEYS[1], ARGV[1])
return stock - tonumber(ARGV[1])
" 1 sku:1 1
Lua 里不要用 KEYS 命令、不要写长循环(会阻塞),脚本执行是原子的,这是它相对 pipeline 的核心优势。
七、过期策略与内存淘汰
Redis 删过期 key 用两种方式配合:
| 方式 | 说明 |
|---|---|
| 惰性删除 | 访问时才检查过期并删 |
| 定期删除 | 每 100ms 随机抽查一部分 key(默认每秒 10 次) |
所以过期 key 不一定立刻释放内存,这是"设了 TTL 但内存还是涨"的原因之一。
内存到上限时的淘汰策略:
CONFIG GET maxmemory
CONFIG GET maxmemory-policy
CONFIG SET maxmemory 4gb
CONFIG SET maxmemory-policy allkeys-lru
| 策略 | 含义 | 适用 |
|---|---|---|
noeviction |
不淘汰,写直接报错 | 默认;缓存场景千万不要用 |
allkeys-lru |
全体 key 里淘汰最久未用 | 纯缓存首选 |
volatile-lru |
只在设了 TTL 的 key 里淘汰 | 混合存储 |
allkeys-lfu |
全体淘汰最少使用 | 有热点且长期稳定的场景 |
volatile-lfu |
同上但限设了 TTL 的 | |
allkeys-random |
随机淘汰 | |
volatile-ttl |
淘汰剩余生存时间最短的 |
maxmemory 一定要设(建议物理内存的 60-70%),否则可能把机器内存吃光被 OOM。
八、危险命令:改名或禁用
在 redis.conf 里把这些命令改名成随机字符串,等于禁用:
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command KEYS "e8f3a1c9keys"
rename-command CONFIG "b2d7f0config"
rename-command SHUTDOWN "a1c3e5shutdown"
rename-command DEBUG ""
rename-command SAVE "" # 用 BGSAVE
"" 表示彻底禁用(执行时返回 unknown command)。
改名后运维脚本要同步改,或者用 redis-cli 时带上新名字。
九、常见误区
- 用
KEYS *查有多少 key:生产环境一发就是事故,用DBSIZE或SCAN。 - 缓存不设 TTL:内存只涨不降,最终写满触发淘汰或 OOM。
- 把 Redis 当数据库用且不开启持久化:重启数据全没。
- 大 key(几 MB 的 String、百万成员的 Set):删除、迁移、网络传输全都是坑,用
--bigkeys定期扫。 - 一个 Redis 实例塞几百个业务:互相影响,一个业务的
KEYS拖垮全部。 DEL删百万成员的集合:同步删除阻塞,要用UNLINK(异步)。- key 名太长:
user:profile:detail:2026:09:23:1234567这种,key 本身占的内存比 value 还多。
十、几条纪律
- 所有缓存 key 必设 TTL,且加随机抖动(防同一秒集体过期)。
- 生产禁用
KEYS,遍历一律SCAN;FLUSHALL/CONFIG改名。 maxmemory设为物理内存 60-70%,策略选allkeys-lru(纯缓存)。- 单个 value 控制在 10KB 以内;大集合拆分(如按用户 id 分桶)。
- 批量用
MGET/pipeline;复合操作要原子就用 Lua。 - 定期
redis-cli --bigkeys、--hotkeys巡检,发现大 key 提前拆。 - 删除大 key 用
UNLINK不用DEL。
十一、延伸阅读
👉 相关在线工具:Linux 命令速查 · 日志分析,免登录、浏览器内处理。