首页 > 数据库 >【转载】Redis -- IO多路复用及redis6的多线程

【转载】Redis -- IO多路复用及redis6的多线程

时间:2024-03-16 20:55:30浏览次数:35  
标签:socket 多路复用 -- redis6 Redis 多线程 服务端 客户端

都知道redis是通过单线程+io多路复用来避免并发问题的,然后在redis6的时候redis引入了多线程,这里就来详细说说IO多路复用模型以及redis的多线程。

Redis 的 I/O 多路复用模型有效的解决单线程的服务端,使用不阻塞方式处理多个 client 端请求问题。在看 I/O 多路复用知识之前,我们先来看看 Redis 的客服端怎么跟客服端建立连接的、单线程 socket 服务端为什么会存在 I/O 阻塞。

1. Redis客户端连接

Redis 通过监听一个 TCP 端口或者 Unix socket 的方式来接收来自 client 端的连接,当一个连接建立后,Redis 内部会进行以下一些操作:

  1. 首先,客户端 socket 会被设置为非阻塞模式,因为 Redis 在网络事件处理上采用的是非阻塞多路复用模型。
  2. 然后为这个 socket 设置 TCP_NODELAY 属性,禁用 Nagle 算法。
  3. 然后创建一个可读的文件事件用于监听这个客户端 socket 的数据发送。

2. 阻塞I/O

在 socket 连接中,一个服务器进程和一个客户端进行通信时,当一个 client 端向服务端写数据时,如果 client 端没有发送数据,那么服务端的 read 将一直阻塞,直到客户端 write 发来数据。在一个客户和服务器通信时没什么问题,当多个客户 与 一个服务器通信时,就存在问题了。如下图,两个客服端同时连接一个服务端进行写数据的时序图。

 

从上图可以看出一个服务器进程和多个客户端进程通信的问题:

  1. client1 和服务端建立连接后,服务端会一直阻塞于 client1,直到 client1 客户端 write 发来数据才开始后面的操作。服务端阻塞期间,即使其他客服端 client2 的数据提前到来,也不能处理 client2 客服端的请求。
  2. 有一个严重的问题就是,如果客户端 client1 一直没有 write 数据到来,那么服务端 service 会一直阻塞,不能处理其他客户的服务。

上面就是 Redis 通过 Unix socket 的方式来接收来自 client 端的连接存在的 I/O 阻塞问题,而 「I/O 多路复用」就是为了解决服务端一直阻塞等待某一个 client 的数据到来,即使其他client的数据提前到来,也不会被处理的问题。

3. I/O多路复用

3.1 I/O多路复用

为什么 Redis 中要使用 I/O 多路复用这种技术呢?因为 Redis 是跑在「单线程」中的,所有的操作都是按照顺序线性执行的,但是「由于读写操作等待用户输入 或 输出都是阻塞的」,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务。而 I/O 多路复用就是为了解决这个问题而出现的。「为了让单线程(进程)的服务端应用同时处理多个客户端的事件,Redis 采用了 IO 多路复用机制。」

这里多路【指的是多个网络连接客户端】复用【指的是复用同一个线程(单进程)】

I/O 多路复用:其实是使用一个线程来检查多个 Socket 的就绪状态,在单个线程中通过记录跟踪每一个 socket(I/O流)的状态来管理处理多个 I/O 流。

如下图是 Redis 的 I/O 多路复用模型:

 

其中:FD即:文件描述符(file descriptor):
Linux 系统中,把一切都看做是文件,当进程打开现有文件或创建新文件时,内核向进程返回一个文件描述符。可以理解文件描述符是一个索引,这样,要操作文件的时候,我们直接找到索引就可以对其进行操作了。我们将这个索引叫做文件描述符(file descriptor),简称fd。

