跳到主要内容

Redis 入门:先建立正确认知

Redis 可以理解为“把常用数据放在内存里、用网络命令访问的数据结构服务器”。它常被用作缓存,但并不只是缓存:字符串、哈希、列表、集合、有序集合和 Stream 让它能实现会话、排行榜、计数和事件流。

完成本章后,你应该能够:

  • 解释 Redis、key、value、TTL 和数据库编号分别是什么;
  • 根据业务选择 String、Hash、List、Set、ZSet 或 Stream;
  • 使用 redis-cli 完成基本读写并观察返回结果;
  • 判断一个需求是否适合放入 Redis;
  • 理解“Redis 很快”不代表所有命令都安全。

1. 准备一个练习环境

如果本机已经有 Redis,先验证连接:

redis-cli -h 127.0.0.1 -p 6379 ping

预期输出:

PONG

如果尚未安装,可以先用临时容器完成本章实验:

docker run --detach \
--name redis-basics \
--publish 127.0.0.1:6379:6379 \
redis:7.4-alpine

docker ps --filter name=redis-basics
redis-cli ping

本章结束后可执行 docker rm --force redis-basics 清理。这个临时容器没有挂载数据卷,不应用于保存重要数据。

2. 五个必须先记住的概念

  • key:数据名称,例如 user:42:profile
  • value:key 对应的数据,类型可以是字符串或集合;
  • TTL:key 的剩余生存时间,过期后 Redis 自动删除它。
  • database:单机 Redis 默认有多个逻辑数据库,编号通常为 0~15;生产建议主要使用 DB 0,并通过 key 前缀隔离业务;
  • command:客户端发送给 Redis 的操作,例如 GETHSETZADD
# 写入一个字符串,EX 60 表示 60 秒后过期
redis-cli SET welcome 'hello redis' EX 60
redis-cli GET welcome
redis-cli TTL welcome

如果 TTL 返回 -1,表示该 key 没有过期时间;缓存类数据大量出现 -1 往往意味着内存会持续增长。返回 -2 表示 key 不存在。

key 命名应能看出业务和对象,推荐使用冒号分层:

业务:对象类型:对象ID:字段
shop:user:42:profile
shop:product:1001:detail
shop:rate-limit:192.168.1.10

不要把完整 SQL、超长 URL 或敏感信息直接放进 key。key 自身也占内存,并会出现在日志、监控和诊断输出中。

3. 动手认识六种常用数据类型

3.1 String:缓存、计数和开关

String 可以保存文本、数字或二进制内容,单个 value 最大可达 512 MB,但生产中不应接近这个上限。

# 缓存商品名称,并设置 5 分钟过期
redis-cli SET 'shop:product:1001:name' '机械键盘' EX 300
redis-cli GET 'shop:product:1001:name'

# 原子计数
redis-cli SET 'shop:page:home:views' 0
redis-cli INCR 'shop:page:home:views'
redis-cli INCRBY 'shop:page:home:views' 10
redis-cli GET 'shop:page:home:views'

预期最终计数为 11INCR 是原子命令,多个客户端并发执行不会互相覆盖。

3.2 Hash:保存对象字段

redis-cli HSET 'shop:user:42' name 'Alice' level '3' city 'Shanghai'
redis-cli HGET 'shop:user:42' name
redis-cli HGETALL 'shop:user:42'
redis-cli HINCRBY 'shop:user:42' level 1

Hash 适合字段经常单独读取或修改的对象。不要把无上限增长的所有用户都放进一个巨大 Hash,否则迁移、删除和故障恢复都会变得困难。

3.3 List:有序队列

redis-cli LPUSH 'shop:tasks' 'send-email:1001'
redis-cli LPUSH 'shop:tasks' 'send-email:1002'
redis-cli LRANGE 'shop:tasks' 0 -1
redis-cli RPOP 'shop:tasks'

上面的组合形成先进先出队列。List 适合简单任务流,但需要可靠确认、重试和消费组时应使用 Stream 或专业消息队列。

3.4 Set:去重集合

