晚上八点,活动刚开场,商品详情页的访问量突然翻了十倍。应用服务器本身没报错,数据库 CPU 却一路往上爬。大部分请求查的还是同一批商品:标题、价格、库存展示文案,几秒钟内并没有变化。你当然可以继续给数据库加机器,但这就像为了让一份热门菜单被更多人看到,不断增加档案室的管理员——能解决问题,却没有抓住重复劳动的本质。
Redis 常被放在这里:应用先去 Redis 查一份离计算更近的结果,命中就直接返回;没命中再访问关系数据库,并把结果放回 Redis。这个思路很直观,也确实常用。不过,Redis 不只是缓存。它还是一个通过网络访问的内存数据结构服务器,能直接执行计数、去重、排队、排名、事件追加等操作。
真正需要避免的是另一个极端:一听到“内存”“低延迟”,就把所有数据都搬进 Redis。内存有成本,持久化有丢失窗口,复制通常是异步的,复杂关系查询也不是 Redis 的长项。学 Redis 的第一步不是背命令,而是先回答三个问题:数据怎么访问、故障时允许丢什么、这份数据能不能从别处重建。

应用服务通过 Redis 加速热数据读取,并以关系数据库作为权威数据源;缓存更新策略可能带来短暂不一致。
读完这一章,你应该能做到下面几件事:
关系数据库通常从“表、行、列、关系”开始组织数据。Redis 的入口更简单:客户端发送一个键和一条命令,服务端找到这个键对应的值,在服务端完成操作,再把结果返回。
例如,下面的命令并不是“取出数字、在应用里加一、再写回去”,而是让 Redis 在服务端直接完成自增:
INCR page:view:2026-08-13这点很重要。Redis 的价值不只是把数据放在内存,还在于它把常用的数据结构操作做成了原子命令。多个客户端同时执行 INCR 时,不需要各自在应用层先读后写,也就避开了最常见的计数覆盖问题。
你可以把 Redis 服务器想成一个只有一个取件窗口、但窗口后面工具很齐全的工作台。请求从网络来到窗口,工作人员拿到键,根据命令使用计数器、集合、排行榜或队列工具,完成后立即返回。工具选对了,应用代码会很短;工具选错了,再快的内存也会被大对象、全量扫描和无边界增长拖住。
Redis 在一个系统里可能同时承担多种工作,但每一种工作的验收标准并不一样。
同一个 Redis 实例也可以混合这些角色,但这往往会让容量和故障策略变得难以解释。缓存可以接受淘汰,支付状态却不行;排行榜允许短暂落后,库存扣减通常不能靠“差不多”。生产设计里,经常会把不同可靠性要求的工作负载拆开。
Redis 既不是“只能当缓存”,也不是“加了 AOF 就能无条件替代关系数据库”。它能不能保存核心数据,取决于访问模式、持久化配置、复制拓扑、备份方案和业务对数据丢失的容忍度。
最常见的旁路缓存流程是:读请求先查 Redis,未命中时查数据库,再把结果写入 Redis,并设置过期时间;写请求先更新数据库,再删除对应缓存,让下一次读取重新加载。
读取:查 Redis → 命中则返回
→ 未命中则查数据库 → 写入 Redis(带 TTL)→ 返回
写入:更新数据库 → 删除 Redis 中的旧缓存这个流程容易理解,但并不会自动获得强一致性。并发读写时,旧值仍可能在很短的窗口里被重新写入缓存;热门键同时过期时,大量请求也可能一起打到数据库。TTL 只能限制旧数据存活多久,不能证明任何时刻都一致。
所以工程上还会结合请求合并、过期时间抖动、版本号、消息失效通知或业务校验。具体方案要看“旧几秒是否可接受”,不能只看缓存是否跑通。
Redis 的全局键空间可以先理解成一张映射表:每个键对应一个值,而这个值有明确的数据类型。同一个键在同一时刻不会既是 Hash 又是 List。你用错误类型的命令操作它,Redis 会返回类型错误,而不是偷偷替你转换。
SET user:42:name "小林"
TYPE user:42:name这里的键是 user:42:name,类型是 String,值是一段字节序列。Redis 的 String 并不只保存人类可读文本,也可以保存整数形式的计数、序列化内容或二进制数据。命令是否可用,取决于值能不能被该命令正确解释。
键名最好能让人看出业务边界。常见写法是用冒号分段:
业务:对象:标识:用途
shop:product:8848:detail
auth:user:42:session
rank:course:2026-08这不是 Redis 强制的语法,只是一种可维护的约定。键名过短会难以排查,过长又会额外占内存。更重要的是不要把敏感数据直接塞进键名,因为键会出现在监控、慢日志、运维脚本和排障输出里。

