偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

硬核干貨:HTTP超時、重復(fù)請求必見坑點及解決方案

網(wǎng)絡(luò)
HTTP調(diào)用即通過HTTP協(xié)議執(zhí)行一次網(wǎng)絡(luò)請求。既然是網(wǎng)絡(luò)請求,就有超時的可能性(可能你的網(wǎng)卡,也可能服務(wù)器所處網(wǎng)絡(luò)卡),因此在開發(fā)中需要注意

[[351757]]

 1 超時,無法避免的痛

HTTP調(diào)用即通過HTTP協(xié)議執(zhí)行一次網(wǎng)絡(luò)請求。既然是網(wǎng)絡(luò)請求,就有超時的可能性(可能你的網(wǎng)卡,也可能服務(wù)器所處網(wǎng)絡(luò)卡),因此在開發(fā)中需要注意:

  • 框架設(shè)置的默認超時時間是否合理
  • 過短,請求還未處理完成,你就急不可待了!
  • 過長,請求早已超出正常響應(yīng)時間而掛了
  • 考慮網(wǎng)絡(luò)不穩(wěn)定性,超時后可以通過定時任務(wù)請求重試
  • 注意考慮服務(wù)端接口冪等性設(shè)計,即是否允許重試
  • 考慮框架是否會像瀏覽器那樣限制并發(fā)連接數(shù),以免在高并發(fā)下,HTTP調(diào)用的并發(fā)數(shù)成為瓶頸

1.1 HTTP調(diào)用框架技術(shù)選型

  • Spring Cloud全家桶

          使用Feign進行聲明式的服務(wù)調(diào)用。

  • 只使用Spring Boot

           HTTP客戶端Apache HttpClient進行服務(wù)調(diào)用。

1.2 連接超時配置 && 讀取超時參數(shù)

雖然應(yīng)用層是HTTP協(xié)議,但網(wǎng)絡(luò)層始終是TCP/IP協(xié)議。TCP/IP是面向連接的協(xié)議,在傳輸數(shù)據(jù)之前需要建立連接。所以網(wǎng)絡(luò)框架都會提供如下超時參數(shù):

 

  • 連接超時參數(shù)ConnectTimeout

          可自定義配置的建立連接最長等待時間

  • 讀取超時參數(shù)ReadTimeout

          控制從Socket上讀取數(shù)據(jù)的最長等待時間。

1.3 常見踩坑點

連接超時配置過長

比如60s。TCP三次握手正常建立連接所需時間很短,在ms級最多到s級,不可能需要十幾、幾十秒,多半是網(wǎng)絡(luò)或防火墻配置問題。這時如果幾秒還連不上,那么可能永遠也連不上。所以設(shè)置特別長的連接超時無意義,1~5秒即可。

如果是純內(nèi)網(wǎng)調(diào)用,還可以設(shè)更短,在下游服務(wù)無法連接時,快速失敗

無腦排查連接超時問題

服務(wù)一般會有多個節(jié)點,若別的客戶端通過負載均衡連接服務(wù)端,那么客戶端和服務(wù)端會直接建立連接,此時出現(xiàn)連接超時大概率是服務(wù)端問題

而若服務(wù)端通過Nginx反向代理來負載均衡,客戶端連接的其實是Nginx,而非服務(wù)端,此時出現(xiàn)連接超時應(yīng)排查Nginx

讀取超時參數(shù)和讀取超時“坑點”

只要讀取超時,服務(wù)端程序的正常執(zhí)行就一定中斷了?

案例

client接口內(nèi)部通過HttpClient調(diào)用服務(wù)端接口server,客戶端讀取超時2秒,服務(wù)端接口執(zhí)行耗時5秒。

 

調(diào)用client接口后,查看日志:

  • 客戶端2s后出現(xiàn)SocketTimeoutException,即讀取超時

  • 服務(wù)端卻泰然地在3s后執(zhí)行完成

Tomcat Web服務(wù)器是把服務(wù)端請求提交到線程池處理,只要服務(wù)端收到請求,網(wǎng)絡(luò)層面的超時和斷開便不會影響服務(wù)端的執(zhí)行。因此,出現(xiàn)讀取超時不能隨意假設(shè)服務(wù)端的處理情況,需要根據(jù)業(yè)務(wù)狀態(tài)考慮如何進行后續(xù)處理。

讀取超時只是Socket網(wǎng)絡(luò)層面概念,是數(shù)據(jù)傳輸?shù)淖铋L耗時,故將其配置很短

