Ⅰ java Nio读写为什么是双向
作者:美团技术团队
链接:https://zhuanlan.hu.com/p/23488863
来源:知乎
着作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
NIO(Non-blocking I/O,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。
那么NIO的本质是什么样的呢?它是怎样与事件模型结合来解放线程、提高系统吞吐的呢?
本文会从传统的阻塞I/O和线程池模型面临的问题讲起,然后对比几种常见I/O模型,一步步分析NIO怎么利用事件模型处理I/O,解决线程池瓶颈处理海量连接,包括利用面向事件的方式编写服务端/客户端程序。最后延展到一些高级主题,如Reactor与Proactor模型的对比、Selector的唤醒、Buffer的选择等。
注:本文的代码都是伪代码,主要是为了示意,不可用于生产环境。
传统BIO模型分析
让我们先回忆一下传统的服务器端同步阻塞I/O处理(也就是BIO,Blocking I/O)的经典编程模型:
{
ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//线程池
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(8088);
while(!Thread.currentThread.isInturrupted()){//主线程死循环等待新连接到来
Socket socket = serverSocket.accept();
executor.submit(new ConnectIOnHandler(socket));//为新的连接创建新的线程
}
class ConnectIOnHandler extends Thread{
private Socket socket;
public ConnectIOnHandler(Socket socket){
this.socket = socket;
}
public void run(){
while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){死循环处理读写事件
String someThing = socket.read()....//读取数据
if(someThing!=null){
......//处理数据
socket.write()....//写数据
}
}
}
}
这是一个经典的每连接每线程的模型,之所以使用多线程,主要原因在于socket.accept()、socket.read()、socket.write()三个主要函数都是同步阻塞的,当一个连接在处理I/O的时候,系统是阻塞的,如果是单线程的话必然就挂死在那里;但CPU是被释放出来的,开启多线程,就可以让CPU去处理更多的事情。其实这也是所有使用多线程的本质:
利用多核。
当I/O阻塞系统,但CPU空闲的时候,可以利用多线程使用CPU资源。
现在的多线程一般都使用线程池,可以让线程的创建和回收成本相对较低。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的I/O并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。
不过,这个模型最本质的问题在于,严重依赖于线程。但线程是很"贵"的资源,主要表现在:
线程的创建和销毁成本很高,在linux这样的操作系统中,线程本质上就是一个进程。创建和销毁都是重量级的系统函数。
线程本身占用较大内存,像Java的线程栈,一般至少分配512K~1M的空间,如果系统中的线程数过千,恐怕整个JVM的内存都会被吃掉一半。
线程的切换成本是很高的。操作系统发生线程切换的时候,需要保留线程的上下文,然后执行系统调用。如果线程数过高,可能执行线程切换的时间甚至会大于线程执行的时间,这时候带来的表现往往是系统load偏高、CPU sy使用率特别高(超过20%以上),导致系统几乎陷入不可用的状态。
容易造成锯齿状的系统负载。因为系统负载是用活动线程数或CPU核心数,一旦线程数量高但外部网络环境不是很稳定,就很容易造成大量请求的结果同时返回,激活大量阻塞线程从而使系统负载压力过大。
所以,当面对十万甚至百万级连接的时候,传统的BIO模型是无能为力的。随着移动端应用的兴起和各种网络游戏的盛行,百万级长连接日趋普遍,此时,必然需要一种更高效的I/O处理模型。
NIO是怎么工作的
很多刚接触NIO的人,第一眼看到的就是Java相对晦涩的API,比如:Channel,Selector,Socket什么的;然后就是一坨上百行的代码来演示NIO的服务端Demo……瞬间头大有没有?
我们不管这些,抛开现象看本质,先分析下NIO是怎么工作的。
常见I/O模型对比
所有的系统I/O都分为两个阶段:等待就绪和操作。举例来说,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写。
需要说明的是等待就绪的阻塞是不使用CPU的,是在“空等”;而真正的读写操作的阻塞是使用CPU的,真正在"干活",而且这个过程非常快,属于memory ,带宽通常在1GB/s级别以上,可以理解为基本不耗时。
下图是几种常见I/O模型的对比:
密码:380p以上都是小编收集了大神的灵药,喜欢的拿走吧!喜欢小编就轻轻关注一下吧!
Ⅱ Java NIO和IO的区别
Java NIO和IO的主要区别如下:
1.NIO 的创建目的是为了让 Java 程序员可以实现高速 I/O 而无需编写自定义的本机代码。NIO 将最耗时的 I/O 操作(即填充和提取缓冲区)转移回操作系统,因而可以极大地提高速度。传统的IO操作属于阻塞型,严重影响程序的运行速度。
2,。流与块的比较。原来的 I/O 库(在 java.io.*中) 与 NIO 最重要的区别是数据打包和传输的方式。正如前面提到的,原来的 I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
面向流 的 I/O 系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的 I/O 通常相当慢。
3.一个 面向块 的 I/O 系统以块的形式处理数据。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。
Ⅲ offheap鏄鎸囧摢绉嶅唴瀛
1. offheap鏄鎸囧摢绉嶅唴瀛桡纻
offheap鏄鎸囩洿鎺ュ唴瀛桡纴涔熷氨鏄鍦ㄥ爢澶栧垎閰岖殑鍐呭瓨銆傚湪java涓锛屽爢鏄榛樿ょ殑鍐呭瓨鍒嗛厤鍖哄烟锛岃宱ffheap鍙浠ラ氲繃浣跨敤Unsafe绫绘垨钥匓yteBuffer绫荤瓑API鍦ㄥ爢澶栧垎閰嶅唴瀛樼┖闂淬
2. offheap镄勪紭镣瑰拰阃傜敤鍦烘櫙
鐩歌缉浜庡爢鍐呭瓨锛宱ffheap链変互涓嬩紭镣癸细
1. 镞燝C锛氩爢鍐呭瓨鐢变簬瑕佽繘琛屽瀮鍦惧洖鏀讹纴钥宱ffheap镞犻渶杩涜孏C锛屽洜姝ゅ彲浠ラ檷浣嶨C铡嫔姏锛屾彁鍗囩▼搴忓彲闱犳у拰鍝嶅簲镐ц兘锛
2. 镟撮珮镄勫唴瀛樻墿灞曟э细鍫嗗唴瀛樻墿瀹归渶瑕佽繘琛屽嶅埗鍙婅皟鏁达纴钥宱ffheap鍙浠ョ洿鎺ョ敌璇锋洿澶氱殑鐗╃悊鍐呭瓨锛
3. 镟翠绠镄勫唴瀛树娇鐢锛氱敱浜庢病链夊硅薄澶村拰纰庣墖绌洪棿锛宱ffheap鐩歌缉浜庡爢鍐呭瓨鍙浠ユ洿楂樻晥鍦颁娇鐢ㄥ唴瀛樸
offheap阃傜敤浜庨渶瑕佸垱寤哄ぇ閲忓硅薄鎴栬呴渶瑕侀珮镐ц兘镄勫簲鐢ㄥ満鏅锛屾瘆濡傛暟鎹缂揿瓨銆佹暟鎹搴撹繛鎺ユ睁绛夈
3. 浣跨敤ByteBuffer绫昏繘琛宱ffheap鍐呭瓨镎崭綔
ByteBuffer绫绘槸Java NIO涓镄勪竴涓鍏抽敭绫伙纴瀹冩彁渚涗简瀵筼ffheap鍐呭瓨镄勭洿鎺ユ搷浣溿备笅闱㈡槸涓涓浣跨敤ByteBuffer绫昏繘琛宱ffheap鍐呭瓨镎崭綔镄勭ず渚嬩唬镰侊细
```
// 鐢宠1GB镄刼ffheap鍐呭瓨绌洪棿
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024 * 1024);
// 阃氲繃putXXX鏂规硶寰offheap绌洪棿鍐椤叆鏁版嵁
buffer.putDouble(10.0);
buffer.putInt(100);
// 阃氲繃getXXX鏂规硶浠巓ffheap绌洪棿璇诲彇鏁版嵁
double d = buffer.getDouble(0);
int i = buffer.getInt(8);
```
4. 浣跨敤Unsafe绫昏繘琛宱ffheap鍐呭瓨镎崭綔
Unsafe绫绘槸JDK涓涓嶅叕寮镄勭被锛屼絾鍙浠ヤ娇鐢ㄥ弽灏勬潵銮峰彇鍏朵腑镄勬柟娉曘备笌ByteBuffer绫讳笉钖岋纴Unsafe绫诲彲浠ュ湪offheap绌洪棿涓婅繘琛屽师瀛愭搷浣灭瓑镎崭綔锛屽疄鐜版洿锷犵伒娲汇备笅闱㈡槸涓涓浣跨敤Unsafe绫昏繘琛宱ffheap鍐呭瓨镎崭綔镄勭ず渚嬩唬镰侊细
```
// 鐢宠1GB镄刼ffheap鍐呭瓨绌洪棿
Unsafe unsafe = getUnsafe();
long address = unsafe.allocateMemory(1024 * 1024 * 1024);
// 瀵筼ffheap绌洪棿杩涜屽啓鍏ユ暟鎹镎崭綔
unsafe.putDouble(address, 10.0);
unsafe.putInt(address + 8, 100);
// 瀵筼ffheap绌洪棿杩涜岃诲彇鏁版嵁镎崭綔
double d = unsafe.getDouble(address);
int i = unsafe.getInt(address + 8);
```
5. offheap镄勭己镣瑰拰闇瑕佹敞镒忕殑鍦版柟
1. 镞犳硶杩涜岀被鍨嬫镆ワ细鐢变簬offheap镄勫唴瀛樻槸鐩存帴镎崭綔瀛楄妭娴侊纴锲犳ら渶瑕佺▼搴忓憳镓嫔姩鎺у埗鍐呭瓨镄勫ぇ灏忓拰鏁版嵁绫诲瀷銆傚傚逛簬ByteBuffer绫伙纴濡傛灉鍐椤叆镄勬暟鎹绫诲瀷涓庤诲彇镄勬暟鎹绫诲瀷涓崭竴镊达纴浼氩艰嚧杩愯屾椂寮傚父锛
2. 鍐呭瓨鍒嗛厤鍜岄喷鏀鹃渶璋ㄦ厧锛氱敱浜巓ffheap鍦ㄥ爢澶栧垎閰嶅唴瀛桡纴锲犳ら渶瑕佹坠锷ㄩ喷鏀惧唴瀛桡纴骞朵笖闇瑕佽皑鎱庢带鍒跺唴瀛樼敌璇峰垎閰嶃傚傛灉棰戠箒鐢宠枫侀喷鏀惧唴瀛树细瀵艰嚧鍐呭瓨纰庣墖鍖栫殑闂棰桡纴闄崭绠绋嫔簭镄勬ц兘锛
3. 瀵笿VM镄勪緷璧栬缉楂桡细Unsafe鏄疛DK涓镄勪笉鍏寮绫伙纴浣跨敤镞堕渶瑕佸弽灏勮幏鍙栧叾涓镄勬柟娉曪纴钥屼笖鍦ㄤ笉钖岀殑JDK鐗堟湰涓嬩细链変笉钖岀殑瀹炵幇锛屽笿VM镄勪緷璧栬缉楂樸
6. offheap镄勫簲鐢ㄧず渚
1. 鏁版嵁搴撹繛鎺ユ睁锛氩彲浠ヤ娇鐢╫ffheap𨱒ュ瓨鍌–onnection瀵硅薄锛岄伩鍏嶉戠箒镄凣C镎崭綔鍜屽嶅埗锛屾彁鍗囱繛鎺ユ睁镄勬ц兘锛
2. 鍐呭瓨鏁版嵁搴揿拰缂揿瓨锛歰ffheap鍙浠ョ敤浜庣紦瀛树腑瀛桦偍澶ч噺鏁版嵁銆佺储寮旷瓑锛岄伩鍏嶉戠箒镄凣C镎崭綔锛屾彁鍗囩▼搴忕殑镐ц兘锛
3. 澶ц勬ā鏁版嵁鍒嗘瀽锛歰ffheap鍙浠ョ敤浜庡瓨鍌ㄥぇ瑙勬ā镄勬暟鎹闆嗭纴𨱒ユ彁鍗囨暟鎹鍒嗘瀽镄勬ц兘銆
7. 镐荤粨
offheap鏄鎸囩洿鎺ュ唴瀛桡纴鍦╦ava涓阃氲繃Buffer绫诲拰Unsafe绫荤瓑API鍙浠ュ湪鍫嗗栧垎閰嶅唴瀛樼┖闂达纴骞朵笖鐩歌缉浜庡爢鍐呭瓨链夋棤GC銆侀珮镓╁𪾢镐с佷绠鍐呭瓨浣跨敤绛変紭镣广傚湪浣跨敤offheap镞堕渶瑕佹敞镒忓唴瀛桦垎閰嶅拰閲婃斁銆佹棤娉曡繘琛岀被鍨嬫镆ョ瓑锛屽悓镞跺彲浠ュ簲鐢ㄤ簬鏁版嵁搴撹繛鎺ユ睁銆佸唴瀛樻暟鎹搴揿拰缂揿瓨銆佸ぇ瑙勬ā鏁版嵁鍒嗘瀽绛夐嗗烟锛屾彁鍗囩▼搴忕殑镐ц兘鍜屽彲闱犳с
Ⅳ Java中IO与NIO的区别和使用场景
在java2以前,传统的socket IO中,需要为每个连接创建一个线程,当并发的连接数量非常巨大时,线程所占用的栈内存和CPU线程切换的开销将非常巨大。java5以后使用NIO,不再需要为每个线程创建单独的线程,可以用一个含有限数量线程的线程池,甚至一个线程来为任意数量的连接服务。由于线程数量小于连接数量,所以每个线程进行IO操作时就不能阻塞,如果阻塞的话,有些连接就得不到处理,NIO提供了这种非阻塞的能力。
NIO 设计背后的基石:反应器模式,用于事件多路分离和分派的体系结构模式。
反应器(Reactor):用于事件多路分离和分派的体系结构模式
通常的,对一个文件描述符指定的文件或设备, 有两种工作方式: 阻塞 与非阻塞 。所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待 状态, 直到有东西可读或者可写为止。而对于非阻塞状态, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待 。
一种常用做法是:每建立一个Socket连接时,同时创建一个新线程对该Socket进行单独通信(采用阻塞的方式通信)。这种方式具有很高的响应速度,并且控制起来也很简单,在连接数较少的时候非常有效,但是如果对每一个连接都产生一个线程的无疑是对系统资源的一种浪费,如果连接数较多将会出现资源不足的情况。
另一种较高效的做法是:服务器端保存一个Socket连接列表,然后对这个列表进行轮询,如果发现某个Socket端口上有数据可读时(读就绪),则调用该socket连接的相应读操作;如果发现某个 Socket端口上有数据可写时(写就绪),则调用该socket连接的相应写操作;如果某个端口的Socket连接已经中断,则调用相应的析构方法关闭该端口。这样能充分利用服务器资源,效率得到了很大提高。
传统的阻塞式IO,每个连接必须要开一个线程来处理,并且没处理完线程不能退出。
非阻塞式IO,由于基于反应器模式,用于事件多路分离和分派的体系结构模式,所以可以利用线程池来处理。事件来了就处理,处理完了就把线程归还。而传统阻塞方式不能使用线程池来处理,假设当前有10000个连接,非阻塞方式可能用1000个线程的线程池就搞定了,而传统阻塞方式就需要开10000个来处理。如果连接数较多将会出现资源不足的情况。非阻塞的核心优势就在这里。
为什么会这样,下面就对他们做进一步细致具体的分析:
首先,我们来分析传统阻塞式IO的瓶颈在哪里。在连接数不多的情况下,传统IO编写容易方便使用。但是随着连接数的增多,问题传统IO就不行了。因为前面说过,传统IO处理每个连接都要消耗一个线程,而程序的效率当线程数不多时是随着线程数的增加而增加,但是到一定的数量之后,是随着线程数的增加而减少。这里我们得出结论,传统阻塞式IO的瓶颈在于不能处理过多的连接。
然后,非阻塞式IO的出现的目的就是为了解决这个瓶颈。而非阻塞式IO是怎么实现的呢?非阻塞IO处理连接的线程数和连接数没有联系,也就是说处理 10000个连接非阻塞IO不需要10000个线程,你可以用1000个也可以用2000个线程来处理。因为非阻塞IO处理连接是异步的。当某个链接发送请求到服务器,服务器把这个连接请求当作一个请求"事件",并把这个"事件"分配给相应的函数处理。我们可以把这个处理函数放到线程中去执行,执行完就把线程归还。这样一个线程就可以异步的处理多个事件。而阻塞式IO的线程的大部分时间都浪费在等待请求上了。
所谓阻塞式IO流,就是指在从数据流当中读写数据的的时候,阻塞当前线程,直到IO流可以
重新使用为止,你也可以使用流的avaliableBytes()函数看看当前流当中有多少字节可以读取,这样
就不会再阻塞了。
Ⅳ 读取大量数据时数据时内存溢出怎样分批读取该怎么处理
众所周知,java在处理数据量比较大的时候,加载到内存必然会导致内存溢出,而在一些数据处理中我们不得不去处理海量数据,在做数据处理中,我们常见的手段是分解,压缩,并行,临时文件等方法;例如,我们要将数据库(不论是什么数据库)的数据导出到一个文件,一般是Excel或文本格式的CSV;对于Excel来讲,对于POI和JXL的接口,你很多时候没有法去控制内存什么时候向磁盘写入,很恶心,而且这些API在内存构造的对象大小将比数据原有的大小要大很多倍数,所以你不得不去拆分Excel,还好,POI开始意识到这个问题,在3.8.4的版本后,开始提供cache的行数,提供了SXSSFWorkbook的接口,可以设置在内存中的行数,不过可惜的是,他当你超过这个行数,每添加一行,它就将相对行数前面的一行写入磁盘(如你设置2000行的话,当你写第20001行的时候,他会将第一行写入磁盘),其实这个时候他些的临时文件,以至于不消耗内存,不过这样你会发现,刷磁盘的频率会非常高,我们的确不想这样,因为我们想让他达到一个范围一次性将数据刷如磁盘,比如一次刷1M之类的做法,可惜现在还没有这种API,很痛苦,我自己做过测试,通过写小的Excel比使用目前提供刷磁盘的API来写大文件,效率要高一些,而且这样如果访问的人稍微多一些磁盘IO可能会扛不住,因为IO资源是非常有限的,所以还是拆文件才是上策;而当我们写CSV,也就是文本类型的文件,我们很多时候是可以自己控制的,不过你不要用CSV自己提供的API,也是不太可控的,CSV本身就是文本文件,你按照文本格式写入即可被CSV识别出来;如何写入呢?下面来说说。。。在处理数据层面,如从数据库中读取数据,生成本地文件,写代码为了方便,我们未必要1M怎么来处理,这个交给底层的驱动程序去拆分,对于我们的程序来讲我们认为它是连续写即可;我们比如想将一个1000W数据的数据库表,导出到文件;此时,你要么进行分页,oracle当然用三层包装即可,mysql用limit,不过分页每次都会新的查询,而且随着翻页,会越来越慢,其实我们想拿到一个句柄,然后向下游动,编译一部分数据(如10000行)将写文件一次(写文件细节不多说了,这个是最基本的),需要注意的时候每次buffer的数据,在用outputstream写入的时候,最好flush一下,将缓冲区清空下;接下来,执行一个没有where条件的SQL,会不会将内存撑爆?是的,这个问题我们值得去思考下,通过API发现可以对SQL进行一些操作,例如,通过:PreparedStatementstatement=connection.prepareStatement(sql),这是默认得到的预编译,还可以通过设置:PreparedStatementstatement=connection.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);来设置游标的方式,以至于游标不是将数据直接cache到本地内存,然后通过设置statement.setFetchSize(200);设置游标每次遍历的大小;OK,这个其实我用过,oracle用了和没用没区别,因为oracle的jdbcAPI默认就是不会将数据cache到java的内存中的,而mysql里头设置根本无效,我上面说了一堆废话,呵呵,我只是想说,java提供的标准API也未必有效,很多时候要看厂商的实现机制,还有这个设置是很多网上说有效的,但是这纯属抄袭;对于oracle上面说了不用关心,他本身就不是cache到内存,所以java内存不会导致什么问题,如果是mysql,首先必须使用5以上的版本,然后在连接参数上加上useCursorFetch=true这个参数,至于游标大小可以通过连接参数上加上:defaultFetchSize=1000来设置,例如:jdbc:mysql://xxx.xxx.xxx.xxx:3306/abc?zeroDateTimeconvertToNull&useCursorFetch=true&defaultFetchSize=1000上次被这个问题纠结了很久(mysql的数据老导致程序内存膨胀,并行2个直接系统就宕了),还去看了很多源码才发现奇迹竟然在这里,最后经过mysql文档的确认,然后进行测试,并行多个,而且数据量都是500W以上的,都不会导致内存膨胀,GC一切正常,这个问题终于完结了。我们再聊聊其他的,数据拆分和合并,当数据文件多的时候我们想合并,当文件太大想要拆分,合并和拆分的过程也会遇到类似的问题,还好,这个在我们可控制的范围内,如果文件中的数据最终是可以组织的,那么在拆分和合并的时候,此时就不要按照数据逻辑行数来做了,因为行数最终你需要解释数据本身来判定,但是只是做拆分是没有必要的,你需要的是做二进制处理,在这个二进制处理过程,你要注意了,和平时read文件不要使用一样的方式,平时大多对一个文件读取只是用一次read操作,如果对于大文件内存肯定直接挂掉了,不用多说,你此时因该每次读取一个可控范围的数据,read方法提供了重载的offset和length的范围,这个在循环过程中自己可以计算出来,写入大文件和上面一样,不要读取到一定程序就要通过写入流flush到磁盘;其实对于小数据量的处理在现代的NIO技术的中也有用到,例如多个终端同时请求一个大文件下载,例如视频下载吧,在常规的情况下,如果用java的容器来处理,一般会发生两种情况:其一为内存溢出,因为每个请求都要加载一个文件大小的内存甚至于,因为java包装的时候会产生很多其他的内存开销,如果使用二进制会产生得少一些,而且在经过输入输出流的过程中还会经历几次内存拷贝,当然如果有你类似nginx之类的中间件,那么你可以通过send_file模式发送出去,但是如果你要用程序来处理的时候,内存除非你足够大,但是java内存再大也会有GC的时候,如果你内存真的很大,GC的时候死定了,当然这个地方也可以考虑自己通过直接内存的调用和释放来实现,不过要求剩余的物理内存也足够大才行,那么足够大是多大呢?这个不好说,要看文件本身的大小和访问的频率;其二为假如内存足够大,无限制大,那么此时的限制就是线程,传统的IO模型是线程是一个请求一个线程,这个线程从主线程从线程池中分配后,就开始工作,经过你的Context包装、Filter、拦截器、业务代码各个层次和业务逻辑、访问数据库、访问文件、渲染结果等等,其实整个过程线程都是被挂住的,所以这部分资源非常有限,而且如果是大文件操作是属于IO密集型的操作,大量的CPU时间是空余的,方法最直接当然是增加线程数来控制,当然内存足够大也有足够的空间来申请线程池,不过一般来讲一个进程的线程池一般会受到限制也不建议太多的,而在有限的系统资源下,要提高性能,我们开始有了newIO技术,也就是NIO技术,新版的里面又有了AIO技术,NIO只能算是异步IO,但是在中间读写过程仍然是阻塞的(也就是在真正的读写过程,但是不会去关心中途的响应),还未做到真正的异步IO,在监听connect的时候他是不需要很多线程参与的,有单独的线程去处理,连接也又传统的socket变成了selector,对于不需要进行数据处理的是无需分配线程处理的;而AIO通过了一种所谓的回调注册来完成,当然还需要OS的支持,当会掉的时候会去分配线程,目前还不是很成熟,性能最多和NIO吃平,不过随着技术发展,AIO必然会超越NIO,目前谷歌V8虚拟机引擎所驱动的node.js就是类似的模式,有关这种技术不是本文的说明重点;将上面两者结合起来就是要解决大文件,还要并行度,最土的方法是将文件每次请求的大小降低到一定程度,如8K(这个大小是经过测试后网络传输较为适宜的大小,本地读取文件并不需要这么小),如果再做深入一些,可以做一定程度的cache,将多个请求的一样的文件,cache在内存或分布式缓存中,你不用将整个文件cache在内存中,将近期使用的cache几秒左右即可,或你可以采用一些热点的算法来配合;类似迅雷下载的断点传送中(不过迅雷的网络协议不太一样),它在处理下载数据的时候未必是连续的,只要最终能合并即可,在服务器端可以反过来,谁正好需要这块的数据,就给它就可以;才用NIO后,可以支持很大的连接和并发,本地通过NIO做socket连接测试,100个终端同时请求一个线程的服务器,正常的WEB应用是第一个文件没有发送完成,第二个请求要么等待,要么超时,要么直接拒绝得不到连接,改成NIO后此时100个请求都能连接上服务器端,服务端只需要1个线程来处理数据就可以,将很多数据传递给这些连接请求资源,每次读取一部分数据传递出去,不过可以计算的是,在总体长连接传输过程中总体效率并不会提升,只是相对相应和所开销的内存得到量化控制,这就是技术的魅力,也许不要太多的算法,不过你得懂他。类似的数据处理还有很多,有些时候还会将就效率问题,比如在HBase的文件拆分和合并过程中,要不影响线上业务是比较难的事情,很多问题值得我们去研究场景,因为不同的场景有不同的方法去解决,但是大同小异,明白思想和方法,明白内存和体系架构,明白你所面临的是沈阳的场景,只是细节上改变可以带来惊人的效果。
Ⅵ java Netty NIO 如何突破 65536 个端口的限制如何做到10万~50万的长连接
通常情况下衡陵是不可以突破的,端口有限制.单独对外提供请求的服务不用考虑尘册端口数量问题,监听某一个端口即可.但咐兄戚是向提供代理服务器,就不得不考虑端口数量受限问题了.当前的1M并发连接测试,也需要在客户端突破6万可用端口的限制.端口为16进制,那么2的16次方值为65536,在linux系统里面,1024以下端口都是超级管理员用户(如root)才可以使用,普通用户只能使用大于1024的端口值.
服务器是只监听一个端口,所有的客户端连接,都是连接到服务器的同一个端口上的。也就是说服务器只是用了一个端口。就比如Http服务器。默认只用了80端口。
nio 在linux上使用的是epoll ,epoll支持在一个进程中打开的FD是操作系统最大文件句柄数,而不是你所说的16位short表示的文件句柄。 而 select模型 单进程打开的FD是受限的 select模型默认FD是1024 。操作系统最大文件句柄数跟内存有关,1GB内存的机器上,大概是10万个句柄左右。