news 2026/9/9 7:23:13

Redis单线程为何能支撑10万QPS?高并发架构核心拆解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis单线程为何能支撑10万QPS?高并发架构核心拆解

最近在技术群里被问到最多的一个问题就是:Redis 明明是单线程的,凭什么能扛住 10 万 QPS?很多刚接触 Redis 的开发者一听到“单线程”这个词,下意识就觉得它和高并发不搭边,甚至有人问我“是不是 Redis 内部用了多线程但对外宣传是单线程”。其实 Redis 能跑到 10 万 QPS,并不是靠堆线程堆出来的,恰恰是因为它在设计上做了极其精准的取舍,把“单线程执行模型”这个看似劣势的特性,变成了一套非常高效的工作方式。

这篇文章作为“Redis 实用技巧”系列的架构解析上篇,我会从事件循环、内存存储、数据结构、命令执行链路这几个角度,把“单线程 Redis 为什么快”这件事彻底讲明白。不管你是准备面试、还是在生产环境排查性能问题,又或者只是单纯好奇 Redis 的内部实现,这篇都值得花几分钟看完。看完你会发现,所谓高并发,真正靠的不是“并行”,而是“减少不必要的工作”。

1. 为什么单线程反而是个优点:先看懂设计者的账本

1.1 你看到的瓶颈,和 Redis 实际面临的瓶颈不是一回事

大多数时候我们聊多线程,之所以觉得“线程越多越好”,是因为大家都默认任务会吃满 CPU。比如图片处理、视频转码、复杂计算,这类任务属于 CPU 密集型,多核确实能带来立竿见影的收益。但 Redis 的核心场景是内存读写,操作的是已经存在内存里的数据,CPU 对单个命令的计算量非常小,真正的时间往往花在网络 I/O数据拷贝上下文切换上。

如果你把所有精力都放在“如何把 Redis 改成多线程”,你会发现最直接的结果是:线程多了,锁也多了,线程间为了抢共享数据互相等待,CPU 频繁切换线程上下文,缓存命中率下降,性能不升反降。这是当年 Memcached 用多线程、但 Redis 坚持单线程的核心理由之一——在内存操作这个场景里,串行执行不仅不慢,反而更可控

1.2 省下三笔大开销:切换、锁、缓存失效

CPU 在处理一个线程时,需要把当前线程的上下文保存下来,再加载下一个线程的上下文,这个动作就叫上下文切换。上下文切换本身是有代价的,而且一旦线程数超过 CPU 核数,切换会变得非常频繁。Redis 用单线程执行命令,意味着命令执行过程里没有上下文切换,CPU 可以专注于把当前命令处理完,再处理下一个。

锁竞争也是多线程模型里最头疼的问题。多个线程同时修改同一个哈希表、同一个链表,必须有锁来保护,否则会出现数据错乱。而单线程天然不需要锁,因为命令是一条一条执行的,不会有两个线程同时往同一个 key 上写数据。这带来的好处不仅是快,更重要的是所有命令天然具备原子性——你不需要额外加事务,执行 INCR、LPUSH 这类操作时,中间不会被其他命令插进来。

还有一个容易被忽略的点:CPU 缓存。现代 CPU 有 L1、L2、L3 多级缓存,单线程执行时,热点数据很容易一直留在 CPU 缓存里,读取速度极快。一旦多线程切换,缓存里的数据会被反复淘汰,每次都要回到内存里取数据,反而慢了。

1.3 单线程模型的隐藏红利:确定性和可观测性

多线程程序难调试,因为执行顺序无法预知,BUG 经常是“偶发”的。Redis 单线程模型下,命令执行顺序就是客户端发送顺序,任何时刻只会有一个命令在跑,排查问题的时候只需要关注事件循环里发生了什么,就能复现绝大多数性能问题。这也是我特别喜欢在生产环境里用 Redis 的原因之一——它的行为是高度确定性的,出了故障可定位性很好。

2. 核心细节拆解:一条命令从进入到返回,走过了一条什么样的路

2.1 非阻塞 I/O + 多路复用:单个线程管上万连接的关键

