7.10 Redis 教程
简介
Redis(Remote Dictionary Server)是开源的内存型键值数据库,常用作缓存、会话存储、计数与排行榜、轻量队列等。数据主要驻留在内存,读写延迟低;通过键过期、多种数据结构、可选持久化,在 Web 与微服务中与 MySQL 等关系库配合:热数据、短时状态放 Redis,强一致与复杂查询仍由关系库承担(参见 7.8 MySQL 数据库教程)。
本地或团队环境可用包管理器安装,也可用 Docker 快速拉起(参见 7.9 Docker 基础入门)。本章覆盖核心概念、redis-cli 与五种主要数据类型、过期与持久化入门、事务与管道、与 Go 连接、使用 Redis 做缓存(旁路缓存、失效与典型风险),以及应用实践(分布式锁、计数与限流、幂等、队列与排行榜等)与安全注意点。
核心概念
| 概念 | 说明 |
|---|---|
| 键值(Key-Value) | 每个键对应一种数据类型的值;键为二进制安全字符串,业务上常用带前缀的命名,如 user:1001:profile。 |
| 内存存储 | 读写主要在内存完成;需结合 TTL、淘汰策略 与 持久化 控制成本与可恢复性。 |
| 单线程执行命令 | 传统模型下命令在主线程串行执行,单条命令原子;多命令组合需用 事务、Lua 或应用层协议保证语义。 |
| 逻辑库(database) | 默认多库编号 0~15(可配置);SELECT 切换。集群模式下常用单库 0,避免跨槽习惯。 |
| redis-server / redis-cli | 服务端守护进程与命令行客户端。 |
安装与运行
Docker(推荐与 7.9 一致)
docker run -d \
--name redis-dev \
-p 6379:6379 \
redis:7-alpine \
redis-server --appendonly yes带密码示例(勿在生产示例中沿用弱口令):
docker run -d \
--name redis-dev \
-p 6379:6379 \
redis:7-alpine \
redis-server --requirepass 'your_strong_password' --appendonly yes默认端口 6379。
本机安装(点到为止)
- macOS:
brew install redis,brew services start redis。 - Linux:发行版包
redis-server或官方源。
自检
redis-cli -h 127.0.0.1 -p 6379 ping
# 若设置了密码:
redis-cli -a 'your_strong_password' ping返回 PONG 即服务可用。-a 可能进入 shell 历史,生产更宜用 redis-cli 交互内 AUTH 或配置文件。
redis-cli 基础
连接
redis-cli -h 127.0.0.1 -p 6379
# 进入后可执行:
AUTH your_strong_password查看帮助与类型
HELP @string
TYPE mykey遍历键:慎用 KEYS,优先 SCAN
KEYS pattern:阻塞式全表扫描,键多时影响线上延迟,生产禁用。SCAN cursor [MATCH pattern] [COUNT hint]:迭代游标,在应用或脚本中分批遍历。
SCAN 0 MATCH user:* COUNT 100数据类型与常用命令
String(字符串)
| 命令 | 说明 |
|---|---|
SET key value | 设置值。 |
GET key | 获取值。 |
INCR key / DECR key | 原子自增、自减(值为整数时)。 |
SET key value EX seconds | 设置并带过期秒数。 |
EXPIRE key seconds / TTL key | 单独设置过期、查看剩余秒数。 |
示例:
SET page:home "<html>...</html>" EX 300
GET page:home
INCR api:visits
TTL page:homeHash(哈希)
适合存对象的多个字段,避免大量独立键。
HSET user:1001 name Alice age 30
HGET user:1001 name
HGETALL user:1001List(列表)
双端列表;可作 FIFO 队列雏形。
LPUSH jobs:pending job-a job-b
RPOP jobs:pending
LRANGE jobs:pending 0 -1Set(集合)
无序、成员唯一。
SADD tags:post:1 go redis
SMEMBERS tags:post:1
SISMEMBER tags:post:1 goSorted Set(有序集合,ZSet)
带分数的成员排序,适合排行榜。
ZADD leaderboard 100 player:1 200 player:2
ZREVRANGE leaderboard 0 9 WITHSCORES
ZRANK leaderboard player:1Stream(流,了解)
Redis 5 起提供 Stream,支持消费组、持久化消息流,语义接近日志型消息队列;入门阶段掌握 List 与专业 MQ 的差异即可,深入可查官方 Streams 文档。
Key 设计建议
- 使用业务前缀,避免键名冲突,如
cache:user:{id}、lock:order:{id}。 - 为缓存键设置 TTL,避免内存无限增长。
- 控制 单个 value 体积(大 Key 问题见下文)。
过期与内存管理
过期
EXPIRE key seconds、PEXPIRE、创建键时SET … EX均可设置生存时间。TTL返回剩余秒数;-1表示未设置过期;-2表示键不存在。PERSIST key去掉过期时间。
内存上限与淘汰策略
配置 maxmemory 后,内存打满时需 maxmemory-policy 决定如何腾出空间,例如:
| 策略(常见) | 含义(简述) |
|---|---|
noeviction | 默认常见写法;写满时拒绝写入命令并返回错误。 |
allkeys-lru | 从全体键中按 LRU 近似淘汰。 |
volatile-lru | 仅带过期的键中淘汰。 |
具体取值以部署配置与 Redis 版本文档为准;缓存场景常与 allkeys-lru 或业务可接受的策略搭配。
持久化入门
| 方式 | 说明 |
|---|---|
| RDB | 在间隔点生成快照文件;恢复快,两次快照间宕机可能丢失中间写入。 |
| AOF | 追加写日志;可配置 appendfsync(如 everysec)在性能与丢失窗口间折中。 |
Docker 示例中 --appendonly yes 即开启 AOF。生产需结合备份策略与 RDB/AOF 重写参数调优,超出本章范围。
事务与管道
事务:MULTI / EXEC / DISCARD
MULTI
SET a 1
INCR b
EXECEXEC前命令入队;EXEC时依次执行,中间不会被其他客户端插入(保证队列批处理)。- 与关系库事务不同:不支持对多数命令失败自动回滚;语法错误可能导致整批丢弃或部分执行,需查阅版本行为。
- 需要「判断再写」的原子逻辑,优先 Lua 脚本 或 单条命令(如
SET NX)。
Pipeline(管道)
客户端一次发送多条命令、一次读回结果,减少网络往返;不保证与 MULTI/EXEC 相同的原子边界,适合批量读写的性能优化。
与 Go 衔接
Go 生态中常用 go-redis(模块路径 github.com/redis/go-redis/v9)。最小连接示例:
package main
import (
"context"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "", // 与 redis-server 配置一致
DB: 0,
})
defer rdb.Close()
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
err := rdb.Set(ctx, "hello", "redis", time.Minute).Err()
if err != nil {
panic(err)
}
val, err := rdb.Get(ctx, "hello").Result()
if err != nil {
panic(err)
}
fmt.Println("hello =", val)
}依赖安装:
go get github.com/redis/go-redis/v9连接地址、密码、库编号建议来自环境变量或配置中心,勿写死生产密钥。
应用实践举例
以下以 redis-cli 语义 为主说明模式;Go 中对应 SET 带 NX/EX、Eval 执行 Lua 等 API,可查 go-redis 文档。
1. 分布式锁(原子抢占)
获取锁:使用 SET lock:资源名 唯一令牌 NX EX 过期秒。NX 表示不存在才设置,EX 防止客户端崩溃导致死锁。
SET lock:order:1001 token-uuid-abc NX EX 30
# 返回 OK 表示抢到;返回 (nil) 表示已被占用释放锁:必须先校验「值是否为当前持有者」再删除,避免误删他人锁。应用侧常用 Lua 脚本 保证「比较 + 删除」原子:
-- KEYS[1] 锁键, ARGV[1] 期望的 token
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end边界:单机 Redis 上上述锁可防并发误用,但主从切换、时钟、业务过长持锁等仍可能引发边界问题;跨多节点的强一致锁需 Redlock 等方案并阅读官方争议与运维成本,本章不展开。
2. 原子计数与简单限流
计数:
INCR api:2026-04-17:user:1001
EXPIRE api:2026-04-17:user:1001 86400固定窗口限流(示例思路):键名带时间窗口,INCR 后若首次则 EXPIRE 窗口长度;超过阈值则拒绝请求。更精细的限流可结合 Sorted Set 时间戳或令牌桶在应用层实现。
3. 幂等 / 防重
对「请求 id」占位,处理前 SET idempotency:订单号:请求ID 1 NX EX 3600;若返回 (nil) 说明已处理过或正在处理,业务层直接返回上次结果或拒绝重复。
4. 简易任务队列
生产者 LPUSH,消费者 BRPOP(阻塞右端弹出),避免空轮询:
LPUSH jobs:pending "{\"type\":\"email\",\"to\":\"a@b.com\"}"
BRPOP jobs:pending 10与 Kafka、RabbitMQ 等相比,无内置高可用投递语义与复杂路由,适合轻量、可丢或可补偿场景。
5. 排行榜 / 热搜
上文 Sorted Set 的 ZADD / ZREVRANGE 即可;分数可为销量、积分、时间戳等。注意控制成员数量与 大 Key 风险。
6. 缓存(Cache-Aside)
典型「读多写少」场景用 Redis 减轻 MySQL(7.8)压力时,多采用 旁路缓存 模式,流程与注意点见下文 「使用 Redis 做缓存」。
使用 Redis 做缓存
本节说明如何把 Redis 当作读缓存:与关系库的职责划分、推荐流程、键与 TTL 设计,以及缓存穿透 / 击穿 / 雪崩等常见问题的应对思路(概念级)。
与 MySQL 的分工
| 存储 | 适合 |
|---|---|
| MySQL | 权威数据、复杂查询、事务与持久化要求高的记录。 |
| Redis | 热点读、可容忍短暂不一致或可按 TTL 重建的视图、会话等。 |
缓存里的数据是派生副本;业务上应以 数据库成功写入 为最终一致目标,Redis 仅加速访问。
旁路缓存(Cache-Aside)流程
读路径
- 根据业务主键构造缓存键(如
cache:user:1001)。 GET:命中则直接返回。- 未命中:查 MySQL,若无记录则视业务决定是否缓存「空值」(短 TTL)以防穿透(见下文)。
- 将序列化后的结果
SET key value EX ttl写入 Redis,再返回给调用方。
写路径(更新 / 删除)
- 以 先成功写库 为前提,再处理缓存,常见两种做法:
- 删缓存:对对应键执行
DEL(或按前缀批量删,需谨慎设计键名)。下次读会回源重载,实现简单。 - 更新缓存:写库成功后
SET新值;需保证与 DB 字段同源,避免应用层算错。
- 删缓存:对对应键执行
- 「先删缓存再写库」与「先写库再删缓存」在并发下各有极端窗口期的讨论,团队应统一规范并在必要时用版本号、延迟双删或只读从库等策略;入门阶段掌握「写库为准 + 显式失效或覆盖缓存」即可。
键与 TTL 设计
- 命名:带业务前缀与主键,如
cache:article:{id}、v1:cache:user:{id},便于扫描与批量失效(仍优先SCAN)。 - TTL:根据业务可接受的最大陈旧时间设置;无 TTL 的缓存键在内存紧张时依赖全局 淘汰策略,行为不如主动过期可预测。
- 结构选型:整对象常用 String(JSON/MessagePack 序列化);字段独立更新频繁可用 Hash 分字段缓存,减少整包重写。
缓存穿透、击穿、雪崩(概念)
| 现象 | 含义(简述) | 常见应对 |
|---|---|---|
| 穿透 | 查询根本不存在的数据,缓存与库都无,请求每次都打穿到库。 | 对合法不存在的键缓存短 TTL 空值;或 布隆过滤器 在进库前拦截。 |
| 击穿 | 单个热点键过期瞬间,大量请求同时未命中,并发压库。 | 互斥锁(如单飞 SET NX 只允许一个回源)、逻辑过期(异步重建)、或热点键不设过短 TTL 并配合主动刷新。 |
| 雪崩 | 大量键同时过期或 Redis 宕机,请求洪峰打满数据库。 | TTL 加随机抖动(如 EXPIRE 在基础秒数上 ± 随机范围);高可用与限流降级;关键路径不唯依赖缓存。 |
与 go-redis 配合(读缓存伪流程)
逻辑上等价于:Get → 未命中则查库 → Set 带 Expiration;写库成功后对失效键 Del。注意 context 超时与错误处理,避免缓存层拖死主流程。
// 伪代码示意,非完整可运行示例
// val, err := rdb.Get(ctx, key).Result()
// if err == redis.Nil {
// row := loadFromMySQL(id)
// if row == nil { rdb.Set(ctx, key, "NULL", 30*time.Second); return nil, nil }
// payload, _ := json.Marshal(row)
// rdb.Set(ctx, key, payload, 5*time.Minute)
// return row, nil
// }生产代码中应处理序列化失败、MySQL 错误、缓存降级(Redis 不可用时直读库并限流)等。
安全与部署注意点
- 绑定地址:默认若只监听
127.0.0.1则仅本机可连;监听0.0.0.0时需防火墙限制来源,禁止无认证暴露公网。 - 认证:
requirepass(Redis 6 前常见)或 ACL(用户 + 权限粒度,Redis 6+)。 - TLS:敏感环境可对传输加密,由运维与云厂商文档配置。
- 命令改名/禁用:可对危险命令如
FLUSHALL做重命名或关闭,防止误操作与入侵。
最佳实践与常见误区
- 禁用
KEYS *,用SCAN迭代。 - 避免大 Key:单 value 过大阻塞持久化与主线程时间片,宜拆分或压缩。
- 热 Key:同一键 QPS 过高时考虑本地缓存、拆分或读写分离架构。
LRANGE key 0 -1在列表极长时开销大,应分页。- 缓存与 DB:明确 TTL 与淘汰策略;关键业务不唯一依赖 Redis 持久化 unless 已专项设计;穿透/击穿/雪崩在「使用 Redis 做缓存」一节有对照表,设计接口时顺带考虑。
小结
本章介绍了 Redis 的定位、Docker/本机运行与 redis-cli ping 自检、SCAN 与 KEYS 的差异,以及 String / Hash / List / Set / Sorted Set 常用命令、过期与淘汰、RDB/AOF、MULTI/EXEC 与 Pipeline、go-redis 最小连接、使用 Redis 做缓存(旁路缓存、键与 TTL、穿透/击穿/雪崩)、分布式锁、计数与限流、幂等、队列与排行榜,以及安全与最佳实践。可自行对照下列清单自检:
- 能启动 Redis 并用
redis-cli完成SET/GET、过期、INCR、一种集合类型 的基本操作。 - 理解
SCAN替代KEYS的原因。 - 能描述 Cache-Aside 的读路径与写路径,并说出 穿透、击穿、雪崩 的含义与一条对应手段。
- 能说明 SET NX EX 抢锁与 Lua 释放 的目的;知道单机锁的适用边界。
- 知道 RDB 与 AOF 的取舍大意,以及 maxmemory-policy 的存在意义。
- 能在 Go 中用 go-redis 建立连接并完成一次 Set/Get。
参考资料
- Redis 官方文档
- Redis 命令参考
- go-redis
- Redis 持久化
- Distributed locks with Redis(官方对锁模式的说明,含演进与注意点)