这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 本章建立 Redis 的概念地图:key、database、value、TTL、编码、客户端连接与请求模型。这些名词会贯穿全书。
2.1 Redis 的数据视图
Redis 可以被看作一个巨大的 Map:
┌──────────────── Redis Instance ─────────────────┐
│ DB0 │
│ "user:1001" -> Hash │
│ "stock:sku:1" -> String("99") │
│ "rank:score" -> ZSet │
│ "session:abc" -> String / JSON, TTL=1800 │
│ DB1 ... DB15 │
└──────────────────────────────────────────────────┘
- key 是唯一的字符串;
- value 是某种类型的数据结构;
- key 可以设置 TTL,到期后不可访问;
- 默认有 16 个逻辑 DB(
databases=16),通过SELECT切换。
2.2 Key 设计规范
Redis 没有表和库的强模型,key 设计就是“schema”。推荐:
业务:对象类型:ID[:属性]
示例:
shop:product:10001
shop:stock:sku:10001
user:session:9f8c...
rank:activity:2026:score
rate:login:user:1001
命名建议
- 使用冒号分层,方便按前缀管理和检索;
- 长度控制在几十字节内,语义清晰但不要冗长;
- 不要使用随机超长 key;
- 版本化破坏性结构:
user:profile:1001:v2; - 避免一个 key 装下几十万字段的大 Hash。
一个常见误解
Redis 不会按 : 自动建立目录索引。SCAN shop:* 的匹配仍是扫描 key 空间,只是跳过不匹配项。想高效统计对象数量,应使用业务索引集合或外部元数据,而不是频繁 SCAN。
2.3 逻辑 DB
默认 DB 数量:
databases 16
客户端连接后默认在 DB0:
SELECT 3
FLUSHDB # 只清当前 DB
FLUSHALL # 清所有 DB
多 DB 只是命名空间隔离,不提供权限隔离,也不隔离内存和 CPU。因此:
- 单体应用可以用 DB 区分开发/测试数据;
- 生产多业务/多租户建议独立实例或 Cluster;
- Redis Cluster 模式下通常只用 DB0。
2.4 Value 类型总览
| 类型 | 典型语义 | 常用命令 | 场景 |
|---|---|---|---|
| String | 字符串/整数/二进制 | SET GET INCR APPEND |
缓存对象、计数、token |
| Hash | 字段到值的映射 | HSET HGET HGETALL |
对象属性、购物车 |
| List | 双端链表/快速列表 | LPUSH RPOP LRANGE |
队列、时间线 |
| Set | 无序唯一集合 | SADD SISMEMBER SINTER |
标签、好友、去重 |
| ZSet | 带 score 的有序集合 | ZADD ZRANGE ZRANGEBYSCORE |
排行榜、延迟队列 |
| Bitmap | 位图(String 上操作) | SETBIT BITCOUNT |
签到、活跃标记 |
| HyperLogLog | 基数估算 | PFADD PFCOUNT |
UV 估算 |
| GEO | 地理位置 | GEOADD GEOSEARCH |
附近门店 |
| Stream | 消息流 | XADD XREAD XGROUP |
日志流、事件队列 |
选型原则:优先贴近业务语义,其次考虑命令复杂度和内存占用。不要把所有数据都 JSON 序列化成 String;也不要为了局部更新一个字段而创建上千个细碎 key。
2.5 TTL 与过期时间
SET session:abc "user-1001" EX 1800
TTL session:abc
EXPIRE session:abc 3600
PERSIST session:abc
要点:
- TTL 以 key 为单位,不能只给 Hash 的某个 field 设置过期;
- Redis 7.4+ 开始支持
HEXPIRE等 field 过期能力,需确认版本; - TTL 到期后 key 逻辑上不可访问;
- 删除可能是惰性或周期性的,不代表到期瞬间一定立刻释放内存;
- 重写 value 或某些操作会保留 TTL,
SET默认清除 TTL,除非显式KEEPTTL。
详细机制见第 14 章。
2.6 原子性:Redis 的单线程红利
单条 Redis 命令在服务端串行执行,因此:
INCR counter
不会出现两个客户端同时读到 9、都写回 10 的交错。多个客户端的命令总有一个先后顺序。
但这不等于“多命令事务”:
INCR a
INCR b
两条命令之间可能插入其他客户端命令。要保证原子执行,需要:
MULTI/EXEC入队后顺序执行;- Lua 脚本在服务端一次执行;
- 某些组合命令天然一步完成(如
SET ... NX PX)。
见第 9 章。
2.7 RESP 协议与请求模型
客户端与 Redis 使用 RESP(REdis Serialization Protocol)通信。简化过程:
Client Redis
| --- 命令文本/RESP 请求 ------> |
| 解析命令
| 查找 key
| 执行命令
| <------ RESP 响应 ------------ |
RESP2 常见响应类型:
+OK:简单字符串;-ERR ...:错误;:123:整数;$5\r\nhello:批量字符串;*2\r\n...:数组。
RESP3 新增 map、double、boolean、push 等类型,让客户端可以拿到更丰富的语义,例如连接中断 push、哈希字段值类型提示。
2.8 Redis 的线程模型概览
Redis 6.0 之前通常被称为“单线程”,更准确地说:
- 命令执行始终由主线程完成;
- 网络 IO 在 6.0 后可配置多线程;
- 后台线程处理关闭文件、AOF 刷盘、异步删除等任务;
- BIO/惰性释放避免主线程被昂贵释放操作阻塞。
io-threads 4
io-threads-do-reads yes
因此,即使开启 IO 多线程,慢命令依然会阻塞整个实例。线程模型细节见第 15 章。
2.9 持久化与高可用概念速览
| 概念 | 作用 |
|---|---|
| RDB | 定期生成内存快照,恢复快、文件小 |
| AOF | 记录写命令,丢失窗口更小 |
| 混合持久化 | RDB 头 + 增量 AOF,兼顾恢复速度与数据安全 |
| 主从复制 | 建立副本,读写分离与灾备 |
| Sentinel | 自动故障检测与主从切换 |
| Cluster | 分片、横向扩容、去中心化路由 |
这些机制分别在第 16-19 章展开。
2.10 与 MySQL 的概念对照
| MySQL | Redis | 差异 |
|---|---|---|
| Database/Table | 逻辑 DB / key 前缀 | Redis 无严格 schema |
| Row | key-value | Redis value 是结构 |
| Column | Hash field | Hash 更适合局部字段更新 |
| Index | Set/ZSet/业务索引 | 需业务自己维护 |
| SQL | Redis 命令 | 无 join/复杂查询 |
| Transaction | MULTI/Lua | 能力有限,非 SQL 事务 |
| 主从复制 | replication | Redis 常见异步复制 |
把 Redis 当“可远程访问的内存数据结构服务”,比当“更快的 MySQL”更准确。
本章小结
- Redis 数据模型是 key 到结构化 value 的映射;
- key 设计是长期可维护性的核心,推荐
业务:类型:ID; - 逻辑 DB 只是命名空间,不隔离资源与权限;
- TTL 以 key 为单位,删除有惰性和周期两种路径;
- 单条命令原子,多条命令需要 MULTI 或 Lua;
- 命令执行单线程,网络 IO 可多线程,慢命令仍是全局风险。
思考题
- 为什么不建议所有对象都序列化成 JSON 字符串存 String?
- 如果要更新用户资料的昵称字段,String JSON 和 Hash 哪个更合适?为什么?
SCAN user:*会不会因为使用了:分层而变得高效?为什么?