2 深刻分析 Java IO的工做機制(二)

2.5 I/O調優

  • 下面總結一些磁盤I/O和網絡I/O的經常使用優化技巧。

2.5.1 磁盤I/O優化
1. 性能檢測ios

  • 應用程序一般都須要訪問磁盤來讀取數據,而磁盤I/O一般都很耗時,要判斷I/O是不是一個瓶頸,有一些參數指標可 以參考。
  • 咱們能夠壓力測試應用程序,看系統的I/O wait指標是否正常,例如,測試機器有4個CPU,那麼理想的I/O wait參數不該該超過25%,若是超過25%,I/O可能成爲應 用程序的性能瓶頸。在Linux操做系統下能夠經過iostat命令查看。
  • 一般咱們在判斷I/O性能時還會看到另一個參數,就是IOPS,即要查看應用程序須要的最低的IOPS是多少,磁盤的 IOPS能不能達到要求。每一個磁盤的IOPS一般在一個範圍內,這和存儲在磁盤上的數據塊的大小和訪問方式也有關,但主要是由磁盤的轉速決定的,磁盤的轉速越高,磁盤的IOPS也越高。
  • 如今爲了提高磁盤I/O的性能,一般採用一種叫做RAID的技術,就是將不一樣的磁盤組合起來以提升I/O性能,目前有多種RAID技術,每種RAID技術對I/O性能的提高會有不一樣,能夠用一個RAID因子來表明,磁盤的讀寫吞吐量能夠經過iostat命令來獲取,因而能夠計算出一個理論的IOPS值,計 算公式以下:
  • (磁盤數 * 每塊磁盤的IOPS)/(磁盤讀的吞吐量 + RAID因子 * 磁盤寫的吞吐量)= IOPS

2. 提高I/O性能緩存

  • 一般提高磁盤I/O性能的方法有:
    • 增長緩存,減小磁盤訪問次數。
    • 優化磁盤的管理系統,設計最優的磁盤方式策略,以及磁盤的尋址策略,這是在底層操做系統層面考慮的。
    • 設計合理的磁盤存儲數據塊,以及訪問這些數據塊的策略,這是在應用層面考慮的。例如,咱們能夠給存放的數據設計索引,經過尋址索引來加快和減小磁盤的訪問量,還能夠採用異步和非阻塞的方式加快磁盤的訪問速度。
    • 應用合理的RAID策略提高磁盤I/O,RAID策略及說明如表2-3所示。

2.5.2 TCP網絡參數調優網絡

  • 要可以創建一個TCP鏈接,必須知道對方的IP和一個未被使用的端口號,因爲32位操做系統的端口號一般由兩個字節表示,也就是隻有2^16=65535個,因此一臺主機可以同時創建的鏈接數是有限的,固然操做系統還有一些端口0~1024是受保護的,如80端口、22端口,這些端口都不能被隨意佔用。併發

  • 在Linux中能夠經過查看/proc/sys/net/ipv4/ip_local_port_range文件來知道當前這個主機可使用的端口範圍,如圖2-22所示。
    異步

  • 圖2-22表示可使用的端口爲61000-42768=18232。若是能夠分配的端口號偏少,在遇到大量併發請求時就會成爲瓶頸,因爲端口有限致使大量請求等待創建鏈接,這樣性能就會壓不上去。另外若是發現有大量的TIME_WAIT的話,能夠設置/proc/sys/net/ipv4/tcp_fin_timeout爲更小的值來快速釋放請求。咱們經過另一個主機ab -c 30 -n 1000000 10.232.101.208.8080/來壓測這臺機器,看看網絡的鏈接狀況,如圖2-23所示。
    tcp

  • 能夠看出TIME_WAIT的鏈接有26364個,咱們設置sudo sh -c "echo 3 > /proc/sys/net/ipv4/tcp_fin_timeout"後再進行測試,如圖2-24所示。
    函數

  • 調整後TIME_WAIT的數量明顯減小。除了增大端口範圍以外,還可讓TCP鏈接複用等,這些調優參數如表2-4所示。

    工具

  • 注意,以上設置都是臨時性的,系統從新啓動後就會丟失。另外iain,Linux還提供了一些工具可用於查看當前的TCP統計信息,以下所示。性能

    • cat /proc/net/netstat: 查看TCP的統計信息
    • cat /proc/net/snmp:查看當前系統的鏈接狀況
    • netstat -s:查看網絡的統計信息

2.5.3 網絡I/O優化測試

  • 網絡I/O優化一般有以下一些基本處理原則。

    • 減小網絡交互的次數。

    • 減小網絡傳輸數據量的大小。

    • 儘可能減小編碼。

  • 根據應用場景設計合適的交互方式。所謂的交互場景主要包括同步與異步、阻塞與非阻塞方式,下面進行詳細介紹。

  • 1. 同步與異步

  • 2. 阻塞與非阻塞

  • 3. 兩種方式的組合

  • 組合的方式有4鍾,分別是同步阻塞、同步非阻塞、異步阻塞、異步非阻塞,如表2-5所示。

  • 雖然異步和非阻塞可以提高I/O的性能,可是也會帶來一些額外的性能成本,例如,會增長線程數量從而增長CPU的消耗,同時也會致使程序設計複雜度的上升。

  • 下面舉一些異步和阻塞的操做實例。

  • 在Cassandra中要查詢數據一般會向多個數據節點發送查詢命令,可是要檢查每一個節點返回數據的完整性,就須要一個異步查詢同步結果的應用場景,部分代碼以下:

class AsyncResult implements IAsyncResult {
    private byte[] result_;
    private AtomicBoolean done = new AtomicBoolean(false);
    private Lock lock_ = new ReentrentLock();
    private Condition condition_;
    private long startTime_;
    public AsyncResult() {
        condition_ = lock_.newCondition();//建立一個鎖
        startTime_ = System.currentTimeMillis();
    }
    //檢查須要的數據是否已經返回,若是沒有返回阻塞
    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 - (System.currentTimeMillis() - startTime_);
                    if (overall_timeout > 0) //設置等待超時的時間
                        bVal = condition_.await(overall_timeout, TimeUnit.MILLISECONDS);
                    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_ = response.getMessageBody();//設置返回的數據
                done_.set(true);
                condition_.signal(); //喚醒阻塞的線程
            }
        } finally { lock_.unlock(); }
    }
}
相關文章
相關標籤/搜索