網絡 I/O 優化
網絡 I/O 優化通常有一些基本處理原則
一個是減少網絡交互的次數要減少網絡交互的次數通常我們在需要網絡交互的兩端會設置緩存比如 Oracle 的 JDBC 驅動程序就提供了對查詢的 SQL 結果的緩存在客戶端和數據庫端都有可以有效的減少對數據庫的訪問關於 Oracle JDBC 的內存管理可以參考《 Oracle JDBC 內存管理》除了設置緩存還有一個辦法是合並訪問請求如在查詢數據庫時我們要查 個 id我可以每次查一個 id也可以一次查 個 id再比如在訪問一個頁面時通過會有多個 js 或 css 的文件我們可以將多個 js 文件合並在一個 HTTP 鏈接中每個文件用逗號隔開然後發送到後端 Web 服務器根據這個 URL 鏈接再拆分出各個文件然後打包再一並發回給前端浏覽器這些都是常用的減少網絡 I/O 的辦法
減少網絡傳輸數據量的大小減少網絡數據量的辦法通常是將數據壓縮後再傳輸如 HTTP 請求中通常 Web 服務器將請求的 Web 頁面 gzip 壓縮後在傳輸給浏覽器還有就是通過設計簡單的協議盡量通過讀取協議頭來獲取有用的價值信息比如在代理程序設計時有 層代理和 層代理都是來盡量避免要讀取整個通信數據來取得需要的信息
盡量減少編碼通常在網絡 I/O 中數據傳輸都是以字節形式的也就是通常要序列化但是我們發送要傳輸的數據都是字符形式的從字符到字節必須編碼但是這個編碼過程是比較耗時的所以在要經過網絡 I/O 傳輸時盡量直接以字節形式發送也就是盡量提前將字符轉化為字節或者減少字符到字節的轉化過程
根據應用場景設計合適的交互方式所謂的交互場景主要包括同步與異步阻塞與非阻塞方式下面將詳細介紹
同步與異步
所謂同步就是一個任務的完成需要依賴另外一個任務時只有等待被依賴的任務完成後依賴的任務才能算完成這是一種可靠的任務序列要麼成功都成功失敗都失敗兩個任務的狀態可以保持一致而異步是不需要等待被依賴的任務完成只是通知被依賴的任務要完成什麼工作依賴的任務也立即執行只要自己完成了整個任務就算完成了至於被依賴的任務最終是否真正完成依賴它的任務無法確定所以它是不可靠的任務序列我們可以用打電話和發短信來很好的比喻同步與異步操作
在設計到 IO 處理時通常都會遇到一個是同步還是異步的處理方式的選擇問題因為同步與異步的 I/O 處理方式對調用者的影響很大在數據庫產品中都會遇到這個問題因為 I/O 操作通常是一個非常耗時的操作在一個任務序列中 I/O 通常都是性能瓶頸但是同步與異步的處理方式對程序的可靠性影響非常大同步能夠保證程序的可靠性而異步可以提升程序的性能必須在可靠性和性能之間做個平衡沒有完美的解決辦法
阻塞與非阻塞
阻塞與非阻塞主要是從 CPU 的消耗上來說的阻塞就是 CPU 停下來等待一個慢的操作完成 CPU 才接著完成其它的事非阻塞就是在這個慢的操作在執行時 CPU 去干其它別的事等這個慢的操作完成時CPU 再接著完成後續的操作雖然表面上看非阻塞的方式可以明顯的提高 CPU 的利用率但是也帶了另外一種後果就是系統的線程切換增加增加的 CPU 使用時間能不能補償系統的切換成本需要好好評估
兩種的方式的組合
組合的方式可以由四種分別是同步阻塞同步非阻塞異步阻塞異步非阻塞這四種方式都對 I/O 性能有影響下面給出分析並有一些常用的設計用例參考
表 四種組合方式
組合方式
性能分析
同步阻塞
最常用的一種用法使用也是最簡單的但是 I/O 性能一般很差CPU 大部分在空閒狀態
同步非阻塞
提升 I/O 性能的常用手段就是將 I/O 的阻塞改成非阻塞方式尤其在網絡 I/O 是長連接同時傳輸數據也不是很多的情況下提升性能非常有效
這種方式通常能提升 I/O 性能但是會增加 CPU 消耗要考慮增加的 I/O 性能能不能補償 CPU 的消耗也就是系統的瓶頸是在 I/O 還是在 CPU 上
異步阻塞
這種方式在分布式數據庫中經常用到例如在網一個分布式數據庫中寫一條記錄通常會有一份是同步阻塞的記錄而還有兩至三份是備份記錄會寫到其它機器上這些備份記錄通常都是采用異步阻塞的方式寫 I/O
異步阻塞對網絡 I/O 能夠提升效率尤其像上面這種同時寫多份相同數據的情況
異步非阻塞
這種組合方式用起來比較復雜只有在一些非常復雜的分布式情況下使用像集群之間的消息同步機制一般用這種 I/O 組合方式如 Cassandra 的Gossip 通信機制就是采用異步非阻塞的方式
它適合同時要傳多份相同的數據到集群中不同的機器同時數據的傳輸量雖然不大但是卻非常頻繁這種網絡 I/O 用這個方式性能能達到最高
雖然異步和非阻塞能夠提升 I/O 的性能但是也會帶來一些額外的性能成本例如會增加線程數量從而增加 CPU 的消耗同時也會導致程序設計的復雜度上升如果設計的不合理的話反而會導致性能下降在實際設計時要根據應用場景綜合評估一下
下面舉一些異步和阻塞的操作實例
在 Cassandra 中要查詢數據通常會往多個數據節點發送查詢命令但是要檢查每個節點返回數據的完整性所以需要一個異步查詢同步結果的應用場景部分代碼如下
清單 異步查詢同步結果
class AsyncResult implements IAsyncResult{
private byte[] result_;
private AtomicBoolean done_ = new AtomicBoolean(false)
private Lock lock_ = new ReentrantLock()
private Condition condition_;
private long startTime_;
public AsyncResult(){
condition_ = lock_newCondition()// 創建一個鎖
startTime_ = SystemcurrentTimeMillis()
}
/*** 檢查需要的數據是否已經返回如果沒有返回阻塞 */
public byte[] get(){
lock_lock()
try{
if (!done_get()){condition_await()}
}catch (InterruptedException ex){
throw new AssertionError(ex)
}finally{lock_unlock()}
return result_;
}
/*** 檢查需要的數據是否已經返回 */
public boolean isDone(){return done_get()}
/*** 檢查在指定的時間內需要的數據是否已經返回如果沒有返回拋出超時異常 */
public byte[] get(long timeout TimeUnit tu) throws TimeoutException{
lock_lock()
try{ boolean bVal = true;
try{
if ( !done_get() ){
long overall_timeout = timeout (SystemcurrentTimeMillis() startTime_)
if(overall_timeout > )// 設置等待超時的時間
bVal = condition_await(overall_timeout TimeUnitMILLISECONDS)
else bVal = false;
}
}catch (InterruptedException ex){
throw new AssertionError(ex)
}
if ( !bVal && !done_get() ){// 拋出超時異常
throw new TimeoutException(Operation timed out)
}
}finally{lock_unlock() }
return result_;
}
/*** 該函數拱另外一個線程設置要返回的數據並喚醒在阻塞的線程 */
public void result(Message response){
try{
lock_lock()
if ( !done_get() ){
result_ = responsegetMessageBody()// 設置返回的數據
done_set(true)
condition_signal()// 喚醒阻塞的線程
}
}finally{lock_unlock()}
}
}
總結
本文闡述的內容較多從 Java 基本 I/O 類庫結構開始說起主要介紹了磁盤 I/O 和網絡 I/O 的基本工作方式最後介紹了關於 I/O 調優的一些方法
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26096.html