Redis 完全指南:从 5 种数据结构到 Cluster 部署实战

Redis 是当代后端系统的"瑞士军刀" —— 缓存、计数器、排行榜、分布式锁、消息队列、限流、地理位置、布隆过滤器,几乎所有内存级别的数据结构需求都能用它解决。但要真用好 Redis,得理解每种数据结构的底层实现、持久化的代价、集群模式的差异。这篇文章把 Redis 从核心数据结构讲到生产部署,所有要点都配命令和实战。

5 种基础数据结构 + 3 种高级

String:不只是字符串

SET user:1 "{\"name\":\"mores\"}"
GET user:1

INCR counter:visits         # 原子计数
INCRBY counter:visits 10
DECR counter:visits

SETEX session:abc 3600 "user_id=1"   # 带过期时间
GETSET key value                      # 取旧值同时设新值,原子

MSET k1 v1 k2 v2            # 批量
MGET k1 k2

# 位操作(BitMap)
SETBIT visit:20260515 12345 1   # 用户 12345 今天访问了
BITCOUNT visit:20260515         # 今天多少用户访问
BITOP AND visit:7days visit:0 visit:1 ... # 7 天连续访问的用户

String 底层用 SDS(Simple Dynamic String),记录长度、预分配空间,避免 C 字符串的几个缺陷(长度 O(n)、缓冲区溢出、二进制不安全)。

Hash:对象存储

HSET user:1 name "mores" age 30 city "Beijing"
HGET user:1 name
HGETALL user:1
HINCRBY user:1 age 1

# 适合"半结构化对象",比 JSON 字符串更省内存(小对象用 ziplist 压缩)
# 而且能对单个字段操作,不用整个 GET + 解析 + SET

Hash 在元素少且 value 短时用 listpack(7.0+,原 ziplist)紧凑存储,内存远小于普通哈希表。

List:双向链表

LPUSH queue "task1"
RPUSH queue "task2"
LPOP queue              # 从头取
BLPOP queue 0            # 阻塞 LPOP,做简单队列

LRANGE queue 0 -1        # 看队列内容
LLEN queue
LTRIM queue 0 99         # 只保留最近 100 个

List 底层是 quicklist —— 链表节点是 listpack。这个设计兼顾"链表 O(1) 头尾操作"和"小节点紧凑内存"。

Set:无序集合

SADD followers:user1 100 200 300
SISMEMBER followers:user1 100
SCARD followers:user1     # 元素数
SINTER followers:user1 followers:user2   # 共同关注
SUNION ...
SDIFF ...

SRANDMEMBER followers:user1 5   # 随机取 5 个(抽奖)

Set 元素都是整数且数量少时用 intset 紧凑存储,否则用 hashtable。

Sorted Set:带分数的有序集合

ZADD leaderboard 100 "user1" 200 "user2" 300 "user3"
ZRANGE leaderboard 0 -1 WITHSCORES         # 升序
ZREVRANGE leaderboard 0 9 WITHSCORES        # 前 10
ZRANK leaderboard "user1"                   # 第几名
ZINCRBY leaderboard 5 "user1"               # 加 5 分

ZRANGEBYSCORE leaderboard 100 500          # 分数范围
ZREMRANGEBYRANK leaderboard 0 -101         # 删除名次 100 之后的

Sorted Set 底层是 skiplist + hashtable。skiplist 维持有序(支持 O(log n) 范围查询),hashtable 让"按 key 查 score"也是 O(1)。这是 Redis 排行榜的核心数据结构。

高级数据结构

HyperLogLog:基数估计

PFADD visitors "user1" "user2" "user3"
PFCOUNT visitors              # 大约 3
# 误差率 ~0.81%,但只占 12KB,能估计上亿基数

"今天网站独立访问者数"用 Set 要存所有用户 ID,几千万就 GB 级。HyperLogLog 固定 12KB 解决。代价是只能查"大约数",且不能"判断某个元素是否存在"。

Geo:地理位置

GEOADD bikes 116.40 39.90 "bike_1" 116.41 39.91 "bike_2"
GEORADIUS bikes 116.4 39.9 1 km          # 1 公里内的车
GEODIST bikes "bike_1" "bike_2" m         # 两点距离

