大多数人初次接触 Redis 时把它当作"一个缓存",但这低估了它。Redis 是一个内存数据结构存储:它把丰富的数据类型——字符串、哈希、列表、集合、有序集合、流——保存在内存里,并通过网络对外暴露针对这些结构的原子操作。数据结构微秒级延迟的组合,正是为什么缓存、会话存储、限流器、排行榜、队列、发布/订阅常常都落在 Redis 上,甚至同一个系统里全都用它。这就是我们在缓存排行榜文章里反复用到的技术,下面讲讲它到底怎么工作。

⚡ 速览要点
  • 不只是缓存,而是数据结构服务器。字符串、哈希、列表、集合、有序集合、流、位图、HyperLogLog、地理位置,各自带有原子的 O(1)/O(log n) 操作。
  • 内存 + 单线程执行命令——内存速度加上无锁,意味着操作简单、可预测、极快(单节点约每秒 10 万+ 操作)。
  • 持久化是可选且可调的——RDB 快照(紧凑、重启快、可能丢最近的写)对比 AOF 追加日志(更持久、文件更大);常常两者都开。
  • 淘汰策略让它成为缓存——设置 maxmemory 和策略(LRU/LFU/TTL),内存压力下 Redis 自动淘汰。
  • 高可用靠复制 + Sentinel;扩展靠 Cluster——Cluster 把键空间分散到 16384 个哈希槽。
  • 当心大 key、热 key 和阻塞命令——一条慢的 O(n) 命令会卡住单线程,殃及所有客户端。
tldr

Redis 把带类型的数据结构放在内存里,在单线程上执行命令,所以操作天然原子且飞快。根据能容忍多少数据丢失,用 RDB(快照)和/或 AOF(写日志)持久化。用淘汰策略把它当缓存跑,用复制 + Sentinel 做高可用,用 Cluster(哈希槽)做横向扩展。它的数据类型解锁的远不止缓存:排行榜(有序集合)、限流、队列、发布/订阅、分布式锁。

为什么是内存 + 单线程

两个设计选择解释了 Redis 的速度。第一,数据放在内存(RAM)里,读写完全不碰磁盘——延迟是微秒级而非毫秒级。第二,命令执行是单线程的:一次只在一个核上跑一条命令,通过事件循环处理。这听起来像限制,其实是特性:没有锁、命令之间没有竞态,每个操作天然原子。你永远不会拿到一个执行了一半的自增。对内存存储来说 CPU 很少是瓶颈——网络和内存带宽才是——所以单线程也能轻松扛住每秒 10 万+ 操作。(现代 Redis 确实用线程做 I/O 和后台任务,但核心命令执行仍是单线程。)

关键推论

因为一个线程服务所有人,一条慢命令会阻塞其他所有客户端。在一百万元素的集合上跑 KEYS * 或大 SMEMBERS,会让服务器卡住整段时间。运维 Redis 的第一准则就是:让单条命令保持快速,避免在热路径上对大结构做 O(n) 操作。

数据结构

Redis 与 Memcached 这类纯键值缓存的区别,在于它的带类型的值以及在服务端执行的操作:

服务端、原子的数据结构操作
SET   session:42 "..."  EX 3600     # 字符串 + 1 小时 TTL
INCR  views:home                    # 原子计数器
HSET  user:42 name "Ada" tier gold  # 用哈希存对象
ZADD  board 9820 alice              # 有序集合 → 排行榜
ZREVRANK board alice                # O(log n) 求排名
LPUSH jobs "task1"  /  BRPOP jobs 0  # 简单队列

持久化:RDB 对比 AOF

内存存储不一定意味着易失。Redis 提供两种持久化机制,让它能在重启后存活:

方面RDB(快照)AOF(追加文件)
存什么数据集的某个时间点完整转储每条写命令,启动时重放
持久性可能丢上次快照以来的写最多丢约 1 秒(everysec fsync)
文件大小/重启紧凑;重启快较大;重放较慢
成本周期性 fork 可能让内存陡增持续写入 + 重写/压实

RDB 适合备份和快速重启;AOF 在你无法容忍丢超过一秒的写时更合适。常见的生产配置是两者都开——AOF 保证持久性,RDB 做快速备份——很多还用混合格式。如果纯把 Redis 当缓存用,可以完全关掉持久化,把数据当作可重新计算的。

