1. 项目背景
1.1 业务场景
公司准备把一个日活百万的传统电商系统改造成"缓存优先"的高并发架构。产品需求文档里列了一长串功能:登录验证码要5分钟过期、商品详情要缓存加速、购物车要支持实时修改、积分排行榜要实时展示、附近门店要按距离排序、订单状态变更要异步通知下游系统。
技术评审会上,每个人对 Redis 的认知都不一样。后端开发小王说:"Redis 就是个带 TTL 的 HashMap,我把对象序列化一下存进去就行。"测试主管张姐问:"那 Redis 挂了数据会丢吗?如果会丢,我们的测试用例怎么验证数据一致性?"运维老李直接摇头:“我们之前线上 Redis 内存满了直接 OOM,重启后发现一部分 key 消失了,你们知道是为什么吗?”
1.2 痛点
这种认知差异会直接带来生产事故:
| Redis=HashMap | 把所有业务数据塞进一个大 key,单次 GET 返回 50MB | 某电商大促期间,商品详情接口的 Redis 响应包超过网络带宽限制 |
| Redis=数据库 | 依赖 Redis 强持久化,不做数据库兜底 | Redis 崩溃后依赖 AOF 恢复,发现最后 2 秒数据丢失 |
| 单线程一定快 | 线上写 KEYS * 排查问题,整个 Redis 阻塞 3 秒 | 某社交 App 用户搜索接口大面积超时 |
| 不加过期时间 | 计数器 key 无限增长,内存从 4GB 涨到 32GB | 某内容平台接口调用计数 key 达到 8000 万个 |
| 忽视内存成本 | 所有数据都放 Redis,不做容量规划 | 某 SaaS 平台 Redis 月度成本超过 RDS 成本 |
更麻烦的是,一旦线上出现慢请求,团队第一反应是怀疑"Redis 单线程扛不住",却没有真正理解 Redis 的请求处理链路——问题可能是网络延迟、客户端连接池耗尽、慢命令阻塞、fork 子进程导致的写时复制、或者是 Cluster 重定向过多。
1.3 本章目标
本章不是背命令手册,而是建立一张 Redis 的"房屋结构图":知道门在哪里(客户端连接)、客厅怎么走(命令执行)、每个房间放什么(数据结构)、水电气怎么通(持久化、复制、集群)。只有先知道每个术语在系统里所处的位置,后续学习 String、Hash、Stream、Cluster、源码时才不会碎片化。
另一个重要目标是让读者直观感受 Redis 的请求处理链路。我们会用 Docker 启动一个 Redis 8.6,执行一组基础命令,用调试工具追踪一次请求的生命周期,最后用一张文本架构图把"一条命令从客户端到 Redis 再返回"的过程串起来。
2. 项目设计
2.1 第一幕:Redis 到底是个什么东西?
小胖(嘴里还嚼着薯片):“我就不明白了,Redis 不就是个会过期的 Map 吗?我 SET user:1 张三,然后 GET user:1,这和 Java 里的 HashMap 到底有啥区别?为什么面试的时候问 Redis,面试官老爱追问什么内存模型、线程模型、数据结构?”
小白(放下手中的技术书,推了推眼镜):“如果只是 HashMap,那它怎么支持网络访问?怎么让几十个客户端同时读写?怎么在你重启电脑后数据还在?怎么让数据在多个服务器之间自动同步?再说,HashMap 里的 Key 可以是任何对象,Redis 的 Key 只能是字符串——这本身就是设计取舍。”
小胖:“那你说它到底是什么?缓存?数据库?消息队列?网上有人把它当缓信用,有人把它当临时数据库用,还有人用它做分布式锁和限流器,它到底是干啥的?”
大师(把白板推到中间,拿起记号笔):“好问题。你们先放下技术偏见。我把 Redis 画成四层楼,你们就清楚了。”
┌─────────────────────────────────────────────┐
│ 客户端层 (Client Layer) │
│ redis-cli / Jedis / Lettuce / Redis-py │
│ redis-benchmark / RESP 协议 │
└──────────────────┬──────────────────────────┘
│ TCP / TLS / Unix Socket
▼
┌─────────────────────────────────────────────┐
│ 网络接入层 (Networking Layer) │
│ 连接管理、RESP 协议解析、IO 多路复用 │
│ readQueryFromClient → processInputBuffer │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 命令执行层 (Command Execution Layer) │
│ 命令查找表、权限校验(ACL)、Lua 脚本引擎 │
│ processCommand → lookupCommand → call │
│ 慢日志记录、统计信息收集 │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 数据结构层 (Data Structures Layer) │
│ String / Hash / List / Set / ZSet │
│ Stream / Bitmap / HyperLogLog / GEO │
│ RedisObject (type + encoding + lru + ptr) │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 生命周期与可靠性层 (Lifecycle & Durability) │
│ 过期删除 (Expire) / 内存淘汰 (Eviction) │
│ RDB 快照 / AOF 日志 / 混合持久化 │
│ 主从复制 / Sentinel 高可用 / Cluster 集群 │
│ 模块扩展 (Module: JSON / Search / Vector) │
└─────────────────────────────────────────────┘
大师:“Redis 是一家高效奶茶店。客户端是顾客,网络层是点单窗口——支持多个窗口同时排队但只有一个操作台。命令执行层就是操作台后的茶饮师,按照配方表(命令表)快速制作。数据结构层是身后的原料架——糖、奶、茶、珍珠,每种原料就是 Redis 的一个数据结构。生命周期层意味着:珍珠放久了会扔掉(过期),糖不够了要计划进货(淘汰),每天打烊后把营业记录写进账本(持久化),分店之间互相同步配方(复制/集群)。”
小胖:“嘶——这么说我就懂了!那为什么大家都说 Redis 是单线程?一个茶饮师能忙过来吗?”
技术映射:Redis 的核心执行引擎是单线程事件循环(Event Loop),所有命令在单个线程内排队执行。每一笔操作都必须在微秒级完成,否则后续所有请求都会等待。
2.2 第二幕:单线程为什么快?
小白:“对啊,这也是我最大的疑问。Java 的 HashMap 是线程不安全的,多个线程同时写会出问题。如果 Redis 是单线程,那它怎么处理高并发?现在服务器都是多核的,只用一个核不是浪费吗?”
大师:“问得好。Redis 快不是因为它无脑单线程,而是因为它做对了几件事。”
他用白板画了第二个图:
┌──────────────────────────────────────────────────┐
│ Redis Event Loop │
│ ┌──────────────────────────────────────────┐ │
│ │ IO 多路复用 (epoll/kqueue) │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │ fd1 │ │ fd2 │ │ fd3 │ │ fd4 │ … │ │
│ │ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │
│ └─────┼────────┼────────┼────────┼─────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ 命令队列(按序执行) │ │
│ │ GET user:1 → SET order:1001 → INCR cnt │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ 内存数据操作(纳秒~微秒级) │ │
│ │ 哈希表查找 → 编码转换 → 值读写 → 响应 │ │
│ └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
大师:“Redis 的快来自三个核心设计。第一,纯内存操作——数据都在内存里,一次 GET 的内存访问时间大约 100 纳秒,而一次磁盘访问大约 10 毫秒,差了 10 万倍。第二,IO 多路复用——Redis 用 epoll(Linux)或类似的系统调用同时监听成千上万个连接。当一个连接有数据到达,epoll 通知 Redis,Redis 读取并处理,然后立刻处理下一个。这就像茶饮师同时盯着 10 个顾客,哪个顾客举手了就立刻服务他,而不是一个一个轮着问’你要点什么’。第三,数据结构紧凑、编码优化——小 String 用 embstr 编码,小 Hash 用 listpack 编码,小 ZSet 用 listpack 替代跳表,每一项设计都在省内存、省时间。”
小白:“但我还是觉得单线程有风险。如果一个命令执行了 100 毫秒,那这 100 毫秒内所有其他客户端都在干等?”
大师:“完全正确。这就是 Redis 最大的坑——阻塞命令。”
技术映射:Redis 的单线程模型本质是"用极简换取极快",但它要求使用者必须了解每个命令的复杂度,避免在单线程上执行长耗时操作。
2.3 第三幕:Redis 8.x 能力地图
小胖(挠头):“那 Redis 8.x 到底能干什么?除了缓存,还有什么大招?”
大师:“Redis 8.x 已经远不是’缓存中间件’了。我把它的能力地图画出来:”
| 缓存加速 | String/Hash 缓存、TTL、淘汰策略、旁路缓存 | 商品详情、用户会话、配置热加载 | 第3-4章、第12章 |
| 计数器与状态 | INCR/DECR、Bitmap 签到、HyperLogLog 去重统计 | 浏览量、月活、接口 UV | 第3章、第8-9章 |
| 关系与排序 | Set 集合运算、ZSet 排行榜、GEO 地理位置 | 社交关系、积分榜、附近门店 | 第6-7章、第10章 |
| 队列与消息 | List 队列、Stream 消息流、Pub/Sub 广播 | 异步任务、订单事件、配置推送 | 第5章、第20-21章 |
| 原子操作 | Lua 脚本、事务(WATCH/MULTI)、分布式锁 | 库存扣减、限购、防重提交 | 第13-14章、第18章 |
| 持久化 | RDB 快照、AOF 日志、混合持久化 | 数据恢复、迁移、灾备 | 第22章 |
| 高可用 | 主从复制、Sentinel 自动故障转移、Cluster 分片 | 读写分离、自动切主、水平扩容 | 第23-25章 |
| 可观测性 | INFO/CLIENT LIST/SLOWLOG/LATENCY/MONITOR | 慢查询治理、容量规划、告警 | 第15章、第27-28章 |
| 安全 | ACL 用户体系、TLS 加密、命令禁用 | 多租户隔离、合规审计 | 第29章 |
| 扩展与 AI | Module API、JSON/Query/Vector/TimeSeries | 语义缓存、RAG、实时索引 | 第38-39章 |
小胖(眼睛瞪大):“这么多?!我以为 Redis 就是用来做缓存的……”
大师:“这就是为什么我们第一课要画地图。你不必记住每一条命令,但必须知道’遇到什么问题该去 Redis 地图的哪个区域找答案’。后面 40 章就是带你一步步走完这张地图。”
小白:“那我问一个落地问题——Redis 8.6 相比旧版本有什么值得关注的变化?”
大师:“几个关键点。第一,读写 IO 多线程支持(Redis 6 开始引入,到 8.x 更加成熟),这解决的不是命令执行慢的问题,而是网络读写的大包传输问题,尤其是大规模 Pipeline 和 LRANGE 大返回时。第二,ACL v2 支持更细粒度的 key 前缀权限控制和通道权限,多租户场景不再只能靠命名约定隔离。第三,Functions(Redis Function)作为 Lua 脚本的演进方案,有更好的版本管理能力和库依赖。第四,字段级过期(HEXPIRE/HPEXPIRE)让 Hash 的字段可以独立过期。第五,内存效率的持续优化——listpack 逐步替代 ziplist,降低了内存碎片风险。”
技术映射:Redis 8.x 的核心演进方向是"更安全(ACL/TLS)、更高效(IO 多线程/listpack)、更灵活(Functions/字段过期)、更智能(Vector/Query)"。
2.4 第四幕:一条命令的完整旅程
小胖:“那我就想搞清楚一件事——我敲一个 SET user:1 xiaopang,Redis 内部到底发生了啥?”
大师:“好,这是今天最重要的图。跟着我的笔走:”
Step 1: 客户端发送
┌──────────────┐
│ redis-cli │ 序列化: *3\\r\\n$3\\r\\nSET\\r\\n$6\\r\\nuser:1\\r\\n$8\\r\\nxiaopang\\r\\n
│ (客户端) │────── TCP Socket ──────▶
└──────────────┘
Step 2: 网络层接收
┌──────────────────────────────────────┐
│ Redis Server: networking.c │
│ epoll 通知 fd 可读 │
│ acceptTcpHandler → createClient │
│ readQueryFromClient │
│ → 解析 RESP 协议: argc=3, argv=["SET","user:1","xiaopang"]
└──────────────────────────────────────┘
Step 3: 命令执行
┌──────────────────────────────────────┐
│ Redis Server: server.c │
│ processCommand │
│ → lookupCommand("SET") → setCommand │
│ → 权限校验 (ACL check) │
│ → 参数校验 (arity check) │
│ → call(c, CMD_CALL_FULL) │
└──────────────────────────────────────┘
Step 4: 数据操作
┌──────────────────────────────────────┐
│ Redis Server: t_string.c / db.c │
│ setGenericCommand │
│ → 创建/更新 RedisObject │
│ type: OBJ_STRING │
│ encoding: OBJ_ENCODING_EMBSTR │
│ ptr → "xiaopang" │
│ lru: 当前时间戳 │
│ → dbAdd(c->db, key, val) │
│ → dictAdd(db->dict, key, val) │
│ → 如果设置了 EX/PX,写入 expire 字典 │
└──────────────────────────────────────┘
Step 5: 响应返回
┌──────────────────────────────────────┐
│ Redis Server: networking.c │
│ addReply(c, shared.ok) // "+OK\\r\\n" │
│ → 写入 client->buf 或 reply list │
│ → 注册可写事件 │
│ → sendReplyToClient → write 系统调用 │
└──────────────────────────────────────┘
大师:“整个过程在单线程中一气呵成。从 RESP 解析到哈希表写入到 +OK 返回,没有任何锁竞争、没有上下文切换、没有磁盘 IO。这就是为什么 SET 能跑到 10 万 QPS 以上。”
小白:“那如果我在 SET 的同时有人 GET user:1,会不会读到不完整的数据?”
大师:“不会。因为所有命令都是串行执行的。SET 执行完才轮到下一条 GET,GET 读到的要么是旧值要么是新值,不存在’写了一半’的问题。这是单线程模型最优雅的地方——你用不着考虑数据竞争和加锁。”
技术映射:Redis 的单线程执行模型天然保证了单个命令的原子性,无需开发者在应用层做额外并发控制。
3. 项目实战
3.1 环境准备
启动 Redis 8.6(使用 Docker):
# 拉取镜像
docker pull redis:8.6
# 启动容器(不带密码,方便初学体验)
docker run –name redis-lab-ch01 -p 6379:6379 -d redis:8.6
# 验证容器运行状态
docker ps –filter "name=redis-lab-ch01"
# 进入 Redis CLI
docker exec -it redis-lab-ch01 redis-cli
如果已经运行过其他 Redis 容器,端口可能冲突。换一个端口:
docker run –name redis-lab-ch01 -p 6380:6379 -d redis:8.6
docker exec -it redis-lab-ch01 redis-cli -p 6379
3.2 执行第一组命令:感受 Redis 的响应速度
# 基础连通性测试
127.0.0.1:6379> PING
PONG
# 写入一个 key
127.0.0.1:6379> SET user:1 "xiaopang"
OK
# 读取这个 key
127.0.0.1:6379> GET user:1
"xiaopang"
# 查看 key 是否存在
127.0.0.1:6379> EXISTS user:1
(integer) 1
# 查看 key 的类型
127.0.0.1:6379> TYPE user:1
string
# 查看 key 的内部编码
127.0.0.1:6379> OBJECT ENCODING user:1
"embstr"
# 设置 60 秒过期
127.0.0.1:6379> EXPIRE user:1 60
(integer) 1
# 查看剩余时间
127.0.0.1:6379> TTL user:1
(integer) 56
# 删除 key
127.0.0.1:6379> DEL user:1
(integer) 1
127.0.0.1:6379> GET user:1
(nil)
预期现象与解释:
- PING 返回 PONG:客户端和 Redis 服务端通信正常,RESP 协议解析正确。
- OBJECT ENCODING user:1 返回 "embstr":短字符串(小于 44 字节)使用嵌入式字符串编码,对象头和内容分配在同一块内存中,减少内存碎片。
- TTL 返回的整数值每秒递减,到期后 key 自动删除(惰性删除或定期删除)。
3.3 观察 Redis 运行状态
# 服务端基本信息
127.0.0.1:6379> INFO server
# 重点关注: redis_version, redis_mode, os, process_id, tcp_port
# 客户端连接信息
127.0.0.1:6379> INFO clients
# 重点关注: connected_clients, maxclients
# 内存使用
127.0.0.1:6379> INFO memory
# 重点关注: used_memory_human, maxmemory_human, mem_fragmentation_ratio
# 统计信息
127.0.0.1:6379> INFO stats
# 重点关注: total_commands_processed, instantaneous_ops_per_sec,
# keyspace_hits, keyspace_misses, expired_keys, evicted_keys
# 查看所有连接的客户端
127.0.0.1:6379> CLIENT LIST
# 输出: id=3 addr=127.0.0.1:xxxxx fd=8 name= age=10 idle=0 …
# 查看当前所有 key
127.0.0.1:6379> KEYS *
# 注意: 仅学习环境使用,生产环境应使用 SCAN
3.4 用 redis-benchmark 感受 Redis 吞吐量
redis-benchmark 是 Redis 自带的性能压测工具。退出 redis-cli(按 Ctrl+C),在容器内运行:
# 进入容器 bash
docker exec -it redis-lab-ch01 bash
# 基础压测:10 万请求,50 并发连接,只测 SET 和 GET
redis-benchmark -n 100000 -c 50 -t set,get -q
# 输出示例(实际数值因机器性能而异):
# SET: 85470.09 requests per second, p50=0.271 msec
# GET: 90909.09 requests per second, p50=0.239 msec
参数解释:
- -n 100000:总请求数 10 万。
- -c 50:并发 50 个客户端连接。
- -t set,get:只测试 SET 和 GET 命令。
- -q:安静模式,只显示 QPS 结果。
加上延迟分位数:
redis-benchmark -n 100000 -c 50 -t set,get –latency
额外测试几个常用命令的性能差异:
# 测试 PING(最快,纯协议响应)
redis-benchmark -n 100000 -c 50 -t ping -q
# 测试 INCR(原子自增)
redis-benchmark -n 100000 -c 50 -t incr -q
# 测试 LPUSH(List 写入)
redis-benchmark -n 100000 -c 50 -t lpush -q
观察到的性能排序通常是:PING > SET/GET > INCR > LPUSH。这是因为不同命令涉及的数据结构操作复杂度不同:PING 几乎不操作数据,SET/GET 只做哈希表查找,INCR 需要读取、自增、写回,LPUSH 需要操作链表节点。
3.5 亲手触发一次"阻塞"
这个实验非常重要——让你直观感受慢命令对 Redis 的影响。
在一个终端执行慢命令(使用 Lua 模拟一个耗时操作):
docker exec -it redis-lab-ch01 redis-cli
先写入一些数据:
127.0.0.1:6379> SET test:1 "hello"
OK
127.0.0.1:6379> SET test:2 "world"
OK
打开第二个终端,执行一个耗时 3 秒的 Lua 脚本(Redis 的 Lua 脚本会阻塞事件循环):
终端 B(先准备好,但不要回车):
docker exec -it redis-lab-ch01 redis-cli EVAL "local start=redis.call('TIME')[1]; while(redis.call('TIME')[1]-start<3) do end; return 'slow done'" 0
终端 A(先执行一个带时间戳的命令来校准):
docker exec -it redis-lab-ch01 redis-cli TIME
然后在终端 B回车执行慢脚本,同时在终端 A立即执行:
docker exec -it redis-lab-ch01 redis-cli PING
你会发现 PING 等了大约 3 秒才返回 PONG。这就是单线程模型的"副作用"——一个慢命令会拖慢所有其他请求。
这个实验的价值在于:它让你亲眼看见了"慢命令阻塞"不是理论,而是真实可复现的。后续学习时,你会对 KEYS *、大 key 删除、复杂 Lua 脚本保持敬畏。
3.6 查看慢查询日志
Redis 内置了慢查询日志功能,可以记录执行时间超过阈值的命令:
# 配置慢查询阈值为 1000 微秒(1 毫秒)
127.0.0.1:6379> CONFIG SET slowlog-log-slower-than 1000
# 配置慢查询日志最多保存 128 条
127.0.0.1:6379> CONFIG SET slowlog-max-len 128
# 查看最近的慢查询
127.0.0.1:6379> SLOWLOG GET 5
# 1) 1) (integer) 3 # 日志 ID
# 2) (integer) 1680000000 # 时间戳
# 3) (integer) 3012541 # 执行耗时(微秒)
# 4) 1) "EVAL" # 命令
# 2) "local start=…" # 参数
# 3) "0"
# 查看当前慢查询数量
127.0.0.1:6379> SLOWLOG LEN
# 重置慢查询日志
127.0.0.1:6379> SLOWLOG RESET
3.7 用 MONITOR 实时观察命令流
MONITOR 可以实时看到 Redis 收到的每一条命令——这是学习和调试的利器(但生产环境慎用,本身有性能开销):
# 终端 A:开启 MONITOR
127.0.0.1:6379> MONITOR
OK
# 终端 B:执行一些命令
docker exec -it redis-lab-ch01 redis-cli SET key1 val1
docker exec -it redis-lab-ch01 redis-cli GET key1
docker exec -it redis-lab-ch01 redis-cli INCR counter
回到终端 A,你会看到实时输出:
1680000000.123456 [0 127.0.0.1:54321] "SET" "key1" "val1"
1680000000.234567 [0 127.0.0.1:54322] "GET" "key1"
1680000000.345678 [0 127.0.0.1:54323] "INCR" "counter"
MONITOR 的每一行包含时间戳、数据库编号、客户端地址和完整命令。
3.8 探索 Redis 配置文件
查看当前运行配置:
# 查看所有配置
127.0.0.1:6379> CONFIG GET *
# (输出会很长,建议按需检索)
# 查看特定配置
127.0.0.1:6379> CONFIG GET maxmemory
127.0.0.1:6379> CONFIG GET save
127.0.0.1:6379> CONFIG GET appendonly
127.0.0.1:6379> CONFIG GET timeout
# 修改配置(运行时生效,重启后丢失)
127.0.0.1:6379> CONFIG SET maxmemory 256mb
OK
3.9 常见坑
坑1:KEYS * 生产事故。KEYS 命令会遍历整个键空间,复杂度 O(N),100 万个 key 时可能阻塞数秒。应使用 SCAN 替代:
# 危险!
127.0.0.1:6379> KEYS user:*
# 安全替代
127.0.0.1:6379> SCAN 0 MATCH user:* COUNT 100
坑2:无视 OBJECT ENCODING 的陷阱。同一个 SET 命令,短字符串和长字符串的内部编码完全不同:
127.0.0.1:6379> SET short "hi"
OK
127.0.0.1:6379> OBJECT ENCODING short
"embstr"
127.0.0.1:6379> SET long "这是一段超过44字节的字符串,会触发不同编码…"
OK
127.0.0.1:6379> OBJECT ENCODING long
"raw"
如果你不了解编码差异,在做内存估算时会严重低估或高估。
坑3:INFO 输出误读。used_memory_human 显示的是 Redis 当前使用的内存,但不包括内存碎片。真正需要关注的是 used_memory_rss_human(操作系统视角的物理内存)和 mem_fragmentation_ratio(碎片率,等于 used_memory_rss / used_memory)。
4. 项目总结
4.1 本章核心收获
| Redis 定位 | 以内存数据结构为核心、基于事件驱动的高性能服务端系统,远不止是"带 TTL 的 HashMap" |
| 单线程模型 | 核心命令执行路径是单线程串行的,借助 IO 多路复用和纯内存操作达到微秒级延迟 |
| 性能关键 | 快不是魔法——紧凑编码+内存访问+无锁设计+IO 多路复用;慢命令是最大杀手 |
| 请求链路 | 客户端 → RESP 编码 → epoll 通知 → 命令解析 → 权限校验 → 数据操作 → RESP 响应 |
| Redis 8.x 能力 | 从缓存扩展到消息流、向量检索、字段级过期、ACL v2、Functions、JSON 索引等 |
4.2 优点与缺点
| 命令模型简单,学习曲线平缓 | 单线程执行路径害怕慢命令、大 key、复杂脚本 |
| 数据结构贴近业务,建模自然 | 内存成本高,需要容量和淘汰策略规划 |
| 纯内存访问,常规命令延迟微秒级 | 复杂查询和多表关联不是 Redis 的强项 |
| 支持持久化、复制、集群和模块扩展 | 初学者容易忽视编码差异、key 规范和 TTL 策略 |
| 原子命令天然线程安全,无锁编程 | 单个命令原子 ≠ 多命令事务原子(需要 Lua 或 MULTI/EXEC) |
4.3 适用与不适用场景
适用场景:
不适用场景:
4.4 常见踩坑经验
| 某电商大促 P99 延迟暴涨 | 商品详情接口从 5ms 涨到 2s | 运维用 KEYS * 排查问题,阻塞整个 Redis 3 秒 | 禁用 KEYS,改用 SCAN;配置 ACL 禁止危险命令 |
| 某社交 App 内存持续上涨 | Redis 内存从 8GB 涨到 32GB | 大量计数器 key 未设 TTL,永不过期 | 制定 key 命名规范和 TTL 策略;定期巡检无 TTL key |
| 某支付系统 Redis “幽灵 key” | 某些 key 超 TTL 后仍能读到 | 主从复制中,从节点用绝对过期时间,删除命令延迟到达 | 校验数据时增加 TTL 二次判断;用 Lua 读写前先检查 TTL |
4.5 思考题
设计题:如果你需要设计一个"最近 7 天每个用户每天发送短信次数"的计数器系统,你会如何设计 Redis key 和过期策略?写出 key 格式和到期逻辑。
排查题:线上 Redis 的 instantaneous_ops_per_sec 突然从 5 万降到 2 千,但 connected_clients 没有明显变化。请列出至少 5 种可能导致此现象的根因,并说明优先排查哪一项。
(答案提示将在下一章末尾给出。)
下一章预告:第2章——Windows、WSL2 与 Docker 环境搭建。我们将编写一份 Docker Compose 文件,启动带密码、AOF、数据挂载和 RedisInsight 的本地实验环境,为后续所有章节打好基础。
延伸阅读与资源
MongoDB 实战进阶与内核修炼 python入门:Rquests从菜鸟脚本到企业级SDK的网络实战圣经 Milvus向量数据库实战修炼:从 0 到 1精通向量检索与生产落地 后端工程师的 AI 转型第一课:Ollama 与私有化大模型实战 10倍开发者的 Dify 魔法书:从零构建全栈 AI 应用 后端工程师转型AI第一课-Ollama 与私有化大模型实战
大型语言模型(LLM) vLLM 高性能推理落地实战
Agent开发之LlamaIndex 实战修炼与源码进阶
大语言模型Transformers 实战修炼与源码剖析