底层用 Sorted Set 存 GeoHash 编码,所以 Geo 命令本质是 sorted set 操作。

Stream:消息队列 + Append Log

XADD events * type "click" user "u1"   # * 表示自动生成 ID
XLEN events
XRANGE events - +                       # 全部消息
XREAD COUNT 10 BLOCK 0 STREAMS events $  # 阻塞读新消息

# 消费组(可靠消息消费)
XGROUP CREATE events group1 $
XREADGROUP GROUP group1 consumer1 COUNT 10 STREAMS events >
XACK events group1 <message_id>          # 确认消费

Stream 5.0+ 引入,是 Redis 内置的"轻量 Kafka"。适合中小规模消息队列、事件溯源。

持久化:RDB vs AOF

RDB(快照)

SAVE                # 阻塞当前进程,生产用不到
BGSAVE              # fork 子进程做快照
# 配置:save 900 1 = 900 秒内有 1 次写就快照

优点:
  快照文件小、加载快
  适合"宕机后丢几分钟数据可接受"的场景

缺点:
  fork 时主进程被阻塞(短暂),内存大时延迟明显
  两次快照之间宕机会丢数据

AOF(追加日志)

# 配置:
appendonly yes
appendfsync everysec    # 每秒 fsync,折中
# 每条写命令都追加到 AOF 文件

优点:
  数据可靠(最多丢 1 秒)
  AOF rewrite 自动压缩文件

缺点:
  文件大,加载慢
  写入吞吐略低于 RDB

混合持久化(4.0+)

AOF rewrite 时把当前数据用 RDB 格式存,后续操作追加 AOF 格式。开启 aof-use-rdb-preamble yes。结合两者优点:加载快 + 数据可靠。生产环境推荐这个

三种集群模式

1. 主从 + Sentinel

一主多从,Sentinel 监控故障切换。简单稳定,适合中等规模(几十 GB)。

# 主从复制
replicaof master_ip 6379

# Sentinel 配置
sentinel monitor mymaster 192.168.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000

2. Redis Cluster

原生分片集群,16384 个 slot,数据按 CRC16 散到节点。每个 slot 有主有从。

# 创建 6 节点集群(3 主 3 从)
redis-cli --cluster create 192.168.0.1:7000 192.168.0.1:7001 \
    192.168.0.1:7002 192.168.0.1:7003 192.168.0.1:7004 192.168.0.1:7005 \
    --cluster-replicas 1

# 看 slot 分布
CLUSTER NODES
CLUSTER SLOTS

限制:跨 slot 的事务 / Lua 脚本不支持(除非用 hash tag {user:1}.cart 强制同 slot)。

3. Codis / Twemproxy(已过时)

代理层方案,新项目用 Redis Cluster 即可。

实战:典型场景

分布式锁

SET lock:order_1001 "owner_uuid" NX EX 10
# 成功:拿到锁,处理业务
# 失败:已被锁

# 释放(Lua 保证原子)
script = """
if redis.call('GET', KEYS[1]) == ARGV[1] then
    return redis.call('DEL', KEYS[1])
end
return 0
"""

缓存模式

def get_user(id):
    cached = redis.get(f"user:{id}")
    if cached:
        return json.loads(cached)
    user = db.query(...)
    redis.set(f"user:{id}", json.dumps(user), ex=3600)
    return user

缓存穿透 / 击穿 / 雪崩

  • 穿透:大量查不存在的 key,直接打 DB。解:布隆过滤器、空值缓存(短 TTL)。
  • 击穿:hot key 突然失效,大量请求同时回源。解:互斥锁(只让一个请求回源)、永不过期 + 后台更新。
  • 雪崩:大量 key 同时失效。解:TTL 加随机偏移、多级缓存。

性能优化

  • Pipeline:批量发命令,减少 RTT。从单连接 1万 QPS 提升到 10万+。
  • Lua 脚本:多步操作合并成一次网络往返,且原子。
  • 避免大 key:大 Hash / List / Set 慢。监控 redis-cli --bigkeys
  • 避免阻塞命令:KEYS、FLUSHDB、SMEMBERS(大 set)在主线程跑,会卡其他请求。用 SCAN 渐进式遍历。

