堵塞與非堵塞原理
傳統硬件的堵塞(IO)如下,從內存中讀取數據,然後寫到磁盤,而CPU一直等到磁盤寫完成,磁盤的寫操作是慢的,這段時間CPU被堵塞不能發揮效率。
使用非堵塞(NIO)的DMA如下圖:CPU只是發出寫操作這樣的指令,做一些初始化工作,DMA具體執行,從內存中讀取數據,然後寫到磁盤,當完成寫後發出一箇中斷事件給CPU。這段時間CPU是空閒的,可以做別的事情。這個原理稱爲Zero.copy零拷貝。
非阻塞IO模型
幾種 IO 模型比較
Linux 的 IO 多路複用模型
IO 多路複用模型又稱爲事件驅動模型。IO 多路複用通過把多個 IO 阻塞複用到同一個 select 的阻塞上,從而使得系統在單線程的情況下,可以同時處理多個 client 請求。IO 多路複用實際上就是通過一種機制,一個進程可以監視多個描 fd,一旦某個 fd 就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作,目前支持 IO 多路複用的系統有 select、pselect、poll、epoll,但它們本質上都是同步 IO。
多路複用三種模型的區別
類別 | select | poll | epoll |
---|---|---|---|
支持的最大連接數 | 由 FD_SETSIZE 限制 1024(x86) or 2048(x64) |
基於鏈表存儲,沒有限制 | 受系統最大句柄數限制 |
IO效率 | 每次調用進行線性變量,時間複雜度O(n) | 同 select | 使用“事件”通知方式,每當fd就緒,系統註冊的回調函數就會被調用,將就緒fd放到rdlist裏面,這樣epoll_wait返回的時候應用就拿到了就緒的fd。時間複雜度O(1) |
fd 劇增的影響 | 線性掃描 fd 導致性能很低 | 同 select | 基於 fd 上 callback 實現,沒有性能下降的問題 |
消息傳遞機制 | 內核需要將消息傳遞到用戶空間,需要內核拷貝 | 同 select | epoll 通過內核與用戶空間共享內存來實現 |
Java的NIO
Netty是一個高性能事件驅動的異步的非堵塞的IO(NIO)框架,用於建立TCP等底層的連接,基於Netty可以建立高性能的Http服務器。Netty的一個線程服務於很多請求,如下圖,當從Java NIO獲得一個Selector事件,將激活通道Channel。
WebClient
WebClient是spring的一個響應式客戶端,它提供了RestTemplate的替代方法。它公開了一個功能齊全、流暢的API,非阻塞的、被動的客戶端執行HTTP請求。WebClient使用與WebFlux服務器應用程序相同的編解碼器,並與服務器功能Web框架共享公共基本包。默認情況下,它使用Reactor Netty作爲HTTP客戶端庫,但其他人可以通過自定義ClientHttpConnector插入。
WebClient運行時核心類庫
WebClient請求就是這麼簡單
WebClient.builder()
.baseUrl("http://www.baidu.com")
.build()
.get()
.exchange()
.flatMap(response->response.bodyToMono(String.class))
.subscribe(s->{
System.out.println(s);
});
運行時核心代碼分析
- 訂閱http請求響應
//reactor.core.publisher.Mono
public final Disposable subscribe(
@Nullable Consumer<? super T> consumer,
@Nullable Consumer<? super Throwable> errorConsumer,
@Nullable Runnable completeConsumer,
@Nullable Consumer<? super Subscription> subscriptionConsumer) {
return subscribeWith(new LambdaMonoSubscriber<>(consumer, errorConsumer,
completeConsumer, subscriptionConsumer));
}
//reactor.ipc.netty.http.client.MonoHttpClientResponse
public void subscribe(final CoreSubscriber<? super HttpClientResponse> subscriber) {
ReconnectableBridge bridge = new ReconnectableBridge();
bridge.activeURI = startURI;
Mono.defer(() -> parent.client.newHandler(new HttpClientHandler(this, bridge),
parent.options.getRemoteAddress(bridge.activeURI),
HttpClientOptions.isSecure(bridge.activeURI),
bridge))
.retry(bridge)
.cast(HttpClientResponse.class)
.subscribe(subscriber);
}
- 發起http請求
//reactor.ipc.netty.http.client.MonoHttpClientResponse
public Publisher<Void> apply(NettyInbound in, NettyOutbound out) {
try {
URI uri = bridge.activeURI;
HttpClientOperations ch = (HttpClientOperations) in;
String host = uri.getHost();
int port = uri.getPort();
if (port != -1 && port != 80 && port != 443) {
host = host + ':' + port;
}
ch.getNettyRequest()
.setUri(uri.getRawPath() + (uri.getQuery() == null ? "" :
"?" + uri.getRawQuery()))
.setMethod(parent.method)
.setProtocolVersion(HttpVersion.HTTP_1_1)
.headers()
.add(HttpHeaderNames.HOST, host)
.add(HttpHeaderNames.ACCEPT, ALL);
if (Objects.equals(parent.method, HttpMethod.GET)
|| Objects.equals(parent.method, HttpMethod.HEAD)
|| Objects.equals(parent.method, HttpMethod.DELETE)) {
ch.chunkedTransfer(false);
}
if (parent.handler != null) {
return parent.handler.apply(ch);
}
else {
return ch.send();
}
}
catch (Throwable t) {
return Mono.error(t);
}
}
- 接收http數據
exchange()
//org.springframework.web.reactive.function.client.ExchangeFunctions
public Mono<ClientResponse> exchange(ClientRequest request) {
return this.connector
.connect(request.method(), request.url(),
clientHttpRequest -> request.writeTo(clientHttpRequest, this.strategies))
.doOnSubscribe(subscription -> logger.debug("Subscriber present"))
.doOnRequest(n -> logger.debug("Demand signaled"))
.doOnCancel(() -> logger.debug("Cancelling request"))
.map(response -> {
if (logger.isDebugEnabled()) {
int status = response.getRawStatusCode();
HttpStatus resolvedStatus = HttpStatus.resolve(status);
logger.debug("Response received, status: " + status +
(resolvedStatus != null ? " " + resolvedStatus.getReasonPhrase() : ""));
}
return new DefaultClientResponse(response, this.strategies);
});
}
- 合併數據
//org.springframework.core.io.buffer.DataBufferUtils
public static Mono<DataBuffer> join(Publisher<DataBuffer> dataBuffers) {
return Flux.from(dataBuffers)
.collectList()
.filter(list -> !list.isEmpty())
.map(list -> {
DataBufferFactory bufferFactory = list.get(0).factory();
return bufferFactory.join(list);
});
}
- 轉換數據
flatMap(response->response.bodyToMono(String.class));
public Mono<T> decodeToMono(Publisher<DataBuffer> inputStream, ResolvableType elementType,
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
return DataBufferUtils.join(inputStream)
.map(buffer -> decodeDataBuffer(buffer, elementType, mimeType, hints));
}
protected String decodeDataBuffer(DataBuffer dataBuffer, ResolvableType elementType,
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
Charset charset = getCharset(mimeType);
CharBuffer charBuffer = charset.decode(dataBuffer.asByteBuffer());
DataBufferUtils.release(dataBuffer);
return charBuffer.toString();
}
reactor core核心接口分析
構建
接口 | 說明 | 舉例 |
---|---|---|
from | 從其他響應式流中創建reactor core的流,適用於所有不同reactor stream的實現間的適配 | Mono.from(Publisher<? extends T> source) |
defer | 延遲建立新的流,defer一開始不會馬上創建Publisher,直到有訂閱者訂閱時纔會創建,且每次都創建全新的 Publisher。 | Mono.defer(Supplier<? extends Mono<? extends T>> supplier) |
just | 基於對象創建流,參數是流將要發送的元素 | Mono.just(T t) |
轉換
接口 | 說明 | 舉例 |
---|---|---|
map | 將流的元素按指定的方法變換 | map(o-> o.toString()) |
flatMap | 異步轉換當前流發出的項,返回另一個流發出的值(可能更改值類型) | flatMap(s->Mono.just(s)) |
flatMap適用兩種場景
a. 將當前流的元素轉換爲多個元素,並在新的流中逐一發出轉換的多個元素。這裏的flat平鋪就是這個含義。
b. 由當前流的元素事件觸發一個新的流。在http請求場景下,Mono<ClientResponse> clientResponse = request.exchange(); clientResponse這個流在http請求response header返回時得到了流元素ClientResponse,但此時response body還在管道中傳輸,ClientResponse實際沒有拿到完整的body內容,所以ClientResponse將body封裝到了一個新的流中,如果下游需要取到這個流,就必須通過flatMap獲取。
參考文檔
Unix 網絡 IO 模型及 Linux 的 IO 多路複用模型: https://matt33.com/2017/08/06/unix-io/
Netty 之 Java 的 I/O 演進之路: https://www.jianshu.com/p/22f8586bb6f3
I/O多路複用和異步I/O:https://www.cnblogs.com/bigberg/p/8034629.html
I/O模型與多路複用: http://www.rainybowe.com/blog/2016/09/12/IO%E6%A8%A1%E5%9E%8B%E4%B8%8E%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8/index.html
使用 Reactor 進行反應式編程
https://www.ibm.com/developerworks/cn/java/j-cn-with-reactor-response-encode/index.html?lnk=hmhm
Reactive響應式編程
https://www.jdon.com/reactive.html
那些年我們錯過的響應式編程
http://www.open-open.com/lib/view/open1429151543096.html
Reactor Reactive編程介紹
https://luyiisme.github.io/2017/02/11/spring-reactor-programing/
Reactor 入門與實踐
https://studygolang.com/articles/11341?fr=sidebar
Reactor 實例解析
https://www.infoq.cn/article/reactor-by-example
Netty原理和使用
https://www.jdon.com/concurrent/netty.html
使用 UT 玩轉 defer 和 retryWhen
https://www.jianshu.com/p/d42793eba591
照虎畫貓深入理解響應式流規範
http://blog.51cto.com/liukang/2090922
Spring 5 WebClient
https://www.baeldung.com/spring-5-webclient