back to index

redis线程模型

2025/10/31
loading

单线程模型

Redis 的 “单线程” 并非指服务器进程仅含一个线程,而是核心的命令执行逻辑由单个线程串行处理。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。

Redis 服务器进程包含多个线程,核心逻辑与辅助操作完全分离:

  1. 核心线程(主线程):负责处理客户端命令请求(如get/set)、执行数据读写操作、解析命令语法 —— 这是 Redis “单线程模型”的核心载体,所有命令执行严格串行,不存在并发竞争。
  2. 辅助线程:负责耗时的非核心操作,不参与命令执行,例如:
  3. 网络 IO 相关线程:处理 TCP 连接建立、数据接收与发送的底层 IO 操作(部分版本引入)
  4. 后台线程:执行 AOF 日志重写、RDB 持久化、大 Key 删除等耗时任务,避免阻塞主线程;
  5. 集群相关线程:处理节点心跳检测、数据同步等集群维护操作。、

IO多路复用

单线程的天然局限是 “无法同时处理多个 IO 请求”—— 若一个客户端连接占用线程等待数据,其他客户端会被阻塞。而 Redis 通过IO 多路复用机制,让单线程可同时管理数万甚至数十万客户端连接。

IO 多路复用的核心思想是:由一个线程/进程监听多个 socket(客户端连接)的 IO 事件(可读 / 可写),当某个 socket 的事件就绪时(如客户端发送数据),内核通知应用程序处理,处理完成后继续监听其他 socket。一个Socket在内核对应一个fd,避免为每一个Socket创建线程

Redis通过封装AE事件处理器模块,根据操作系统自动选择最优的多路复用实现,

单线程工作流程

img

  1. Server Socket:监听端口的主Socket,用于接收新连接
  2. I/O多路复用:监听各个Socket的可读/可写状态,redis使用epoll多路复用API
  3. Socket01:客户端连接建立后建立的通信Socket
  4. 队列:事件队列,存放就绪的I/O事件
  5. AE_READABLE:表示Socket可读(有数据到达)
  6. AE_WRITABLE:表示Socket可写(可以发送响应)

Redis 启动时创建一个 server socket,绑定到指定端口,使用listen()监听该端口,等待客户端连接,I/O 多路复用 模块开始工作,监控 server socket 是否可读(即是否有新连接)。

客户端 client 01 发起 TCP 连接请求,server socket 接收到连接请求,触发 AE_READABLE 事件,I/O 多路复用 检测到 server socket 可读,将事件放入 **队列,**事件分发处理器 从队列取出事件,调用 **连接应答处理器,**连接应答处理器 调用 accept() 创建新的 socket 01,新的 socket 01 被注册到 I/O 多路复用,监听其 AE_READABLE 事件。

客户端发送命令 set a 1 socket 01,数据到达 socket 01,触发AE_READABLE事件,I/O 多路复用 检测到 socket 01 可读,将事件放入 **队列,**事件分发处理器 从队列取出事件,调用 **命令请求处理器,**命令请求处理器 从 socket 01 读取数据,解析成命令(如 SET a 1),调用 执行命令 模块,实际执行 Redis 内部逻辑(如更新内存数据)

命令执行完成后,生成响应,命令回复处理器 将响应数据准备好,并设置 socket 01 AE_WRITABLE(可写),I/O 多路复用 检测到socket 01可写,触发 AE_WRITABLE 事件

事件进入队列,事件分发处理器 调用 **命令回复处理器,**命令回复处理器 触发 **执行应答程序,**执行应答程序 调用 write() 将响应数据写入 socket 01,写完后,解除 AE_WRITABLE 事件绑定(避免重复触发)

I/O多路复用的本质:事件通知机制

用户空间:一个线程调用epoll_wait(),阻塞等待

内核空间:监控多个文件描述符(fd),当任一fd就绪,唤醒该线程

多线程模型

redis多线程只用在了IO读写方面,redis命令执行还是单线程的。Redis的主要性能瓶颈是内存或网络带宽而并非CPU,为解决网络IO问题引入多线程模型。

大key删除问题: 正常情况下使用 del 指令可以很快的删除数据,而当被删除的 key 是一个非常大的对象时,例如包含了成千上万个元素的 hash 集合时,那么 del 指令就会造成 Redis 主线程卡顿。

通过惰性删除有效避免Redis卡顿问题(大key删除等问题):先将key从数据库字典中解绑,将删除工作交给后台异步子线程完成。

I/O 的读和写本身是堵塞的,比如当 socket 中有数据时,Redis 会通过调用先将数据从内核态空间拷贝到用户态空间,再交给 Redis 调用,而这个拷贝的过程就是阻塞的,当数据量越大时拷贝所需要的时间就越多,而这些操作都是基于单线程完成的。

