type
status
date
slug
summary
tags
category
icon
password
 

一、Redis 并不是单线程?

 
Redis 是单线程,主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。
 

二、Redis 为什么用单线程?

 
这里就得对比一下单线程和多线程的开销问题,多线程是越多越好吗,显然不是,如下图:
 
notion image
多线程是可以提高系统整体的吞吐量,但前提是在合理分配资源的条件下,即在保证资源足够多不会发生竞争的情况下是会随着线程数的增加而增加的;但是他的问题瓶颈在于,系统中通常会存在被多线程同时访问的共享资源,为了保证共享资源的安全性,就必须有额外的机制来保证,这个机制会带来额外的开销,会造成吞吐量趋于平缓甚至下降的问题。
 
多线程访问共享资源,可以加锁解决,这个锁的粒度如何:
  • 倘若加了锁,发生资源竞争,大部分线程都在等待获取锁,似乎也不会提高系统的吞吐量;
  • 加锁在操作系统层面是添加了同步原语,这些原语是会降低系统代码的易调试性和可维护性
因此 Redis 采用了单线程,当然仅仅单纯的单线程还不够,还得有一个机制来合理利用这一个单线程,这个机制就是 IO多路复用
 

三、单线程 Redis 为什么那么快?

 
总结起来其实有三大方面:
  1. 基于内存
  1. 高效的数据结构,哈希表跳表等
  1. 单线程采用 IO 多路复用 机制
 

四、基本 IO 模型与阻塞点

 
如下图,是一个基本 IO 模型,一个线程完成全部过程:
  • 网络 IO 处理:绑定端口、监听端口、建立连接、读取请求、解析请求
  • 键值数据读写:通过解析的请求去内存读取键值数据
  • 网络 IO 处理:将从内存读取到的数据通过网络发送出去
 
notion image
那么,那些地方会发生阻塞情况呢,主要有两个地方:
  • accept:建立连接,若一直无法建立连接,则会在此处阻塞
  • recv:建立连接成功,若一直无法从客户端读取到请求数据,也会在此处发生阻塞
 
因此,基本 IO 模型效率很低,不过,Socket 网络模型本身支持非阻塞模式
 

五、非阻塞 IO 模型

 
Socket 网络模型的非阻塞模式设置,主要体现在以下三个关键的函数调用上:
 
notion image
  • listen:可设置非阻塞,调用 accept 方法后若一直未有请求到达,该线程可以处理其他事情
  • accept:可设置非阻塞,调用 recv 方法后若已连接 socket 上一直未有数据到达,该线程可以处理其他事情
 
这样是有问题的?
  1. listen 方法设置非阻塞,未有请求到达该线程可以处理其他事情,但必须得有其他线程去监听后续连接请求
  1. accept 方法设置非阻塞,未有数据到达该线程可以处理其他事情,但必须得有其他线程监听后续数据到达
 
要保证:
  • 不会像基本 IO 模型发生阻塞
  • 也不会导致无法处理其他请求或数据到达
 
为了解决这个问题,就要引出 IO 多路复用机制了!
 

六、基于多路复用的高性能 I/O 模型

 
Linux 中的 IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。
 
先来看个小例子:
病人去医院瞧病。在医生实际诊断前,每个病人(等同于请求)都需要先分诊、测体温、登记等。如果这些工作都由医生来完成,医生的工作效率就会很低。
所以,医院都设置了分诊台,分诊台会一直处理这些诊断前的工作(类似于 Linux 内核监听请求),然后再转交给医生做实际诊断。这样即使一个医生(相当于 Redis 单线程),效率也能提升。
核心就两个:Linux 内核线程处理监听事件 + Redis IO 单线程依次处理事件队列中的事件
 
notion image
 
对于上图的一些介绍:
  • 在 listen 和 accept 之间多了 epoll_wait 和几大事件,这部分就是 epoll 机制利用内核线程去监听这些事件:
    • AcceptEvent:即已连接套接字或已连接事件
    • ReadEvent:即读套接字或读事件
    • WriteEvent:即写套接字或写事件
    • 当然这些套接字 FD 可能是多个,即 Redis 可以同时连接多个客户端并处理请求,来提高并发性
 
内核监听到事件后,如何通知 Redis IO 单线程去处理?
这里是用了 select/epoll 提供的基于事件的回调机制,即针对不同事件的发生,调用相应的处理函数。
  • 任何事件到达都会被内核线程放到一个事件处理队列
  • Redis IO 单线程只需要依次处理队列中的事件即可(这个处理基于内核的回调
  • 这个处理就会根据事件类型去调用不同处理方法:
    • 已连接事件对应 accept 方法去处理
    • 读事件对应 get 方法去处理
    • 写事件对应 put 方法去处理
    •  
Redis IO 单线程挣个过程一直在高速处理事件队列中发生的事件,因此是可以及时对客户端的请求做出响应的!
 
设计模式之七大设计原则02 - 数据结构:快速的Redis有哪些慢操作?
Loading...
ITNXD
ITNXD
一个普通的干饭人🍚
最新发布
Java 并发编程
2025-7-31
Spring 源码系列第三章 - 后置 Bean 处理器与 Bean 生命周期
2022-12-25
Spring 源码系列第一章 - Spring 核心组件接口
2022-12-11
Spring 源码系列第二章 - 后置工厂处理器与 Bean 生命周期
2022-12-10
行为型模式之迭代器设计模式
2022-12-2
行为型模式之责任链设计模式
2022-12-1