I/O多路复用-redis单线程模型快的根本原因

目录

BIO

BIO单线程模式

BIO多线程模型

NIO

IO multiplexing

select函数:

poll函数:

epoll函数:


首先了解同步和异步,阻塞和非阻塞的概念:

同步:发起请求的一方需要等待操作完成并获得结果后才能继续执行后续的操作,换句话说,同步操作会阻塞当前线程或进程,直到操作完成。

异步:发起请求的一方可以继续执行后续的操作,而不必等待操作完成。异步操作通常会使用回调函数、事件处理器或者轮询的方式来处理结果。在异步模式下,不同的参与方可以独立地进行操作,无需等待其他操作的完成。

阻塞:在阻塞模式下,当一个I/O操作被调用时,程序会一直等待,直到操作完成或者发生错误才会返回结果。在阻塞状态下,调用线程或进程会被挂起,无法进行其他任务,直到I/O操作完成或者超时。

非阻塞:在非阻塞模式下,当一个I/O操作被调用时,程序会立即返回,而不会等待操作完成。如果操作不能立即完成,会返回一个错误或标记来指示当前操作无法立即完成,而不会阻塞调用线程或进程。

阻塞和非阻塞他们描述的是操作等待的状态,同步和异步描述的是操作执行的顺序。


BIO

在没有IO多路复用之前,遇到并发多客户端连接,通常采用 BIO 模型来处理并发多连接问题。

BIO单线程模式

最开始的BIO单线程(同步)模式:

code案例:

public class redisServerBIO {public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(6379);while (true) {System.out.println("模拟RedisServer启动------111等待连接");Socket accept = serverSocket.accept();System.out.println("--------222 成功连接:" + IdUtil.simpleUUID());InputStream inputStream = accept.getInputStream();int length = -1;byte[] bytes = new byte[1024];System.out.println("-------333等待读取");while ((length = inputStream.read(bytes)) != -1) {System.out.println("------444 成功读取 " + new String(bytes,0,length));System.out.println("===================" + "\t" + IdUtil.simpleUUID());System.out.println();}inputStream.close();accept.close();}}
}

存在的问题:如果客户端与服务端建立了连接,但连接的客户端迟迟不发数据,线程就会一直阻塞在read()方法上,这样其让客户旧不能进行连接。

BIO多线程模型

进一步改进BIO单线程模型,采用BIO多线程模型:为每一个客户端连接分配一个线程处理请求,这样,read()方法旧阻塞在每一个具体的线程上,而不会阻塞主线程。

code案例:

public class redisServerBIOMultiThread {public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(6379);while (true) {System.out.println("------111等待连接");Socket accept = serverSocket.accept();System.out.println("--------222 成功连接:" + IdUtil.simpleUUID());new Thread(new Runnable() {@SneakyThrows@Overridepublic void run() {InputStream inputStream = accept.getInputStream();int length = -1;byte[] bytes =  new byte[1024];System.out.println("-------333等待读取");while ((length = inputStream.read(bytes)) != -1) {System.out.println("------444 成功读取 " + new String(bytes,0,length));System.out.println("===================" + "\t" + IdUtil.simpleUUID());System.out.println();}inputStream.close();accept.close();}}).start();}}
}

存在的问题:为每一个客户端都开辟一个线程的话,并发量大的话,那么开辟的线程也将及其庞大,而在操作系统中用户态不能直接开辟线程,需要调用内核态来创建线程,其中会涉及到上下文的切换,十分耗资源。

改进BIO多线程模型:

方式一:使用线程池

在客户端连接少的情况下可以使用,但是用户量大的情况下,不知道线程池要多大,太大了内存可能不够,太小了也不行。

方式二:采用NIO(非阻塞式IO)方式

因为read方法阻塞了,所以要开辟多个线程,如果有什么方法能使read()方法不阻塞,这样就不用开辟多个线程了——NIO


NIO

在NIO模式中,一切都是非阻塞的:accept()方法式非阻塞的,如果没有客户端连接,就返回无连接标识;read()方法是非阻塞的,如果read()方法读取不到数据就返回空闲中标识,如果读取到数据那么只阻塞read()方法读数据的时间。

在NIO模式中,只有一个线程,当一个客户端与服务端进行连接,这个socket就会加入到一个数组中,隔一段时间就会遍历一次,看当前socket的read()方法能否读取到数据,这样一个线程就能处理多个客户端的连接和读取了。

code案例:

