Redis是单线程还是多线程?

121 阅读4分钟

一、Redis为何选择单线程

Redis的定位,是内存k-v存储,是做短平快的热点数据处理,一般来说执行很快,执行本身不应该成为瓶颈,而瓶颈通常在网络I/O,处理逻辑多线程并不会有太大收益

Redis本身秉承着简介高效的概念,代码的简单性,可维护性是Redis一致依赖的追求,引入多线程带来的复杂性远比想象的要大,而且多线程本身也会引入额外成本;

1、多线程引入的复杂性是极大的

首先,多线程引入后,Redis原来的顺序执行特性就不复存在,为了支持事务的原子性、隔离性,Redis就不得不引入一些很复杂的实现

其次,Redis的数据结构,极其高效,在单线程模式下做了很多特性的优化,如果引入多线程,那么所有底层数据结构都要改造为线程安全,这回事极其复杂的操作

2、多线程带来额外的成本

除了引入复杂度,多线程还会带来额外的成本,

  • 上下文切换成本
  • 同步机制的开销:公共资源单线程直接访问就行了,多线程需要通过加锁等方式去进行同步,这也是不可忽视的CPU开销
  • 一个线程本身也占据内存大小,对Redis这种内存数据库而言,内存非常珍贵,多线程本身带来的内存使用的成本也需要谨慎决策

二、单线程为什么能这么快?

1、单线程

几个关键点:

  • 第一:Redis的大部分操作在内存上完成,内存操作本身就特别快
  • 第二:Redis追求机制,选择了很多高效的数据结构,并作了非常多的优化,比如ziplist,跳表,有时候一种对象底层有几种实现应对与不同场景
  • 第三:Redis采用了多路复用机制,使其在网络I/O操作中能并发处理大量的客户端请求,实现高吞吐量

2、I/O可能的潜在点

首先,我们知道Redis是完全在内存中处理数据,所以我们最应该考虑的瓶颈是I/O,我们下面通过分析一次请求,来看一下,一个单线程在一次完整的处理中,会有哪些地方拖慢整个流程。

Redis的服务端在启动的时候,已经bind了端口,并且用listen操作监听客户端请求,此时客户端就可以发起连接请求。

比如:客户端发来一个请求,服务端需要哪些事情:

  1. 客户端请求到来时候,使用accept建立连接
  2. 调用rec从套接字中读取请求
  3. 解析客户端发送请求,拿到参数
  4. 处理请求
  5. 最后将数据通过send发送给客户端

image-20240709195703994

我们要知道,套接字是默认阻塞模式的,这里阻塞可能会发生在两个地方。

  • 一个是accept,比如accept建立时间过长
  • 另一个recv时客户端一直没有发送数据

此时,Redis服务就会阻塞在那里。

Redis本身定位就是单线程,发生这种阻塞会将整个服务都卡住。所以不能让这两个操作阻塞,这里Redis将套接字设置为非阻塞式的,这样accept和recv都可以非阻塞调用

非阻塞调用下,如果没数据,不会阻塞在那里,而是让你返回做其他事情,这样可以解决卡死的问题,但我们也需要一种机制,能回过头来看看这些操作是否已经就绪

最简单的思路,我们可以通过一个循环来不断轮询,但这样方式显然低效,好在各个操作系统实现了一种机制,叫I/O多路复用

什么是I/O复用?简单来说,就是有I/O操作触发的时候,就会产生通知,收到通知,再去处理通知对应的时间,针对I/O多路复用,Redis做了一层包装,叫Reactor模型

如下图,本质就是监听各种事件,当事件发生时,将事件发给不同的处理器

image-20240709200359646

这样就不会阻塞在某一个操作上,充分发挥性能,可以说I/O多路复用让Redis单线程也有了较大的并发度,注意这里是并发,而不是并行,在这种模式下,Redis单核的性能可以说是被充分利用了