Redis 基础回顾:数据结构概览、常用命令、单线程模型与性能优势
在深入缓存读写分离架构之前,必须夯实 Redis 核心基础。本章将系统梳理 Redis 的五大基础数据结构、对应的常用命令、单线程事件驱动模型及其性能优势,为后续的接口契约设计提供底层认知支撑。
2.1 五大基础数据结构概览
Redis 并非简单的键值存储,其值类型支持多种数据结构。理解每种结构的底层实现与适用场景,是正确设计缓存接口的前提。
| 数据结构 | 底层编码(3.2+) | 最大元素数 | 典型应用场景 |
|---|---|---|---|
| String | int / embstr / raw | 512 MB | 缓存对象、计数器、分布式锁 |
| List | quicklist | 2³² - 1 | 消息队列、时间线、最新列表 |
| Hash | ziplist / hashtable | 2³² - 1 | 对象字段缓存、用户属性 |
| Set | intset / hashtable | 2³² - 1 | 标签系统、去重、共同好友 |
| Sorted Set | ziplist / skiplist | 2³² - 1 | 排行榜、延时队列、限流窗口 |
编码转换要点:当 Hash 或 Sorted Set 的元素数量较少且元素体积较小时,Redis 自动使用压缩的 ziplist 编码以节省内存;超过阈值后自动切换为 hashtable 或 skiplist。接口契约中应避免依赖特定编码,但了解编码有助于预估内存开销。
2.2 常用命令分类速查
命令是接口契约的最小单元。以下按数据结构分类列出最常用的命令,并标注时间复杂度,帮助你在设计接口时预判性能瓶颈。
String 命令
SET key value [EX seconds|PX milliseconds] [NX|XX] # O(1)
GET key # O(1)
INCR key # O(1)
MSET key1 val1 key2 val2 # O(N)
MGET key1 key2 # O(N)
STRLEN key # O(1)
List 命令
LPUSH key value [value ...] # O(1) 每个元素
RPUSH key value [value ...] # O(1)
LPOP key # O(1)
RPOP key # O(1)
LRANGE key start stop # O(S+N),S为偏移量,N为返回元素数
LLEN key # O(1)
Hash 命令
HSET key field value # O(1)
HGET key field # O(1)
HMSET key f1 v1 f2 v2 # O(N)
HMGET key f1 f2 # O(N)
HDEL key field [field ...] # O(N)
HGETALL key # O(N),N为字段数
HLEN key # O(1)
Set 命令
SADD key member [member ...] # O(1) 每个元素
SREM key member [member ...] # O(1)
SMEMBERS key # O(N)
SISMEMBER key member # O(1)
SCARD key # O(1)
SINTER key1 key2 # O(N*M)
Sorted Set 命令
ZADD key score member [score member ...] # O(log N) 每个元素
ZRANGE key start stop [WITHSCORES] # O(log N + M)
ZREVRANGE key start stop [WITHSCORES] # O(log N + M)
ZREM key member [member ...] # O(log N) 每个元素
ZSCORE key member # O(1)
ZCARD key # O(1)
ZRANK key member # O(log N)
接口设计警示:KEYS、FLUSHALL、SMEMBERS 等 O(N) 命令在生产环境应严格禁止在接口路径中直接暴露。如需遍历,应使用 SCAN、SSCAN 等游标命令替代。
2.3 单线程模型与性能优势
Redis 的核心处理线程是单线程的,但这并非性能瓶颈,反而是其高性能的关键设计。理解单线程模型有助于在接口契约中合理控制并发与阻塞操作。
单线程模型的核心优势
| 优势维度 | 说明 | 对接口契约的影响 |
|---|---|---|
| 无锁竞争 | 所有操作串行执行,无需加锁,不会出现死锁或数据竞争 | 接口天然线程安全,无需在应用层加分布式锁(除非跨实例) |
| 低上下文切换 | 单线程避免了 CPU 频繁切换线程的开销 | 高并发下延迟稳定,适合对 p99 延迟敏感的接口 |
| 纯内存访问 | 数据全部在内存中,读写速度在纳秒级 | 接口响应时间主要受网络开销影响,而非磁盘 I/O |
| I/O 多路复用 | epoll/kqueue 高效管理大量连接,单线程可处理数万并发 | 连接数不再是瓶颈,但需注意慢查询会阻塞后续请求 |
| 原子性保证 | 单条命令天然原子,无需事务即可保证简单操作的原子性 | INCR、SETNX 等命令可直接用于计数器、分布式锁接口 |
性能基准参考:在普通物理机(Intel Xeon E5-2650 v3,64GB RAM)上,Redis 单实例 GET/SET 操作可达 10万+ QPS,Pipeline 批量操作可达 100万+ QPS。接口契约设计时应充分利用 Pipeline 减少网络往返。
单线程模型的注意事项
- 慢查询阻塞:单个耗时命令(如
KEYS *、SMEMBERS大集合、ZRANGE大范围)会阻塞整个事件循环,导致后续所有请求排队等待。接口契约中必须对这类操作设置超时或改用 SCAN 系列命令。 - 大 Key 风险:单个 Key 存储超过 10 MB 的数据(如大 String、大 Hash)会导致读写延迟飙升。接口契约应限制单 Key 大小,或采用分片策略。
- CPU 密集型操作:虽然 Redis 操作主要是内存访问,但
SORT、ZUNIONSTORE等命令会消耗较多 CPU。接口设计时应评估复杂度,避免在高峰期执行。
2.4 数据结构选择与接口契约的映射关系
在缓存读写分离架构中,接口契约的数据结构选择直接影响读写性能与一致性维护成本。以下给出常见业务场景与推荐数据结构的映射关系。
| 业务场景 | 推荐数据结构 | 接口契约示例 | 理由 |
|---|---|---|---|
| 用户会话缓存 | String (JSON) 或 Hash | GET session:{token} |
Hash 支持部分字段更新,减少序列化开销 |
| 商品详情缓存 | Hash | HGETALL product:{id} |
字段独立,可按需返回部分字段 |
| 最新文章列表 | List (LPUSH + LTRIM) | LRANGE articles:latest 0 19 |
天然支持分页,LTRIM 控制列表长度 |
| 文章标签去重 | Set | SISMEMBER tags:article:{id} "redis" |
O(1) 判断成员是否存在 |
| 实时排行榜 | Sorted Set | ZREVRANGE leaderboard 0 9 WITHSCORES |
按分数排序,支持范围查询 |
| 接口限流计数器 | String (INCR + EXPIRE) | INCR rate:limit:{userId} |
原子自增,配合过期时间实现滑动窗口 |
接口契约设计原则:优先使用 Hash 和 Sorted Set 替代 String 序列化存储对象,前者支持部分字段更新,减少网络传输量;后者支持范围查询,避免全量拉取。在读写分离架构中,写接口应尽量使用
HSET、ZADD 等部分更新命令,减少主库写入压力。