public class redisServerNIO {static ArrayList<SocketChannel> socketList = new ArrayList<SocketChannel>();static ByteBuffer byteBuffer = ByteBuffer.allocate(1024);public static void main(String[] args) throws IOException {System.out.println("--------RedisServerNIO 启动等待中........");ServerSocketChannel serverSocket = ServerSocketChannel.open();serverSocket.bind(new InetSocketAddress("127.0.0.1",6379));serverSocket.configureBlocking(false);//设置为非阻塞模式while (true) {for (SocketChannel element : socketList) {int read = element.read(byteBuffer);if(read > 0){System.out.println("------读取数据:" + read);byteBuffer.flip();byte[] bytes = new byte[read];byteBuffer.get(bytes);System.out.println(new String(bytes));byteBuffer.clear();}}SocketChannel socketChannel = serverSocket.accept();if (socketChannel != null){System.out.println("-------成功连接:");socketChannel.configureBlocking(false);//设置为非阻塞模式socketList.add(socketChannel);System.out.println("------socketList size:" + socketList.size());}}}}

NIO模式依然存在问题:

问题一:有一万个客户端进行连接,那么每次就要遍历一万个socket,如果一万个socket中只有10个socket有数据,就会做很多的无用功,每次遍历read()返回-1 时仍然时一次浪费资源的系统调用。

问题二:遍历socket的过程是在用户态,用户态哦按段socket是否有数据还是在内核态调用read()方法来实现的,还是会涉及到用户态和内核态的切换,开销依然很大。

抛开NIO存在的问题,其实上述的 NIO 模型的实现思路就是我们要讨论的IO多路复用(IO multiplexing)的思想。


IO multiplexing

IO multiplexing:就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。可以基于一个阻塞对象并同时在多个描述符上等待就绪,而不是使用多个线程(每个文件描述符一个线程,每次new一个线程),这样可以大大节省系统资源。所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态。

对于NIO存在的问题二,我们只需要在操作系统内核中提供类似NIO模型的函数,就避免了用户态和内核态之间的切换,所以,在操作系统内核就有了select、poll、epoll函数来实现IOmultiplexing。

select函数:

其实类似于上述的NIO模型的代码逻辑。只不过把NIO中用户态要遍历的fd数组(socket连接数组)拷贝到了内核态,让内核态来遍历,这样就不用在判断每个socket中是否有数据的时候切换用户态和内核态了,问题二解决。

select函数是一个阻塞函数,当没有数据时会一直阻塞在select那一行。select函数使用bitmap来代替我们上述代码中的Arraylist,当有数据时会将bitmap中对应的位置置为1,但并不会返回哪一个socket有数据。用户态只需要遍历bitmap,看bitmap哪一位为1,就read哪一个socket,不在遍历每一个socket了,问题一解决。

select函数存在的问题:

1、每次的bitmap不可重用,只能置1,不能恢复

2、bitmap默认大小为1024,虽可调整但还是有限度

3、需要将fd数组从用户态拷贝到内核态,由内核态调用read(),但还是由拷贝的开销

4、select并没有通知用户态哪一个socket有数据,仍然要O(N)的遍历。

poll函数:

阻塞函数没有数据时会阻塞在poll那一行。用数组代替了bitmap,哪个socket有数据,就将对应的位置置为POLLIN(置1),遍历数组找到置1的socket后,再置0,恢复数组,便于重用。解决了select的问题1、2。

epoll函数:

非阻塞函数。当有数据的时候,会把相应的文件描述符(可以理解为socket)“置位”,但是epoll并不是真正的置位,这时候会把有数据的文件描述符放到队首,epoll会返回有数据的文件描述符(socket)的个数N,根据返回的个数只需要遍历钱N个文件描述符即可。解决了select的问题4

epoll函数对select的问题3的解决的处理,看了大牛的帖子,是这样解决的,但还不是很理解,记录一下:

  epoll函数之所以不需要用户态将文件描述符(fd)数组拷贝到内核态,是因为它利用了操作系统内核的事件表机制,可以直接操作内核态中的数据结构。

        当使用epoll机制时,首先通过epoll_create函数创建一个epoll实例,该实例会在内核态中创建一个事件表。然后,使用epoll_ctl函数向事件表中添加感兴趣的文件描述符和关注的事件类型。

        在添加文件描述符到事件表时,内核会直接引用用户程序传递的文件描述符,而不是将文件描述符数组从用户态拷贝到内核态。这样可以避免不必要的内存拷贝开销。

        当调用epoll_wait函数等待事件发生时,内核会阻塞等待,直到有事件发生或超时。一旦有事件就绪,内核会将就绪的事件信息直接填充到用户程序提供的事件数组中,而不需要再次进行内存拷贝。这样用户程序可以直接访问内核态中的事件信息。

