整天BIO通信機制不好,你真的瞭解他的性能瓶頸嗎?

一、BIO通訊模型

採用BIO通信模型的服務端,通常都會使用一個Acceptor線程負責監聽客戶端的連接!接收到客戶端的連接請求之後,爲每一個客戶端創建一個新的線程進行鏈路處理,處理完成之後,通過輸出流返回應答給客戶端!線程銷燬!典型的一請求一應答的通信模型!

在這裏插入圖片描述

弊端

該模型的最大問題就是缺乏彈性伸縮的能力,當客戶端併發量增加後,服務端的線程數和客戶端併發訪問數呈1:1的正比關係!當線程數膨脹之後系統的性能將急劇下降,隨着併發訪問的訪問量繼續增大,不能對外提供服務!

二、模擬時鐘服務請求(BIO方式) 僞異步IO操作

時間服務器 服務端

package com.netty.bio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @program: nodes-netty->TimeServer
 * @description: BIO服務端
 * @author: huangfu
 * @date: 2019/11/25 12:40
 **/
public class TimeServer {
    /**
     * 定義一個線程池
     */
    private static final ThreadPoolExecutor fastTriggerPool = new ThreadPoolExecutor(50, 200, 60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1000),
            r -> new Thread(r, "TimeServer" + r.hashCode()));


    public static void main(String[] args) {
        int port = 8080;

        ServerSocket serverSocket = null;
        try{
            serverSocket = new ServerSocket(port);
            System.out.println("這個時間服務已經啓動了,端口是8080");
            Socket socket = null;
            while (true){
                //等待獲取連接
                socket = serverSocket.accept();
                fastTriggerPool.execute(new TimeServerHandler(socket));
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(serverSocket != null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace( );
                }
                serverSocket = null;
            }
        }
    }
}

class TimeServerHandler implements Runnable{

    private Socket socket;

    public TimeServerHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            out = new PrintWriter(this.socket.getOutputStream(),true);
            String currentTime = null;
            String body = null;
            while (true){
                body = in.readLine();
                if (body == null){
                    break;
                }
                System.out.println("客戶端發送請求:"+body );
                String dateTimeStr = LocalDateTime.now( ).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                currentTime = "查詢時間".equals(body)?dateTimeStr:"END QUERY";
                out.println(currentTime);
            }
        } catch (IOException e) {
            e.printStackTrace( );
        }finally {
            if(in != null){
                try {
                    in.close();
                    in = null;
                } catch (IOException e) {
                    e.printStackTrace( );
                }
            }

            if(out != null){
                out.close();
                out = null;
            }

            if(socket != null){
                try {
                    this.socket.close();
                } catch (IOException e) {
                    e.printStackTrace( );
                }

                this.socket = null;
            }
        }
    }
}

時間服務器 客戶端

package com.netty.bio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * @program: nodes-netty->TimeClient
 * @description: 時間客戶端調用
 * @author: huangfu
 * @date: 2019/11/25 13:08
 **/
public class TimeClient {
    public static void main(String[] args) throws IOException {
        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;
        try{
            socket = new Socket("127.0.0.1",8080);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(),true);
            out.println("查詢時間");
            String timeDate = in.readLine( );
            System.out.println(timeDate );
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(out != null){
                out.close();
            }
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace( );
                }
            }
            if(socket!=null){
                socket.close();
            }
        }
    }
}

僞異步IO分析

僞異步IO操作五大從根源上解決線程阻塞的問題,我們試着分析一下他的弊端:

JAVA輸入流的弊端

	/**
	* This method blocks until input data is available, end of file is detected, or an exception is thrown.
	**/
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

當然爲了節省閱讀時間,我只摘錄出比較重要的一段,有興趣可以看看java源碼java.io.InputStream#read(byte[])

這句註釋的中文意思是,該方法會一直處於阻塞的狀態,除非有三種情況發生

  1. 有數據可讀
  2. 可用數據已經讀取完畢
  3. 發生異常

這意味着什麼?當對方發送的請求或者應答消息過慢時,再或者網絡傳輸慢,讀取輸入流的一方將被阻塞!知道上面那三種情況發生!

JAVA輸出流的弊端

當調用OutputStream的write方法,寫出輸出流的時候,他也會被阻塞,知道所有要發送的數據全部寫入完畢,或者發生異常。當消息接收方處理緩慢時,不能及時的從TCP緩衝區讀取數據,將會導致發送方的TCP window size不斷介紹,直到爲0.雙發處於Keep-Alive狀態;消息發送方加個不會向TCP緩衝區寫入任何數據,直至windiw size大於0或者發生異常!

JAVA BIO讀寫都是同步阻塞的,阻塞的時間依賴於雙方IO線程的處理速度和網絡速度!網絡不可靠,我們無法保證其處理速度,所以即使是僞異步IO線程,也無法保證不發生同步線程阻塞

所以這就是他不好的原因!那麼偉大的工程師們是如何引入NIO的概念呢?NIO又是如何解決的呢?歡迎關注作者呀!我們一點一點的去了解!

歡迎關注公衆號 關注公衆號,回覆架構師,提供各類技術的學習資料提供參閱!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章