欢迎光临
我们一直在努力

【Redis】网络高并发模型

目录

    • 一、 核心场景:百万并发下的秒杀大考
      • 1. 传统多线程服务器会怎么样?
      • 2. Redis 凭什么能抗住?
        • 第一步:建立连接(非阻塞 + epoll)
        • 第二步:读取请求(非阻塞 I/O)
        • 第三步:执行命令(单线程串行,纯内存操作)
        • 第四步:返回结果(非阻塞写)
    • 二、 深度对比:多线程阻塞 vs 单线程非阻塞
    • 三、 演进:Redis 6.0+ 的多线程 I/O 革命
    • 四、 微观视角:一个秒杀请求的时间线拆解
    • 五、 致命死穴:如果某个命令很慢怎么办?
    • 本篇总结

一、 核心场景:百万并发下的秒杀大考

假设运营着一个电商平台,双十一零点有 100 万用户同时抢购 1 件商品。后端使用 Redis 存储商品库存:

SET stock 1

每个用户发起请求时,需要原子地检查并扣减库存,核心业务伪代码如下:

if stock > 0:
stock = stock 1
return success
else:
return fail

⚠️ 注意:在真实生产环境中,该操作必须使用 Redis 的 DECR 命令或 Lua 脚本 来执行,以保证复合操作的原子性。

1. 传统多线程服务器会怎么样?

假设采用传统的 Tomcat + MySQL 架构:

  • 每一个请求到达服务器后都会分配一个独立的线程。
  • 100 万并发会导致系统频繁进行线程上下文切换,引发剧烈的锁竞争,进而导致 CPU 瞬间爆满。
  • 随后,数据库的磁盘 I/O 成为致命瓶颈,整个系统几乎会立刻崩溃,或者响应时间从 1ms 飙升至 10s 以上。

2. Redis 凭什么能抗住?

面对同样的百万级并发洪峰,Redis 依靠其独特的网络模型,将整个处理链路巧妙地拆解为以下四个步骤:

第一步:建立连接(非阻塞 + epoll)
  • 注册监听:Redis 启动时,主线程会创建一个监听 Socket,并将其注册到 Linux 内核的 epoll 实例中。
  • 内核排队:当 100 万个客户端同时发起 TCP 连接时,操作系统内核会将这些连接放入全连接队列中。
  • 按需感知:Redis 主线程循环调用 epoll_wait,内核会直接返回当前有事件就绪的 Socket 列表(可能只有几千个)。
  • 高效分发:主线程依次调用 accept 接受连接,生成对应的 Client Socket,并同样注册到 epoll 中。
  • ✨ 关键点:这一步完全由 Linux 内核处理,Redis 主线程只是轮询就绪列表,而不需要盲目遍历 100 万个空闲连接,算法复杂度为

    O

    (

    就绪数

    )

    O(就绪数)

    O(就绪数)

第二步:读取请求(非阻塞 I/O)

每个 Client Socket 上都可能传来用户的“扣库存”命令。

  • 当数据到达时,epoll 会将该 Socket 标记为可读状态。
  • Redis 主线程从 epoll 就绪列表中取出该 Socket,调用 read 读取数据。由于采用了非阻塞模式,如果当前没有数据会立马返回,绝不原地死等。
  • 读取到的原始命令会被放入 Redis 的输入缓冲区。
  • ✨ 现象:即便 100 万个连接同时发送请求,内核也会把数据暂存在 Socket 接收缓冲区中。Redis 主线程一次循环只处理几千个就绪的 Socket 并读走数据,单线程运行,没有任何线程切换开销。
第三步:执行命令(单线程串行,纯内存操作)
  • Redis 从输入缓冲区中取出解析好的命令(如 DECR stock)。
  • 直接在内存中执行该修改操作(耗时通常仅需几十纳秒)。
  • ✨ 关键点:由于执行核心是单线程,同一时刻有且仅有一个命令在修改内存,因此不需要对 stock 加任何锁(天然串行化)。即便 100 万个命令同时到达,它们也会在内存缓冲区里排队依次执行,天生免疫超卖问题。