主要实现思路是将主线程的 IO 读写任务拆分给一组独立的线程去执行,这样就可以使多个 socket 的读写可以并行化了,采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),将最耗时的Socket的读取、请求解析、写入单独外包出去,剩下的命令执行仍然由主线程串行执行并和内存的数据交互。

img

多线程执行流程:

主线程获取 socket 放入等待列表

将 socket 分配给各个 IO 线程(并不会等列表满)

主线程**阻塞等待 IO 线程(多线程)**读取 socket 完毕

主线程执行命令 - 单线程(如果命令没有接收完毕,会等 IO 下次继续)

主线程阻塞等待 IO 线程(多线程)将数据回写 socket 完毕(一次没写完,会等下次再写)

img

主从复制

为了避免单点故障,通常的做法是将数据库复制多个副本以部署在不同的服务器上,这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。

数据库分为两类,一类主数据库master,一类从数据库slave。主数据库可以进行读写操作,当写操做导致数据变化时自动将数据同步给从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。

img

全量复制:

  1. 从服务器发送SYNC命令,请求开始同步
  2. 主服务器生成RDB快照,将生成的RDB快照发送给从服务器
  3. 从服务器接收RDB文件后,会清空当前数据集,并载入RDB文件中的数据
  4. 在RDB文件生成和传输期间,主服务器会记录所有接收到的写命令,写入到replication backlog buffer
  5. replication backlog buffer中的写命令发送给从服务器保证数据的一致性

增量复制:

增量同步允许从服务器从断点处继续同步,而不是每次都进行完全同步。

主服务器如何知道将哪些增量数据发送给从服务器

  • repl_backlog_buffer:是一个环形缓冲区,用于主从服务器断连后,从中找到差异的数据
  • Replication offset:标记上面那个缓冲区的同步进度,主从服务器都有各自的偏移量,主服务器使用master_repl_offset来记录自己写到的位置,从服务器使用slave_repl_offset来记录自己读到的位置。

在主服务器进行命令传播时,不仅会将写命令发送给从服务器,还会将写命令写入到repl_backlog_buffer缓冲区中,因此这个缓冲区里会保存最近传播的写命令

网络断开后,当从服务器重新连接上主服务器时,从服务器会通过psync命令将自己的复制偏移量slave_repl_offset发送给主服务器,主服务器根据自己的master_repl_offsetslave_repl_offset之间的差距,来决定对从服务器执行哪种同步操作:

  • 如果判断出从服务器要读取的数据还在 repl_backlog_buffer 缓冲区里,那么主服务器将采用增量同步的方式;
  • 相反,如果判断出从服务器要读取的数据已经不存在 repl_backlog_buffer 缓冲区里,那么主服务器将采用全量同步的方式。

哨兵模式

哨兵的出现主要是解决了主从复制出现故障时需要人为干预的问题、

主要功能

  1. 集群监控:负责监控 redismaster 和 slave 进程是否正常工作
  2. 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员
  3. 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上
  4. 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址

原理

  • 主节点的信息配置在哨兵 sentinel 的配置文件中。
  • 哨兵节点会和配置的主节点建立起两条连接命令连接和订阅连接
  • 哨兵会通过命令连接每 10s 发送一次 INFO 命令,通过 INFO 命令,主节点会返回自己的 run_id 和自己的从节点信息。
  • 哨兵会对这些从节点也建立两条连接命令连接和订阅连接。
  • 哨兵通过命令连接向从节点发送 INFO 命令,获取到他的一些信息(偏移量等)
  • 哨兵节点会对redis指定频道发送对主节点信息的判断以及自身节点信息,其他哨兵节点也会订阅该频道
  • 哨兵之间建立连接完成心跳检测。

故障转移

  • 主节点故障,从节点同步连接中断,主从复制停止。
  • 哨兵节点通过定期监控发现主节点出现故障。哨兵节点与其他哨兵节点进行协商,达成多数认同主节点故障的共识(客观下线)。这步主要是防止该情况:出故障的不是主节点,而是发现故障的哨兵节点或者是因为网络问题,该情况经常发生于哨兵节点的网络被孤立的场景下。
  • 哨兵节点之间使用Raft算法选举出一个领导角色,由该节点负责后续的故障转移工作。
  • 哨兵领导者开始执行故障转移:从节点中选择一个作为新主节点,让其他从节点同步新主节点。该工作也就是前面我们说到的人工操作恢复故障的工作。
  • 哨兵节点会自动的通知客户端程序告知新的主节点是谁,并且后续客户端再进行写操作,就会针对新的主节点进行操作了。

集群模式

redis 的哨兵模式基本已经可以实现高可用、读写分离,但是在这种模式每台 redis 服务器都存储相同的数据,很浪费内存资源,所以在 redis3.0 上加入了 Cluster 群集模式,实现了 redis 的分布式存储,也就是每台 redis 节点存储着不同的内容。