Redis 的键一次只对应一种数据类型,不同结构适合解决不同的数据组织问题。
String 是最基础的类型,适合一个键对应一个值。常见场景包括验证码、页面缓存、配置开关、序列化对象和计数器。
SET auth:code:13800000000 "739214" EX 300 NX
INCR article:9527:views
MGET feature:checkout feature:couponEX 300 表示键在 300 秒后过期,NX 表示只有键不存在时才写入。多个选项组合后,SET 已经能覆盖不少“判断后写入”的场景。
代价也很直白:如果把一个巨大 JSON 全塞进 String,只改其中一个字段时通常需要整体读取、解析和回写。数据很大、更新又频繁时,网络流量和序列化成本会冒出来。
Hash 适合把同一对象的多个字段放在一个 Redis 键下,例如购物车、用户展示资料或设备状态。
HSET cart:42 sku:1001 2 sku:1008 1
HINCRBY cart:42 sku:1001 1
HGETALL cart:42与“大 JSON String”相比,Hash 可以单独读写字段。与“每个字段一个 Redis 键”相比,它又能把相关字段收在同一对象下。
但 Hash 不是关系表的一行。它没有外键,不会替你做跨对象连接,也不适合无限堆积字段。对一个超大 Hash 执行 HGETALL,仍然可能一次返回大量数据。需要增量遍历时应考虑 HSCAN。
List 保存按位置排列的字符串元素,允许重复,擅长从两端推入和弹出。它适合最近记录、简单工作队列和有限长度的时间线。
LPUSH user:42:recent "文章A"
LPUSH user:42:recent "文章B"
LTRIM user:42:recent 0 99
LRANGE user:42:recent 0 9这里 LPUSH 和 LTRIM 配合,让列表只保留最近 100 条。注意,List 的优势集中在两端操作;把它当成可以随意在中间定位和修改的大数组,性能与可维护性都会变差。
如果任务需要消费确认、失败重领、多消费者组和积压检查,Stream 往往比简单 List 更合适。
Set 保存不重复的成员,不保证业务顺序。它很适合标签、去重、共同好友、活动参与者和一次性行为判定。
SADD event:2026:visitors user:42 user:77
SISMEMBER event:2026:visitors user:42
SINTER user:42:tags user:77:tagsSADD 返回真正新增的成员数量,因此它本身就能帮助判断“是不是第一次”。不过,集合运算的成本会随集合规模变化。对几个超大集合直接求交集,不能因为命令只有一行就当它没有代价。
Sorted Set,也常写作 ZSet,为每个唯一成员关联一个分数,并按分数维护顺序。排行榜、延迟任务、优先级队列和时间范围索引都很常见。
ZADD rank:course 92 user:42 87 user:77
ZINCRBY rank:course 3 user:77
ZREVRANGE rank:course 0 9 WITHSCORES同一个成员再次 ZADD 通常是更新分数,不会生成重复成员。不同成员可以有相同分数;分数相同时还会按成员值确定稳定顺序。
Sorted Set 很好用,也容易被滥用。分数是数值,不适合承载任意多维排序逻辑;排行榜成员无限增长时,也需要按业务周期分键或定期裁剪。
Stream 面向追加式事件。每条消息包含一个 ID 和若干字段,既能按范围读取,也支持消费者组、待确认消息和重新认领。
XADD order:events * type created order_id 8848
XRANGE order:events - + COUNT 10如果只是“把消息发出去,在线订阅者收到了就算”,Pub/Sub 很轻;如果需要保存事件、离线后继续读、跟踪处理状态,Stream 更接近需求。不过,Stream 也不是自动等价于专业消息平台。消息积压、裁剪、确认、重试、故障切换时的数据丢失窗口,都需要设计。
只说 Redis 有“五种数据类型”已经过时。经典入门通常围绕 String、Hash、List、Set 和 Sorted Set,后来又加入了 Stream;现代 Redis 还提供或集成了更多专门结构。
具体可用类型与命令受 Redis 版本、发行方式和客户端支持影响。本章实验只使用普遍支持的基础结构;看到新类型时,先确认部署版本,再决定是否把它写进架构。
选类型时,不要从“我的数据长什么样”开始,而要从“我最频繁做什么”开始。
TTL 是某个键自己的生存时间。到期后,Redis 会通过访问时检查和后台主动过期逐步清理它。你不该依赖“到点的那一微秒内存就立刻减少”,但客户端再次访问时不应继续得到已经过期的值。
淘汰发生在配置了内存上限并达到压力时。Redis 根据 maxmemory-policy 决定拒绝写入,或从某类键中选出受害者。一个没有 TTL 的键也可能被淘汰;一个带 TTL 的键也不一定等到自然过期才消失,取决于策略。
“设置了过期时间”不等于“内存一定够用”。你仍然要估算键数量、平均值大小、数据结构开销、复制与持久化期间的额外内存,并为异常流量留出空间。
Redis 的主要工作集在内存里,避免了把每次数据操作都变成随机磁盘访问。这当然是速度的重要来源,但如果解释停在“内存比磁盘快”,就会漏掉另外几件事:服务端命令直接操作数据结构、常用路径短、事件循环避免为每个连接创建一个执行线程、协议简单,以及客户端可以用批量命令和流水线减少网络往返。
反过来说,Redis 的延迟也不只由 Redis 决定。客户端与服务端的距离、虚拟化调度、网络抖动、大响应、内存交换、后台持久化、主机超卖和应用连接方式,都可能比命令本身更慢。
所以不要拿一张脱离环境的基准测试图,推导自己的接口一定能达到某个固定毫秒数。真正有意义的指标是在接近生产的数据大小、命令比例、并发连接、持久化设置和网络条件下测出来的尾延迟。
许多客户端可以同时连接 Redis。操作系统会告诉 Redis 哪些连接已经准备好读或写,Redis 的事件循环处理这些就绪事件,不需要为每条闲置连接安排一个线程傻等。这就是 I/O 多路复用带来的直觉:一个调度台同时盯住许多窗口,哪个窗口有事就处理哪个。
进入命令执行阶段后,一个 Redis 实例里的命令主要沿主执行路径顺序处理。这让单条命令天然具有很清楚的原子边界,也省掉了共享数据结构上的大量锁竞争。
不过,“Redis 是单线程”只能算一句方便入门的缩写。现代 Redis 会使用后台线程或进程处理部分持久化、异步释放,以及可配置的网络 I/O 工作;不同版本的实现细节也会变化。更准确的说法是:客户端命令对共享数据集的执行主要由主线程串行推进,因此慢命令仍然会影响其他客户端。