淘汰与过期

要把 Redis 当缓存用,你用 maxmemory 给它的内存设上限,并选一个内存满时的淘汰策略:allkeys-lru(淘汰最久未用)、allkeys-lfu(淘汰最少使用——对倾斜访问更好)、volatile-ttl(在设了 TTL 的键里淘汰最快到期的),或 noeviction(拒绝写)。另外,键可以通过 EXPIRE 带上 TTL。Redis 同时用惰性(访问时)和主动(后台采样删除过期键)两种方式过期,在 CPU 与内存间权衡。淘汰(注意:Redis 用采样来近似 LRU/LFU,不是完美的全局排序)正是它能充当我们缓存深入讲解里那一层缓存的原因。

复制与高可用

单个 Redis 节点是单点故障。复制增加一个或多个副本,异步拷贝主节点的数据——可用于读扩展和热备。要做自动故障转移,Redis Sentinel 监控主从;主挂了,Sentinel 达成法定多数,提升一个副本为主并重新配置客户端。由于复制是异步的,故障转移可能丢失尚未到达被提升副本的最后几条写——这正是分布式系统里普遍存在的持久性与可用性权衡(见我们的 DDIA 复制笔记)。

Redis Cluster:横向扩展

当数据集或写吞吐超出单节点,Redis Cluster 把键空间分片到多个主节点。它把键空间划分为 16384 个哈希槽;每个键通过 CRC16(key) mod 16384 映射到一个槽,每个主节点拥有一段槽区间。加一个节点意味着把一部分槽迁移过去——只有受影响的键迁移,而不是整个键空间。

集群:键 → 16384 哈希槽 → 节点
slot(key) = CRC16(key) mod 16384

  节点 A: 槽    0 – 5460      (+ 副本)
  节点 B: 槽 5461 – 10922     (+ 副本)
  节点 C: 槽 10923 – 16383    (+ 副本)

  多键操作必须落在同一个槽 → 用 {hash tag}:
     MGET {cart:42}:items {cart:42}:total   # 两者都对 "cart:42" 取哈希

有个坑:多键操作(以及事务)只有在所有键落在同一个槽时才有效,你用哈希标签(hash tag)来强制这一点——{} 里的部分才是用来取哈希的。这也是让应用代码必须"感知 Cluster"的主要原因。

Redis 实际拿来干什么

这些数据结构直接对应了一大批用途:

Redis 对比 Memcached

方面RedisMemcached
数据类型丰富(列表、集合、ZSET、流……)仅字符串/二进制块
持久化RDB + AOF无(纯缓存)
复制/高可用有(副本、Sentinel、Cluster)无内建复制
线程模型核心单线程多线程
最适合多样数据结构、需要持久化简单的大型多核键值缓存

常见坑

总结

把 Redis 理解成一个单线程、内存的数据结构服务器最为贴切——这个框架解释了它的速度(内存 + 无锁 + 原子操作)、它的主要风险(一条慢命令卡住所有人)以及它的多才多艺(选对数据类型,Redis 就变成缓存、队列、排行榜或锁)。需要持久性时上 RDB/AOF,需要故障转移时上 Sentinel,单节点不够时上 Cluster(哈希槽)。

🎯 面试速答

单线程的 Redis 为什么快?数据在内存里、一次只跑一条命令,所以操作原子且无锁;瓶颈是网络/内存而非 CPU。
RDB 对比 AOF?RDB = 紧凑的时间点快照,重启快,可能丢最近的写;AOF = 追加每条写,约 1 秒持久性,文件更大、重放更慢。常常两者都开。
Redis Cluster 怎么分片?16384 个哈希槽;键经 CRC16 mod 16384 映射到槽;每个主拥有一段槽区间。多键操作需要用 {hash tag} 落到同一个槽。
Redis 对比 Memcached?Redis 有丰富数据类型、持久化和复制;Memcached 是更简单的多线程块缓存。除非只要纯缓存,否则选 Redis。
最大的运维风险?一条慢的 O(n) 命令(KEYS、大集合读)阻塞单线程,殃及所有客户端。

← 上一篇
GraphQL 与 REST 对比