很多人有个误区,觉得“单线程”就是一次只能服务一个客户端。实际完全不是这样。Redis 单线程能服务成千上万个连接,靠的是I/O 多路复用

可以这样理解:传统阻塞式网络模型里,每个连接都需要一个线程专门等着,数据没来就阻塞住,线程白白浪费。而 Redis 的做法是,一个线程同时盯着所有连接,内核告诉他“哪个连接有数据可读”,他就去处理哪个连接。Linux 下这个机制叫 epoll,macOS 和 BSD 下叫 kqueue,Redis 自己封装了一层事件驱动库,屏蔽了不同操作系统的差异。

整个工作流程是这样的:Redis 主线程进入一个事件循环,不断问内核“有没有新消息来了”,如果某个客户端发来了一条命令,事件循环就把这个连接标记为可读,然后读取数据、解析命令、执行命令、把结果写到输出缓冲区,再继续下一轮循环。整个过程里没有一个连接会阻塞主线程等待数据,都是“有数据了就处理,没数据就继续看别的”。

所以 Redis 单线程真正在做的不是“一次服务一个请求”,而是“一次服务所有请求中已经就绪的那一个”,这个效率比每个线程盯一个连接高得多。假如有 1 万个空闲连接,阻塞式模型需要 1 万个线程蹲守,变成多路复用后,一个 Redis 进程就够了。

2.2 内存里的数据天生就快:一次磁盘 I/O 的时间能做几百万次内存访问

Redis 之所以能有十万级 QPS,最根本的物质基础还是它把数据全部放在了内存里。内存随机访问的延迟一般在 80 纳秒到 100 纳秒级别,而普通 SSD 随机读的延迟在 20 到 100 微秒,机械硬盘更是在毫秒级别。也就是说,内存访问比 SSD 快几百倍,比机械硬盘快几万倍。

这就出现了一个很关键的设计取向:Redis 牺牲了“数据持久化绝对可靠”这一点,把绝大部分时间都省下来服务请求。磁盘只负责定期把内存里的数据落盘,或者以追加日志的方式记录写操作,而且这些操作都尽量做成异步,不让主线程等着。换句话说,一个 Redis 请求在内存里很快就完成了,磁盘 I/O 都靠后台线程或者子进程去处理,主线程自然有能力不停接收新请求。

2.3 底层数据结构设计:每个命令都尽量做到 O(1) 或者 O(log N)

内存快是一方面,数据结构设计也得跟上。Redis 内部并没有直接用原生的字符串、链表、哈希表去存储数据,而是针对自己的使用场景做了大量定制。比如字符串用的是 SDS(Simple Dynamic String),获取长度是 O(1),而且可以避免 C 语言字符串里常见的缓冲区溢出问题;哈希对象底层有 ziplist 和 hashtable 两种编码,小数据用紧凑结构,大数据自动切换;有序集合底层用跳跃表加哈希表,让 ZRANGE、ZSCORE 这些操作都保持高效的复杂度。

正因为每种数据类型背后都有一套针对性优化的结构,Redis 才能做到大部分命令都是 O(1) 或者 O(log N)。你想想,如果执行一个 GET 还需要遍历链表,就算放在内存里也不可能跑出十万 QPS。所以“快”不是一个单点结果,而是每一层都做了极致优化的综合结果。

2.4 轻量级通信协议:解析快、传输省

Redis 的客户端和服务器之间用的是 RESP(Redis Serialization Protocol)协议。这个协议非常简单,只有几种类型,比如简单字符串、错误、整数、批量字符串、数组。命令就是数组,数组里的每个元素都是批量字符串。因为格式固定、没有复杂的语义解析,Redis 解析一条命令的开销非常低,CPU 大部分精力都能留给真正的数据操作。

这点和关系型数据库相比尤其明显。一个 SQL 要经过词法分析、语法分析、生成执行计划等一系列流程,解析代价非常高。Redis 的协议简单到可以手写,省掉的解析时间积少成多,在高并发场景下差别很大。另外,RESP 协议还有内联命令格式和管道支持,客户端可以一次发送多条命令,减少网络往返,这也是吞吐量提升的重要途径。

