如何解决高并发中的I/O瓶颈?

news2025/1/20 12:14:50

我们都知道,在当前的大数据时代背景下,I/O的速度比内存要慢,尤其是性能问题与I/O相关的问题更加突出。

在许多应用场景中,I/O读写操作已经成为系统性能的一个重要瓶颈,这是不能忽视的。

什么是I/O?

I/O作为机器获取和交换信息的主要渠道,流是执行I/O操作的主要方法。

在计算机中,流表示信息的传输。流保持顺序,因此针对特定的机器或应用程序,我们通常将从外部获得的信息称为输入流(InputStream),将从机器或应用程序发送出去的信息称为输出流(OutputStream)。

它们一起被称为输入/输出流(I/O流)。

当机器或程序交换信息或数据时,它们通常首先将对象或数据转换为一种特定形式的流。

然后,通过流的传输,数据到达指定的机器或程序。在目标位置,流被转换回对象数据。

因此,流可以被视为一种携带数据的手段,促进数据的交换和传输。

Java的I/O操作类位于java.io包中。其中,InputStreamOutputStreamReaderWriter类是I/O包中的四个基本类。

它们分别处理字节流和字符流。下面的图表说明了这一点:

+-------------+  
|   InputStream   |  
+------+------+
^  
|  
+---------+---------+
|       FileInputStream     |
+-----------------------+


+-------------+  
|   OutputStream  |  
+------+------+
^  
|  
+---------+---------+
|     FileOutputStream   |
+-----------------------+


+-------------+  
|       Reader        |  
+------+------+
^  
|  
+----------+---------+
|     FileReader         |
+-----------------------+


+-------------+  
|       Writer         |  
+------+------+
^  
|  
+----------+---------+
|    FileWriter         |
+-----------------------+

无论是文件读写还是网络传输/接收,信息的最小存储单元始终是字节。那么为什么I/O流操作被分类为字节流操作和字符流操作呢?

我们知道,将字符转换为字节需要编码,而这个过程可能是耗时的。

如果我们不知道编码类型,很容易遇到字符乱码等问题。因此,I/O流提供了与字符直接工作的接口,使我们在日常工作中可以方便地进行字符流操作。

字节流。

InputStreamOutputStream是字节流的抽象类,这两个抽象类派生出了几个子类,每个子类都设计用于不同类型的操作。

根据具体要求,您可以选择不同的子类来实现相应的功能。

•如果需要执行文件读写操作,可以使用FileInputStreamFileOutputStream。它们适用于从文件读取数据和将数据写入文件。•如果要使用数组进行读写操作,可以使用ByteArrayInputStreamByteArrayOutputStream。这些类允许您将数据读取和写入字节数组。•如果要进行常规字符串读写操作,并希望引入缓冲以提高性能,可以使用BufferedInputStreamBufferedOutputStream。这些类在读写过程中引入了缓冲区,有效地减少了实际的I/O操作次数,从而提高了效率。

字符流。

ReaderWriter是字符流的抽象类,这两个抽象类也派生出了几个子类,每个子类都设计用于不同类型的操作。具体细节如下图所示:

+---------+  
|   Reader    |  
+------+------+
^  
|  
+---------+---------+
|   InputStreamReader   |
+-----------------------+
|      FileReader          |
+-----------------------+
|      CharArrayReader   |
+-----------------------+


+---------+  
|    Writer    |  
+------+------+
^  
|  
+---------+---------+
|   OutputStreamWriter   |
+-----------------------+
|      FileWriter          |
+-----------------------+
|      CharArrayWriter   |
+-----------------------+

I/O性能问题。

我们知道,I/O操作可以分为磁盘I/O操作和网络I/O操作。

前者涉及将数据从磁盘源读取到内存中,然后将读取的信息持久化到物理磁盘中。

后者涉及将网络中的信息获取到内存中,最终将信息传输回网络。

然而,无论是磁盘I/O还是网络I/O,在传统I/O系统中都会遇到显着的性能问题。