比如100ms。

發(fā)生讀取超時,網(wǎng)絡(luò)層面無法區(qū)分如下原因:

  • 服務(wù)端沒有把數(shù)據(jù)返回給客戶端
  • 數(shù)據(jù)在網(wǎng)絡(luò)上耗時較久或丟包

但TCP是連接建立完成后才傳輸數(shù)據(jù),對于網(wǎng)絡(luò)情況不是特差的服務(wù)調(diào)用,可認為:

  • 連接超時

          網(wǎng)絡(luò)問題或服務(wù)不在線

  • 讀取超時

          服務(wù)處理超時。讀取超時意味著向Socket寫入數(shù)據(jù)后,我們等到Socket返回數(shù)據(jù)的超時時間,其中包含的時間或者說絕大部分時間,是服務(wù)端處理業(yè)務(wù)邏輯的時間

超時時間越長,任務(wù)接口成功率越高,便將讀取超時參數(shù)配置過長

HTTP請求一般需要獲得結(jié)果,屬同步調(diào)用。

若超時時間很長,在等待 Server 返回數(shù)據(jù)同時,Client 線程(通常為 Tomcat 線程)也在等待,當下游服務(wù)出現(xiàn)大量超時,程序可能也會受到拖累創(chuàng)建大量線程,最終崩潰。

  • 對定時任務(wù)或異步任務(wù),讀取超時配置較長問題不大
  • 但面向用戶響應(yīng)的請求或是微服務(wù)平臺的同步接口調(diào)用,并發(fā)量一般較大,應(yīng)該設(shè)置一個較短的讀取超時時間,以防止被下游服務(wù)拖慢,通常不會設(shè)置讀取超時超過30s。

評論可能會有人問了,若把讀取超時設(shè)為2s,而服務(wù)端接口需3s,不就永遠拿不到執(zhí)行結(jié)果?

的確,因此設(shè)置讀取超時要結(jié)合實際情況:

過長可能會讓下游抖動影響到自己

過短又可能影響成功率。甚至,有些時候我們還要根據(jù)下游服務(wù)的SLA,為不同的服務(wù)端接口設(shè)置不同的客戶端讀取超時。

1.4 最佳實踐

連接超時代表建立TCP連接的時間,讀取超時代表了等待遠端返回數(shù)據(jù)的時間,也包括遠端程序處理的時間。在解決連接超時問題時,我們要搞清楚連的是誰;在遇到讀取超時問題的時候,我們要綜合考慮下游服務(wù)的服務(wù)標準和自己的服務(wù)標準,設(shè)置合適的讀取超時時間。此外,在使用諸如Spring Cloud Feign等框架時務(wù)必確認,連接和讀取超時參數(shù)的配置是否正確生效。

2 Feign&&Ribbon

2.1 如何配置超時

為Feign配置超時參數(shù)的難點在于,F(xiàn)eign自身有兩個超時參數(shù),它使用的負載均衡組件Ribbon本身還有相關(guān)配置。這些配置的優(yōu)先級是啥呢?

2.2 案例

  • 測試服務(wù)端超時,假設(shè)服務(wù)端接口,只休眠10min

  • Feign調(diào)用該接口:

 

  • 通過Feign Client進行接口調(diào)用

在配置文件僅指定服務(wù)端地址的情況下:

  1. clientsdk.ribbon.listOfServers=localhost:45678 

得到如下輸出:

  1. [21:46:24.222] [http-nio-45678-exec-4] [WARN ] [o.g.t.c.h.f.FeignAndRibbonController:26  ] -  
  2.     執(zhí)行耗時:222ms 錯誤:Connect to localhost:45679 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1]  
  3.         failed: Connection refused (Connection refused) executing  
  4.             POST http://clientsdk/feignandribbon/server 

Feign默認讀取超時是1秒,如此短的讀取超時算是“坑”。

分析源碼

自定義配置Feign客戶端的兩個全局超時時間

可以設(shè)置如下參數(shù):

  1. feign.client.config.default.readTimeout=3000 
  2. feign.client.config.default.connectTimeout=3000 

修改配置后重試,得到如下日志:

  1. [http-nio-45678-exec-3] [WARN ] [o.g.t.c.h.f.FeignAndRibbonController    :26  ] - 執(zhí)行耗時:3006ms 錯誤:Read timed out executing POST http://clientsdk/feignandribbon/server 

