Hello World
- 讓我們從最簡單的單線程阻塞模型開始
ServerSocket server = new ServerSocket(8080);
Socket socket = server.accept();
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
sb.append(new String(bytes, 0, len,"UTF-8"));
}
System.out.println("get message from client: " + sb);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello World".getBytes("UTF-8"));
outputStream.close();
inputStream.close();
socket.close();
server.close();
-
這個初始版本只能單線程處理悬包,其中的IO會堵塞,性能很差
tomcat
- tomcat是我們比較熟悉的傳統(tǒng)web容器紊扬。提供了多線程的線程模型和NIO多路復(fù)用
- 用一個Acceptor線程統(tǒng)一處理連接
- 連上后交給Poller處理底層協(xié)議和一般性的IO亥揖,Poller線程數(shù)為CPU個數(shù)
- Poller線程處理完底層的協(xié)議之后把servlet相關(guān)的請求交給worker處理烛亦, worker線程數(shù)默認是200個
- 但是tomcat的異步處理只是占一部分, 并不徹底
tomcat io 對比
- 另外servlet模型本身對異步的支持到3.1才開始坝咐。按照我的理解servlet3.1本質(zhì)上是提供了脫離tomcat線程的回調(diào)語法。[這篇文件寫得極好](https://www.cnblogs.com/mianteno/p/10780257.html)
netty
- netty提供了更高效率的線程模型撒桨。當(dāng)然他也使用了DirectBuffer, JCTools的高性能無鎖隊列查刻。這里只介紹線程模型:
- EventLoop類似于tomcat中的Poller,主要處理連接之外的全部IO凤类。線程數(shù)是CPU*2
- 另外Eventloop也會處理協(xié)議和業(yè)務(wù)回調(diào)赖阻。參考源碼:NIOEventLoop#run
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys(); // 處理IO
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
// 用相同的時間處理回調(diào)業(yè)務(wù)
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
- IO處理和任務(wù)處理的默認比例是1:1
- Netty這樣做有幾個好處
- 使用較少的線程意味著減少上下文切換
- 使用較少內(nèi)存,java每個線程棧默認1M開銷
- 一個EventLoop綁定多個Channel和對應(yīng)的業(yè)務(wù)處理踱蠢。從channel的角度看是單線程的,不存在線程切換棋电,不需要處理并發(fā)問題茎截。
- 當(dāng)然Netty的線程處理有個大前提。不能block Eventloop!!!
- 但是傳統(tǒng)的業(yè)務(wù)處理都是阻塞型的赶盔,必須開另外的線程池處理企锌,這就和tomcat比優(yōu)勢不明顯了
vert.x
- vert.x是對netty的高級別封裝,其核心還是EventLoop
- 他通過全異步的方式使得IO全異步處理而通過少量線程性能最大化
- 這個例子里通過Vert.x的EventLoop接管http/redis的IO和回調(diào)
- 理論上所有的代碼都可以異步于未,vert.x提供了大量的常用框架的異步IO適配撕攒。
- 對于不能或不方便的阻塞代碼, 通過worker線程池處理陡鹃,幷提供異步回調(diào)接口(例子中的mysql調(diào)用)