第四步:返回结果(非阻塞写)
  • 命令执行完毕后,执行结果会被写入 Client Socket 的输出缓冲区。
  • 如果客户端接收较慢导致发送缓冲区满了,Redis 会将该 Socket 标记为可写并注册到 epoll 中。
  • 当 Socket 恢复可写状态时,Redis 再将剩余数据异步写回客户端。整个过程不会因为某个客户端的“网速慢”而阻塞其他客户端。

通过一张图可能会看的更加清晰:

┌─────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Linux 操作系统内核 (Kernel)
└───────────────────────────────────────┬─────────────────────────▲───────────────────────────────────────┘
│ ① 100万并发连接 │ ④ 异步网络写 (非阻塞)
│ 注册并触发呼叫铃 │ 网卡空闲时推回结果
▼ │
┌─────────────────────────────────────────────────────────────────┴───────────────────────────────────────┐
│ I/O 多路复用监听器 (epoll_wait)
└───────────────────────────────────────┬─────────────────────────────────────────────────────────────────┘
│ ② 返回当前活跃的【就绪队列】(如3000个)

┌─────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Redis 主线程 (Single Thread)
│ │
│ Step 1: 网络读取 (Read) ──► Step 2: 命令解析 ──► Step 3: 单线程核心执行 ──► Step 4: 网络写入 (Write)
│ ┌─────────────────────┐ ┌───────────────┐ ┌──────────────────────┐ ┌─────────────────────┐ │
│ │ 非阻塞网络读 │ │ 将字节流解析 │ │ 💡核心操作: 纯内存 │ │ 执行结果放入 │ │
│ │ 从内核缓冲区把 │ │ 为具体的 Redis │ │ 无论是【读内存】(GET)│ │ 输出缓冲区 │ │
│ │ 字节流命令搬运过来 │ │ 命令 │ │ 还是【改内存】(SET) │ │ │ │
│ └──────────┬──────────┘ └───────┬───────┘ └──────────▲───────────┘ └──────────┬──────────┘ │
└─────────────┼───────────────────────┼────────────────────────┼──────────────────────────┼───────────────┘
│ │ │ │
▼ ▼ │ ▼
┌─────────────────────────────────────────────┐ ┌────────┴─────────────┐ ┌────────────────────────┐
│ Redis 输入缓冲区 (Input Buffer) │ │ Redis 内存数据库 │ │ Redis 输出缓冲区 │
[ GET name ] [ DECR stock ] [ ... ] │ │ (String/Hash/List...)│ │ (Output Buffer)
└─────────────────────────────────────────────┘ └──────────────────────┘ └────────────────────────┘


二、 深度对比:多线程阻塞 vs 单线程非阻塞

为了更直观地理解,我们将传统多线程阻塞模型与 Redis 单线程非阻塞模型在处理高并发秒杀场景时的表现进行横向对比:

维度多线程阻塞模型(如传统 Tomcat)单线程非阻塞模型(Redis 自身)
并发连接能力 1 个连接

\\rightarrow

1 个线程,1 万个线程将产生约 1GB 的内存开销

1 万个连接通过 epoll 集中管理,仅需消耗 几 MB 内存
上下文切换 极其频繁,导致大量 CPU 资源空转损耗 零 上下文切换
锁竞争开销 需要依赖分布式锁或数据库事务,开销极大 无锁 设计,内存直接操作
吞吐量极限 受限于线程极限与锁屏障,约 5~10 万 QPS(实际往往更低) 单核即可轻松跑满 10 万+ QPS
高并发稳定性 洪峰下极易引发线程池耗尽,导致系统雪崩 表现极其稳定,但易受慢查询命令的影响

三、 演进:Redis 6.0+ 的多线程 I/O 革命

既然单线程这么好,为什么 Redis 6.0 之后又引入了多线程?

在极端的超高吞吐场景下,网络数据的读取、解析以及结果写回(即网络 I/O) 往往会先于内存执行挤爆单核 CPU 的带宽。为了突破这个瓶颈,Redis 6.0 引入了 多线程 I/O。

