简介:这份PPT面向后端开发、运维工程师及Redis初学者,系统梳理了Redis的核心知识体系,可用于技术分享、内部培训或自学复习。资源包共1个pptx文件,大小约1.59MB,以幻灯片形式组织内容,便于直接演示或按页查阅。目前已有161人学习下载。内容覆盖Redis简介、配置文件详解、持久化机制与常用命令四大模块:从读写性能、原子性、主从复制等优缺点切入,逐项说明redis.conf中端口、超时、守护进程、数据库数量等配置含义;重点对比RDB快照与AOF日志两种持久化策略的配置项、适用场景及各自优劣;并整理key、String等命令的用法与返回值。整体结构清晰、要点集中,适合作为Redis入门梳理或团队分享的参考材料。
1. 单线程的 Redis 为什么能跑到 11 万 QPS:从一次压测翻车说起
很多人第一次看到 Redis 的性能数据都会愣一下:读 110000 次/s、写 81000 次/s,而它处理命令的主循环居然是单线程的。按直觉,单线程应该慢才对,多线程才能压榨多核。可现实恰恰相反——Redis 用一条线程把绝大多数请求都扛了下来,还成了缓存领域的事实标准。这背后的机制值得搞懂,因为它直接决定了你写业务代码时哪些操作是安全的、哪些会拖垮整个实例。这份 Redis 技术分享把简介、配置、持久化、命令、事务、发布订阅串成了一条线,正好可以顺着它把「为什么这么设计」一层层拆开。搞懂之后你会发现,Redis 的快不是玄学,而是一连串取舍的结果。
2. 内存 + 单线程 + 原子性:性能数字背后的三个设计支点
2.1 为什么把数据放内存里,而不是「内存 + 磁盘」混合
Redis 被定义为一个「内存存储的 key-value 数据结构服务器」,这句话里最关键的字是「内存」。磁盘随机读一次大概要几十到上百微秒,内存访问只要几十纳秒,差了三个数量级。Redis 把全部数据常驻内存,读写路径上几乎没有磁盘 I/O,这是它能跑到十万级 QPS 的物理基础。
但纯内存有个致命问题:断电就没了。所以 Redis 又补了持久化机制,把内存数据定期或实时落到磁盘。注意这里的顺序——内存是主存储,磁盘是备份,而不是反过来。这跟传统数据库「磁盘为主、内存做缓存」的思路正好相反。理解这个定位,后面看 RDB 和 AOF 的取舍就顺了:它们服务的目标不是「每次读写都保证落盘」,而是「尽量少丢数据,同时别拖慢主路径」。
常见的实现思路是,Redis 在启动时把磁盘上的持久化文件加载回内存,之后所有读写都在内存完成,持久化在后台异步进行。所以 Redis 的「持久」是有窗口的,不是每笔操作都同步落盘。
2.2 单线程模型:不是不能多线程,是不值得
Redis 的核心命令处理是单线程的,这一点经常被误解成「Redis 只能用一个 CPU 核」。准确说法是:命令的执行是单线程的,但持久化、集群同步这些事会 fork 子进程或开额外线程去做。
为什么命令执行要用单线程?因为多线程处理共享数据结构要加锁,加锁就有竞争、有上下文切换、有死锁风险。Redis 的操作大多是内存里的简单读写,单次执行极快,如果为了并行而引入锁,锁的开销可能比操作本身还大。单线程反而避免了锁竞争,让每次操作都是原子的——这正是分享里强调的「所有操作都是原子性的」的来源。
注意:单线程的原子性指的是「单条命令」原子,不是「多条命令组合」原子。多条命令要原子,得靠事务或 Lua 脚本。
单线程也带来一个硬约束:任何一条慢命令都会阻塞后面所有请求。比如对一个存了几百万 key 的实例执行KEYS *,它会遍历整个键空间,期间所有请求排队等待。这就是为什么生产环境禁用KEYS,改用SCAN系列增量迭代。
2.3 原子性与数据结构:为什么丰富类型比「只有字符串」更值
分享里列了 string、hash、set、sorted set、list 五种基础结构。很多人觉得这只是「方便」,其实它直接影响原子性和性能。举个例子,用 hash 存一个对象的多个字段,HSET、HINCRBY这些命令在服务端一次完成,天然原子;如果只有 string,你得先GET出来、在客户端改、再SET回去,这中间就有并发窗口,两个客户端同时改就会丢更新。
INCR是另一个典型。计数器场景下,INCR key在服务端一步完成读-改-写,不需要客户端加锁。如果换成「读出来加一写回去」,高并发下必然出错。所以数据结构的丰富性不只是 API 好用,它把「需要原子保证的操作」下沉到了服务端,减少了客户端协调成本。
代价是内存开销。每种结构都有自己的底层编码,比如小数据量时 list 用 ziplist、大了转 quicklist,hash 小了用 ziplist、大了转 hashtable。这些编码转换对使用者透明,但会影响内存占用和单条命令的耗时。理解这层,才能解释为什么同样存一百万个 key,用 hash 聚合和用独立 string,内存能差出好几倍。
3. RDB 与 AOF:持久化到底在保什么,代价是什么
3.1 RDB 快照:fork 出来的子进程在干什么
RDB 的思路很直白:把某一时刻内存里的全部数据写成一个二进制快照文件,默认叫dump.rdb。恢复时直接把这个文件读回内存。核心配置是save 60 1000,意思是 60 秒内有 1000 次写操作就触发一次保存。
真正值得拆的是「怎么在不阻塞主线程的前提下把内存写下来」。Redis 用的是 fork 加写时复制(Copy-On-Write)。执行保存时,主进程 fork 出一个子进程,子进程看到的是 fork 那一刻的内存快照,它负责把这份数据写盘。主进程继续处理请求。当主进程要修改某个内存页时,操作系统会先把这一页复制一份给子进程,保证子进程看到的数据不变。
这个机制解释了分享里 RDB 的两个缺点。第一,fork 本身要复制页表,数据量越大越耗时,期间主进程会被短暂卡住,这就是「数据量大时 fork 非常耗时,导致无法在毫秒级响应」。第二,如果保存过程中出错,stop-writes-on-bgsave-error no决定是否停止接收写入——设成 no 意味着即使快照失败也继续写,风险是下次恢复时数据更旧。
RDB 的优点是文件紧凑、适合备份、恢复快;缺点是两次快照之间的数据会丢。save 60 1000意味着最坏情况下丢 60 秒的写入。
3.2 AOF 日志:为什么「重放命令」能恢复数据
AOF 换了个思路:不存数据快照,而是把每一条写命令追加到文件末尾。恢复时从头到尾重放一遍,数据就回来了。配置里appendonly yes开启,appendfsync控制同步策略:
| 策略 | 行为 | 丢数据窗口 |
|---|---|---|
| always | 每条命令都 fsync 到磁盘 | 几乎不丢 |
| everysec | 每秒 fsync 一次 | 最多丢 1 秒 |
| no | 交给操作系统决定何时刷盘 | 不可控 |
everysec是默认值,也是大多数场景的平衡点。它把 fsync 放到后台线程做,主线程只管写缓冲区,所以不会因为磁盘慢而卡住命令执行。
AOF 文件会越来越大,因为同一个 key 可能被改了一万次,文件里就有一万条命令。所以需要 rewrite:auto-aof-rewrite-percentage 100表示当前文件是上次 rewrite 后的一倍时触发,auto-aof-rewrite-min-size 64m表示文件至少到 64MB 才考虑。rewrite 不是去读旧文件,而是直接根据当前内存状态生成一份「最小命令集」,比如一个 key 最终值是 5,就只写一条SET key 5,而不是历史上所有修改。
no-appendfsync-on-rewrite no这个配置容易被忽略。rewrite 期间子进程在大量写盘,如果此时主线程还要 fsync,磁盘 I/O 会打架。设成 yes 表示 rewrite 期间不 fsync,牺牲一点安全性换性能。
3.3 两种持久化怎么选:不是二选一,是看你能丢多少
分享里给了对比,但实际决策可以更具体。如果数据能从源头重建(比如纯缓存),RDB 足够,甚至可以不持久化。如果数据丢了业务会出问题(比如订单状态),用 AOF 的everysec,最多丢一秒。如果两者都要,可以同时开启——Redis 重启时优先用 AOF 恢复,因为它的数据更新。
一个常见的坑是:只开了 RDB,然后以为「我配了持久化就不会丢数据」。实际上save 60 1000在写入不频繁时可能几十分钟都不触发一次,宕机就丢一大段。另一个坑是 AOF 文件涨到几十 GB 才发现没配 rewrite,恢复时重放要几个小时。这些都不是配置写错,而是没理解机制背后的时间窗口。
4. 命令与事务:哪些操作是原子的,哪些只是看起来像
4.1 key 管理与过期:TTL 背后的惰性删除
EXPIRE key seconds设置生存时间,TTL key查剩余时间,PERSIST key移除过期。这里有个容易误解的点:key 过期后并不是立刻从内存消失。Redis 用的是「惰性删除 + 定期删除」组合——访问一个 key 时发现它过期了才删,同时后台定期抽样删除一批过期 key。
这带来一个现象:你EXISTS一个已过期的 key 返回 0,但内存里可能还占着。如果大量 key 同时过期又没被访问,内存不会马上释放。排查内存异常时,这是常见盲区。
KEYS pattern支持通配符,KEYS *匹配所有 key。前面说过,这个命令会遍历整个键空间,生产环境慎用。替代方案是SCAN cursor MATCH pattern COUNT count,它每次只返回一小批,不阻塞主线程。
4.2 五种数据结构的命令设计逻辑
String 的命令围绕「值可以是数字也可以是文本」展开。INCR、DECR、INCRBY、DECRBY把字符串当整数做原子增减,key 不存在时先初始化为 0。APPEND追加内容,GETRANGE取子串,STRLEN取长度。SETEX是SET加过期的组合,适合存会话这类有时效的数据。
Hash 适合存对象。HSET设字段,HGET取字段,HGETALL取全部。HINCRBY对字段做原子增减,适合做对象内的计数器。HSETNX只在字段不存在时设置,可以用来做「首次写入才生效」的逻辑。
List 是双向链表语义。LPUSH从头部插入,RPUSH从尾部插入,LPOP/RPOP从两端弹出。BLPOP/BRPOP是阻塞版本,列表为空时连接会挂起直到有元素或超时——这是实现简单队列的常用手段。LTRIM只保留指定区间,配合LPUSH可以做一个固定长度的最新列表。
Set 是无序不重复集合。SADD添加,SISMEMBER判断存在,SINTER/SUNION/SDIFF做交并差。SINTERSTORE把交集结果存到另一个 key,适合做「共同好友」这类计算后还要复用的场景。
Sorted Set 在 Set 基础上给每个成员带一个分数,按分数排序。分享里没展开它的命令,但它的典型用途是排行榜:分数是热度或时间,ZADD更新,ZRANGE按排名取。
4.3 事务:MULTI/EXEC 到底保证了什么
Redis 事务用MULTI开始,之后命令进队列,EXEC时按顺序一次性执行。DISCARD放弃,WATCH监视 key。
关键要理解它和数据库事务的区别。Redis 事务不保证「部分失败回滚」——如果队列里第三条命令执行时报错,前两条已经生效,后面的继续执行。它保证的是「这批命令在执行期间不会被其他客户端的命令插进来」,也就是隔离性,不是原子回滚。
WATCH提供乐观锁。执行WATCH key后,如果EXEC之前这个 key 被别的客户端改过,整个事务被打断。这是实现「读-改-写」并发安全的常用模式:WATCH余额,读出,计算,MULTI,SET新值,EXEC。如果中途有人改了余额,EXEC返回失败,客户端重试。
# 乐观锁更新余额的典型流程 import redis r = redis.Redis() def transfer(key, delta): while True: try: r.watch(key) # 监视 key current = int(r.get(key) or 0) new_val = current + delta pipe = r.pipeline() pipe.multi() # 开启事务 pipe.set(key, new_val) pipe.execute() # 若 key 被改过则抛异常 return new_val except redis.WatchError: continue # 冲突则重试这段代码里watch是乐观锁的入口,multi把后续命令入队,execute触发原子执行。WatchError表示监视期间 key 被改动,需要重试。注意重试是客户端行为,Redis 不会自动帮你重来。
4.4 发布订阅:为什么它不保证消息可靠
SUBSCRIBE channel订阅,PUBLISH channel message发布,PUBSUB CHANNELS列出活跃频道,PUBSUB NUMSUB查订阅者数量,UNSUBSCRIBE退订。
发布订阅的模型是「即发即忘」:消息发出时,只有当前在线的订阅者能收到,没有订阅者消息就丢了,也不会存起来等订阅者上线。这跟消息队列的持久化投递是两回事。所以它适合做实时通知、配置推送这类「错过就错过」的场景,不适合做订单、支付这类不能丢的消息。
理解这一点,就不会拿它当可靠队列用。需要可靠投递,得用 List 的BLPOP或者专门的流结构。
5. 避坑与排查:那些配置看起来对、跑起来翻车的时刻
5.1 现象:写入正常但重启后数据少了一大截
原因:只配了 RDB,且save阈值没触发。比如save 60 1000,业务写入量小,60 秒内不到 1000 次,快照一直没生成,宕机时内存数据全丢。
解决:确认数据能丢多少。不能丢就开 AOF,appendfsync everysec。或者调小 RDB 阈值,但注意频繁 fork 的代价。
5.2 现象:AOF 文件涨到几十 GB,重启恢复要几小时
原因:没配 rewrite,或者auto-aof-rewrite-percentage设得太大。AOF 记录的是所有写命令,同一个 key 改一万次就有一万条。
解决:确认auto-aof-rewrite-percentage 100和auto-aof-rewrite-min-size 64m生效,观察日志里有没有 rewrite 记录。已经涨大的文件可以手动触发一次 rewrite。
5.3 现象:执行某个命令后整个实例卡住几秒
原因:大概率是KEYS *、HGETALL大 hash、SMEMBERS大 set 这类 O(N) 命令。单线程模型下,一条慢命令阻塞所有请求。
解决:用SCAN、HSCAN、SSCAN增量迭代替代全量命令。排查时看慢查询日志,slowlog-log-slower-than设一个阈值,比如 10000 微秒,把慢命令记下来。
5.4 现象:内存占用比预期高很多,删了 key 也没降
原因:过期 key 惰性删除,没被访问就不释放;或者大量小 key 的元数据开销累积。
解决:用SCAN配合TTL找出过期未删的 key,主动DEL。评估内存时把每个 key 的元数据开销算进去,别只算值的大小。
5.5 现象:主从同步时从机数据比主机旧
原因:主从复制是异步的,主机写完后立即返回,同步到从机有延迟。分享里提到「主机从机的宕机都会导致前端部分读写请求失败」,就是这个窗口。
解决:对一致性要求高的读,走主机;能容忍延迟的读,走从机。别默认从机数据实时一致。
6. 从「会用」到「敢调」:把配置项当成设计决策来读
回头看这份分享里的配置表,每一条其实都是一个设计决策的暴露面。timeout 0表示不主动断开空闲连接,省了重连开销但占着文件描述符;tcp-backlog 511是三次握手队列长度,高并发下太小会丢连接;databases 16给了 16 个逻辑库,但集群模式下只有 0 号库可用。这些不是「默认值背下来就行」,而是要知道改了会动到什么。
我自己的习惯是,每次动持久化配置前,先问三个问题:这份数据丢了能不能从源头重建?能接受多长的丢失窗口?恢复时能等多久?这三个答案基本就决定了 RDB 和 AOF 怎么配。RDB 适合「能重建、要快恢复」,AOF 适合「不能丢、能等恢复」,两个都要就同时开,接受 AOF 恢复慢的代价。
另一个值得建立的判断是:Redis 的快来自「内存 + 单线程 + 原子命令」这套组合,任何破坏其中一环的操作都会让它变慢。大 key、全量遍历命令、频繁 fork,都是在跟这套设计对着干。排查性能问题时,先看有没有这类操作,比盲目加机器有效得多。
至于在线扩容难这个缺点,本质是数据分片和迁移的复杂度问题,不是配置能解决的。容量规划要提前做,别等集群满了才想扩。这份分享把命令、配置、持久化、事务、发布订阅都覆盖到了,适合当成一份随时翻的底稿——不是背命令,而是理解每条命令和每个配置背后的取舍。需要完整内容的话,可以顺着这份材料把每个机制自己跑一遍,比只看结论扎实得多。希望帮到你。
本文还有配套的精品资源,点击获取