        通过避免文件描述符数组的用户态到内核态的拷贝,epoll能够提高效率并减少系统调用的开销,特别是在大规模的并发连接处理中。这是epoll相比于传统的selectpoll机制更高效的一个重要原因。

redis是单线程模型之所以这么快的根本原因也是因为底层使用epoll函数实现了IO多路复用,将连接信息和事件放到队列中(通过IO多路复用),一次放到事件分配器,事件分配器将事件分发给事件处理器

文件事件分配器:

文件事件分派器是Redis事件驱动模型的核心组件。它使用I/O多路复用机制(如select、poll、epoll等)来监视文件描述符(包括网络套接字和文件描述符)的就绪状态。当一个文件描述符就绪时(如可读或可写),文件事件分派器会将就绪事件分派给相应的事件处理器进行处理。

事件处理器:

事件处理器是一组处理不同类型事件的回调函数。每种事件类型都有对应的事件处理器。当文件事件分派器将一个就绪事件分派给事件处理器时,相应的事件处理器会被调用来处理事件。事件处理器根据事件类型执行相应的逻辑,如处理客户端请求、读取或写入数据等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/899.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LayUI之CRUD(增删改查)

目录 一、前期准备 1.数据表格 2.弹出层 3.用户表数据查询 二、用户管理后台编写 三、前端JS编写 四、效果展示 一、前期准备 1.数据表格 在layui官网找到我们需要的数据表格 根据需求复制修改代码&#xff0c;再找到表单复制一个输入框和按钮&#xff0c;做一个搜索功能…

React Native 解决 TextInput 设置 maxLength 后,拼音输入法无法输入的问题

目前只发现在 iOS 上存在这个问题&#xff0c;这是由于 iOS 系统的问题&#xff0c;需要添加一个 OC 分类来解决&#xff0c;这里有一个封装好的 react-native-textinput-maxlength-fixed &#xff0c;直接安装即可。 npm install react-native-textinput-maxlength-fixed npx…

数字游民 digital nomad 常见问题解答

目录 什么是数字游民&#xff1f;数字游民是怎么产生的&#xff1f;如何向数字游民转型&#xff1f;常见的数字游民网络聚集地数字游民旅居城市推荐国际版柏林(德国)巴塞罗那(西班牙)布达佩斯(匈牙利)里斯本(葡萄牙)首尔/釜山(韩国) 国内版杭州成都三亚大连厦门 什么是数字游民…

数据仓库-拉链算法

数据仓库-拉链算法&#xff0c;如何处理开链、闭链数据 \timing on set client_encodingGBK; /******程序功能说明*********************************//*****以下根据任务不同进行变量设置*****************/SELECT:AUTO_PDMVIEW AS "PDM_VIEW",:20230710 AS &qu…

语言模型的自洽性思维链推理技术

论文标题&#xff1a;Self-Consistency Improves Chain of Thought Reasoning in Language Models 论文链接&#xff1a;https://arxiv.org/abs/2203.11171 论文来源&#xff1a;ICLR 2023 一、概述 尽管语言模型在一系列NLP任务中展现出了显著的成功&#xff0c;但它们在推理能…

QGIS批量将OSM水系进行可视化显示

要批量将OSM水系进行可视化显示&#xff0c;可以使用QGIS软件和Python语言来实现。以下是详细步骤&#xff1a; 1. 打开QGIS软件&#xff0c;导入OSM数据&#xff0c;可以使用插件OSMDownloader下载OSM数据&#xff0c;或者使用OSM数据下载网站下载数据。 2. 在QGIS中创建新图…

进制转换详解(解释原理简单易懂)

前言&#xff1a;在网上看了许多篇关于不同进制之间如何转换的文章&#xff0c;包括很多浏览量上万的博客。大多都只是把转换的规则罗列了出来&#xff0c;例如十进制转二进制&#xff0c;可能大家都知道方法&#xff0c;“除以2反向取余数&#xff0c;直到商为0”。应用该方法…

Kubespray v2.22.1 在线部署 kubernetes v1.26.5 集群

文章目录 1. 介绍2. 预备条件3. 配置 hostname4. yum5. 下载介质5.1 git 下载5.2 下载 kubespray v2.22.1 6. 编写 inventory.ini7. 配置互信8. 安装 ansible9. 关闭防火墙10. 安装 docker11. 配置内核参数12. 启动容器 kubespray13. 部署14. 配置连接集群 1. 介绍 kubespray​…

论文阅读—2023.7.13:遥感图像语义分割空间全局上下文信息网络(主要为unet网络以及改unet)附加个人理解与代码解析

前期看的文章大部分都是深度学习原理含量多一点&#xff0c;一直在纠结怎么改模型&#xff0c;论文看的很吃力&#xff0c;看一篇忘一篇&#xff0c;总感觉摸不到方向。想到自己是遥感专业&#xff0c;所以还是回归遥感影像去谈深度学习&#xff0c;回归问题&#xff0c;再想着…

5月更新,docsify综合漏洞知识库!

项目介绍 一个知识库&#xff0c;集成了Vulhub、Peiqi、EdgeSecurity、0sec、Wooyun等开源漏洞库&#xff0c;涵盖OA、CMS、开发框架、网络设备、开发语言、操作系统、Web应用、Web服务器、应用服务器等多种漏洞。 关注【Hack分享吧】公众号&#xff0c;回复关键字【230428】获…

C#设计模式之---观察者模式

观察者模式&#xff08;Observer Pattern&#xff09; 观察者模式&#xff08;Observer Pattern&#xff09;是一种对象行为模式。它定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。在观察者…

spring boot security自定义认证

前言 前置阅读 spring boot security快速使用示例 spring boot security之前后端分离配置 说明 实际场景&#xff0c;我们一般是把用户信息保存在db中&#xff08;也可能是调用三方接口&#xff09;&#xff0c;需要自定义用户信息加载或认证部分的逻辑&#xff0c;下面提供…

bug:file name too long文件名超出系统最大限制

各操作系统支持最长的文件和目录名称长度&#xff08;Linux、Win、Mac&#xff09; 今天开发需求的时候发现无法新建文件&#xff0c;提示file name too lang&#xff0c;于是翻阅和查询了一些资料&#xff0c;发现不同操作系统下文件名和目录名最长的长度不同。 操作系统文件名…

小程序主包超1.5MB分包处理流程优化方案

"subPackages": [// 分包1 {"root": "src, // 根目录"pages": [{"path": "views/business/index", // 页面路径"name": "business_index","aliasPath": "/business/index",&…

make/makefile的使用

make/makefile 文章目录 make/makefile初步认识makefile的工作流程依赖关系和依赖方法make的使用 总结 make是一个命令&#xff0c;是一个解释makefile中指令的命令工具&#xff0c;makefile是一个文件&#xff0c;当前目录下的文件&#xff0c;两者搭配使用&#xff0c;完成项…

快速上手dva

Dva 是一个基于 redux 和 redux-saga 的前端框架&#xff0c;它简化了 redux 和 redux-saga 的使用&#xff0c;并且内置了 react-router 和 fetch&#xff0c;所以也支持路由和异步操作。以下是一些基本的 Dva 使用方法&#xff1a; 快速上手 安装&#xff1a;首先&#xff…

异常参数处理:如何处理前端传递的非法参数,确保系统安全稳定

当前端将参数传递给后端时&#xff0c;考虑到各种异常情况&#xff0c;以下是对应的解决示例&#xff1a; 缺少必要的参数&#xff1a; 异常情况&#xff1a;前端未传递必要的参数&#xff0c;导致后端无法正常处理请求。 解决方案&#xff1a;在后端进行参数校验&#xff0c;如…

HarmonyOS学习路之方舟开发框架—学习ArkTS语言(基本语法 二)

自定义组件 创建自定义组件 在ArkUI中&#xff0c;UI显示的内容均为组件&#xff0c;由框架直接提供的称为系统组件&#xff0c;由开发者定义的称为自定义组件。在进行 UI 界面开发时&#xff0c;通常不是简单的将系统组件进行组合使用&#xff0c;而是需要考虑代码可复用性、…

从后验与模型推断视角深入剖析MATLAB中的变分贝叶斯蒙特卡罗(VBMC)算法:理论、代码示例与下载指南

在实践复杂的计算模型时&#xff0c;贝叶斯推断经常被用来进行参数估计和模型比较。其中&#xff0c;变分贝叶斯蒙特卡罗(VBMC)是一种高效的近似推理方法&#xff0c;能够在有限的预算下对具有潜在噪声似然评估的计算模型进行拟合和评估。这种方法并不只提供最优参数向量&#…

Python采集课堂视频教程, m3u8视频解密

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 环境使用: Python 3.8 解释器 Pycharm 编辑器 模块使用&#xff1a; requests >>> pip install requests pycryptodome --> pip install pycryptodome re 第三方模块安装方法&#xff1a; win R 输…