# 1. 多次内存复制。

在传统I/O中,我们可以使用InputStream从源读取数据,并将数据流输入到缓冲区中。然后,我们可以使用OutputStream将数据输出到外部设备,包括磁盘和网络。

在继续之前,您可以查看操作系统中输入操作的具体过程,如下图所示:

9501e1f7ff7d9733e2d07c7faaaff617.png

 

•JVM发起read()系统调用,并向内核发送读取请求。•内核向硬件发送读取命令,等待数据准备好。•内核将数据复制到自己的缓冲区中。•操作系统

的内核将数据复制到用户空间缓冲区中,然后read()系统调用返回。

在此过程中,数据首先从外部设备复制到内核空间,然后从内核空间复制到用户空间。

这导致了两次内存复制操作。这些操作导致不必要的数据复制和上下文切换,最终降低了I/O的性能。

# 2. 阻塞。

在传统I/O中,InputStreamread()操作通常是使用while循环实现的。它持续等待数据准备好后才返回。

这意味着如果没有准备好的数据,读取操作将一直等待,导致用户线程被阻塞。

在连接请求较少的情况下,这种方法效果良好,提供快速的响应时间。

然而,在处理大量连接请求时,创建大量的监听线程变得必要。在这种情况下,如果线程等待未准备好的数据,它将被阻塞并进入等待状态。

一旦线程被阻塞,它们将不断争夺CPU资源,导致频繁的CPU上下文切换。这种情况增加了系统的性能开销。

这就是为什么在具有高并发需求的场景中,由于线程管理和上下文切换的高成本,传统的阻塞式I/O可能变得效率低下的原因。

通常使用异步编程和非阻塞I/O技术来缓解这些问题,并提高系统效率。

如何优化I/O操作?

# 1. 使用缓冲。

使用缓冲是优化读写流操作的有效方法,减少频繁的磁盘或网络访问,从而提高性能。以下是使用缓冲来优化读写流操作的一些方法:

使用缓冲流:Java提供了类似BufferedReaderBufferedWriter的类,可以包装其他输入和输出流,在读写操作期间引入缓冲机制。这允许批量读取或写入数据,减少了实际I/O操作的频率。•指定缓冲区大小:在创建缓冲流时,您可以指定缓冲区的大小。根据数据量和性能要求选择适当的缓冲区大小,可以优化读写操作。•使用java.nio:Java NIO(新I/O)库提供了更灵活和高效的缓冲管理。通过使用诸如ByteBuffer之类的缓冲类,您可以更好地管理内存和数据。•一次性读取或写入多个项:通过使用适当的API,您可以一次性读取或写入多个数据项,减少I/O操作次数。•合并操作:如果需要执行连续的读取或写入操作,请考虑将它们合并为更大的操作,以减少系统调用的开销。•及时刷新:对于输出流,及时调用flush()方法可以确保数据立即写入目标,而不仅仅停留在缓冲区中。•使用try-with-resources:在Java 7及更高版本中,使用try-with-resources可以确保在操作完成后自动关闭流并释放资源,避免资源泄漏。

以下是使用缓冲进行文件读写的示例代码片段:

try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
     BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {


    String line;
    while ((line = reader.readLine()) != null) {
        // 处理行
        writer.write(line);
        writer.newLine(); // 添加新行
    }


} catch (IOException e) {
    e.printStackTrace();
}

# 2. 使用DirectBuffer减少内存复制。

使用DirectBuffer是一种减少I/O操作中内存复制的技术,特别是在Java NIO(新I/O)的上下文中。

DirectBuffer允许您直接使用非堆内存,这可以导致Java和本地代码之间更有效的数据传输。

在涉及大量数据的I/O操作中,这可能特别有益。

以下是如何使用DirectBuffer减少内存复制的方法:

1.分配DirectBuffer:不要使用传统的Java堆基数组,而是使用诸如ByteBuffer.allocateDirect()之类的类从本地内存中分配DirectBuffer。2.包装现有缓冲区:您还可以使用ByteBuffer.wrap()来包装现有的本地内存缓冲区,只需指定本地内存地址。3.与通道I/O一起使用:当使用NIO通道(FileChannelSocketChannel等)时,可以直接将数据读入DirectBuffer或直接从DirectBuffer写入数据,无需额外的复制。4.与JNI一起使用:如果通过Java本地接口(JNI)与本机代码一起工作,使用DirectBuffer可以使您的本机代码直接访问和操作数据,而无需昂贵的内存复制。5.注意内存释放:请记住,当您使用完DirectBuffer时,需要显式地释放直接内存,以防止内存泄漏。调用DirectBuffer上的cleaner()方法以释放关联的本地内存。

以下是在ByteBuffer中使用DirectBuffer以进行高效I/O的简化示例:

try (FileChannel channel = FileChannel.open(Paths.get("data.bin"), StandardOpenOption.READ)) {
    int bufferSize = 4096; // 根据需要调整
    ByteBuffer directBuffer = ByteBuffer.allocateDirect(bufferSize);






 int bytesRead;
    while ((bytesRead = channel.read(directBuffer)) != -1) {
        directBuffer.flip(); // 准备读取
        // 在直接缓冲区中处理数据
        // ...


        directBuffer.clear(); // 准备下一次读取
    }


} catch (IOException e) {
    e.printStackTrace();
}

# 3. 避免阻塞并优化I/O操作。

避免阻塞并优化I/O操作是提高系统性能和响应性的关键。以下是实现这些目标的一些方法:

1.使用非阻塞I/O:采用非阻塞I/O技术,如Java NIO,允许程序在等待数据准备就绪时继续执行其他任务。这可以通过选择器实现,它使单个线程能够处理多个通道。2.利用异步I/O:异步I/O允许程序提交I/O操作并在完成时得到通知。Java NIO2(Java 7+)提供了异步I/O的支持。这减少了线程阻塞,并使其他任务能够在等待I/O完成时执行。3.使用线程池:有效地利用线程池管理线程资源,避免为每个连接创建新线程。这减少了线程创建和销毁的开销。4.利用事件驱动模型:利用诸如Reactor、Netty等事件驱动框架可以有效地管理连接和I/O事件,实现高效的非阻塞I/O。5.分离CPU密集型和I/O操作:将CPU密集型任务与I/O操作分开,以防止I/O阻塞CPU。可以使用多线程或多进程进行分离。6.批量处理:将多个小的I/O操作合并为一个更大的批量操作,减少单独操作的开销,提高效率。7.使用缓冲区:使用缓冲区减少频繁的磁盘或网络访问,提高性能。这适用于文件I/O和网络I/O。8.定期维护和优化:定期监控和优化磁盘、网络和数据库等资源,以确保它们保持良好的性能。9.使用专门的框架:选择适当的框架,如NettyVert.x等,这些框架具有高效的非阻塞和异步I/O功能。

根据您的应用场景和要求,您可以实现其中一个或多个方法,以避免阻塞,优化I/O操作,并增强系统性能和响应性。

# 4. 通道。

正如前面所讨论的,传统的I/O最初依赖于InputStreamOutputStream操作流,这些流按字节为单位工作。

在高并发和大数据的情况下,这种方法很容易导致阻塞,从而导致性能下降。

此外,从用户空间复制输出数据到内核空间,然后再复制到输出设备,增加了系统性能开销。

为了解决性能问题,传统的I/O后来引入了缓冲作为缓解阻塞的手段。

它使用缓冲块作为最小单元。然而,即使使用缓冲,整体性能仍然不够理想。

然后出现了NIO(新I/O),它基于缓冲块单元操作。

在缓冲的基础上,它引入了两个组件:“通道”和“选择器”。这些补充使得非阻塞I/O操作成为可能。

NIO非常适合具有大量I/O连接请求的情况。这三个组件共同增强了I/O的整体性能。

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

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

相关文章