数据分片

  • Redis集群采用的算法是哈希槽分区算法。Redis集群中有16384个哈希槽(槽的范围是 0 -16383),将不同的哈希槽分布在不同的Redis节点上面进行管理,也就是每个Redis节点只负责一部分的哈希槽。在对数据进行操作的时候,集群会对使用CRC16算法对key进行计算并对16384取模(slot = CRC16(key)%16383),得到的结果就是 Key-Value 所放入的槽,通过这个值,去找到对应的槽所对应的Redis节点,然后直接到这个对应的节点上进行存取操作。
  • 默认情况下,redis集群的读和写都是到master上去执行的,不支持slave节点读和写,因为Redis的读写分离,是为了横向任意扩展slave节点去支撑更大的读吞吐量。而redis集群架构下,本身master就是可以任意扩展的,如果想要支撑更大的读或写的吞吐量,都可以直接对master进行横向扩展。且从节点虽然有数据副本,但是不会参与slot路由决策

优点:可以方便的添加或者移除节点,并且无论是添加删除或者修改某一个节点,都不会造成集群不可用的状态。

Hash tag

集群模式中用于控制 key 分片(sharding)行为的一种特殊语法机制。确保多个相关的 key 被分配到同一个 hash slot(哈希槽),从而落在同一个 Redis 节点上

因为集群不允许跨节点执行多key命令,所以通过在key中使用{}包裹一部分字符串,redis只对{}中的内容计算hash slot。

节点通信

不同的节点之间采用gossip协议进行通信,节点之间通讯的目的是为了维护节点之间的元数据信息。这些元数据就是每个节点包含哪些数据,是否出现故障,通过gossip协议,达到最终数据的一致性。

gossip协议常见的消息类型包含: ping、pong、meet、fail等等。

  • meet:主要用于通知新节点加入到集群中,通过「cluster meet ip port」命令,已有集群的节点会向新的节点发送邀请,加入现有集群。
  • ping:用于交换节点的元数据。每个节点每秒会向集群中其他节点发送 ping 消息,消息中封装了自身节点状态还有其他部分节点的状态数据,也包括自身所管理的槽信息等等。
  • pong:ping和meet消息的响应,同样包含了自身节点的状态和集群元数据信息。
  • fail:某个节点判断另一个节点 fail 之后,向集群所有节点广播该节点挂掉的消息,其他节点收到消息后标记已下线。

因为发送ping命令时要携带一些元数据,如果很频繁,可能会加重网络负担。因此,一般每个节点每秒会执行 10 次 ping,每次会选择 5 个最久没有通信的其它节点。

如果发现某个节点通信延时达到了 cluster_node_timeout / 2,那么立即发送 ping,避免数据交换延时过长导致信息严重滞后。比如说,两个节点之间都 10 分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题。所以 cluster_node_timeout 可以调节,如果调得比较大,那么会降低 ping 的频率。

每次 ping,会带上自己节点的信息,还有就是带上 1/10 其它节点的信息,发送出去,进行交换。至少包含 3 个其它节点的信息,最多包含 (总节点数 - 2)个其它节点的信息。

img

集群请求

Redis集群采用去中心化的思想,没有中心节点,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node。请求重定向到其他节点中获取

img

重定向过程显然会增加集群的网络负担和单次请求耗时。所以大部分的客户端都是smart的。所谓 smart客户端,就是指客户端本地维护一份hashslot => node的映射表缓存,大部分情况下,直接走本地缓存就可以找到hashslot => node,不需要通过节点进行moved重定向

集群收缩与扩容

由于每个节点中保存着槽数据,因此当缓存节点数出现变动时,这些槽数据会根据对应的虚拟槽算法被迁移到其他的缓存节点上。

  1. 扩容:添加新节点后,需要将一些槽和数据从旧节点迁移到新节点

img

  1. 收缩:迁移槽,通过命令cluster forget {downNodeId}通知其他的节点。

为了安全删除节点,Redis集群只能下线没有负责槽的节点。因此如果要下线有负责槽的master节点,则需要先将它负责的槽迁移到其他节点。迁移的过程也与上线操作类似,不同的是下线的时候需要通知全网的其他节点忘记自己,此时通过命令 cluster forget {downNodeId} 通知其他的节点。

img

CATALOG
  1. 1. 单线程模型
    1. 1.1. IO多路复用
    2. 1.2. 单线程工作流程
  2. 2. 多线程模型
    1. 2.1. 多线程执行流程:
  3. 3. 主从复制
  4. 4. 哨兵模式
    1. 4.1. 主要功能
    2. 4.2. 原理
    3. 4.3. 故障转移
  5. 5. 集群模式
    1. 5.1. 数据分片
    2. 5.2. Hash tag
    3. 5.3. 节点通信
    4. 5.4. 集群请求
    5. 5.5. 集群收缩与扩容