网络与IO知识扫盲(四):C10K问题、BIO的弊端与NIO的引入
C10K 問(wèn)題
C10K 問(wèn)題: http://www.kegel.com/c10k.html
我們使用BIO的時(shí)候,來(lái)一個(gè)連接就拋出一個(gè)線程。被拋出的獨(dú)立的線程進(jìn)行阻塞,等待接收已連接的client發(fā)來(lái)的數(shù)據(jù),這樣不會(huì)影響其他client繼續(xù)連接。每個(gè)線程自己忙自己的。
但是隨著連接數(shù)的變大,拋出的線程越多,由于線程之間的切換,系統(tǒng)的性能會(huì)越來(lái)越低。
舉一個(gè)例子
一個(gè)客戶端可以通過(guò)2個(gè)不同的ip,與服務(wù)端創(chuàng)建2*65000個(gè)連接。
C10Kclient.java
package com.bjmashibing.system.io;import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SocketChannel; import java.util.LinkedList;public class C10Kclient {public static void main(String[] args) {LinkedList<SocketChannel> clients = new LinkedList<>();InetSocketAddress serverAddr = new InetSocketAddress("192.168.150.11", 9090);for (int i = 10000; i < 65000; i++) { // 一個(gè)客戶端可以通過(guò)2個(gè)不同的ip,與服務(wù)端創(chuàng)建2*65000個(gè)連接try {SocketChannel client1 = SocketChannel.open();SocketChannel client2 = SocketChannel.open();/*linux中你看到的連接就是:client...port: 10508client...port: 10508*/client1.bind(new InetSocketAddress("192.168.150.1", i)); // 這臺(tái)機(jī)器上的第一個(gè)ip// 192.168.150.1:10000 192.168.150.11:9090client1.connect(serverAddr);boolean c1 = client1.isOpen();clients.add(client1);client2.bind(new InetSocketAddress("192.168.110.100", i)); // 這臺(tái)機(jī)器上的第二個(gè)ip// 192.168.110.100:10000 192.168.150.11:9090client2.connect(serverAddr);boolean c2 = client2.isOpen();clients.add(client2);} catch (IOException e) {e.printStackTrace();}}System.out.println("clients " + clients.size());try {System.in.read();} catch (IOException e) {e.printStackTrace();}} }關(guān)于為什么需要在Linux上單獨(dú)配一個(gè)路由條目
在Linux上單獨(dú)配一個(gè)路由條目
因?yàn)槲锢頇C(jī)的192.168.110.100和虛擬機(jī)的192.168.150.0不是直連關(guān)系
192.168.150.2是虛擬機(jī)所在網(wǎng)絡(luò)的網(wǎng)關(guān),用于完成網(wǎng)絡(luò)地址轉(zhuǎn)換。
來(lái)自192.168.110.100的網(wǎng)絡(luò)包在返回給客戶端的時(shí)候,經(jīng)過(guò)NAT地址轉(zhuǎn)換,目標(biāo)ip被改成了192.168.150.2,Windows收到之后不知道這個(gè)ip應(yīng)該發(fā)給誰(shuí)了,導(dǎo)致三次握手的第二次回的包被windows丟棄了,沒(méi)有連接上。
關(guān)于為什么連接的速度并不快?(一秒鐘僅能夠建立4個(gè)連接左右)
為什么BIO慢?
創(chuàng)建一個(gè)連接的過(guò)程如下圖所示。
- accept系統(tǒng)調(diào)用,是一個(gè)阻塞的循環(huán)過(guò)程,這個(gè)過(guò)程耗費(fèi)時(shí)間。
- 拋出一個(gè)線程的速度比較慢。
這就是整個(gè)BIO的弊端。想要調(diào)優(yōu),就要解決阻塞的問(wèn)題,但阻塞是由內(nèi)核提供給我們的API決accept receive定的。
NIO 的引入
NIO的N是啥意思呢?有兩個(gè)角度可以理解
- Non-Blocking IO (操作系統(tǒng)中)
- New IO (JDK中)
Linux中的文件描述符是輸入輸出雙向的。
Java中ServerSocketChannel也是淡化了輸入、輸出的概念,把輸入、輸出合在一起了。
一段NIO的代碼
SocketNIO.java
運(yùn)行起來(lái)
ss.configureBlocking(true);阻塞狀態(tài)下
ss.configureBlocking(false);非阻塞狀態(tài)下
非阻塞了,這有什么意義?
假設(shè)這時(shí)候有兩個(gè)客戶端連接進(jìn)來(lái)了,其中一個(gè)的示例是下面這個(gè)樣子的:
曾經(jīng)我們是需要拋出一個(gè)線程,把這個(gè)線程扔出去,讓它自己去讀取數(shù)據(jù)。
現(xiàn)在不需要拋線程了,完全在一個(gè)線程中執(zhí)行,只不過(guò)是無(wú)數(shù)個(gè)循環(huán)。而在沒(méi)有連接的時(shí)候,循環(huán)也不會(huì)被阻塞,依然繼續(xù)循環(huán),從而讓循環(huán)后半部分的讀取數(shù)據(jù)的邏輯就有機(jī)會(huì)在當(dāng)前循環(huán)當(dāng)中被執(zhí)行到。
再用C10K壓測(cè)一下
用C10K壓測(cè)一下NIO的性能(單客戶端10W連接):大約每秒鐘能建立50個(gè)左右的連接
現(xiàn)在使用NIO的瓶頸是:當(dāng)連接進(jìn)來(lái)很多客戶端時(shí),for (SocketChannel c : clients)遍歷每一個(gè)客戶端去讀取數(shù)據(jù)的過(guò)程耗費(fèi)了性能。
另外,報(bào)錯(cuò)超出文件描述符的數(shù)量,這個(gè)是可以設(shè)置的:ulimit -SHn 500000(軟硬openfile,改成50萬(wàn))
注:為啥ulimit -n 1024,但是連接數(shù)超過(guò)了1024呢?
這個(gè)理論是對(duì)的,只不過(guò)要看用戶。權(quán)限對(duì)root來(lái)說(shuō)等于虛設(shè),很多資源的約束在root用戶也是放開(kāi)的。而且在公司里,生產(chǎn)環(huán)境肯定是非root用戶啟動(dòng)程序。
總結(jié)
以上是生活随笔為你收集整理的网络与IO知识扫盲(四):C10K问题、BIO的弊端与NIO的引入的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 网络与IO知识扫盲(三):从系统调用的角
- 下一篇: 网络与IO知识扫盲(五):从 NIO 到