基于RabbitMQ的模拟消息队列之四——内存管理

文章目录 一、设计数据结构二、管理集合1.交换机2.队列3.绑定4.消息5.队列上的消息6.待确认消息7.恢复数据 一、设计数据结构 针对交换机、队列、绑定、消息、待确认消息设计数据结构。 交换机集合 exchangeMap 数据结构:ConcurrentHashMap key:交换机name value:交…

视频剪辑高手揭秘:如何巧妙改变尺寸,打造完美画面

视频剪辑高手揭秘:如何巧妙改变尺寸,打造完美画面 在数字媒体时代,视频剪辑已经成为一项至关重要的技能。不仅在专业电影制作领域,也在个人创作和社交媒体传播中发挥着重要作用。本文将向你介绍一位视频剪辑高手,并揭…

NIO原理浅析(二)

IO分类 阻塞和非阻塞 阻塞IO:用户空间引发内核空间的系统调用,需要内核IO操作彻底完成之后,返回值才会返回到用户空间,执行用户的操作。阻塞指的用户空间程序的执行状态,用户空间程序需要等到IO操作彻底执行完毕。j…

《关键跨越:从业务高手到优秀主管》:最大化团队产出

作者:北森人才管理研究院 阅读时长:6小时21分钟 评分:5星 失控最鲜明的特征之一是管理者工作的时间越来越长,但结果越来越糟。很多新手管理者看到下属无法完成任务,或者担心出错,对下属不放心,出…

Linux学习之RAID删除

参考《Linux软件raid删除》 我部署 RAID的步骤在《Linux学习之RAID》 sudo umount /dev/md0先进行卸载。 sudo mdadm -S /dev/md0停止/dev/md0。 sudo mdadm -A -s /dev/md0可以重新开始/dev/md0,这里只是拓展一下。 sudo mdadm -S /dev/md0停止/dev/md0。 s…

Cesium 加载 geojson 文件并对文件中的属性值进行颜色设置