Redis 通过 I/O 多路复用观察客户端连接,命令执行主要在主线程顺序完成;持久化、异步释放和部分网络 I/O 可由后台线程协助。
如果前面有一个需要遍历百万级元素的命令,后面即使只是 GET,也得等主执行通道空出来。复杂度标成 O(1) 也不等于永远快:值非常大时,网络传输、内存分配和响应编码照样有成本。
常见风险包括:
SCAN 渐进遍历。客户端执行十条很快的命令,如果每条都等待一次网络响应,花费可能主要来自十次往返。能用 MGET、MSET 等聚合命令时,优先减少命令数量;无法聚合但彼此不依赖时,可以使用 pipeline 一次发送多条命令,再按顺序读取响应。
pipeline 解决的是网络往返,不会把一组命令自动变成事务。别的客户端仍可能在适当的命令边界插入操作。反过来,MULTI/EXEC 让队列中的命令连续执行,也不代表具有关系数据库那种失败回滚。
单条 INCR 是原子的,下面这个“先读再写”流程却不是:
GET stock:8848
应用计算新库存
SET stock:8848 新值两个客户端可能读到同一个旧值,然后互相覆盖。更好的方向是优先使用 Redis 已有的原子命令;需要把多步逻辑放在服务端时,再评估事务、乐观锁、Lua 脚本或 Functions。
这里还有一个容易误会的点:Redis 事务保证排队的命令不会被其他客户端插入执行,但运行期错误不会触发整组回滚。把 Redis 事务想成“连续执行的一包命令”比想成关系数据库事务更准确。
不要把慢逻辑塞进脚本,指望“原子”自动等于“安全”。脚本在主执行路径上运行,执行时间过长同样会让其他客户端排队。原子性解决并发边界,不会消除计算成本。
讨论可靠性时,最容易把几个词混在一起:
它们会互相配合,但不能互相替代。一个实时复制的副本会忠实复制误删除;它不是历史备份。Cluster 能把键分片;它不会自动告诉你业务数据该保留多久。AOF 能帮助重放写入;它也不等于跨机房灾备。

