張開濤:超時(shí)與重試機(jī)制(1)
在實(shí)際開發(fā)過程中,筆者見過太多故障是因?yàn)槌瑫r(shí)沒有設(shè)置或者設(shè)置的不對(duì)而造成的。而這些故障都是因?yàn)闆]有意識(shí)到超時(shí)設(shè)置的重要性而造成的。如果應(yīng)用不設(shè)置超時(shí),則可能會(huì)導(dǎo)致請(qǐng)求響應(yīng)慢,慢請(qǐng)求累積導(dǎo)致連鎖反應(yīng),甚至應(yīng)用雪崩。而有些中間件或者框架在超時(shí)后會(huì)進(jìn)行重試(如設(shè)置超時(shí)重試兩次),讀服務(wù)天然適合重試,但寫服務(wù)大多不能重試(如寫訂單,如果寫服務(wù)是冪等,則重試是允許的),重試次數(shù)太多會(huì)導(dǎo)致多倍請(qǐng)求流量,即模擬了DDoS攻擊,后果可能是災(zāi)難,因此,務(wù)必設(shè)置合理的重試機(jī)制,并且應(yīng)該和熔斷、快速失敗機(jī)制配合。在進(jìn)行代碼Review時(shí),一定記得Review超時(shí)與重試機(jī)制。
本文主要從Web應(yīng)用/服務(wù)化應(yīng)用的角度出發(fā)介紹如何設(shè)置超時(shí)與重試(系統(tǒng)層面的超時(shí)設(shè)置本文沒有涉及),而Web應(yīng)用需要在如下鏈條中設(shè)置超時(shí)與重試機(jī)制。
從上圖來看,在整個(gè)鏈條中的每一個(gè)點(diǎn)都要考慮設(shè)置超時(shí)與重試機(jī)制。而其中最重要的超時(shí)設(shè)置是網(wǎng)絡(luò)連接/讀/寫的超時(shí)時(shí)間設(shè)置。
本文將按照如下分類進(jìn)行超時(shí)與重試機(jī)制的講解。
- 代理層超時(shí)與重試:如Haproxy、Nginx、Twemproxy,這些組件實(shí)現(xiàn)代理功能,如Haproxy和Nginx可以實(shí)現(xiàn)請(qǐng)求的負(fù)載均衡。而Twemproxy可以實(shí)現(xiàn)Redis的分片代理。需要設(shè)置代理與后端真實(shí)服務(wù)器之間的網(wǎng)絡(luò)連接/讀/寫超時(shí)時(shí)間。
- Web容器超時(shí):如Tomcat、Jetty等,提供HTTP服務(wù)運(yùn)行環(huán)境的。需要設(shè)置客戶端與容器之間的網(wǎng)絡(luò)連接/讀/寫超時(shí)時(shí)間,和在此容器中默認(rèn)Socket網(wǎng)絡(luò)連接/讀/寫超時(shí)時(shí)間。
- 中間件客戶端超時(shí)與重試:如JSF(京東SOA框架)、Dubbo、JMQ(京東消息中間件)、CXF、Httpclient等,需要設(shè)置客戶的網(wǎng)絡(luò)連接/讀/寫超時(shí)時(shí)間與失敗重試機(jī)制。
- 數(shù)據(jù)庫客戶端超時(shí):如Mysql、Oracle,需要分別設(shè)置JDBC Connection、Statement的網(wǎng)絡(luò)連接/讀/寫超時(shí)時(shí)間。事務(wù)超時(shí)時(shí)間,獲取連接池連接等待時(shí)間。
- NoSQL客戶端超時(shí):如Mongo、Redis,需要設(shè)置其網(wǎng)絡(luò)連接/讀/寫超時(shí)時(shí)間,獲取連接池連接等待時(shí)間。
- 業(yè)務(wù)超時(shí):如訂單取消任務(wù)、超時(shí)活動(dòng)關(guān)閉。還有如通過Future#get(timeout,unit)限制某個(gè)接口的超時(shí)時(shí)間。
- 前端Ajax超時(shí):瀏覽器通過Ajax訪問時(shí)的網(wǎng)絡(luò)連接/讀/寫超時(shí)時(shí)間。
從如上分類可以看出,其中最重要的超時(shí)設(shè)置是網(wǎng)絡(luò)相關(guān)的超時(shí)設(shè)置。
一、代理層超時(shí)與重試
對(duì)于代理層我們以Nginx和Twemproxy案例來講解。首先,看下Nginx的相關(guān)超時(shí)設(shè)置。
1. Nginx
Nginx主要有四類超時(shí)設(shè)置:客戶端超時(shí)設(shè)置、DNS解析超時(shí)設(shè)置、代理超時(shí)設(shè)置,如果使用ngx_lua,則還有l(wèi)ua相關(guān)的超時(shí)設(shè)置。
(1) 客戶端超時(shí)設(shè)置
對(duì)于客戶端超時(shí)主要設(shè)置有讀取請(qǐng)求頭超時(shí)時(shí)間、讀取請(qǐng)求體超時(shí)時(shí)間、發(fā)送響應(yīng)超時(shí)時(shí)間、長連接超時(shí)時(shí)間。通過客戶端超時(shí)設(shè)置避免客戶端惡意或者網(wǎng)絡(luò)狀況不佳造成連接長期占用,影響服務(wù)端的可處理的能力。
- client_header_timeout time:設(shè)置讀取客戶端請(qǐng)求頭超時(shí)時(shí)間,默認(rèn)為60s,如果在此超時(shí)時(shí)間內(nèi)客戶端沒有發(fā)送完請(qǐng)求頭,則響應(yīng)408(RequestTime-out)狀態(tài)碼給客戶端。
- client_body_timeout time:設(shè)置讀取客戶端內(nèi)容體超時(shí)時(shí)間,默認(rèn)為60s,此超時(shí)時(shí)間指的是兩次成功讀操作間隔時(shí)間,而不是發(fā)送整個(gè)請(qǐng)求體的超時(shí)時(shí)間,如果在此超時(shí)時(shí)間內(nèi)客戶端沒有發(fā)送任何請(qǐng)求體,則響應(yīng)408(RequestTime-out)狀態(tài)碼給客戶端。
- send_timeout time:設(shè)置發(fā)送響應(yīng)到客戶端的超時(shí)時(shí)間,默認(rèn)為60s,此超時(shí)時(shí)間指的也是兩次成功寫操作間隔時(shí)間,而不是發(fā)送整個(gè)響應(yīng)的超時(shí)時(shí)間。如果在此超時(shí)時(shí)間內(nèi)客戶端沒有接收任何響應(yīng),則Nginx關(guān)閉此連接。
- keepalive_timeout timeout [header_timeout]:設(shè)置HTTP長連接超時(shí)時(shí)間,其中,第一個(gè)參數(shù)timeout是告訴Nginx長連接超時(shí)時(shí)間是多少,默認(rèn)為75s。第二個(gè)參數(shù)header_timeout是用于設(shè)置響應(yīng)頭“Keep-Alive: timeout=time”,即告知客戶端長連接超時(shí)時(shí)間。兩個(gè)參數(shù)可以不一樣,“Keep-Alive:timeout=time”響應(yīng)頭可以在Mozilla和Konqueror系列瀏覽器起作用,而MSIE長連接默認(rèn)大約為60s,而不會(huì)使用“Keep-Alive: timeout=time”。如Httpclient框架會(huì)使用“Keep-Alive: timeout=time”響應(yīng)頭的超時(shí)(如果不設(shè)置默認(rèn),則認(rèn)為是永久)。如果timeout設(shè)置為0,則表示禁用長連接。
此參數(shù)要配合keepalive_disable 和keepalive_requests一起使用。keepalive_disable 表示禁用哪些瀏覽器的長連接,默認(rèn)值為msie6,即禁用一些老版本的MSIE的長連接支持。keepalive_requests參數(shù)作用是一個(gè)客戶端可以通過此長連接的請(qǐng)求次數(shù),默認(rèn)為100。
首先,瀏覽器在請(qǐng)求時(shí)會(huì)通過如下請(qǐng)求頭告知服務(wù)器是否支持長連接。
http/1.0默認(rèn)是關(guān)閉長連接的,需要添加HTTP請(qǐng)求頭“Connection:Keep-Alive”才能啟用。而http/1.1默認(rèn)啟用長連接,需要添加HTTP請(qǐng)求頭“Connection: close”才關(guān)閉。
接著,如果Nginx設(shè)置keepalive_timeout 5s,則瀏覽器會(huì)收到如下響應(yīng)頭。
下圖是wireshark抓包,可以看到后兩次請(qǐng)求沒有三次握手。
如果Nginx設(shè)置keepalive_timeout 10s 10s,則瀏覽器會(huì)收到如下響應(yīng)頭。
服務(wù)器端會(huì)在10s后發(fā)送FIN主動(dòng)關(guān)閉連接。
如果Nginx設(shè)置keepalive_timeout為75s 30s。
如下是Chrome瀏覽器的Wireshark抓包,在45秒時(shí),Chrome發(fā)送了TCPKeep-Alive來保活TCP連接,在第57秒時(shí),瀏覽器又發(fā)出了一次請(qǐng)求。而132秒時(shí),Nginx發(fā)出了FIN來關(guān)閉連接(75秒連接沒活躍了)。
如下是IE瀏覽器抓包數(shù)據(jù),在請(qǐng)求后第65秒左右時(shí),瀏覽器重置了連接。
可以看出不同瀏覽器超時(shí)處理方式不一樣,而HTTP響應(yīng)頭“Keep-Alive: timeout=30”對(duì)Chrome和IE都沒有起作用。
接著,如果keepalive_timeout 0,則瀏覽器會(huì)收到如下響應(yīng)頭。
對(duì)于客戶端超時(shí)設(shè)置,要根據(jù)實(shí)際場景來決定,如果是短連接服務(wù),則可以考慮設(shè)置的短一些,如果是文件上傳,則需要考慮設(shè)置的時(shí)間長一些。另外,筆者見過很多人長連接并沒有配置正確,建議配置完成后通過抓包查看長連接是否起作用了。keepalive_timeout和keepalive_requests是控制長連接的兩個(gè)維度,只要其中一個(gè)到達(dá)設(shè)置的閾值連接就會(huì)被關(guān)閉。
(2) DNS解析超時(shí)設(shè)置
resolver_timeout 30s:設(shè)置DNS解析超時(shí)時(shí)間,默認(rèn)為30s。其配合resolver address ... [valid=time]進(jìn)行DNS域名解析。當(dāng)在Nginx中使用域名時(shí),就需要考慮設(shè)置這兩個(gè)參數(shù)。在社區(qū)版Nginx中采用如下配置。
- upstream backend {
- server c0.3.cn;
- server c1.3.cn;
- }
如上兩個(gè)域名會(huì)在Nginx解析配置文件的階段被解析成IP地址并記錄到upstream上,當(dāng)這兩個(gè)域名對(duì)應(yīng)的IP地址發(fā)生變化時(shí),該upstream不會(huì)更新。Nginx商業(yè)版是支持動(dòng)態(tài)更新的。
一種簡單辦法是使用如下方式,每次都會(huì)動(dòng)態(tài)解析域名,這種情況在多域名情況下比較麻煩,實(shí)現(xiàn)就不優(yōu)雅了。
- location /test {
- proxy_pass http://c0.3.cn;
- }
如果使用Openresty,則可以通過Lua庫lua-resty-dns進(jìn)行DNS解析。
- localresolver = require "resty.dns.resolver"
- local r, err = resolver:new{
- nameservers = {"8.8.8.8",{"8.8.4.4", 53} },
- retrans = 5, -- 5 retransmissions on receive timeout
- timeout = 2000, -- 2 sec
- }
當(dāng)使用Nginx 1.5.8、1.7.4及遇到
- could not be resolved(110:Operation timed out);
或者
- wrong ident 37278 response for ***.jd.local, expected 33517
- unexpected response for ***.jd.local
可能是遇到了如下BUG(http://nginx.org/en/CHANGES-1.6、http://nginx.org/ en/CHANGES-1.8)。
- Bugfix: requests might hang if resolver was usedand a timeout
- occurred during a DNS request.
請(qǐng)考慮升級(jí)到Nginx 1.6.2、1.7.5或者在Nginx本機(jī)部署dnsmasq提升DNS解析性能。
(3) 代理超時(shí)設(shè)置
Nginx配置如下所示。
- upstream backend_server {
- server 192.168.61.1:9080 max_fails=2 fail_timeout=10s weight=1;
- server 192.168.61.1:9090 max_fails=2 fail_timeout=10s weight=1;
- }
- server {
- ……
- location /test {
- proxy_connect_timeout 5s;
- proxy_read_timeout 5s;
- proxy_send_timeout 5s;
- proxy_next_upstream error timeout;
- proxy_next_upstream_timeout 0;
- proxy_next_upstream_tries 0;
- proxy_pass http://backend_server;
- add_header upstream_addr $upstream_addr;
- }
- }
backend_server定義了兩個(gè)上游服務(wù)器192.168.61.1:9080(返回hello)和192.168.61.1:9090(返回hello2)。
如上指令主要有三組配置:網(wǎng)絡(luò)連接/讀/寫超時(shí)設(shè)置、失敗重試機(jī)制設(shè)置、upstream存活超時(shí)設(shè)置。
網(wǎng)絡(luò)連接/讀/寫超時(shí)設(shè)置。
- proxy_connect_timeout time:與后端/上游服務(wù)器建立連接的超時(shí)時(shí)間,默認(rèn)為60s,此時(shí)間不超過75s。
- proxy_read_timeout time:設(shè)置從后端/上游服務(wù)器讀取響應(yīng)的超時(shí)時(shí)間,默認(rèn)為60s,此超時(shí)時(shí)間指的是兩次成功讀操作間隔時(shí)間,而不是讀取整個(gè)響應(yīng)體的超時(shí)時(shí)間,如果在此超時(shí)時(shí)間內(nèi)上游服務(wù)器沒有發(fā)送任何響應(yīng),則Nginx關(guān)閉此連接。
- proxy_send_timeout time:設(shè)置往后端/上游服務(wù)器發(fā)送請(qǐng)求的超時(shí)時(shí)間,默認(rèn)為60s,此超時(shí)時(shí)間指的是兩次成功寫操作間隔時(shí)間,而不是發(fā)送整個(gè)請(qǐng)求的超時(shí)時(shí)間,如果在此超時(shí)時(shí)間內(nèi)上游服務(wù)器沒有接收任何響應(yīng),則Nginx關(guān)閉此連接。
對(duì)于內(nèi)網(wǎng)高并發(fā)服務(wù),請(qǐng)根據(jù)需要調(diào)整這幾個(gè)參數(shù),比如內(nèi)網(wǎng)服務(wù)TP999為1s,可以將連接超時(shí)設(shè)置為100~500毫秒,而讀超時(shí)可以為1.5~3秒左右。
失敗重試機(jī)制設(shè)置。
- proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504 |http_403 | http_404 | non_idempotent | off ...:
配置什么情況下需要請(qǐng)求下一臺(tái)上游服務(wù)器進(jìn)行重試。默認(rèn)為“errortimeout”。error表示與上游服務(wù)器建立連接、寫請(qǐng)求或者讀響應(yīng)頭出錯(cuò)。timeout表示與上游服務(wù)器建立連接、寫請(qǐng)求或者讀響應(yīng)頭超時(shí)。invalid_header表示上游服務(wù)器返回空的或錯(cuò)誤的響應(yīng)頭。http_XXX表示上游服務(wù)器返回特定的狀態(tài)碼。non_idempotent表示RFC-2616定義的非冪等HTTP方法(POST、LOCK、PATCH),也可以在失敗后重試下一臺(tái)上游服務(wù)器(即默認(rèn)冪等方法GET、HEAD、PUT、DELETE、OPTIONS、TRACE才可以重試)。off表示禁用重試。
重試不能無限制進(jìn)行,因此,需要如下兩個(gè)指令控制重試次數(shù)和重試超時(shí)時(shí)間。
- proxy_next_upstream_tries number:設(shè)置重試次數(shù),默認(rèn)0表示不限制,注意此重試次數(shù)指的是所有請(qǐng)求次數(shù)(包括第一次和之后的重試次數(shù)之和)。
- proxy_next_upstream_timeout time:設(shè)置重試最大超時(shí)時(shí)間,默認(rèn)0表示不限制。
即在proxy_next_upstream_timeout時(shí)間內(nèi)允許proxy_next_upstream_tries次重試。如果超過了其中一個(gè)設(shè)置,則Nginx也會(huì)結(jié)束重試并返回客戶端響應(yīng)(可能是錯(cuò)誤碼)。
如下配置表示當(dāng)error/timeout時(shí)重試upstream中的下一臺(tái)上游服務(wù)器,如果重試的總時(shí)間超出了6s或者重試了1次,則表示重試失敗(因?yàn)橹耙呀?jīng)請(qǐng)求一次了,所以還能重試一次),Nginx結(jié)束重試并返回客戶端響應(yīng)。
- proxy_next_upstream error timeout;
- proxy_next_upstream_timeout 6s;
- proxy_next_upstream_tries 2;
(4) upstream存活超時(shí)設(shè)置
max_fails和fail_timeout:配置什么時(shí)候Nginx將上游服務(wù)器認(rèn)定為不可用/不存活。當(dāng)上游服務(wù)器在fail_timeout時(shí)間內(nèi)失敗了max_fails次,則認(rèn)為該上游服務(wù)器不可用/不存活。并在接下來的fail_timeout時(shí)間內(nèi)從upstream摘掉該節(jié)點(diǎn)(即請(qǐng)求不會(huì)轉(zhuǎn)發(fā)到該上游服務(wù)器)。
什么情況下被認(rèn)定為失敗呢?其由 proxy_next_upstream定義,不過,不管 proxy_next_upstream如何配置,error, timeout and invalid_header 都將被認(rèn)為是失敗。
如server 192.168.61.1:9090max_fails=2 fail_timeout=10s;表示在10s內(nèi)如果失敗了2次,則在接下來的10s內(nèi)認(rèn)定該節(jié)點(diǎn)不可用/不存活。這種存活檢測機(jī)制是只有當(dāng)訪問該上游服務(wù)器時(shí),采取惰性檢查,可以使用ngx_http_upstream_check_module配置主動(dòng)檢查。
max_fails設(shè)置為0表示不檢查服務(wù)器是否可用(即認(rèn)為一直可用),如果upstream中僅剩一臺(tái)上游服務(wù)器時(shí),則該服務(wù)器是不會(huì)被摘除的,將從不被認(rèn)為不可用。
(5) ngx_lua超時(shí)設(shè)置
當(dāng)我們使用ngx_lua時(shí),也請(qǐng)考慮設(shè)置如下網(wǎng)絡(luò)連接/讀/寫超時(shí)。
- lua_socket_connect_timeout 100ms;
- lua_socket_send_timeout 200ms;
- lua_socket_read_timeout 500ms;
在使用lua時(shí),我們會(huì)按照如下策略進(jìn)行重試。
- if (status == 502 or status == 503 or status ==504) and request_time < 200 then
- resp =capture(proxy_uri)
- status =resp.status
- body =resp.body
- request_timerequest_time = request_time + tonumber(var.request_time) * 1000
- end
即如果狀態(tài)碼是500/502/503/504時(shí),并且該次請(qǐng)求耗時(shí)在200毫秒以內(nèi),則我們進(jìn)行一次重試。
2. Twemproxy
Twemproxy是Twitter開源的Redis和Memcache代理中間件,其目的是減少與后端緩存服務(wù)器的連接數(shù)。
- timeout:表示與后端服務(wù)器建立連接、接收響應(yīng)的超時(shí)時(shí)間,默認(rèn)永不超時(shí)。
- server_retry_timeout和server_failure_limit:當(dāng)開啟auto_eject_hosts,即當(dāng)后端服務(wù)器不可用時(shí)自動(dòng)摘除這些節(jié)點(diǎn)并在一定時(shí)間后進(jìn)行重試。server_failure_limit設(shè)置連續(xù)失敗多少次后將節(jié)點(diǎn)臨時(shí)摘除,server_retry_timeout設(shè)置摘除節(jié)點(diǎn)后等待多久進(jìn)行重試,從而保證不永久性的將節(jié)點(diǎn)摘除。
二、Web容器超時(shí)
筆者生產(chǎn)環(huán)境用的Java Web容器是Tomcat,本部分將以Tomcat8.5作為例子進(jìn)行講解。
- connectionTimeout:配置與客戶端建立連接超時(shí)時(shí)間,從接收到連接后在配置的時(shí)間內(nèi)還沒有接收到客戶端請(qǐng)求行時(shí),將被認(rèn)定為連接超時(shí),默認(rèn)為60000(60s)。
- socket.soTimeout:從客戶端讀取請(qǐng)求數(shù)據(jù)的超時(shí)時(shí)間,默認(rèn)同connectionTimeout,NIO and NIO2 支持該配置。
- asyncTimeout:Servlet 3異步請(qǐng)求的超時(shí)時(shí)間,默認(rèn)為30000(30s)。
- disableUploadTimeout 和connectionUploadTimeout:當(dāng)配置disableUploadTimeout為false時(shí)(默認(rèn)為true,和connectionTimeout一樣),文件上傳將使用connectionUploadTimeout作為超時(shí)時(shí)間。
- keepAliveTimeout和maxKeepAliveRequests:和Nginx配置類似。keepAliveTimeout默認(rèn)為connectionTimeout,配置-1表示永不超時(shí)。maxKeepAliveRequests默認(rèn)為100。
三、中間件客戶端超時(shí)與重試
JSF是京東自研的SOA框架,主要有三個(gè)組件:注冊(cè)中心、服務(wù)提供端、服務(wù)消費(fèi)端。
- 首先是服務(wù)提供端/消費(fèi)端與注冊(cè)中心之間的進(jìn)行服務(wù)注冊(cè)/發(fā)現(xiàn)時(shí)可以配置timeout(調(diào)用注冊(cè)中心超時(shí)時(shí)間,默認(rèn)為5s)和connectTimeout(連接注冊(cè)中心的超時(shí)時(shí)間,默認(rèn)為20s)。
- 服務(wù)提供端可以配置timeout(服務(wù)端調(diào)用超時(shí),默認(rèn)為5s)。
- 服務(wù)消費(fèi)端可以配置timeout(調(diào)用端調(diào)用超時(shí)時(shí)間,默認(rèn)為5s),connectTimeout(建立連接超時(shí)時(shí)間,默認(rèn)為5s),disconnectTimeout(斷開連接/等待結(jié)果超時(shí)時(shí)間,默認(rèn)為10s),reconnect(調(diào)用端重連死亡服務(wù)端的間隔,配置小于0表示不重連,默認(rèn)為10s),heartbeat(調(diào)用端往服務(wù)端發(fā)心跳包間隔,配置小于0代表不發(fā)送,默認(rèn)為30s),retries(失敗后重試次數(shù),默認(rèn)0不重試)。
Dubbo也有類似的配置,在此就不闡述了。
JMQ是京東消息中間件,主要有四個(gè)組件:注冊(cè)中心、Broker(JMQ的服務(wù)端實(shí)例,生產(chǎn)和消費(fèi)消息都跟它交互)、生產(chǎn)者、消費(fèi)者。
- 首先是生產(chǎn)者/消費(fèi)者與Broker進(jìn)行發(fā)送/接收消息時(shí)可以配置connectionTimeout(連接超時(shí))、sendTimeout(發(fā)送超時(shí))、soTimeout(讀超時(shí))。
- 生產(chǎn)者可以配置retryTimes(發(fā)送失敗后的重試次數(shù),默認(rèn)為2次)。
- 消費(fèi)者可以配置pullTimeout(長輪詢超時(shí)時(shí)間,即拉取消息超時(shí)時(shí)間)、maxRetrys(最大重試次數(shù),對(duì)于消費(fèi)者要允許無限制重試,即一直拉取消息)、retryDelay(重試延遲,通過exponential配置延遲增加倍數(shù)一直增加到maxRetryDelay)、maxRetryDelay(最大重試延遲)。消費(fèi)者還需要配置應(yīng)答超時(shí)時(shí)間(服務(wù)端需要等待客戶端返回應(yīng)答才能移除消息,如果沒有應(yīng)答返回,則會(huì)等待應(yīng)答超時(shí),在這段時(shí)間內(nèi)鎖定的消息不能被消費(fèi),必須等待超時(shí)后才能被消費(fèi))。
對(duì)于消息中間件我們實(shí)際應(yīng)用中關(guān)注超時(shí)配置會(huì)少一些,因?yàn)樯a(chǎn)者默認(rèn)配置了重試次數(shù),可能會(huì)存在重復(fù)消息,消費(fèi)者需要進(jìn)行去重處理。
CXF可以通過如下方式配置CXF客戶端連接超時(shí)、等待響應(yīng)超時(shí)和長連接。
- HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
- httpClientPolicy.setConnectionTimeout(30000);//默認(rèn)為30s
- httpClientPolicy.setReceiveTimeout(60000); //默認(rèn)為60s
- httpClientPolicy.setConnection(ConnectionType.KEEP_ALIVE);//默認(rèn)為Keep- Alive
- ((HTTPConduit)client.getConduit()).setClient(httpClientPolicy);
Httpclient 4.2.x可以通過如下代碼配置網(wǎng)絡(luò)連接、等待數(shù)據(jù)超時(shí)時(shí)間。
- HttpParams params = new BasicHttpParams();
- //設(shè)置連接超時(shí)時(shí)間
- Integer CONNECTION_TIMEOUT = 2 * 1000; //設(shè)置請(qǐng)求超時(shí)2秒鐘
- Integer SO_TIMEOUT = 2 * 1000; //設(shè)置等待數(shù)據(jù)超時(shí)時(shí)間2秒鐘
- Long CONN_MANAGER_TIMEOUT = 1L * 1000; //定義了當(dāng)從ClientConnectionManager中檢索ManagedClientConnection實(shí)例時(shí)使用的毫秒級(jí)的超時(shí)時(shí)間
- params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,CONNECTION_TIMEOUT);
- params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT,SO_TIMEOUT);
- //在提交請(qǐng)求之前,測試連接是否可用
- params.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK,true);
- //這個(gè)參數(shù)期望得到一個(gè)java.lang.Long類型的值。如果這個(gè)參數(shù)沒有被設(shè)置,則連接請(qǐng)求就不會(huì)超時(shí)(無限大的超時(shí)時(shí)間)
- params.setLongParameter(ClientPNames.CONN_MANAGER_TIMEOUT,CONN_MANAGER_TIMEOUT);
- PoolingClientConnectionManager conMgr = new PoolingClientConnectionManager();
- conMgr.setMaxTotal(200);//設(shè)置最大連接數(shù)
- //是路由的默認(rèn)最大連接(該值默認(rèn)為2),限制數(shù)量實(shí)際使用DefaultMaxPerRoute并非MaxTotal
- //設(shè)置過小,無法支持大并發(fā)(ConnectionPoolTimeoutException: Timeout waiting for connection frompool),路由是對(duì)maxTotal的細(xì)分
- conMgr.setDefaultMaxPerRoute(conMgr.getMaxTotal());//(目前只有一個(gè)路由,因此讓他等于最大值)
- //設(shè)置訪問協(xié)議
- conMgr.getSchemeRegistry().register(new Scheme("http",80, PlainSocketFactory. getSocketFactory()));
- conMgr.getSchemeRegistry().register(new Scheme("https",443, SSLSocketFactory. getSocketFactory()));
- httpClient = newDefaultHttpClient(conMgr, params);
- httpClient.setHttpRequestRetryHandler(newDefaultHttpRequestRetryHandler(0, false));
因?yàn)槲覀兪褂胔ttp connection連接池,所以需要配置CONN_MANAGER_TIMEOUT,表示從連接池獲取http connection的超時(shí)時(shí)間。
此處還通過httpClient.setHttpRequestRetryHandler(newDefaultHttpRequestRetry Handler(0, false))配置了請(qǐng)求重試策略(默認(rèn)重試3次)。當(dāng)執(zhí)行請(qǐng)求時(shí)遇到異常時(shí)會(huì)調(diào)用retryRequest來判斷是否進(jìn)行重試,而retryRequest在以下情況不會(huì)進(jìn)行重試:達(dá)到重試次數(shù)、服務(wù)器不可達(dá)、連接被拒絕、連接終止、請(qǐng)求已發(fā)送。而冪等HTTP方法的請(qǐng)求、requestSentRetryEnabled=true且請(qǐng)求還未成功發(fā)送時(shí)可以重試。
如果響應(yīng)是503錯(cuò)誤狀態(tài)碼時(shí),如上重試機(jī)制是不可用的,則可以考慮使用AutoRetryHttpClient客戶端,其可以配置ServiceUnavailableRetryStrategy,默認(rèn)實(shí)現(xiàn)為DefaultServiceUnavailableRetryStrategy,可以配置重試次數(shù)maxRetries和重試間隔retryInterval。每次重試之前都會(huì)等待retryInterval毫秒時(shí)間。
假設(shè)我們服務(wù)有多個(gè)機(jī)房提供,其中一個(gè)機(jī)房服務(wù)出現(xiàn)問題時(shí)應(yīng)該自動(dòng)切到另一個(gè)機(jī)房,可以考慮使用如下方法。
- public static String get(List<String> apis, Object[] args, String encoding,Header[] headers, Integer timeout) throws Exception {
- Stringresponse = null;
- for(String api : apis) {
- String uri =UriComponentsBuilder.fromHttpUrl(api).buildAndExpand(args). toUriString();
- response = HttpClientUtils.getDataFromUri(uri, encoding, headers,timeout);
- //如果失敗了,重試一次
- if(Objects.equal(response, HTTP_ERROR)){
- continue;
- }
- //如果域名解析失敗重試
- if(Objects.equal(response,HTTP_UNKNOWN_HOST_ERROR)) {
- response = HTTP_ERROR; //掉用方根據(jù)這個(gè)判斷是否有問題
- continue;
- }
- if(Objects.equal(response,HTTP_SOCKET_TIMEOUT_ERROR)) {
- response = HTTP_ERROR; //調(diào)用方根據(jù)這個(gè)判斷是否有問題
- continue;
- }
- return response;
- }
- return response;
- }
參數(shù)傳入不同機(jī)房的API即可,當(dāng)其中一個(gè)不可用自動(dòng)重試另一個(gè)機(jī)房的API。
【本文是51CTO專欄作者“張開濤”的原創(chuàng)文章,作者微信公眾號(hào):開濤的博客( kaitao-1234567)】