3秒讀取超時生效。

注意:這里有一個大坑,如果希望只修改讀取超時,可能會只配置這么一行:

  1. feign.client.config.default.readTimeout=3000 

測試會發(fā)現(xiàn),這樣配置無法生效。

要配置Feign讀取超時,必須同時配置連接超時

查看FeignClientFactoryBean源碼

  • 只有同時設(shè)置ConnectTimeout、ReadTimeout,Request.Options才會被覆蓋

想針對單獨的Feign Client設(shè)置超時時間,可以把default替換為Client的name:

  1. feign.client.config.default.readTimeout=3000 
  2. feign.client.config.default.connectTimeout=3000 
  3. feign.client.config.clientsdk.readTimeout=2000 
  4. feign.client.config.clientsdk.connectTimeout=2000 

單獨的超時可覆蓋全局超時

  1. [http-nio-45678-exec-3] [WARN ] [o.g.t.c.h.f.FeignAndRibbonController    :26  ] -  
  2. 執(zhí)行耗時:2006ms 錯誤:Read timed out executing  
  3. POST http://clientsdk/feignandribbon/server 

除了可以配置Feign,也可配置Ribbon組件的參數(shù)以修改兩個超時時間

參數(shù)首字母要大寫,和Feign的配置不同。

  1. ribbon.ReadTimeout=4000 
  2. ribbon.ConnectTimeout=4000 

可以通過日志證明參數(shù)生效:

  1. [http-nio-45678-exec-3] [WARN ] [o.g.t.c.h.f.FeignAndRibbonController    :26  ] -  
  2. 執(zhí)行耗時:4003ms 錯誤:Read timed out executing  
  3. POST http://clientsdk/feignandribbon/server 

同時配置Feign和Ribbon的參數(shù)

誰會生效?

  1. clientsdk.ribbon.listOfServers=localhost:45678 
  2. feign.client.config.default.readTimeout=3000 
  3. feign.client.config.default.connectTimeout=3000 
  4. ribbon.ReadTimeout=4000 
  5. ribbon.ConnectTimeout=4000 

最終生效的是Feign的超時:

  1. [http-nio-45678-exec-3] [WARN ] [o.g.t.c.h.f.FeignAndRibbonController    :26  ] -  
  2. 執(zhí)行耗時:3006ms 錯誤:Read timed out executing  
  3. POST http://clientsdk/feignandribbon/server 

同時配置Feign和Ribbon的超時,以Feign為準

在LoadBalancerFeignClient源碼

如果Request.Options不是默認值,就會創(chuàng)建一個FeignOptionsClientConfig代替原來Ribbon的DefaultClientConfigImpl,導(dǎo)致Ribbon的配置被Feign覆蓋:

但若這么配置,最終生效的還是Ribbon的超時(4秒),難點Ribbon又反覆蓋了Feign?不,這還是因為坑點二,單獨配置Feign的讀取超時無法生效:

  1. clientsdk.ribbon.listOfServers=localhost:45678 
  2. feign.client.config.default.readTimeout=3000 
  3. feign.client.config.clientsdk.readTimeout=2000 
  4. ribbon.ReadTimeout=4000 

3 Ribbon自動重試請求

一些HTTP客戶端往往會內(nèi)置一些重試策略,其初衷是好的,畢竟因為網(wǎng)絡(luò)問題導(dǎo)致丟包雖然頻繁但持續(xù)時間短,往往重試就能成功,

但要留心這是否符合我們期望。

3.1 案例

短信重復(fù)發(fā)送的問題,但短信服務(wù)的調(diào)用方用戶服務(wù),反復(fù)確認代碼里沒有重試邏輯。

那問題究竟出在哪里?

Get請求的發(fā)送短信接口,休眠2s以模擬耗時:

 配置一個Feign供客戶端調(diào)用:

Feign內(nèi)部有一個Ribbon組件負責(zé)客戶端負載均衡,通過配置文件設(shè)置其調(diào)用的服務(wù)端為兩個節(jié)點:

  1. SmsClient.ribbon.listOfServers=localhost:45679,localhost:45678 

客戶端接口,通過Feign調(diào)用服務(wù)端

在45678和45679兩個端口上分別啟動服務(wù)端,然后訪問45678的客戶端接口進行測試。因為客戶端和服務(wù)端控制器在一個應(yīng)用中,所以45678同時扮演了客戶端和服務(wù)端的角色。