如上图对 Redis 的 I/O 多路复用模型进行一下描述说明:

  1. 一个 socket 客户端与服务端连接时,会生成对应一个套接字描述符(套接字描述符是文件描述符的一种),每一个 socket 网络连接其实都对应一个文件描述符。
  2. 多个客户端与服务端连接时,Redis 使用 「I/O 多路复用程序」 将客户端 socket 对应的 FD 注册到监听列表(一个队列)中。当客服端执行 read、write 等操作命令时,I/O 多路复用程序会将命令封装成一个事件,并绑定到对应的 FD 上。
  3. 「文件事件处理器」使用 I/O 多路复用模块同时监控多个文件描述符(fd)的读写情况,当 accept、read、write 和 close 文件事件产生时,文件事件处理器就会回调 FD 绑定的事件处理器进行处理相关命令操作。
  4. 文件事件分派器接收到I/O多路复用程序传来的套接字fd后,并根据套接字产生的事件类型,将套接字派发给相应的事件处理器来进行处理相关命令操作。
    • 例如:以Redis的I/O多路复用程序 epoll函数为例
    • 多个客户端连接服务端时,Redis会将客户端socket对应的fd注册进epoll,然后epoll同时监听多个文件描述符(FD)是否有数据到来,如果有数据来了就通知事件处理器赶紧处理,这样就不会存在服务端一直等待某个客户端给数据的情形。
  5. I/O多路复用程序函数有select、poll、epoll、kqueue
  6. 整个文件事件处理器是在单线程上运行的,但是通过 I/O 多路复用模块的引入,实现了同时对多个 FD 读写的监控,当其中一个 client 端达到写或读的状态,文件事件处理器就马上执行,从而就不会出现 I/O 堵塞的问题,提高了网络通信的性能。
  7. 如上图,Redis 的 I/O 多路复用模式使用的是 「Reactor 设计模式」的方式来实现。

3.2 IO多路复用总结

  1. Redis 的 I/O 多路复用程序函数有 select、poll、epoll、kqueue。select 作为备选方案,由于其在使用时会扫描全部监听的文件描述符,并且只能同时服务 1024 个文件描述符,所以是备选方案。
  2. I/O 多路复用模型是利用 select、poll、epoll 函数可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉。当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),依次顺序的处理就绪的流,这种做法就避免了大量无用的等待操作


4. Redis6新引入的多线程

4.1 先说说之前的单线程优缺点


对于一个请求操作, Redis 主要做 3 件事情: 从客户端读取数据、执行 Redis 命令、回写数据给客户端(如果再准确点, 其实还包括对协议的解析)。所以主线程其实就是把所有操作的这 3 件事情, 串行一起执行, 因为是基于内存, 所以执行速度非常快:

优点 VS 缺点:

  • 优势: a. 不存在锁的问题 b. 避免线程间 CPU 切换
  • 缺点: a. 单线程无法利用多 CPUb. 串行操作, 某个操作"出问题"会"阻塞"后续操作

4.2 redis的多线程

Redis 多线程的优化思路
因为网络 I/O 在 Redis 执行期间占用了大部分 CPU 时间, 所以把网络 I/O 部分单独抽离出来, 做成多线程的方式。这里所说的多线程, 其实就是将 Redis 单线程中做的这两件事情"从客户端读取数据、回写数据给客户端"(也可以称为网络 I/O), 处理成多线程的方式, 但是"执行 Redis 命令"还是在主线程中串行执行, 这个逻辑保持不变。

可能有同学会问, 主线程和多个 I/O 线程, 都同时处理图中的"队列", 是不是会存在锁竞争的关系呢? 这里有个巧妙的设计, 就是当 epoll 获取 socket 链接时, 会将该事件先全部扔进队列中, 比如扔了 N 个事件, 这时主线程就会处于忙等 (spinlock 自旋锁的效果)状态。然后多个 I/O 线程开始去并行进行网络 I/O, 并对数据进行协议解析, 当队列全部处理完毕后, 主线程会对队列中请求串行"执行 Redis 命令", 然后清空该队列。所以整个执行流程总结下来: 主线程执行请求入队列 -> I/O 线程并行进行网络读 -> 主线程串行执行 Redis 命令 -> I/O 线程并行进行网络写 -> 主线程清空队列, 并接收下一批请求。

优点 VS 缺点

  1. 优点: 提高响应速度, 充分使用 CPU
  2. 缺点:  增加了代码复杂性

4.3 总结

  • 5.0 版本及以前, 处理客户端请求的线程只有一个, 串行处理;
  • 6.0 版本引入了 worker Thread, 只处理网络 IO 读取和写入, 核心 IO 负责串行处理客户端指令。

4.4 关于redis的多线程详解可以看

