使用Vertx編寫HTTP客戶端

談談Apache的HTTP Client

談到HTTP客戶端, 在Java界最有名的當屬Apache HTTP Client庫了。我相信絕大多數人在使用Apache HTTP Client時都是使用的同步版本,即請求發起後需要一直等待響應返回。如果你對程序的吞吐有着更高的要求,可能會嘗試使用HttpAsyncClient, 但是使用起來貌似並不那麼優雅,而且還有點"浪費"。不優雅指的是其API的設計不夠"fluent", 代碼寫起來跟同步調用其實很像; 而"浪費"指的是你在構造Client對象時需要創建一個IO線程池,但此線程池只能是Http Client收發請求使用,別的地方是用不了的。其實,當你需要異步HTTP客戶端時,Vert.x可能更適合你。

什麼時候需要異步HTTP客戶端

在兩種情況下你會需要一個異步HTTP客戶端:一是如果你的整個程序都是建立在異步I/O的基礎之上,那麼你並不希望發起HTTP請求會阻塞不應該阻塞的線程; 二是你需要大量重複且快速的調用某一個HTTP接口來實現"灌庫"的功能,多見於離線定時任務。例如,你需要把一個大txt文件的內容逐行讀取出來然後調用對方的HTTP接口發送出去,對方告訴你最高可承受2000QPS的併發,難道你要開闢一個容量爲2000的線程池來發請求嗎?顯然,你需要異步。

Vert.x HTTP Client基本用法

首先,你需要簡單的引入一個依賴

        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-web</artifactId>
            <version>3.5.4</version>
        </dependency>

就可以暢玩HTTP Client了。

一個最簡單的Client長這樣:

Vertx vertx = Vertx.vertx(); // (0)
HttpClient client = vertx.createHttpClient(); // (1)
client.getNow("www.baidu.com", "/", resp -> System.out.println(resp.statusCode())); // (2)

TimeUnit.SECONDS.sleep(10); // (3)

(0): 先創建一個Vertx對象以提供所需線程。

(1): 構造一個HttpClient對象。

(2): 向http://www.baidu.com發起一個GET請求,並註冊一個回調方法,在回調中我們打印出了服務器返回的狀態碼。

(3): 讓當前線程等會。

好吧,其實這就是玩具代碼,生產環境可不能這麼用。但這已經足夠展示出Vertx相比Apache HTTP Client更加易用了。那麼接下來的問題是,怎麼獲取body?如下:

Handler<Buffer> bodyHandler = buffer -> System.out.println(buffer.toString()); // (0)
client.getNow("www.baidu.com", "/", resp -> resp.bodyHandler(bodyHandler)); // (1)

(0): 先定義一個處理器,用於處理HTTP body buffer,這裏我們將其轉成String並打印出來。

(1): 在調用getNow()時使用lambda註冊一個響應Handler,此Handler會在程序接收到並解析完成HTTP Header時調用。由於這時候body可能還沒有接收完,因此要打印body, 我們需要再註冊一個Handler, 也就是上一行創建的bodyHandler,此Handler會在完整接收到響應 body 後調用,所以傳進來的Buffer對象裏完整的,可以直接用。

到這裏還是不夠,如果baidu因爲壓力太大半天不發送響應怎麼辦?如下:

        client.get("www.baidu.com", "/", resp -> resp.bodyHandler(bodyHandler)) // (0)
                .setTimeout(1000) // (1)
                .exceptionHandler(Throwable::printStackTrace) // (2)
                .end(); // (3)

(0): 這裏我們調用HttpClientget()方法而不是getNow(), 這兩個方法的區別在於getNow()執行完後會馬上將你的請求發出去,而get()則會返回一個HttpClientRequest對象,它其實是在構造你的請求,而不是發送請求。

(1): 設置超時時間爲1000毫秒。

(2): 註冊一個異步處理器,只要調用出現錯誤就會執行此處理器。我們這裏簡單的把棧信息打印出來。

(3): 執行到這一行你的請求才會被髮送,前面的步驟都只是在構造請求對象而已。

接下來,我還想添加一個Header, 想POST一些數據過去該怎麼辦?如下:

        client.post("www.baidu.com", "/", resp -> resp.bodyHandler(bodyHandler)) // (0)
                .setTimeout(1000)
                .exceptionHandler(Throwable::printStackTrace)
                .putHeader("My-Header", "HelloWorld") // (1)
                .setChunked(true) // (2)
                .write("Hi~") // (3)
                .end();

(0): 我們將請求方法改成了POST。不過GET方法也可以有請求體的,不信你試試。

(1): 添加了一個自定義請求頭。

(2): 如果你沒有設置Content-Length請求頭,那麼就必須加上這行。

(3): 在body中添加一些數據。

如果你不想用setChunked()的話,也可以使用end()的重載方法,直接將body中的字符串傳進去:

        client.post("www.baidu.com", "/", resp -> resp.bodyHandler(bodyHandler)) // (0)
                .setTimeout(1000)
                .exceptionHandler(Throwable::printStackTrace)
                .putHeader("My-Header", "HelloWorld")
                .end("Hi~"); // (0)

(0): 在end()方法中指定要發送的數據時Vertx會自動計算出字節長度將設置請求頭。

以上就是Vert.x裏自帶HTTP客戶端的基本用法,這對於絕大多數不需要在單請求中傳輸大量數據的業務場景來說已經足夠使用了。如果你想要更高級的功能,如自動encode, decode body成對象、傳輸大文件等,可以使用Vert.x的Web Client模塊,只需要多加一個依賴就可以了:

<dependency>
 <groupId>io.vertx</groupId>
 <artifactId>vertx-web-client</artifactId>
 <version>3.5.4</version>
</dependency>

如何在1s內發出1000個請求

現在想另一個問題,我想讓我的客戶端能在1s內發出1000個請求該怎麼辦?這時候就需要設置一些參數了,如下:

        // 構造HTTP Client
        HttpClientOptions options = new HttpClientOptions()
                .setKeepAlive(true)
                .setConnectTimeout(1000)
                .setIdleTimeout(10)
                .setMaxWaitQueueSize(500) // (0)
                .setMaxPoolSize(1000); // (1)
        HttpClient client = vertx.createHttpClient(options);

(0): 將請求隊列的最大長度設爲1500。這個參數決定了當連接池不夠用時,最多可以有多少請求在排隊發送,如果超了則會報錯。

(1): 將連接池最大連接數設爲1000。這個操作非常重要,因爲HTTP協議不是全雙工的,你使用一個Connection發出一個請求後,在收到響應之前這個Connection是什麼也幹不了的,無法複用。因此要想一次性發出1000個請求就需要使用1000條連接,這樣就必須設置連接池了。如果連接池小於1000, 如500, 那麼就會導致後500個請求在發送時需要等待前500個請求完成,這樣就降低了吞吐。

到這裏請求是發出去了,接下來怎麼才能"協調"這1000個請求的結果呢?也就是說怎麼才能判斷出來1000個請求都發完且收到響應了,可以繼續發第二批1000個請求了呢?其實這個問題只要看過我上一篇文章 使用Vert.x + SpringBoot編寫業務系統 的朋友應該都知道了,就不再重複了。

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