备份、复制、高可用、扩容分别解决不同问题:RDB 与 AOF 负责数据恢复,复制与 Sentinel 提升可用性,Redis Cluster 通过槽位实现横向分片。
RDB 会在某个时间点生成紧凑的数据集快照。它适合做周期性恢复点和备份材料,加载通常也比较直接。
代价是两个快照之间的写入可能不在最近的 RDB 里。生成快照通常还涉及创建后台子进程和写磁盘;数据集很大、写入频繁或内存余量不足时,写时复制带来的额外内存和延迟要认真观察。
如果业务能接受“故障时回到几分钟前”,RDB 可能已经够用;如果不能,就要继续考虑 AOF 或其他数据安全方案。
AOF 记录会改变数据集的命令,重启时通过重放恢复状态。刷盘策略决定性能与数据丢失窗口:每次写都同步更稳妥但代价更高;每秒同步常在性能与风险之间取平衡;交给操作系统延后刷盘则窗口更难控制。
AOF 会增长,因此需要重写,把冗余历史压缩成能重建当前状态的最小命令集合。重写与刷盘都可能影响磁盘、CPU 和内存。开启 AOF 不是“免费获得不丢数据”。
RDB 与 AOF 可以组合使用。恢复、备份和运维策略需要一起设计,而不是只改一个配置项就结束。
Redis 常用主副本复制。连接正常时,主节点把数据变化持续发给副本;短暂断线后优先尝试部分同步,条件不满足时会做完整同步。
复制默认是异步的。主节点确认写入时,副本可能还没收到或还没处理到那条变化。WAIT 一类机制可以降低特定故障下丢失写入的概率,但不会把整个系统变成严格强一致的数据库。
副本可以承担允许陈旧的读请求,也能为故障切换提供候选节点。读写分离之后,应用必须接受“刚写完去副本读,可能暂时读不到”的现实。
Sentinel 会监控主节点和副本,在达到故障判定条件后选出副本提升为新主节点,并向客户端提供新的主节点地址。它解决的是发现故障和协调切换,不负责把数据分到多个分片。
故障转移期间仍会有短暂不可用和连接重建。由于复制是异步的,新主节点也可能缺少旧主节点最后一小段写入。客户端需要支持 Sentinel 发现与重连,应用还要明确重试是否可能造成重复操作。
Redis Cluster 把键空间映射到 16384 个哈希槽,再把槽分配给不同主节点。客户端根据键计算槽位,把请求发到负责该槽的节点;槽位迁移后,客户端根据重定向更新路由。
Cluster 同时使用主副本模型提高部分节点故障时的可用性,但它的核心价值是分片。涉及多个键的命令、事务或脚本,通常要求这些键位于同一个槽。可以使用哈希标签让相关键落到同一槽:
user:{42}:profile
user:{42}:cart这里真正参与槽位计算的是花括号里的 42。哈希标签能解决相关多键操作,却也可能制造热点:如果把太多数据都绑到一个标签,分片的负载就失衡了。
生产环境不要在“无持久化、进程自动重启”的主节点上盲目挂副本。主节点空数据重启后,副本可能同步成同样的空数据。高可用方案必须和持久化、备份、恢复演练一起验证。
订单、账务、合同、库存流水这类数据通常需要清晰约束、复杂查询、事务边界和长期审计。MySQL、PostgreSQL 等关系数据库以表、索引、约束和 SQL 为中心,更适合表达实体之间的关系。
Redis 当然也能保存对象和索引,但很多关系需要由应用自己维护。一旦你开始手工同步十几个 Set、Hash 和 Sorted Set 来模拟任意查询,就要停下来问一句:是不是在用数据结构命令重新发明一个更难维护的数据库。
常见组合不是二选一,而是各司其职:
关系数据库:保存权威订单与完整查询维度
Redis:保存热点详情、短期幂等标记、实时计数或派生排行榜这个组合的难点不在连接两个系统,而在定义谁是权威、什么时候失效、失败后如何修复。只要这三件事没写清楚,缓存层就可能把数据库压力问题换成一致性问题。
Memcached 同样是内存键值系统,接口集中在获取、设置、删除、过期与计数等基础操作,常见部署由客户端负责把键分散到不同节点。它的模型简单,适合“值能从别处重建,只需要一个轻量缓存池”的场景。
Redis 提供更多服务端数据结构、持久化、复制、脚本、Stream 和集群能力,能承担缓存之外的实时状态操作。更多能力也意味着更多配置、内存模型和故障语义要理解。
如果你只需要存一段可丢弃的序列化结果,Memcached 的简单可能是优点;如果你需要服务端原子计数、集合关系、排行榜或可恢复数据,Redis 往往更顺手。选型不应该变成品牌比赛,而要落在业务动作上。

