Skip to content

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)默认多库编号 015(可配置);SELECT 切换。集群模式下常用单库 0,避免跨槽习惯。
redis-server / redis-cli服务端守护进程与命令行客户端。

安装与运行

Docker(推荐与 7.9 一致)

bash
docker run -d \
  --name redis-dev \
  -p 6379:6379 \
  redis:7-alpine \
  redis-server --appendonly yes

带密码示例(勿在生产示例中沿用弱口令):

bash
docker run -d \
  --name redis-dev \
  -p 6379:6379 \
  redis:7-alpine \
  redis-server --requirepass 'your_strong_password' --appendonly yes

默认端口 6379

本机安装(点到为止)

  • macOS:brew install redisbrew services start redis
  • Linux:发行版包 redis-server 或官方源。

自检

bash
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 基础

连接

bash
redis-cli -h 127.0.0.1 -p 6379
# 进入后可执行:
AUTH your_strong_password

查看帮助与类型

text
HELP @string
TYPE mykey

遍历键:慎用 KEYS,优先 SCAN

  • KEYS pattern:阻塞式全表扫描,键多时影响线上延迟,生产禁用
  • SCAN cursor [MATCH pattern] [COUNT hint]:迭代游标,在应用或脚本中分批遍历。
text
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单独设置过期、查看剩余秒数。

示例:

text
SET page:home "<html>...</html>" EX 300
GET page:home
INCR api:visits
TTL page:home

Hash(哈希)

适合存对象的多个字段,避免大量独立键。

text
HSET user:1001 name Alice age 30
HGET user:1001 name
HGETALL user:1001

List(列表)

双端列表;可作 FIFO 队列雏形。

text
LPUSH jobs:pending job-a job-b
RPOP jobs:pending
LRANGE jobs:pending 0 -1

Set(集合)

无序、成员唯一。

text
SADD tags:post:1 go redis
SMEMBERS tags:post:1
SISMEMBER tags:post:1 go

Sorted Set(有序集合,ZSet)

分数的成员排序,适合排行榜。

text
ZADD leaderboard 100 player:1 200 player:2
ZREVRANGE leaderboard 0 9 WITHSCORES
ZRANK leaderboard player:1

Stream(流,了解)

Redis 5 起提供 Stream,支持消费组、持久化消息流,语义接近日志型消息队列;入门阶段掌握 List 与专业 MQ 的差异即可,深入可查官方 Streams 文档。

Key 设计建议

  • 使用业务前缀,避免键名冲突,如 cache:user:{id}lock:order:{id}
  • 为缓存键设置 TTL,避免内存无限增长。
  • 控制 单个 value 体积(大 Key 问题见下文)。

过期与内存管理

过期

  • EXPIRE key secondsPEXPIRE、创建键时 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

text
MULTI
SET a 1
INCR b
EXEC
  • EXEC 前命令入队;EXEC依次执行,中间不会被其他客户端插入(保证队列批处理)。
  • 与关系库事务不同:不支持对多数命令失败自动回滚;语法错误可能导致整批丢弃或部分执行,需查阅版本行为。
  • 需要「判断再写」的原子逻辑,优先 Lua 脚本单条命令(如 SET NX)。

Pipeline(管道)

客户端一次发送多条命令、一次读回结果,减少网络往返;不保证与 MULTI/EXEC 相同的原子边界,适合批量读写的性能优化。

与 Go 衔接

Go 生态中常用 go-redis(模块路径 github.com/redis/go-redis/v9)。最小连接示例:

go
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)
}

依赖安装:

bash
go get github.com/redis/go-redis/v9

连接地址、密码、库编号建议来自环境变量或配置中心,勿写死生产密钥。

应用实践举例

以下以 redis-cli 语义 为主说明模式;Go 中对应 SETNX/EXEval 执行 Lua 等 API,可查 go-redis 文档。

1. 分布式锁(原子抢占)

获取锁:使用 SET lock:资源名 唯一令牌 NX EX 过期秒NX 表示不存在才设置,EX 防止客户端崩溃导致死锁。

text
SET lock:order:1001 token-uuid-abc NX EX 30
# 返回 OK 表示抢到;返回 (nil) 表示已被占用

释放锁:必须先校验「值是否为当前持有者」再删除,避免误删他人锁。应用侧常用 Lua 脚本 保证「比较 + 删除」原子:

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. 原子计数与简单限流

计数

text
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(阻塞右端弹出),避免空轮询:

text
LPUSH jobs:pending "{\"type\":\"email\",\"to\":\"a@b.com\"}"
BRPOP jobs:pending 10

Kafka、RabbitMQ 等相比,无内置高可用投递语义与复杂路由,适合轻量、可丢或可补偿场景。

5. 排行榜 / 热搜

上文 Sorted SetZADD / ZREVRANGE 即可;分数可为销量、积分、时间戳等。注意控制成员数量与 大 Key 风险。

6. 缓存(Cache-Aside)

典型「读多写少」场景用 Redis 减轻 MySQL7.8)压力时,多采用 旁路缓存 模式,流程与注意点见下文 「使用 Redis 做缓存」

使用 Redis 做缓存

本节说明如何把 Redis 当作读缓存:与关系库的职责划分、推荐流程、键与 TTL 设计,以及缓存穿透 / 击穿 / 雪崩等常见问题的应对思路(概念级)。

与 MySQL 的分工

存储适合
MySQL权威数据、复杂查询、事务与持久化要求高的记录。
Redis热点读、可容忍短暂不一致或可按 TTL 重建的视图、会话等。

缓存里的数据是派生副本;业务上应以 数据库成功写入 为最终一致目标,Redis 仅加速访问。

旁路缓存(Cache-Aside)流程

读路径

  1. 根据业务主键构造缓存键(如 cache:user:1001)。
  2. GET:命中则直接返回。
  3. 未命中:查 MySQL,若无记录则视业务决定是否缓存「空值」(短 TTL)以防穿透(见下文)。
  4. 将序列化后的结果 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 → 未命中则查库 → SetExpiration;写库成功后对失效键 Del。注意 context 超时与错误处理,避免缓存层拖死主流程。

go
// 伪代码示意,非完整可运行示例
// 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 自检、SCANKEYS 的差异,以及 String / Hash / List / Set / Sorted Set 常用命令、过期与淘汰RDB/AOFMULTI/EXECPipelinego-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

参考资料