常见坑

坑 1:Redis 当 DB 用。 Redis 主要是内存数据,持久化是兜底,不是设计来当主存储。重要数据应该在 MySQL / PG 里,Redis 是加速层。

坑 2:用 KEYS 命令。 O(N) 遍历所有 key,几百万 key 直接卡死。生产严格禁用,用 SCAN 替代。

坑 3:不监控大 key。一个 Hash 百万字段、一个 List 亿条元素 —— 一次操作几秒。redis-cli --bigkeys 定期检测。

坑 4:过期策略理解错。 Redis 用"惰性删除 + 定期删除",过期 key 不会瞬间消失,内存释放有延迟。重要的 OOM 边界场景要考虑这点。

坑 5:主从切换数据丢失。 默认异步复制,主挂了从未必完全同步。金融场景用 WAIT 命令强制等同步,代价是延迟。

Redis 内存优化

Redis 是纯内存,内存使用是首要关注。几个关键点:

1. key 命名要短

// 不好:每个 key 几十字节
"user_session_data_for_user_id_123456789"

// 好:用短缩写
"u:s:123456789"

// 一千万 key 节省几百 MB 是常态

2. 选对数据结构

// 存 100 个字段:用 Hash 而不是 100 个 String
HSET user:1 name "x" age 30 ...   // 一个 Hash,几百字节
SET user:1:name "x"                // 100 个 String,每个 key 都有 header,几千字节

3. 利用紧凑编码

Hash / List / Set / SortedSet 元素少且小时用 listpack(更紧凑),超过阈值才转 hashtable / skiplist。配置:

hash-max-listpack-entries 128
hash-max-listpack-value 64
zset-max-listpack-entries 128
zset-max-listpack-value 64
list-max-listpack-size -2

调大阈值能让更多场景用紧凑编码,但操作复杂度从 O(1) 变 O(n)(线性查找)。默认值是经过实践证明的平衡点。

4. 用 BitMap / HyperLogLog

"用户签到"用 Bitmap 一个 byte 存 8 天,一年 46 字节。"UV 统计"用 HyperLogLog 12KB 估计上亿。

内存淘汰策略

maxmemory 8gb
maxmemory-policy allkeys-lru

# 8 种策略:
noeviction          # 满了写入报错,默认。生产几乎不用
allkeys-lru         # 最近最少用,推荐缓存场景
allkeys-lfu         # 最少使用频率,适合明显热点
allkeys-random      # 随机淘汰
volatile-lru        # 仅淘汰带过期时间的
volatile-lfu        # 同上
volatile-random
volatile-ttl        # 淘汰 TTL 短的

实战推荐:allkeys-lru。让 Redis 自动淘汰,业务无需特别管理 TTL。

Pub/Sub 与 Stream 选择

Redis 早期的 Pub/Sub 是"发出去就不管" —— 消费者没在线就丢消息。Stream 解决了这点 —— 消息持久化、消费组、ack 机制都有。

选型:

  • 极简实时通知,不需要历史:Pub/Sub。
  • 需要可靠消费、消费组、消息追溯:Stream。
  • 需要大规模消息(每秒几十万 + 多 topic):Kafka,不是 Redis。

Redis 6 多线程 IO

Redis 长期是单线程,Redis 6 引入多线程 IO(读写网络包)。命令处理依然单线程,但 IO 多线程让网络瓶颈缓解。

io-threads 4
io-threads-do-reads yes

对小集群、低延迟需求作用有限,对大流量集群有 1-2 倍提升。开启时注意 IO 线程数 ≤ CPU 核数 - 1。

常见监控指标

INFO server      # 版本、运行时间
INFO clients     # 连接数
INFO memory      # used_memory、used_memory_peak
INFO stats       # ops_per_sec、命中率
INFO replication # 主从延迟
INFO commandstats # 各命令调用次数和耗时
SLOWLOG GET 10   # 慢日志

