从零起步学习Redis || 第六章:Redis单线程模式的实现详解

32 阅读4分钟

redisblog.png

切入点:

Redis 的核心是 基于事件驱动的单线程模型,它通过 I/O 多路复用(epoll/kqueue/Select) 来管理所有客户端的请求。

流程大概是这样的:

  1. 客户端请求来了(比如 SET key value)。
  2. Redis 的 事件循环 把请求放入队列。
  3. 单线程顺序地从队列里取出请求,执行对应的命令(读写内存、更新数据结构)。
  4. 把结果返回给客户端。

因为 Redis 的操作都是 内存级别 的,速度极快,加上单线程避免了多线程并发的上下文切换、加锁开销,所以性能很高。

核心机制:I/O 多路复用和事件循环

如果 Redis 是单线程的,那它怎么同时处理成千上万个客户端连接呢?
如果用「一线程一连接」就会死掉,因为线程上下文切换、内存开销太大。

Redis 的解法是:一个线程,管理多个连接 → 靠 I/O 多路复用

I/O 多路复用的思想

你可以把它想象成:

  • 服务员(Redis 单线程) → 只有一个。
  • 客人(客户端连接) → 可以有几万个。
  • 服务员不可能一直守着某个客人等点单,所以用一个「呼叫器系统」(多路复用机制:selectepollkqueue)。
  • 哪个客人需要点单(有事件),呼叫器就告诉服务员「这个桌子有人举手了」。

这样,一个线程就能监听很多连接

Redis 的事件循环流程

Redis 内部有一个 事件循环(event loop) ,大体是这样的:

  1. 调用 epoll_wait(Linux)等待事件。

    • 如果有客户端发送请求(可读事件),就返回「哪个连接有数据」。
    • 如果有客户端可以发送结果(可写事件),也会返回。
  2. 事件循环把这些事件放到一个队列里。

  3. Redis 单线程依次处理这些事件:

    • 解析客户端请求。
    • 执行命令(对内存数据结构进行操作)。
    • 把结果写回客户端。
  4. 处理完一批事件后,再回到 epoll_wait → 等下一批事件

epoll是什么?

基本概念

epoll 是 Linux 提供的一种 I/O 多路复用机制,用来同时高效地监听大量文件描述符(比如 socket 连接)。

换句话说:

  • 传统模型:一个线程处理一个连接(线程数爆炸 → 性能差)。
  • epoll 模型:一个线程用 epoll 监听成千上万个连接,只在有事件时才处理

epoll 工作方式

可以分为三步:

  1. 注册事件:把要监听的 socket(客户端连接)交给 epoll,比如「这个连接可读的时候告诉我」。
  2. 等待事件:调用 epoll_wait() 阻塞等待,直到有事件发生。
  3. 返回事件:只返回那些「真的发生了事件」的连接,线程直接处理它们就好。

socket 的本质

socket 就是一个“通信端点”
可以把它理解成 网络中的门口/插口

  • 进程要想和别的进程(可能在另一台机器)通信,得先“开个门口”。
  • 这个门口就是 socket。

socket 工作流程

一次客户端—服务器通信,大体是这样:

  1. 服务器先在本地开一个 socket(监听端口,比如 6379 就是 Redis 的默认端口)。
  2. 客户端也开一个 socket,然后向服务器的 IP + 端口发起请求。
  3. 内核给它们牵线搭桥,两个 socket 建立连接。
  4. 以后客户端和服务器就是通过这对 socket 来 收发数据

所以你可以把 socket 看成是 读写网络的文件描述符
在 Linux 里,socket 甚至就是一种特殊的文件,读写它和操作文件一样。

易错点辨析:端口与socket的关系

1. 端口是什么?

  • 端口(port)就像一栋楼里的房间号
  • 一台机器上可以同时跑很多服务(HTTP 80,MySQL 3306,Redis 6379)。
  • 端口号用来区分「同一台电脑里的不同服务」。

2. socket 是什么?

  • socket 是通信的端点,就像插座:插上去就能通信。
  • 在 TCP 里,一个 socket 是由 (IP 地址, 端口号) 组成的。
  • 客户端和服务器建立连接时,其实就是在两端各创建一个 socket,然后把它们连起来。

3. 两者的关系

你可以这么记:

  • 端口号:只是号码(告诉操作系统「数据要交给谁」)。
  • socket:是操作系统里真正用来收发数据的对象。

也就是说:

  • 端口号 = 地址上的「门牌号」。
  • socket = 门口的「门+对讲机」,真正能收发信息。

举个 Redis 的例子:

  • Redis 默认监听 6379 端口。

  • 当你用客户端连接 Redis 时:

    • 服务器这边创建一个 socket 绑定在 (服务器IP, 6379) 上。
    • 客户端也有自己的 socket,比如 (你的IP, 随机端口号)
    • 两个 socket 建立 TCP 连接,数据就能传输。