Ⅰ 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萬個句柄左右。