從效能角度幫你理解HTTP協議

絲瓜呆呆發表於2021-06-08

因為做效能測試分析的人來說,HTTP 協議可能是繞不過去的一個檻。在講 HTTP 之前,我們得先知道一些基本的資訊。

HTTP(HyperText Transfer Protocol,超文字傳輸協議),顯然是規定了傳輸的規則,但是它並沒有規定內容的規則。

 HTML(HyperText Marked Language,超文字標記語言),規定的是內容的規則。瀏覽器之所以能認識傳輸過來的資料,都是因為瀏覽器具有相同的解析規則。

我們首先關注一下 HTTP 互動的大體內容。想了很久,畫了這麼一張圖,我覺得它展示了我對 HTTP 協議在互動過程上的理解。

 

 在這張圖中,可以看到這些資訊:

1、在互動過程中,資料經過了 Frame、Ethernet、IP、TCP、HTTP 這些層面。不管是傳送和接收端,都必須經過這些層。這就意味著,任何每一層出現問題,都會影響 HTTP 傳輸。

2、在每次傳輸中,每一層都會加上自己的頭資訊。這一點要說重要也重要,說不重要也不重要。重要是因為如果這些頭出了問題,非常難定位(在我之前的一個專案中,就曾經出現過 TCP 包頭的一個 option 因為 BUG 產生了變化,查了兩個星期,一層層抓包,最後才找到原因)。不重要是因為它們基本上不會出什麼問題。

3、HTTP 是請求 - 應答的模式。就是說,有請求,就要有應答。沒有應答就是有問題。

4、客戶端接收到所有的內容之後,還要展示。而這個展示的動作,也就是前端的動作。在當前主流的效能測試工具中,都是不模擬前端時間的,比如說 JMeter。我們在執行結束後只能看到結果,但是不會有響應的資訊。你也可以選擇儲存響應資訊,但這會導致壓力機工作負載高,壓力基本上也上不去。也正是因為不存這些內容,才讓一臺機器模擬成千上百的客戶端有了可能。

如果你希望能理解這些層的頭都是什麼,可以直接抓包來看,比如如下示圖:

 

 從這個圖中,我們就能看到各層的內容都是什麼。

在我看來,只有實踐的操作和理論的結合,才能真正的融會貫通。只講壓力工具而不講原理,是不可能學會處理複雜問題的;空有理論沒有動手能力是不可能解決實際問題的。由於壓力工具並不處理客戶端頁面解析、渲染等動作,所以,以下內容都是從協議層出發的,不包括前端頁面層的部分。

JMeter 指令碼

 

 在這裡只解釋幾個重要資訊:

第一個就是 Protocol。

這個當然重要。從“HTTP”這幾個字元中,我們就能知道這個協議有什麼特點。 HTTP 的特點是建立在 TCP 之上、無連線(TCP 就是它的連線)、無狀態(後來加了 Cookies、Session 技術,用 KeepAlive 來維持,也算是有狀態吧)、請求 - 響應模式等。

第二個是 Method 的選項 GET。

HTTP 中有多少個 Method 呢?我在這裡做個說明。在 RFC 中的 HTTP 相關的定義中(比如 RFC2616、2068),定義了 HTTP 的方法,如下:GET、POST、PUT、PATCH、DELETE、COPY、HEAD、OPTIONS、LINK、UNLINK、PURGE。

GET 方法是怎麼工作的呢?

GET 可以得到由 URI 請求(定義)的各種資訊。同樣的,其他方法也有清楚的規定。我們要注意的是,HTTP 只規定了你要如何互動。它是互動的協議,就是兩個人對話,如何能傳遞過去?小時候一個人手上拿個紙杯子,中間有根線,相互說話能聽到,這就是協議。