# 关键指标:
# - used_memory / maxmemory:接近 100% 就要扩容
# - mem_fragmentation_ratio:>1.5 说明内存碎片严重,可以 MEMORY PURGE
# - keyspace_hits / (hits + misses):命中率
# - instantaneous_ops_per_sec:QPS

Redis Exporter + Prometheus + Grafana 是经典监控栈,务必上线。

Redis 客户端使用模式

连接池

每次请求建一个 Redis 连接是低效的(TCP 握手 + AUTH 几毫秒)。客户端必须用连接池:

# Python redis-py
import redis
pool = redis.ConnectionPool(host='localhost', max_connections=50)
r = redis.Redis(connection_pool=pool)

# Java Jedis
JedisPool pool = new JedisPool(host, port, timeout, password);
try (Jedis jedis = pool.getResource()) {
    jedis.set("k", "v");
}

# Go go-redis 自带连接池
rdb := redis.NewClient(&redis.Options{
    Addr: "localhost:6379",
    PoolSize: 50,
})

Pipeline 批量提速

pipe = r.pipeline()
for i in range(1000):
    pipe.set(f"k:{i}", "v")
results = pipe.execute()
# 一次网络往返完成 1000 个命令
# 比单条 set 快 100x 以上

Lua 脚本

script = """
local current = redis.call('GET', KEYS[1])
if tonumber(current) >= tonumber(ARGV[1]) then
    return 0
end
return redis.call('INCRBY', KEYS[1], ARGV[2])
"""
result = r.eval(script, 1, "counter", "100", "1")
# 多步操作原子执行

Redis 持久化调优

# RDB 快照
save 900 1
save 300 10
save 60 10000

# AOF
appendonly yes
appendfsync everysec
no-appendfsync-on-rewrite yes    # rewrite 时不 fsync,避免阻塞
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 混合持久化(推荐)
aof-use-rdb-preamble yes

每秒 fsync 是大多数业务的合理折中。要求严格不丢可以 appendfsync always(每条命令 fsync),但性能掉到几千 QPS。

Redis 7 的新能力

  • 函数(Functions):替代 Lua 脚本,持久化到 AOF,集群同步。
  • Sharded Pub/Sub:Cluster 模式下的发布订阅。
  • 多路复用 + ACL v2:更细粒度的权限控制。
  • Redis Stack:整合 RedisJSON、RediSearch、RedisTimeSeries 等模块。

故障排查清单

1. 慢命令

SLOWLOG GET 10
-- 看哪些命令耗时长。通常是 KEYS、SMEMBERS(大 set)、HGETALL(大 hash)

2. 内存暴涨

redis-cli --bigkeys
# 找出最大的 key

INFO memory
# used_memory_peak 和 used_memory 差距大说明有过临时高峰

3. 复制延迟

INFO replication
# master_repl_offset - slave_repl_offset = 延迟字节数
# 持续增长说明从节点跟不上

4. 集群健康

redis-cli --cluster check <node>:<port>
CLUSTER NODES
CLUSTER INFO

选 Redis 还是 Memcached

  • Memcached:纯 KV、多线程、无持久化、无复杂数据结构。简单缓存场景比 Redis 略快。
  • Redis:丰富数据结构、持久化、集群、Pub/Sub、Lua、事务。绝大多数场景的首选。

2025 年新项目几乎没人选 Memcached —— Redis 的功能远超过它,且性能差距对大多数业务无感。

写在最后

Redis 是后端工程师必备技能,简单到上手 5 分钟,深到无穷尽。理解它的数据结构和持久化机制,你能解决 80% 的高性能需求;再加上集群和故障处理,生产架构就稳了。不要把 Redis 当万能 —— 它擅长内存级操作,不擅长强一致性和大数据持久存储。把它放在合适的位置(缓存 / 计数 / 排行榜 / 队列),配合 MySQL / Kafka 等专门组件,系统的整体性能和可靠性都会跃升。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

InnoDB 锁机制完全指南:Next-Key Lock、间隙锁与死锁排查

2026-5-15 16:26:44

技术教程

MongoDB 完全指南:从文档模型到分片集群的生产实战

2026-5-15 16:26:45

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索