redis-cli SADD 'shop:user:42:tags' vip new-user mobile
redis-cli SADD 'shop:user:42:tags' vip
redis-cli SMEMBERS 'shop:user:42:tags'
redis-cli SISMEMBER 'shop:user:42:tags' vip

第二次添加 vip 返回 0,因为 Set 中元素唯一。

3.5 ZSet:排行榜

redis-cli ZADD 'game:score:daily' 1200 alice 950 bob 1600 carol
redis-cli ZREVRANGE 'game:score:daily' 0 2 WITHSCORES
redis-cli ZINCRBY 'game:score:daily' 700 bob
redis-cli ZREVRANK 'game:score:daily' bob

ZSet 中每个成员有一个 score,Redis 按 score 排序。排名从 0 开始,因此 ZREVRANK 返回 0 表示第一名。

3.6 Stream:可确认的事件流

redis-cli XADD 'orders:events' '*' order_id 1001 status created
redis-cli XADD 'orders:events' '*' order_id 1002 status paid
redis-cli XRANGE 'orders:events' - + COUNT 10

Stream 会为消息生成递增 ID。生产消费还需要消费组、XACK、pending 检查、重试和长度控制;此处只建立基本认识。

4. Redis 适合与不适合什么

场景为什么适合需要额外注意
查询缓存内存访问快,可设置 TTL必须处理缓存失效和数据库回源
登录会话多台应用可共享状态设 TTL,登出时删除
排行榜有序集合可按分数排序限制榜单长度,避免无限增长
计数/限流INCR 是原子操作计数窗口和过期时间要一起设计
Stream 消费有消费组和待确认记录需处理积压、重试和消费者故障

Redis 不适合作为唯一的永久主数据存储,也不适合大文件、海量长期对象或复杂关联查询。RDB/AOF 是恢复能力,不是替代主数据库的理由。

一个典型缓存请求是怎样工作的

1. 应用先 GET 缓存 key
2. 命中:直接返回缓存内容
3. 未命中:查询 MySQL
4. 将查询结果 SET 到 Redis,并设置 TTL
5. 返回结果

如果大量请求同时发现一个热门 key 过期,它们可能一起查询数据库,这叫缓存击穿。生产系统需要互斥重建、逻辑过期或请求合并,不能只写简单的 GET/SET

5. 为什么它快,也为什么它会慢

Redis 使用内存、事件驱动网络模型,核心命令以单线程执行为主,因此避免了大量锁竞争。相反,单条慢命令会阻塞其他客户端:KEYS *、超大集合运算、大 value、长 Lua 脚本都是常见事故源。

新手的第一条规则是:一次命令只处理有限数据;遍历 key 用 SCAN,不用 KEYS。

可以用下面的实验直观看到危险命令的规模效应,但不要在生产执行:

# 只在临时实验实例创建 1000 个 key
for i in $(seq 1 1000); do redis-cli SET "lab:key:$i" "$i" >/dev/null; done

# DBSIZE 是 O(1),适合查看 key 总数
redis-cli DBSIZE

# SCAN 渐进返回,不会一次遍历完整数据库
redis-cli SCAN 0 MATCH 'lab:key:*' COUNT 20

6. 版本与架构选择

新系统选择仍在维护的 Redis 7.x 小版本并固定软件包/镜像标签。单机只适合开发或可重建缓存;需要自动主从切换用 Sentinel;数据或写入超过单机边界时用 Cluster。Valkey 是 Redis 生态的开源替代方案,迁移前须验证客户端、模块和持久化兼容性。

7. 本章检查

你应该能回答:

  1. TTL 返回 -1-2 分别表示什么?
  2. 用户对象为什么可以用 Hash,而排行榜为什么适合 ZSet?
  3. 为什么 RDB/AOF 不能自动把 Redis 变成可靠的主数据库?
  4. 为什么生产环境禁止 KEYS *

清理本章数据:

redis-cli --scan --pattern 'shop:*'
redis-cli UNLINK \
'shop:product:1001:name' 'shop:page:home:views' 'shop:user:42' \
'shop:tasks' 'shop:user:42:tags'
redis-cli UNLINK 'game:score:daily' 'orders:events'