Redis单线程VS多线程

面试题

一、 redis到底是单线程还是多线程
答:看版本,redis4之后才慢慢支持多线程,直到redis6/7后才稳定
img

二、IO多路复用听说过吗
答:多路复用和非阻塞I/O:Redis使用I/O多路复用功能来监听多个socket连接客户端,这样就可以使用一个线程连接来处理多个请求,减少线程切换带来的开销,同时也避免了I/O阻塞操作。
三、redis为什么快

四、Subtopic

Redis为什么选择单线程

img
Redis是单线程,主要是指Redis的网络IO和键值对读写是由一个线程来完成的,Redis在处理客户端的请求时包括获取(socket读)、解析、执行、内容返回(socket写)等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。这也是Redis对外提供键值存储服务的主要流程。
但Redis的其他功能,比如持久化RDB、AOF、异步删除、集群数据同步等等,其实是由额外的线程执行的。Redis命令工作线程是单线程的,但是,整个Redis来说,是多线程的。
Redis是基于内存操作的,因此他的瓶颈可能是机器的内存或者网络带宽而并非CPU,既然CPU不是瓶颈,那么自然就采用单线程的解决方案了,况且使用多线程比较麻烦。但是在Redis4.0中开始支持多线程了,例如后台删除、备份等功能。

简单来说,Redis4.0之前一直采用单线程的主要原因有以下三个:

  1. 使用单线程模型是Redis的开发和维护更简单,因为单线程模型方便开发和调试;
  2. 即使使用单线程模型也并发的处理多客户端的请求,主要使用的是IO多路复用和非阻塞IO;
  3. 对于Redis系统来说,主要的性能瓶颈是内存或者网络带宽而并非CPU。

Redis3.X单线程时代但性能依旧很快的主要原因

  1. 基于内存操作:Redis的所有数据都存在内存中,因此所有的运算都是内存级别的,所以他的性能比较高;
  2. 数据结构简单:Redis的数据结构是专门设计的,而这些简单的数据结构的查找和操作的时间大部分复杂度都是O(1),因此性能比较高;
  3. 多路复用和非阻塞I/O:Redis使用I/O多路复用功能来监听多个socket连接客户端,这样就可以使用一个线程连接来处理多个请求,减少线程切换带来的开销,同事也避免了I/O阻塞操作
  4. 避免上下文切换:因为是单线程模型,因此就避免了不必要的上下文切换和多线程竞争,这就省去了多线程切换带来的时间和性能上的消耗,而且单线程不会导致思索问题的发生

既然单线程这么好,为什么逐渐又加入了多线程特性

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

  1. 使用惰性删除可以有效的避免Redis卡顿的问题
  2. 在Redis4.0就引入了多个线程来实现数据的异步惰性删除等功能,但是其处理读写请求的仍然只有一个线程,所以仍然算是狭义上的单线程。
  • 案例:
    比如当我(Redis)需要删除一个很大的数据时,因为是单线程原子命令操作,这就会导致Redis服务卡顿,于是在Redis 4.0 中就新增了多线程的模块,当然此版本中的多线程主要是为了解决删除数据效率比较低的问题的。
unlink key
flushdb async
flushall async
# 把删除工作交给了后台的小弟(子线程)异步来删除数据了

因为Redis是单个主线程处理,redis支付antirez一直强调“Lazy Redis is better Redis”,而lazy free的本质就是把某些cost(主要时间复制度,占用主线程cpu时间片)较高删除操作,从redis主线程剥离让bio子线程来处理,极大地减少主线阻塞时间。从而减少删除导致性能和稳定性问题。

redis6/7的多线程特性和IO多路复用入门篇

主线程和IO线程是怎么协作完成请求处理

img
img

阶段一:服务端和客户端建立socket连接,并分配处理线程
首先,主线程负责接收建立连接请求,当有客户端请求和实例建立Socket连接时,主线程会创建和客户端的连接,并把Socket放入全局等待队列中,紧接着,主线程通过轮询方法把Socket连续分配给IO线程。
阶段二:IO线程读取并解析请求
主线程一旦把Socket分配给IO线程,就会进入阻塞状态,等待IO线程完成客户端请求读取和解析,因为有多个IO线程在并行处理,所以,这个过程很快就可以完成。
阶段三:主线程执行请求操作
等到IO线程解析完请求,主线程还是会以单线程的方式执行这些命令操作。
阶段四:IO线程回写Socket和主线程清空全局队列
当主线程执行完请求操作后,会把需要返回的结果写入缓冲区,然后,主线程会阻塞等待IO线程,把这些结果回写到Socket中,并返回给客户端,和IO线程读取和解析请求一样,IO线程回写Socket时,也是有多个线程在并发执行,所以回写到Socket的速度也很快,等到IO线程回写到Socket完毕,主线程会清空全局队列,等待客户端的后续请求。

Unix网络编程中的五种IO模型

一、Blocking IO - 阻塞IO

二、NoneBlocking IO - 非阻塞IO

三、IO Multiplexing - IO多路复用

  1. Linux世界一切皆文件
    文件描述符、简称FD,句柄 —— FileDescriptor
  2. IO多路复用是什么
    一种同步的IO模型,实现一个线程监视多个文件句柄,一旦某个文件句柄就绪就能够通知到对应应用程序进行相应的读写操作,没有文件句柄就绪时就会阻塞应用程序,从而释放CPU资源

I/O:网络I/O,尤其在操作系统层面指数数据在内核态和用户态之间的读写操作
多路:多个客户端连接(连接就是套接字描述符,即socket或者channel)
复用:复用一个或几个线程
IO多路复用:一个或一组线程处理多个TCP连接,使用单进程就能够实现同时处理多个客户端的连接,需要创建或者维护过多的进程/线程。简而言之:一个服务端进程可以同时处理多个套接字描述符。实现IO多路复用的模型有3种:可以分select->poll->epoll三个阶段来描述

  1. 场景体验,说人话引出epoll
    将用户socket对应的文件描述符(FileDescriptor)注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用非阻塞模式。这样,整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的reactor反应模式。
    img
    在单个线程通过记录跟踪每一个Socket(I/O流)的状态来同时管理多个I/O流,一个服务端进程可以同时处理多个套接字描述符。目的是尽量多的提高服务器的吞吐能力。
  2. 小总结
    只使用一个服务端进程可以同时处理多个套接字描述符连接
  3. 面试题:Redis为什么这么快?
    答:IO多路复用+epoll函数使用,才是redis为什么这么快的直接原因,而不是仅仅单线程命令+redis安装在内存中。

四、signal driven IO -信号驱动IO

五、asynchronous IO -异步IO

Redis7默认是否开启了多线程

  1. 设置io-thread-do-reads配置项为yes,表示启动多线程。
  2. 设置线程个数。关于线程数的设置,官方的建议是如果为4核的CPU,建议线程数设置为2或3,如果为8核CPU建议线程数设置为6,线程数一定要小于机器核数,线程数并不是越大越好。
posted @ 2025-04-17 10:05  小肚腩吖  阅读(131)  评论(0)    收藏  举报