在秒杀场景中,它的工作模式演变为:

  • 主线程:依然雷打不动地独占命令执行权(负责扣库存)。
  • I/O 线程池:分配多个辅助线程,并行读取 100 万个连接的网络请求数据,并将处理好的响应结果并行写回网卡。
  • ✨ 核心本质:命令执行依然是单线程的。因此,库存扣减的原子性、无锁特性不受任何影响,只是网络传输的“大门”被拓宽了。

  • 四、 微观视角:一个秒杀请求的时间线拆解

    为了更细腻地感受 Redis 的速度,我们以微秒(

    μ

    s

    \\mu s

    μs)为单位,拆解一个客户端发送 DECR stock 到接收响应的微观全过程:

    ┌─────────────────────────────────────────────────────────────────────────────┐
    │ 网络传输 –> epoll通知 –> 读数据 –> 命令解析 –> 内存执行 –> 写响应 │
    │ 50 μs 几 μs 10 μs 0.5 μs 纳秒级 纳秒级 │
    └─────────────────────────────────────────────────────────────────────────────┘

  • 网络传输:命令数据包从客户端出发,通过网络到达 Redis 服务器的内核缓冲区(约耗时

    50

    μ

    s

    50\\,\\mu s

    50μs,受物理网卡影响)。

  • epoll 通知就绪:Redis 主线程在 epoll_wait 阻塞处被唤醒,获取到可读的 Socket 列表(耗时 几

    μ

    s

    \\mu s

    μs)。

  • 读取数据:调用 read 函数将内核数据拷贝到 Redis 的输入缓冲区(约耗时

    10

    μ

    s

    10\\,\\mu s

    10μs,非阻塞,立即返回)。

  • 命令解析:协议识别为 DECR,并在内存 Dict 中找到 Key 对应的指针(哈希查找,约耗时

    0.5

    μ

    s

    0.5\\,\\mu s

    0.5μs)。

  • 内存执行:在内存中将整数 1 扣减为 0(纳秒级,极其恐怖的速度)。
  • 写回响应:将结果 ":0\\r\\n" 写入输出缓冲区,并向 epoll 注册写事件(纳秒级)。
  • 数据发送:异步将数据推回给客户端网卡(约耗时

    50

    μ

    s

    50\\,\\mu s

    50μs)。

  • 💡 小结:单个请求的闭环处理大约需要

    120

    μ

    s

    120\\,\\mu s

    120μs。在实际运行中,Redis 会通过批量处理(Pipeline 等技术)进一步优化,使单核吞吐量轻松突破 10 万+ QPS。


    五、 致命死穴:如果某个命令很慢怎么办?

    单线程模型虽然无敌,但它有一个致命的阿喀琉斯之踵。

    假设某个开发人员在生产环境中错误地执行了类似 KEYS * 的命令,去全局扫描一个包含 1000 万个 Key 的数据库,该命令耗时将长达 2 秒。

    • 🚨 灾难发生:因为 Redis 是单线程串行执行命令的,在这 2 秒期间,主线程被完全卡死。
    • 连锁反应:在这 2 秒内涌入的所有其他命令(包括高并发秒杀扣库存的请求)全部被迫排队死等,进而导致前端大面积超时崩溃。

    🛑 生产环境高压线:在企业级开发中,必须严格杜绝一切慢查询。严禁使用 KEYS *,应改用渐进式遍历命令 SCAN;同时需要合理设计大 Key,避免使用过于复杂的数据结构。


    本篇总结

  • “单线程”的本质:指 Redis 的命令执行是单线程的,这带来了天然的原子性与免锁特性。
  • 高并发的底层依赖:高并发依靠的是 Linux 内核的 epoll(I/O 多路复用) + 非阻塞 I/O,单线程即可轻松驾驭数十万个活跃连接。
  • 性能瓶颈的真相:在纯缓存场景中,CPU 执行命令(内存修改)极快,根本不是瓶颈;真正的瓶颈在于网络带宽以及多线程带来的上下文切换损耗。
  • 现代化演进:Redis 6.0+ 的多线程 I/O 仅仅优化了“网卡到缓冲区”的搬运速度,其灵魂(核心执行线程)依然纯粹单线程。
  • 赞(0)
    未经允许不得转载:171主机测评 » 【Redis】网络高并发模型
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址