3. 实测一次:10 万 QPS 到底怎么跑出来的

3.1 先用 redis-benchmark 看一组直观数据

Redis 自带的 redis-benchmark 工具,是测试 Redis 性能最直接的途径。我在一台普通测试机上跑过大量测试,单实例、纯内存、不开启 AOF、走本机回环地址,SET/GET 这类简单命令的 QPS 基本都能稳定在 10 万以上。比如下面这条命令:

redis-benchmark -h 127.0.0.1 -p 6379 -t set,get -n 1000000 -c 50 -P 16

参数含义:-t指定测试命令,-n指定总请求数,-c表示并发连接数,-P表示管道批量大小。把管道参数调大,吞吐量会非常可观,因为每个 RTT(网络往返时间)能带上 16 条命令,网络等待被摊薄了。

不过这里要提醒一句,redis-benchmark 的数字是理想值,不等于生产环境真实值。真实业务里命令多样,有慢查询,有大 value,有持久化开销,还隔着真实的网络链路,不能拿 benchmark 结果直接当作容量评估依据。但它可以帮助你了解这台机器的 Redis 性能上限,也能帮你验证配置是否正常。

3.2 单线程事件循环在高并发下的真实处理节奏

假设现在有 1000 个客户端同时连上了 Redis,事件循环不会挨个去询问“你有没有消息”,而是通过 epoll 一次性拿到“就绪事件列表”。这就好比一个接待员同时守着一排窗口,哪个窗口有客户来了,他就过去接待,没人来的窗口他根本不需要管。每个命令的处理时间非常短,往往只有几十微秒,所以一秒钟内他可以来回处理几万个事件。

需要注意的一点是,Redis 的“单线程”指的是命令执行线程是单个的,并不代表整个进程只有一个线程。Redis 在后台还有用于持久化的线程、用于异步删除大 key 的线程、用于关闭文件描述符的线程等。从 Redis 6.0 开始,还增加了多线程 I/O,就是把 socket 读写这部分工作从主线程里面拆出去,让多个线程并行处理“网络数据的读取和写回”,但最终的命令执行依然是在主线程串行完成

用一句好记的话总结:Redis 6.0+ 的“多线程”只是让网络收发更快,命令执行还是“一个一个来”。这样设计的好处是,既能利用多核优势处理海量网络连接,又保留单线程执行模型带来的原子性和确定性,是最稳妥的折中方案。

3.3 真正决定 QPS 的并不只是 Redis 本身,还有客户端和网卡

再快的 Redis,如果客户端不会用,也发挥不出来。实际压测时你会发现,QPS 上限常常卡在网络和客户端侧。比如启用了 TCP Nagle 算法,小包会被合并后才发送,延迟增加,整体吞吐下降;比如客户端频繁创建销毁连接,握手开销占了大头;再比如单条连接上一条一条发命令,没有使用 pipeline,每发一条都要等一个 RTT。

生产环境里想让 Redis 跑出高 QPS,我的建议是:连接池必须要用,连接复用是基础;高吞吐场景尽量启用 pipeline,把多条命令打包发送;关闭 Nagle 算法,让数据包尽量立即发送。另外,网卡中断处理、CPU 频率、内存频率都会影响最终数字。10 万 QPS 从来不是 Redis 单方面的事,而是 Redis、网络、客户端三方配合的结果。

下面这张表是我整理的不同场景下,QPS 差异的主要来源:

影响因素低 QPS 场景高 QPS 场景
网络模型每请求一连接长连接 + 连接池
命令发送单条发送pipeline 批量发送
TCP 参数默认 Nagle关闭 Nagle 降低延迟
命令类型复杂命令、大 key简单命令、小 value
持久化配置AOF alwaysAOF everysec 或关闭

4. 单线程模型下的避坑指南:这些操作千万别乱用

4.1 慢命令是单线程模型最大的敌人,没有之一