在45678日志中可以看到,29秒時客戶端收到請求開始調(diào)用服務(wù)端接口發(fā)短信,同時服務(wù)端收到了請求,2秒后(注意對比第一條日志和第三條日志)客戶端輸出了讀取超時的錯誤信息:

  1. [http-nio-45678-exec-4] [INFO ] [c.d.RibbonRetryIssueClientController:23  ] - client is called 
  2. [http-nio-45678-exec-5] [INFO ] [c.d.RibbonRetryIssueServerController:16  ] - http://localhost:45678/ribbonretryissueserver/sms is called, 13600000000=>a2aa1b32-a044-40e9-8950-7f0189582418 
  3. [http-nio-45678-exec-4] [ERROR] [c.d.RibbonRetryIssueClientController:27  ] - send sms failed : Read timed out executing GET http://SmsClient/ribbonretryissueserver/sms?mobile=13600000000&message=a2aa1b32-a044-40e9-8950-7f0189582418 

而在另一個服務(wù)端45679的日志中還可以看到一條請求,客戶端接口調(diào)用后的1秒:

  1. [http-nio-45679-exec-2] [INFO ] [c.d.RibbonRetryIssueServerController:16  ] - http://localhost:45679/ribbonretryissueserver/sms is called, 13600000000=>a2aa1b32-a044-40e9-8950-7f0189582418 

客戶端接口被調(diào)用的日志只輸出了一次,而服務(wù)端的日志輸出了兩次。雖然Feign的默認讀取超時時間是1秒,但客戶端2秒后才出現(xiàn)超時錯誤。

說明客戶端自作主張進行了一次重試,導(dǎo)致短信重復(fù)發(fā)送。

3.2 源碼揭秘

查看Ribbon源碼,MaxAutoRetriesNextServer參數(shù)默認為1,也就是Get請求在某個服務(wù)端節(jié)點出現(xiàn)問題(比如讀取超時)時,Ribbon會自動重試一次:

解決方案

1.把發(fā)短信接口從Get改為Post

API設(shè)計規(guī)范:有狀態(tài)的API接口不應(yīng)定義為Get。根據(jù)HTTP協(xié)議規(guī)范,Get請求適用于數(shù)據(jù)查詢,Post才是把數(shù)據(jù)提交到服務(wù)端用于修改或新增。選擇Get還是Post的依據(jù),應(yīng)該是API行為,而非參數(shù)大小。

  • 常見誤區(qū):Get請求的參數(shù)包含在Url QueryString中,會受瀏覽器長度限制,所以一些開發(fā)會選擇使用JSON以Post提交大參數(shù),使用Get提交小參數(shù)。

2.將MaxAutoRetriesNextServer參數(shù)配為0,禁用服務(wù)調(diào)用失敗后在下一個服務(wù)端節(jié)點的自動重試。在配置文件中添加一行即可:

  1. ribbon.MaxAutoRetriesNextServer=0 

問責(zé)

至此,問題出在用戶服務(wù)還是短信服務(wù)?

也許雙方都有問題吧。

  • Get請求應(yīng)該是無狀態(tài)或者冪等的,短信接口可以設(shè)計為支持冪等調(diào)用
  • 用戶服務(wù)的開發(fā)同學(xué),如果對Ribbon的重試機制有所了解的話,或許就能在排查問題上少走彎路

最佳實踐

對于重試,因為HTTP協(xié)議認為Get請求是數(shù)據(jù)查詢操作,是無狀態(tài)的,又考慮到網(wǎng)絡(luò)出現(xiàn)丟包是比較常見的事情,有些HTTP客戶端或代理服務(wù)器會自動重試Get/Head請求。如果你的接口設(shè)計不支持冪等,需要關(guān)閉自動重試。但,更好的解決方案是,遵從HTTP協(xié)議的建議來使用合適的HTTP方法。

4 并發(fā)限制爬蟲抓取

HTTP請求調(diào)用還有一個常見的問題:并發(fā)數(shù)的限制,導(dǎo)致程序處理性能無法提升。

4.1 案例

某爬蟲項目,整體爬取數(shù)據(jù)效率很低,增加線程池數(shù)量也無謂,只能堆機器。

現(xiàn)在模擬該場景,探究問題本質(zhì)。

假設(shè)要爬取的服務(wù)端是這樣的一個簡單實現(xiàn),休眠1s返回數(shù)字1:

爬蟲需多次調(diào)用該接口抓取數(shù)據(jù),為確保線程池不是并發(fā)瓶頸,使用了一個無線程上限的newCachedThreadPool,然后使用HttpClient執(zhí)行HTTP請求,把請求任務(wù)循環(huán)提交到線程池處理,最后等待所有任務(wù)執(zhí)行完成后輸出執(zhí)行耗時:

使用默認的PoolingHttpClientConnectionManager構(gòu)造的CloseableHttpClient,測試一下爬取10次的耗時:


雖然一個請求需要1s執(zhí)行完成,但線程池可擴張使用任意數(shù)量線程。

按道理,10個請求并發(fā)處理的時間基本相當于1個請求的處理時間,即1s,但日志中顯示實際耗時5秒:

4.2 源碼解析

PoolingHttpClientConnectionManager源碼有兩個重要參數(shù):

  • defaultMaxPerRoute=2,即同一主機/域名的最大并發(fā)請求數(shù)為2。我們的爬蟲需要10個并發(fā),顯然是默認值太小限制了爬蟲的效率。
  • maxTotal=20,即所有主機整體最大并發(fā)為20,這也是HttpClient整體的并發(fā)度。我們請求數(shù)是10最大并發(fā)是10,20不會成為瓶頸。舉一個例子,使用同一個HttpClient訪問10個域名,defaultMaxPerRoute設(shè)置為10,為確保每一個域名都能達到10并發(fā),需要把maxTotal設(shè)置為100。

HttpClient是常用的HTTP客戶端,那為什么默認值限制得這么小?

很多早期的瀏覽器也限制了同一個域名兩個并發(fā)請求。對于同一個域名并發(fā)連接的限制,其實是HTTP 1.1協(xié)議要求的,這里有這么一段話:

  • Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. A proxy SHOULD use up to 2*N connections to another server or proxy, where N is the number of simultaneously active users. These guidelines are intended to improve HTTP response times and avoid congestion.
  • HTTP 1.1協(xié)議是20年前制定的,現(xiàn)在HTTP服務(wù)器的能力強很多了,所以有些新的瀏覽器沒有完全遵從2并發(fā)這個限制,放開并發(fā)數(shù)到了8甚至更大。
  • 如果需要通過HTTP客戶端發(fā)起大量并發(fā)請求,不管使用什么客戶端,請務(wù)必確認客戶端的實現(xiàn)默認的并發(fā)度是否滿足需求。

嘗試聲明一個新的HttpClient放開相關(guān)限制,設(shè)置maxPerRoute為50、maxTotal為100,然后修改一下剛才的wrong方法,使用新的客戶端進行測試:

輸出如下,10次請求在1秒左右執(zhí)行完成??梢钥吹剑驗榉砰_了一個Host 2個并發(fā)的默認限制,爬蟲效率得到了大幅提升:

4.3 最佳實踐

若你的客戶端有比較大的請求調(diào)用并發(fā),比如做爬蟲,或是扮演類似代理的角色,又或者是程序本身并發(fā)較高,如此小的默認值很容易成為吞吐量的瓶頸,需要及時調(diào)整。

 

責(zé)任編輯:姜華 來源: JavaEdge
相關(guān)推薦

2024-06-24 00:30:00

2014-04-08 09:49:27

PostgreSQL雙緩沖

2015-12-02 15:35:08

Redis Clust遷移解決方案

2020-05-06 14:14:50

Linux依賴軟件

2021-10-18 07:58:33

MyBatis Plu數(shù)據(jù)庫批量插入

2010-07-29 15:56:04

FlexSocket

2019-10-08 16:05:19

Redis數(shù)據(jù)庫系統(tǒng)

2024-09-30 08:43:33

HttpgolangTimeout

2015-05-12 16:31:22

Elasticsear開源分布式搜索引擎

2018-12-12 15:50:13

2018-10-12 14:34:13

2022-12-27 11:06:35

海量接口并發(fā)

2017-08-01 05:44:10

Dockerweave虛擬機

2020-05-08 15:37:20

Redis分布式優(yōu)化點

2017-06-29 10:13:20

信息安全智慧安全藍盾股份

2009-12-07 15:50:27

WCF文件

2015-03-05 13:32:02

遠程醫(yī)療解決方案華為

2023-09-14 15:44:46

分布式事務(wù)數(shù)據(jù)存儲

2011-07-05 14:32:04

FTTH

2012-07-12 17:02:47

華為服務(wù)器
點贊
收藏

51CTO技術(shù)棧公眾號