文章目录 需求分析解决 需求 Cesium 加载 geojson 文件并对文件中的属性值进行颜色设置 分析 在搜寻多种解决方案后,最后总结出 自己的解决方案 方案一,没看懂 var geojsonOptions {clampToGround : true //使数据贴地};var entities;promise Cesium…

详解产品项目管理软件:介绍与比较

产品项目管理是指通过有效的规划、组织和控制来管理产品开发过程的一系列活动。它涵盖了需求分析、产品设计、开发、测试以及上市等不同阶段,并需要协调多个团队成员的工作。通过产品项目管理,团队可以更好地把握产品的战略目标、工作进度和资源分配&…

Oracle-day6:over()函数

目录 一、over()开窗函数 二、无参over()的使用 三、over(partition by 列名) 四、over(order by 列名 asc/desc) 五、over(partition by 列名 order by 列名 asc|desc) 六、练习(笔试) 一、over()开窗函数 拓展:数据库的版本 oracle:8i 9i 10g …

ICCV 2023 | 小鹏汽车纽约石溪:局部上下文感知主动域自适应LADA

摘要 主动域自适应(ADA)通过查询少量选定的目标域样本的标签,以帮助模型从源域迁移到目标域。查询数据的局部上下文信息非常重要,特别是在域间差异较大的情况下,然而现有的ADA方法尚未充分探索这一点。在本文中&#…

六、事务-4.并发事务问题

一、脏读 事务A执行3个操作,第1个操作执行select语句,第2个操作执行update语句。 注意:事务没有执行完成的时候,事务是没有提交的。只有事务的3个操作完成之后,事务才会提交。 但事务A中第2个操作,会把表…

改进YOLOv8系列:原创改进创新点 SIoU-NMS,EIoU-NMS,DIoU-NMS,CIoU-NMS,GIoU-NMS改进

💡该教程为属于《芒果书》📚系列,包含大量的原创首发改进方式, 所有文章都是全网首发原创改进内容🚀 💡本篇文章为YOLOv8独家原创改进:原创改进创新点 DIoU-NMS,SIoU-NMS,EIoU-NMS,CIoU-NMS,GIoU-NMS改进。 💡对自己数据集改进有效的话,可以直接当做自己的原创改…

机器学习笔记之最优化理论与方法(二)凸集的简单认识(上)

机器学习笔记之最优化理论与方法——凸集的简单认识[上] 引言凸优化问题与凸集合凸函数的关系凸优化问题简单示例凸集的简单示例 基本定义:凸集关于凸集性质的等价条件,凸组合,凸包常见凸集 引言 本节将介绍关于凸集的基本信息,包…

【Java】基础入门 (十六)--- 异常

1.异常 1.1 异常概述 异常是指程序在运行过程中出现的非正常的情况,如用户输入错误、除数为零、文件不存在、数组下标越界等。由于异常情况再程序运行过程中是难以避免的,一个良好的应用程序除了满足基本功能要求外,还应具备预见并处理可能发…

青翼科技基于VITA57.1的16路数据收发处理平台产品手册

FMC211是一款基于VITA57.1标准规范的实现16路LVDS数据采集、1路光纤数据收发处理FMC子卡模块。 该板卡支持2路CVBS(复合视频)视频输入,能够自动检测标准的模拟基带电视信号,并将其转变为8位ITU-R.656接口信号或者4:2:2分量视频信…

Qt网络通信——获取本机网络信息

查询一个主机的MAC地址或者IP地址是网络应用中常用到的功能&#xff0c;Qt提供了QHostInfo和QNetworkInterface 类可以用于此类信息的查询 1.QHostInfo 类&#xff08;显示和查找本地的信息&#xff09;是的主要函数 类别 函数原型作用公共函数QList <QHostAdress> addr…

读<一例 Go 编译器代码优化 bug 定位和修复解析>

看到一例 Go 编译器代码优化 bug 定位和修复解析[1]这样一篇文章,感觉有些意思. 在此复现和记录 在Go 1.16版本下,是没有这个bug[2]的(已修复). 参照gvm:灵活的Go版本管理工具[3] 将Go版本切至有问题的1.13.5(或1.14.6) ➜ go versiongo version go1.13.5 darwin/amd64 packag…

Node常用内置模块之url模块和querystring模块

1、URL类 url模块在v16的nodejs中已经明确被废弃&#xff0c;在将来的升级node中&#xff0c;可能被不支持。 官网建议在废弃url、querystring模块后&#xff0c;采用URL类去替代。 图示 URL 各部分 旧版的url模块 作用&#xff1a;url 模块是用于处理和解析 URL 的模块&…

Unity ShaderGraph教程——基础shader

1.基本贴图shader&#xff1a; 基础贴图实现&#xff1a;主贴图、自发光贴图、光滑度贴图、自发光贴图&#xff08;自发光还加入了颜色影响和按 钮开关&#xff09;. 步骤&#xff1a;最左侧操作组——新建texture2D——新建sample texture 2D承…

Linux上部署zentao禅道18.6版本

1. cd /opt 2. 下载 ZenTaoPMS-18.6-zbox_amd64.tar.gz wget https://dl.cnezsoft.com/zentao/18.6/ZenTaoPMS-18.6-zbox_amd64.tar.gz 3. 解压 tar -zxvf ZenTaoPMS-18.6-zbox_amd64.tar.gz 4. 解压成功, 可以看到多了个zbox文件 5. cd zbox/ 进入该目录 6. 修改apache默认…

乙酰基六肽-18——刺激脂肪合成,增加指定部位脂肪,塑造完美曲线

简介 乙酰六肽-18&#xff08;丰胸肽&#xff09;为一种乙酰化的六肽&#xff0c;可显著刺激使用部位脂肪合成&#xff0c;增大胸部或脸颊的体积&#xff0c;塑造完美身材。 INCI 名称 乙酰六肽-18 分子式 C30H54N9O10 分子量 700.32 CAS号 1400634-44-7…