从数据能否重建、访问模式是否固定、是否需要关系查询三个问题,判断关系数据库、Redis 与 Memcached 的适用场景。
能从数据库或事件日志重建的缓存,可以更激进地过期和淘汰。无法重建的业务事实,需要明确的持久化、备份、恢复时间和数据丢失目标。
“按用户 ID 取会话”“取积分最高的前 100 名”是 Redis 擅长的固定访问模式。“运营明天可能任意组合十几个字段查询”更像关系数据库或搜索系统的工作。
不能只数业务值的字节数。键名、对象头、数据结构编码、过期字典、复制缓冲、客户端缓冲和持久化时的额外内存都要算。还要考虑大促、补数据和故障恢复期间的峰值。
下面假设 Redis 已经在本机 127.0.0.1:6379 运行。不要对未知生产实例照抄清理命令。先连接一个专门用于练习的本地实例:
redis-cli命令行提示符出现后,先做最小连通性检查:
PING正常情况下会得到 PONG。这只能说明当前连接能完成一次请求响应,不代表持久化、复制、认证和内存配置都正确。

沿着路线依次体验 Redis 基础命令和四种常用数据结构,实验结束后删除测试数据。
SET lab:session:42 "已登录" EX 120 NX
GET lab:session:42
TYPE lab:session:42
TTL lab:session:42逐行观察:
SET 把值写入 String,并设置 120 秒 TTL;NX 防止覆盖已经存在的键。GET 读取值。TYPE 返回键的数据类型。TTL 返回剩余秒数;-1 表示键存在但没有过期时间,-2 表示键不存在。如果你再次执行同一条带 NX 的 SET,它不会覆盖旧值。想明确更新并重新设置 TTL,可以改用普通 SET ... EX ...。不要先 DEL 再 SET 来模拟条件写入,两条命令之间会暴露并发窗口。
HSET lab:user:42 name "小林" city "杭州" login_count 1
HGET lab:user:42 name
HINCRBY lab:user:42 login_count 1
HGETALL lab:user:42
EXPIRE lab:user:42 600
TTL lab:user:42HSET 可以一次设置多个字段,HINCRBY 在服务端完成字段自增。EXPIRE 作用于整个 lab:user:42 键,不是只作用于刚改的 login_count 字段。
HGETALL 适合这个很小的演示对象。面对字段数量不受控的 Hash,不要把全量读取当成默认接口。
先保存有顺序、允许重复的最近访问记录:
RPUSH lab:recent "Redis 首页" "数据类型" "数据类型"
LRANGE lab:recent 0 -1
LPOP lab:recent
LRANGE lab:recent 0 -1再保存只关心唯一性的标签:
SADD lab:tags "缓存" "排行榜" "缓存"
SCARD lab:tags
SISMEMBER lab:tags "缓存"
SMEMBERS lab:tags你会看到 List 保留了重复的“数据类型”,Set 对重复的“缓存”只保存一份。SMEMBERS 的输出顺序不应该被业务依赖;如果需要排序,应该明确选择 Sorted Set 或在其他层处理。
ZADD lab:rank 80 "小林" 95 "阿青" 88 "小周"
ZINCRBY lab:rank 10 "小林"
ZREVRANGE lab:rank 0 -1 WITHSCORES
ZREVRANK lab:rank "小林"ZINCRBY 把“小林”的分数从 80 增加到 90。ZREVRANGE 按分数从高到低返回成员;ZREVRANK 的排名从 0 开始,所以返回 0 表示第一名。
业务页面通常显示从 1 开始的名次,应用层要记得加一。并列分数时也要提前确定展示规则,不能只看分数就假设两个人会占同一个名次。
XADD lab:events * type login user_id 42
XADD lab:events * type view_page user_id 42 page redis-intro
XLEN lab:events
XRANGE lab:events - + COUNT 10星号让 Redis 生成递增的消息 ID。XRANGE 从最小 ID 读到最大 ID。这里我们还没有创建消费者组,只是感受“按 ID 追加和范围读取”的模型。
本地只有几个键,任何方式看起来都很快。但为了养成可以带到生产环境的习惯,使用渐进式 SCAN:
SCAN 0 MATCH lab:* COUNT 100返回的第一项是下一次扫描要使用的游标,游标回到 0 才表示一轮完成。COUNT 是给 Redis 的工作量提示,不保证每次恰好返回 100 个键。扫描期间键空间还可能变化,所以它适合渐进遍历,不是某个瞬间的严格快照。
确认自己连接的是练习实例后,再删除本章明确创建的键:
DEL lab:session:42 lab:user:42 lab:recent lab:tags lab:rank lab:events不要在共享或生产实例上为了省事执行全库清空。也不要把“能连上 Redis”误认为“可以把端口暴露到公网”。生产环境至少需要网络隔离、认证与 ACL、加密连接、最小权限和安全的运维入口。
到这里,你已经完成了比背诵定义更有价值的一轮实验:同一个 Redis 键空间里,不同类型允许不同的服务端操作;TTL 控制生命周期;每条命令的返回值又能帮助你判断实际发生了什么。
错。命令复杂度、集合规模、值大小、网络响应、脚本时长和机器状态都会影响延迟。先看命令复杂度,再看真实数据规模,并用慢日志和延迟监控观察尾部情况。
不准确。命令对共享数据集的执行主要沿主线程顺序进行,但网络 I/O、持久化和内存释放等工作可以涉及其他线程或进程。这个区分不会改变核心风险:长时间占用主执行路径的命令会阻塞其他请求。
错。异步复制存在延迟,故障切换可能丢掉尚未传到新主节点的写入;误删除也会复制过去。复制提高冗余和可用性,不替代持久化与独立备份。
错。是否在每次写后同步到持久设备,由刷盘策略和操作系统行为共同决定。更强的刷盘策略通常带来更高开销,也仍要面对硬件、文件系统和备份层面的风险。
错。Redis 会结合访问时检查和主动过期回收。过期语义对客户端有效,不意味着后台内存回收在那一微秒完成。大量键同时过期还可能制造负载尖峰,常见做法是给过期时间加随机抖动。
错。Set 不提供可依赖的业务顺序。需要按分数或时间范围查询时,选择 Sorted Set;需要按追加 ID 保存事件时,选择 Stream。
不一定。单个键未命中可能只多查一次数据库;大量热门键同时失效会把压力集中推向权威数据源。还要考虑空结果穿透、热点键击穿和大面积失效引起的雪崩。
你要为在线课堂设计三个功能:验证码 5 分钟失效、同一用户不能重复签到、实时展示积分前 10 名。请为每个功能选择类型、写出核心命令,并说明最重要的失败边界。
现在再看 Redis,可以把它概括成一句更有用的话:Redis 是一台通过键访问、在内存中执行数据结构命令,并可配置持久化、复制与分片的服务器。
“快”只是结果,不是设计依据。真正的设计依据是你要执行什么动作:整体取值、字段更新、两端队列、成员去重、分数排名,还是事件追加。动作确定后,数据类型、复杂度、生命周期和故障语义才有落点。
下一次准备把一份数据放进 Redis 时,先写下这四行:
最常见的读写动作:
数据能否从别处重建:
允许的过期与丢失窗口:
规模上限与清理方式:四行都能答清楚,再开始写命令。这样做看似慢了一分钟,通常能省下后面一整轮“大键为什么出现、缓存为什么不一致、故障切换为什么丢写”的排查。