Redis 6 中的多线程是如何实现的 !

redis 6.0之多线程,深入解读

5. Redis 4.0 之后加入Lazy Free机制

Redis 4.0 之前在处理客户端命令和IO操作时都是以单线程形式运行,期间不会响应其他客户端请求,但若客户端向Redis发送一条耗时较长的命令,比如删除一个含有上百万对象的Set键,或者执行flushdb,flushall操作,Redis服务器需要回收大量的内存空间,这事就会导致Redis服务阻塞,对于负载较高的缓存系统来说将会是个灾难。为了解决这个问题,在Redis 4.0版本引入了Lazy Free,目的是将慢操作异步化,这也是在事件处理上向多线程迈进了一步

标签:socket,多路复用,--,redis6,Redis,多线程,服务端,客户端
From: https://www.cnblogs.com/r1-12king/p/18077596

相关文章

  • 实验1
    #include<stdio.h>intmain(){ printf("o\n"); printf("<H>\n"); printf("II\n"); printf("o\n"); printf("<H>\n"); printf("II\n"); return0;}#inc......
  • 常用快捷键
    ctrl+c复制ctrl+v粘贴ctrl+A全选ctrl+x剪切ctrl+z撤销ctrl+s保存alt+f4关闭窗口shift+delect永久删除ctrl+shift+esc打开任务管理器DOS:打开CMD1、盘符切换E:2、查看当前目录下的所有内容dir3、切换目录cd..切换到上一级4、清理屏幕cls(clearscreen)5......
  • 波动数列
    一、题目描述P8614[蓝桥杯2014省A]波动数列二、问题简析设第一个数为\(x_0\),\(d_i=a~or~-b\),则长度为\(n\)的数列的和为:\[\begin{split}s&=x_0+x_1+x_2+...+x_{n-1}\\&=(x_0+0)+(x_0+d_1)+(x_0+d_1+d_2)+...+(x_0+d_1+...+d_{n-1})\\&=n*x_0+0+(n-1)*d_1+(n-......
  • Python《基础知识》
    1.列表:list列表内的元素通过方括号[]来表示,且可以修改例:list=[1,2,3,"fhdsj","sum"]有关list的函数:cmp(list1,list2)比较两个列表的元素len(list)返回列表元素个数max(list)返回列表元素最大值min(list)返回列表元素最小值list(seq)将元组转换为列表示例:list......
  • Desmos 3D| 向量计算的工具化
    前言计算示例全屏......
  • 离线数仓建设之数据导出
    为了方便报表应用使用数据,需将ADS各项指标统计结果导出到MySQL,方便熟悉SQL人员使用。1MySQL建库建表1.1创建数据库创建car_data_report数据库:CREATEDATABASEIFNOTEXISTScar_data_report#字符集DEFAULTCHARSETutf8mb4#排序规则COLLATEutf8mb4_general_ci;1......
  • 2-02. 导入素材
    购买素材地址购买好之后将素材导入到自己的项目中素材说明空的人物动作以20结尾的图片表示它们的pixelperunit是20,其它图片表示需要自动切割素材切割MaxSize可以改成1024,因为图片是640x460将图片设置改为预设保存到Art同级目录下之后,其它图片可以使用......
  • 3.14毕设
    1.MyBatis简介1.1JDBC存在的问题1.数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。2.Sql语句在代码中硬编码,造成代码不易维护,实际应用sl变化的可能较大,sql变动需要改变java代码。3.使用preparedStatement向占位符号传参数存在硬编码......
  • 【WPF】自定义按钮样式(添加依赖属性、圆角)
    参考https://www.bilibili.com/video/BV13D4y1u7XX/?p=21代码示例1、自定义CustomButton按钮继承ButtonnamespaceWpfStudy.Buttons{publicclassCustomButton:Button{publicCornerRadiusCornerRadius{get{return(CornerRa......
  • 3.15毕设
    前面的HelloWorld,只做了一个查询的Demo,这里尝试另外四种常见的操作。  前面所写的增删改查是存在问题的。主要问题就是冗余代码过多,模板化代码过多。例如,开发一个serDao,可能是下面这样: 引入Mapper,UserDao就基本上用不到了,因为框架已经帮我们做好了 ......