第三個是 Path,也就是請求的路徑。

 這個路徑是在哪裡規定的呢?在我這個 Spring Boot 的示例中。

   @RequestMapping(value = "pabcd")
    public class PABCDController {
        @Autowired
        private PABCDService pabcdService;
        @Autowired
        private PABCDRedisService pabcdRedisService;
        @Autowired
        private PABCDRedisMqService pabcdRedisMqService;
        @GetMapping("/redis_mq/query/{id}")
        public ResultVO<User> getRedisMqById(@PathVariable("id") String id) {
            User user = pabcdRedisMqService.getById(id);
            return ResultVO.<User>builder().success(user).build();
        }

看到了吧。因為我們定義了 request 的路徑,所以,我們必須在 Path 中寫上/pabcd/redis_mq/query這樣的路徑。

第四個是 Redirect,重定向。HTTP 3XX 的程式碼都和重定向有關,從示意上來看,如下所示。

 

 

使用者發了個 URL A 到服務 A 上,服務 A 返回了 HTTP 程式碼 302 和 URL B。 這時使用者看到了接著訪問 URL B,得到了服務 B 的響應。對於 JMeter 來說,它可以處理這種重定向。

第五個是 Content-Encoding,內容編碼。

它是在 HTTP 的標準中對服務端和客戶端之間處理內容做的一種約定。當大家都用相同的編碼時,相互都認識,或者有一端可以根據對端的編碼進行適配解釋,否則就會出現亂碼的情況。

預設是 UTF8。但是我們經常會碰到這種情況。當我們傳送中文字元的時候。比如下面的名字。

 

 當我們傳送出去時,會看到它變成了這種編碼。如下圖所示:

 

 如果服務端不去處理,顯然互動就錯了。如下圖所示:

 

 這時,只能把配置改為如下:

 

 我們這裡用 GBK 來處理中文。就會得到正確的結果。

 

 你就會發現現在用了正常的中文字元。在這個例子,有人選擇用 URL 編碼來去處理,會發現處理不了。這是需要注意的地方。

第六個是超時設定。在 HTTP 協議中,規定了幾種超時時間,分別是連線超時、閘道器超時、響應超時等。

如下所示,JMeter 中可以設定連線和響應超時:

 

 在工具中,我們可以定義連線和響應的超時時間。但通常情況下,我們不用做這樣的規定,只要跟著服務端的超時走就行了。但在有些場景中,不止是應用伺服器有超時時間,網路也會有延遲,這些會影響我們的響應時間。如果 HTTP 預設的 120s 超時時間不夠,我們可以將這裡放大。

在這裡為了演示,我將它設定為 100ms。我們來看一下執行的結果是什麼樣。

 

 從棧的資訊上就可以看到,在讀資料的時候,超時了。

超時的設定是為了保證資料可以正常地傳送到客戶端。做效能分析的時候,經常有人聽到“超時”這個詞就覺得是系統慢導致的,其實有時也是因為配置。

通常,我們會對系統的超時做梳理,每個服務應該是什麼樣的超時設定,我們要有全域性的考量。比如說:

 

 

 超時應該是逐漸放大的(不管你後面用的是什麼協議,超時都應該是這個樣子)。而我們現在的系統,經常是所有的服務超時都設定得一樣大,或者都是跟著協議的預設超時來。

在壓力小的時候,還沒有什麼問題,但是在壓力大的時候,就會發現系統因為超時設定不合理而導致業務錯誤。

如果倒過來的話,你可以想像,使用者都返回超時報錯了,後端還在處理著呢,那就更有問題了。

而我們效能測試人員,都是在壓力工具中看到的超時錯誤。如果後端的系統鏈路比較長,就需要一層層地往後端去查詢,看具體是哪個服務有問題。所以在架構層級來分析超時是非常有必要的。

 

 第七個,在上圖中,還有一個引數是客戶端實現(Client Implementation)。其中有三個選項:空值、HTTPClient4、Java。

官方給出如下的解釋。

JAVA: 使用 JVM 提供的 HTTP 實現,相比 HttpClient 實現來說,這個實現有一些限制,這個限制我會在後面提到。

HTTPClient4:使用 Apache 的 HTTP 元件 HttpClient 4.x 實現。

空值:如果為空,則依賴 HTTP Request 預設方法,或在jmeter.properties檔案中的jmeter.httpsample定義的值。

用 JAVA 實現可能會有如下限制。

  • 在連線複用上沒有任何控制。就是當一個連線已經釋放之後,同一個執行緒有可能複用這個已經釋放掉的連線。
  • API 最適用於單執行緒,但是很多設定都是依賴系統屬性值的,所以都應用到所有連線上了。
  • 不支援 Kerberos Authentication(這是一種計算機網路授權協議,用在非安全網路中,對個人通訊以安全的手段進行身份認證)。
  • 不支援通過 keystore 配置的客戶端證照。
  • 更容易控制重試機制。
  • 不支援 Virtual hosts。
  • 只支援這些方法: GET、POST、HEAD、OPTIONS、PUT、DELETE 和 TRACE。
  • 使用 DNS Cache Manager 更容易控制 DNS 快取。

第八個就是 HTTP 層的壓縮。

我們經常會聽到在效能測試過程中,因為沒有壓縮,導致網路頻寬不夠的事情。當我們截獲一個 HTTP 請求時,你會看到如下內容。

 

 這就是有壓縮的情況。在我們常用的 Nginx 中,會用如下常見配置來支援壓縮:

    gzip  on;   #開啟gzip
    gzip_min_length 2k; #低於2kb的資源不用壓縮
    gzip_comp_level 4; #壓縮級別【1-9】值越大,壓縮率就越高,但是CPU消耗也越多,根據我們在網上看到建議,大部分都是建議設定為中間4、5之類的,這裡我建議大家根據自己的專案實際情況,在壓力測試之後給出適合的值。
    gzip_types text/plain application/javascript;  #設定壓縮型別
    gzip_disable "MSIE [1-6]\.";  # 禁用gzip的條件,支援正則

在 RFC2616 中,Content Codings 部分定義了壓縮的格式 gzip 和 Deflate,不過我們現在看到的大部分都是 gzip。不過在壓縮這件事情上,我們在壓力工具中並不需要做什麼太多的動作,最多也就是加個頭。

 

 第九個就是併發。

在 RFC2616 中的 8.1.1 節明確說明了為什麼要限制瀏覽器的併發。大概翻譯如下,有興趣的去讀下原文:

  • 少開 TCP 連結,可以節省路由和主機(客戶端、服務端、代理、閘道器、通道、快取)的 CPU 資源和記憶體資源。
  • HTTP 請求和響應可以通過 Pipelining 在一個連線上傳送。Pipelining 允許客戶端發出多個請求而不用等待每個返回,一個 TCP 連線更為高效。
  • 通過減少開啟的 TCP 來減少網路擁堵,也讓 TCP 有充足的時間解決擁堵。
  • 後續請求不用在 TCP 三次握手上再花時間,延遲降低。
  • 因為報告錯誤時,沒有關閉 TCP 連線的懲罰,而使 HTTP 可以升級得更為優雅(原文使用 gracefully)。
  • 如果不限制的話,一個客戶端發出很多個連結到伺服器,伺服器的資源可以同時服務的客戶端就會減少。

HTTPS 只是加了一個 S,就在訪問中加了一層。

因為證照是個非常標準的產品,加在中間,就是加密演算法和位數也會對效能產生影響。如果執行場景時報:javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake,就應該把證照也載入進來。

其實對我們做效能測試的人來說,無需關心 HTTP 的內容,我們只要關心資料的流向和處理的邏輯就可以了。

從效能測試的角度來看,如果你要模擬頁面請求,最多也就是正常實現 HTTP 的方法 GET、POST 之類的。

它傳送和接收的內容,只要符合業務系統的正常流程就可以,這樣業務才能正常執行。

比如說,前面提到的 POST 請求。如果我們傳送了一段 JSON。內容如下:

{
    "userNumber": "${Counter}",
    "userName": "Zee_${Counter}",
    "orgId": null,
    "email": "test${Counter}@dunshan.com",
    "mobile": "18611865555"
}

程式碼中的 Service 負責接收 User 物件,同時轉換它的是如下程式碼:

@Override
    public String toString() {
        return "User{" +
            "id='" + id + '\'' +
            ", userNumber='" + userNumber + '\'' +
            ", userName='" + userName + '\'' +
            ", orgId='" + orgId + '\'' +
            ", email='" + email + '\'' +
            ", mobile='" + mobile + '\'' +
            ", createTime=" + createTime +
            '}';
    }

然後通過 Service 的 add 方法 insert 到資料庫中,這裡後面使用的 MyBatis:

    Boolean result = paRedisService.add(user);

而這些,都屬於業務邏輯處理的部分,我們分析時把這個鏈路都想清楚才可以一層層剝離。

總結

對於 HTTP 協議來說,我們在效能分析中,主要關心的部分就是傳輸位元組的大小、超時的設定以及壓縮等內容。

在編寫指令碼的時候,要注意 HTTP 頭部,至於 Body 的內容,只要能讓業務跑起來即可。

相關文章