因为所有命令都是串行执行的,一条命令耗时长,后面所有命令都得等它。单线程最怕的就是慢命令。真正慢的命令往往不是 O(N) 那么简单,而是那些把大量数据一次性取回来或者一次性删除的操作。比如 KEYS 命令,它需要遍历整个键空间匹配模式,数据库里如果有几百万个 key,执行一次就能把 Redis 卡住几百毫秒,线上服务基本就雪崩了。

正确的替代方案是使用 SCAN 命令,游标式地分批遍历,每次返回少量 key,不会长时间阻塞。类似的,HGETALL 用于大哈希、SMEMBERS 用于大集合、LRANGE 用于大列表,也要尽量避免。如果确实需要取全量数据,要么缩小小 key 的规模,要么分批次取。

我在生产环境见过最典型的案例:代码里用 KEYS 做缓存清理,业务量一上来,Redis 每隔几分钟卡一次。后来改成 SCAN 分批删除,问题立刻消失。

4.2 大 Key 的杀伤力往往是延迟和内存的双重爆炸

大 key 指的是单个 key 对应的 value 特别大,比如一个哈希里有几十万个字段,一个列表里有几百万个元素。这种 key 的危害,一是任何针对它的命令都可能耗时很久,阻塞整个 Redis;二是内存占用高,影响持久化和复制效率;三是删除的时候如果直接 DEL,同样会阻塞主线程。

排查大 key 可以直接用 Redis 自带的工具:

redis-cli --bigkeys

它会扫描整个实例,按数据类型统计出最大的 key 有哪些。对于确实存在的大 key,删除时不要用 DEL,可以异步删除:

UNLINK mykey

UNLINK 只在主线程里做很小的整理动作,真正的内存释放交给后台线程。这是 Redis 4.0 之后我非常推荐的操作,能有效避免删除大 key 造成的阻塞。

4.3 阻塞式命令要谨慎,别让“等”变成“卡”

Redis 里还有一类命令,设计初衷是“阻塞等待”,比如 BLPOP、BRPOP。客户端执行 BLPOP 时,如果列表为空,它会一直阻塞到超时或者有新数据进来。这个阻塞等待本身并不会占用 CPU,但是一旦有多个客户端同时在阻塞,Redis 需要维护这些等待关系,生产环境中如果一个列表的消费速度跟不上生产速度,等待的客户端会越来越多,事件循环压力也会变大。

尤其要注意的是,阻塞命令如果在主线程上执行且参数不设置超时,客户端可能长时间占着一个连接不释放,连接数被大量消耗,最终影响其他正常请求。所以要给阻塞命令都配上合理的超时时间,并且充分评估消费能力,不要让生产速度和消费速度严重失衡。

4.4 持久化也尽量别抢主线程的时间

很多人以为 Redis 开了持久化,就是每次写操作都刷磁盘。事实并非如此,具体要看配置。RDB 快照是 fork 一个子进程去生成快照文件,主线程继续服务,fork 瞬间会有一次阻塞;AOF 的写盘频率由 appendfsync 决定,如果配置成 always,每条命令都 fsync,性能会显著下降。推荐配置是 appendfsync everysec,每秒刷一次,既保证安全性,又把对主线程的影响降到最低。

另外,Linux 内核的 fork 机制采用写时复制(Copy-On-Write),正常情况下内存占用不会翻倍。但如果开启了大量写操作,子进程持有了快照,父进程修改内存页时就会触发复制,内存占用会上升,需要提前留足内存余量。生产环境中我见过因为内存不足导致 fork 失败,Redis 直接拒绝写入的情况,所以不要忽略了持久化带来的隐性内存开销。

4.5 延迟排查三板斧:SLOWLOG、LATENCY、redis-cli --latency

当你怀疑 Redis 变慢时,别急着盲目优化,先定位问题。先看慢日志:

SLOWLOG GET 20

慢日志会记录执行时间超过阈值的命令,默认阈值是 10000 微秒,也就是 10 毫秒。生产环境建议把这个阈值调低一些,比如 5000 微秒,能更早发现隐患。命令执行超时通常是慢命令、大 key、或者 CPU 争抢导致的,可以直接从 SLOWLOG 里找到元凶。

