Tomcat執行緒模型 BIO模型原始碼與調優

liudashuang2017發表於2018-04-15

BIO模型:

一個請求建立一個工作執行緒,每個請求都是同步阻塞的,也就是順序訪問完成之後才會給出響應.
其中收到請求分為幾步:1.收到請求,2建立工作執行緒,3.讀取socket請求內容,4.執行業務邏輯,5.寫socket響應.
這幾步都是順序執行的,同步呼叫,阻塞的.意思就是有一個步驟卡頓響應都收不回來.
好處,編碼簡單,
壞處:一個請求一個執行緒,執行緒過多會造成伺服器壓力過大.

一張圖看到Tomcat的BIO

這裡寫圖片描述
使用者請求到達acceptor 執行緒組, 其中一條執行緒 (acceptor)接收請求,之後會建立工作執行緒,封裝出request,經過過濾器filter,去執行Servlet業務邏輯.之後返回響應.

涉及原始碼

1.Accept執行緒組:
Accept 類 執行緒組 acceptorThreadCount=== 決定了acceptor有多少個執行緒,下面列出建立acceptor執行緒的原始碼.
這裡寫圖片描述
這裡數量不多,直接用的執行緒,並且把它設定為守護執行緒.

2.Acceptor extends Runnable
建立完成acceptor下面就是每個acceptor如何工作的.看原始碼如下:
這裡寫圖片描述
執行邏輯:如果達到最大執行緒數,就加入等待佇列進行等待(這裡有個調優的引數acceptCount), 最大執行緒數預設是10000, 下面用工廠方法建立一個socket,本質就是 serverSocket 執行accept( )方法, 如果沒有socket就一直阻塞在這句.再下面就是跑異常或者執行完成,減少執行執行緒計數,之後關閉socket. 真正執行工作執行緒的程式碼是 if(!processSocket(socket)) 這一句程式碼.
3.工作執行緒 SocketProcessor extends Runnable
這裡寫圖片描述
封裝socket.程式設計wrapper
先判斷是否達到最大時間,到了就強制關閉,
再判斷是否開啟SSH連線
再進行建立工作執行緒,new SocketProcessor(wrapper);
maxThread就是指的這個執行緒數,同時業務邏輯有慢查詢或者夯住了,死鎖了都是引起這個執行緒出問題 .
工作執行緒池就是 getExecutor()獲得的執行緒池.

調優引數

acceptCount === 等待最大佇列
address === 繫結客戶端特定地址,127.0.0.1
bufferSize === 每個請求的緩衝區大小,緩衝區總大小是bufferSize * maxThreads
compression === 是否啟用文件
compressableMimeTypes === text/html,text/xml,text/plain
connectionTimeout === 客戶發起連結 到 服務端接收為止,中間最大的等待時間
connectionUploadTimeout === upload 情況下連線超時時間
disableUploadTimeout === true 則使用connectionTimeout
enableLookups === 禁用DNS查詢 true
keepAliveTimeout === 當長連結閒置 指定時間主動關閉 連結 ,前提是客戶端請 求頭 帶上這個 head”connection” ” keep-alive”

maxKeepAliveRequests === 最大的 長連線數
maxHttpHeaderSize === 最大請求頭大小
maxSpareThreads === BIO 模式下 最多線閒置執行緒數
maxThreads === 最大執行執行緒數 預設是10000
minSpareThreads === BIO 模式下 最小線閒置執行緒數

自己實現簡單的BIO

/**
 * 客戶端
 */
public class Client {
    private String host ;
    private int port ;
    private int timeout = 6000;//毫秒

    public Client(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void start(){
        //建立socket
        Socket socket = new Socket();
        BufferedWriter bw ;
        BufferedReader br ;
        try {
            //連結伺服器
            SocketAddress socketAddress = new InetSocketAddress(host,port);
            socket.connect(socketAddress,timeout);
            //通過輸出流向伺服器寫入請求
             bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            //通過輸入流讀取伺服器響應
             br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //通過控制檯輸入作為請求輸入
            Scanner scanner = new Scanner(System.in);
            //迴圈不間斷接收請求輸入
            while (true){
                System.out.println("等待請求來臨");
                String request = scanner.nextLine();
                //請求傳送伺服器
                bw.write(request);
                bw.newLine();//加個換行
                bw.flush();//寫入伺服器
                System.out.println("訊息已經傳送給伺服器:"+request);
                //接收伺服器響應
                String response = br.readLine(); //會阻塞在這句程式碼上
                System.out.println("收到伺服器響應:"+response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

服務端

package wow.tomcat.bio;

import wow.tomcat.IServer;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server implements IServer {
    private static final int PORT = 8080;
    private int port;

    public Server(int port) {
        this.port = port;
    }

    public Server() {
        this.port = PORT;
    }

    public void start(){

        try {
            ServerSocket serverSocket = new ServerSocket(port);
            System.out.println("伺服器啟動");
            while (true){
                //監聽
                System.out.println("開啟監聽");
                Socket socket = serverSocket.accept();
                //接收請求
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String request = br.readLine();
                System.out.println("收到請求:"+request);
                //servlet
                String response = servletWork(request);
                // 響應
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                bw.write(response);
                bw.newLine();
                bw.flush();
                System.out.println("返回響應:"+response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 這是模擬的邏輯
     * @param request
     * @return
     */
    private String servletWork(String request) {
        if("時間".equals(request)) {
            return "當前時間是:" + System.currentTimeMillis();
        }
        return "請輸入正確命令,輸入時間";
    }


}

執行緒版的BIO

package wow.tomcat.bio;

import wow.tomcat.IServer;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*真實的Tomcat中,伺服器還有超時時間,讀超時,寫超時,執行緒超時等.
*/
public class BioServer  implements IServer {
    private ExecutorService executorService ;
    //tomcat原始碼中就是併發10000
    private static final int maxThread = 10000;
    private int threadNum ;
    private int port ;
    private static final int PORT =8080 ;

    public BioServer(int port,int threadNum) {
        this.port = port;
        this.executorService = Executors.newFixedThreadPool(threadNum);
    }

    public BioServer(){
        this.port = PORT;
        this.executorService = Executors.newFixedThreadPool(maxThread);
    }

    public void start(){
        try {
            System.out.println("伺服器啟動");
            ServerSocket serverSocket = new ServerSocket(port);
            while (true){
                Socket socket = serverSocket.accept();
                executorService.submit(new WorkThread(socket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private class WorkThread implements Runnable{
        private Socket socket;

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

        public void run() {
            try {
                //接收請求
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String request = br.readLine();
                System.out.println("收到請求:"+request);
                //servlet
                String response = servletWork(request);
                // 響應
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                bw.write(response);
                bw.newLine();
                bw.flush();
                System.out.println("返回響應:"+response);
            }catch (IOException e){
                e.printStackTrace();
            }finally {

            }
        }

        /**
         * 這是模擬的邏輯
         * @param request
         * @return
         */
        private String servletWork(String request) {
            if("時間".equals(request)) {
                return "當前時間是:" + System.currentTimeMillis();
            }
            return "請輸入正確命令,輸入時間";
        }
    }
}

相關文章