從JDK11新增HttpClient談談非阻塞模型
北京時間 9 月 26 日,Oracle 官方宣佈 Java 11 正式釋出
一、JDK HTTP Client介紹
JDK11中的17個新特性
JDK11中引入HTTP Client的動機
既有的HttpURLConnection存在許多問題
- 其基類URLConnection當初是設計為支援多協議,但其中大多已經成為非主流(ftp, gopher…)
- API的設計早於HTTP/1.1,過度抽象
- 難以使用,存在許多沒有文件化的行為
- 它只支援阻塞模式(每個請求/響應占用一個執行緒)
HTTP Client發展史
在JDK11 HTTP Client出現之前
在此之前,可以使用以下工具作為Http客戶端
- JDK HttpURLConnection
- Apache HttpClient
- Okhttp
- Spring Rest Template
- Spring Cloud Feign
- 將Jetty用作客戶端
- 使用Netty庫。還
初探JDK HTTP Client
我們來看一段HTTP Client的常規用法的樣例 ——
執行GET請求,然後輸出響應體(Response Body)。
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/"))
.build();
client.sendAsync(request, asString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
第一步:建立HttpClient
一般使用JDK 11中的HttpClient的第一步是建立HttpClient物件並進行配置。
- 指定協議(http/1.1或者http/2)
- 轉發(redirect)
- 代理(proxy)
- 認證(authenticator)
HttpClient client = HttpClient.newBuilder()
.version(Version.HTTP_2)
.followRedirects(Redirect.SAME_PROTOCOL)
.proxy(ProxySelector.of(new InetSocketAddress("www-proxy.com", 8080)))
.authenticator(Authenticator.getDefault())
.build();
第二步:建立HttpRequest
從HttpRequest的builder組建request
- 請求URI
- 請求method(GET, PUT, POST)
- 請求體(request body)
- Timeout
- 請求頭(request header)
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/"))
.timeout(Duration.ofMinutes(1))
.header("Content-Type", "application/json")
.POST(BodyPublisher.fromFile(Paths.get("file.json")))
.build()
第三步:send
- http client可以用來傳送多個http request
- 請求可以被以同步或非同步方式傳送
1. 同步傳送
同步傳送API阻塞直到HttpResponse返回
HttpResponse<String> response =
client.send(request, BodyHandler.asString());
System.out.println(response.statusCode());
System.out.println(response.body());
2. 非同步傳送
- 非同步傳送API立即返回一個CompletableFuture
- 當它完成的時候會獲得一個HttpResponse
client.sendAsync(request, BodyHandler.asString())
.thenApply(response -> { System.out.println(response.statusCode());
return response; } )
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
※CompletableFuture是在java8中加入的,支援組合式非同步程式設計
二、從提升單機併發處理能力的技術來看HttpClient的實現細節
提升單機併發處理能力的技術
- 多CPU/多核
- 多執行緒
- 非阻塞(non-blocking)
Java NIO
Java NIO為Java帶來了非阻塞模型。
Lambda表示式
- Lambda表示式可以方便地利用多CPU。
- Lambda表示式讓程式碼更加具有可讀性
回撥
假設以下程式碼是一個聊天應用伺服器的一部分,該應用以Vert.x框架實現。
(Eclipse Vert.x is a tool-kit for building reactive applications on the JVM.)
向connectHandler方法輸入一個Lambda表示式,每當有使用者連線到聊天應用時,都會呼叫該Lambda表示式。這就是一個回撥。
這種方式的好處是,應用不必控制執行緒模型——Vert.x框架為我們管理執行緒,打理好一切相關複雜性,程式設計師只考慮和回撥就夠了。
vertx.createServer()
.connectHandler(socket -> {
socket.dataHandler(new User(socket, this));
}).listen(10_000);
注意,這種設計裡,不共享任何狀態。物件之間通過向事件匯流排傳送訊息通訊,根本不需要在程式碼中新增鎖或使用synchronized關鍵字。併發程式設計變得更加簡單。
Future
大量的回撥會怎樣?請看以下虛擬碼
(1)->{
(2)->{
(3)->{
(4)->{}
}
}
}
大量回撥會形成“末日金字塔”。
如何破解? 使用Future
Future1=(1)->{}
Future2=(Future1.get())->{}
Future3=(Future2.get())->{}
Future4=(Future3.get())->{}
但這會造成原本期望的並行處理,變成了序列處理,帶來了效能問題。
我們真正需要的是將Future和回撥聯合起來使用。下面將要講的CompletableFuture就是結合了Future和回撥,其要點是組合不同例項而無需擔心末日金字塔問題。
CompletableFuture
(new CompletableFuture()).thenCompose((1)->{})
.thenCompose((2)->{})
.thenCompose((3)->{})
.thenCompose((4)->{})
.join()
Reactive Streams
Reactive Streams是一個倡議,它提倡提供一種帶有非阻塞背壓的非同步流處理的標準(Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure)。
JDK 9中的java.util.concurrent.Flow中的概念,與Reactive Streams是一對一對等的。java.util.concurrent.Flow是Reactive Streams標準的實現之一。
多CPU的並行機制讓處理海量資料的速度更快,訊息傳遞和響應式程式設計讓有限的並行執行的執行緒執行更多的I/O操作。
HttpClient的實現細節
請求響應的body與reactive streams
- 請求響應的body暴露為reactive streams
- http client是請求的body的消費者
- http client是響應的body的生產者
HttpRequest內部
public abstract class HttpRequest {
...
public interface BodyPublisher
extends Flow.Publisher<ByteBuffer> { ... }
}
HttpResponse的內部
public abstract class HttpResponse<T> {
...
public interface BodyHandler<T> {
BodySubscriber<T> apply(int statusCode, HttpHeaders responseHeaders);
}
public interface BodySubscriber<T>
extends Flow.Subscriber<List<ByteBuffer>> { ... }
}
相關文章
- 談談對不同I/O模型的理解 (阻塞/非阻塞IO,同步/非同步IO)模型非同步
- 從時間碎片角度理解阻塞IO模型及非阻塞模型模型
- 談談JVM(基礎模型)JVM模型
- 伺服器模型——從單執行緒阻塞到多執行緒非阻塞(下)伺服器模型執行緒
- 伺服器模型——從單執行緒阻塞到多執行緒非阻塞(中)伺服器模型執行緒
- socket阻塞與非阻塞,同步與非同步、I/O模型非同步模型
- 無緩衝阻塞 chan 雜談
- 從linux原始碼看socket的阻塞和非阻塞Linux原始碼
- 從 Linux 原始碼看 socket 的阻塞和非阻塞Linux原始碼
- 從 原始碼 談談 redux compose原始碼Redux
- 談談GPT-4模型的亮點GPT模型
- Java非阻塞I/O模型之NIO說明Java模型
- 從不定方程的非負整數解個數談起
- 從 React render 談談效能優化React優化
- 阻塞IO與非阻塞IO
- 同步非同步,阻塞非阻塞非同步
- 非同步、同步、阻塞、非阻塞非同步
- 淺談領域模型模型
- 來談談限流-從概念到實現
- 談談元件化-從原始碼到理解元件化原始碼
- 深度解讀 | 透過FD耗盡實驗談談使用HttpClient的正確姿勢HTTPclient
- 同步、非同步,阻塞、非阻塞理解非同步
- 同步、非同步、阻塞與非阻塞非同步
- 驅動Driver-阻塞&非阻塞
- 同步非同步 與 阻塞非阻塞非同步
- 理解阻塞、非阻塞、同步、非同步非同步
- 從Minos部署系統談談XML-RPCXMLRPC
- suging閒談-netty 的非同步非阻塞IO執行緒與業務執行緒分離Netty非同步執行緒
- 談談機器學習模型的可解釋性機器學習模型
- IO通訊模型(二)同步非阻塞模式NIO(NonBlocking IO)模型模式BloC
- 淺談雙親委派模型模型
- 從影像融合談起
- 從原始碼角度談談AsyncTask的使用及其原理原始碼
- 從《恥辱》到《掠食》:談談怪物設計
- 從釋出-訂閱模式談談 Flask 的 Signals模式Flask
- 談談從CAP定理到Lambda架構的演化架構
- 淺談樹模型與整合學習-從決策樹到GBDT模型
- 淺談Java記憶體模型Java記憶體模型