如果慢日志里找不到明显问题,可以使用 Redis 提供的延迟监控工具:

redis-cli --latency

这个命令会持续采样,显示网络往返延迟。如果延迟出现明显尖峰,结合慢日志和系统监控,基本就能定位是 Redis 内部问题还是网络问题。还有一个经验是,查看INFO commandstats,统计每个命令的调用次数和耗时占比,很多“隐藏的慢命令”在总量上不明显,但平均耗时很高,能从 commandstats 里看出来。

一些个人经验收个尾

做了这么多年缓存和中间件相关的项目,我对 Redis 的体会是:它的快不是魔法,而是一套清晰的“减法哲学”——用内存替代磁盘,用事件驱动替代阻塞等待,用高效数据结构和简单协议减少每个请求的 CPU 开销,用单线程换掉锁和上下文切换的代价。这套设计在大多数业务场景下都非常合理,因为大多数请求本来就是微秒级的小操作,瓶颈从来不在 CPU,而在网络和客户端。

我也踩过不少坑。早期接手一个项目时,发现 Redis 的 QPS 上不去,第一反应是加机器、加内存,后来用 SLOWLOG 一看,满屏都是 KEYS 和 HGETALL 扫大哈希,把执行时间从微秒级拖到了几十毫秒。把代码改成 SCAN 和小批量处理之后,单实例 QPS 直接翻了两倍多。所以说,理解单线程模型的应用边界,比盲目升级硬件更值钱。这篇文章侧重架构层面的设计原理,后面如果再聊“下”篇,我打算结合实例讲讲如何通过监控、调参和优雅的客户端用法,把 Redis 每个 10 万级 QPS 长期稳定在可控范围内。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 7:19:21

App Inventor离线服务器版Windows部署教程:解决机房网络卡顿

简介:面向 App Inventor 2019 课堂教学与二次开发的汉化版服务器端资源包,适用于中小学信息技术教师、培训机构及希望搭建本地 AI 实验环境的学习者。该版本由 roadlabs 完成汉化,重点解决了手机 AI 伴侣与开发电脑跨网段连接的问题&#xff…

作者头像 李华
网站建设 2026/9/9 7:17:41

办公AI助手深度横评:豆包、Kimi、文小言、通义千问谁更强?

不知道你有没有这种感觉:这两年办公AI助手这个词快被说烂了,但真到要自己选一个用的时候,反而更懵了。打开应用商店,搜“AI助手”能跳出来几十个,每个都说自己能写文案、能读文档、能做PPT、能当秘书,可真要…

作者头像 李华
网站建设 2026/9/9 7:17:16

ARM启动流程详解:链接脚本、段拷贝、XIP与位置无关码

ARM 启动流程第三弹,这次把链接脚本、启动文件里的段拷贝、XIP 和位置无关码四件事一次性讲透。前两弹讲完复位向量、栈初始化和时钟/存储器初始化之后,C 环境能不能真正跑起来,关键就看 text、data、bss 这三大段有没有被正确安排。看过不少…

作者头像 李华
网站建设 2026/9/9 7:16:58

Java新手入门指南:从JDK环境配置到IDEA运行第一个程序

1. 为什么那么多初学者卡在“还没开始写代码”这一步先说一个我观察了很久的现象:很多Java零基础的人,不是被语法难住的,而是被“环境准备”劝退的。Java语法再复杂,也就是几个关键字、几种结构的事,真正让人崩溃的是—…

作者头像 李华
网站建设 2026/9/9 7:14:58

RS-485缓存集线器实战:多设备组网丢包与反射解决方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 7:11:45

2026年AI办公工具精选:12款真正提升效率的实用清单

每年都会有人来问我:现在AI办公工具这么多,到底哪些是真正值得天天用的?不瞒你说,我2024年开始重度使用AI处理日常事务,到现在2026年,办公室电脑里装了删、删了装的工具少说也有四五十款,最后真…

作者头像 李华