1 Overview
常見的微服務(wù)架構(gòu):
做好超時(shí)時(shí)間的限定,用于判定超時(shí)后資源能夠及時(shí)被釋放,用于處理其它的請求筷频,從而提升的性能熏挎。
- 前端:Ajax速勇、Node
- 代理層:DNS、LB(SLB坎拐、F5烦磁、Keepalived+LVS、Haproxy哼勇、A10等)都伪、Ngixn、Gateway
- 服務(wù)容器:Tomcat积担、Jetty
- 中間件:Feign陨晶、Dubbo、HTTPClient帝璧、ES先誉、MongoDB、Redis
- 數(shù)據(jù)庫:MySQL聋溜、Oracle
2 Solution
2.1 Front-end Timeout
2.1.1 ajax Timeout
? ajax底層使用的是XMLHttpRequest谆膳,其超時(shí)參數(shù)可以設(shè)置:連接超時(shí)、讀超時(shí)和寫超時(shí)撮躁。但對于包裝后的ajax漱病,我們通常只需要設(shè)置請求超時(shí)時(shí)間(timeout)即可,具體案例如下:
var ajaxTimeoutTest = $.ajax({
url:'/demo',
// 設(shè)置請求超時(shí)時(shí)間(毫秒)把曼,此設(shè)置將覆蓋全局設(shè)置
timeout : 1000,
type : 'get',
data :{},
dataType:'json',
success:function(data){
alert("成功");
},
complete : function(XMLHttpRequest,status){
// 超時(shí)處理: status還有success,error等值的情況
if(status=='timeout'){
ajaxTimeoutTest.abort();
alert("超時(shí)");
}
}
});
2.1.2 Node.js Timeout
- Server Timeout
const http = require("http");
const server = http.createServer( function(req, res){
// ......
});
// 設(shè)置服務(wù)端請求處理的超時(shí)時(shí)間
server.setTimeout(30 * 1000);
server.listen(3000, "localhost", function(){
console.log("開始監(jiān)聽"+server.address().port+"......");
});
- Client Timeout
const http = require('http');
const options = {host: 'localhost', method: 'GET', port: 8080, path: '/test'}
var req = http.request(options);
// 設(shè)置客戶端每個(gè)外調(diào)的超時(shí)時(shí)間
req.setTimeout(20 * 1000);
req.on('response', (res) => {
res.setEncoding('utf8');
res.on('data', function(chunk){
console.log('收到數(shù)據(jù):%s', chunk);
});
res.on('end', function(){
console.log(res.trailers);
});
});
req.end();
2.2 Ngixn Timeout
2.2.1 keepalive_timeout
? HTTP是一種無狀態(tài)協(xié)議杨帽,其客戶端底層向服務(wù)器發(fā)送一個(gè)TCP請求,服務(wù)端響應(yīng)完畢后就會斷開連接嗤军。如果客戶端向服務(wù)器發(fā)送多個(gè)請求注盈,每個(gè)請求都要建立各自獨(dú)立的連接以傳輸數(shù)據(jù)。
? HTTP的KeepAlive就用于告訴服務(wù)器在處理完請求后保持一段這個(gè)TCP連接的打開狀態(tài)叙赚。若接收到來自客戶端的其它請求老客,服務(wù)端會利用這個(gè)未被關(guān)閉的連接僚饭,而不需要再建立一個(gè)連接。KeepAlive在一段時(shí)間內(nèi)保持打開狀態(tài)胧砰,它們會在這段時(shí)間內(nèi)占用資源鳍鸵,但占用過多就會影響性能。
? 因此尉间,Nginx使用 keepalive_timeout
來指定KeepAlive的超時(shí)時(shí)間偿乖,用于指定每個(gè)TCP 連接最多可以保持多長時(shí)間。Nginx的默認(rèn)值是75 秒
哲嘲,然而有些瀏覽器最多只保持 60秒
贪薪,所以可以設(shè)定為 60 秒
更安全。若將它設(shè)置為 0
眠副,就禁止了 keepalive 連接画切。
# 配置段: http、server侦啸、location, 默認(rèn)值是75秒
keepalive_timeout 60s;
2.2.2 client_body_timeout
? 用于指定客戶端與服務(wù)端建立連接后發(fā)送 request body
的超時(shí)時(shí)間槽唾,如果客戶端在指定時(shí)間內(nèi)沒有發(fā)送一個(gè)完整的 request body
,Nginx就會返回 HTTP 408(Request Timed Out)
光涂。
# 配置段: http庞萍、server、location
client_body_timeout 20s;
2.2.3 client_header_timeout
? 客戶端向服務(wù)端發(fā)送一個(gè)完整的 request header
的超時(shí)時(shí)間忘闻,如果客戶端在指定時(shí)間內(nèi)沒有發(fā)送一個(gè)完整的 request header
钝计,Nginx 返回 HTTP 408(Request Timed Out)
。
# 配置段: http齐佳、server私恬、location
client_header_timeout 10s;
2.2.4 proxy_upstream_fail_timeout
? fail_timeout通常是配合max_fails一起來使用的,實(shí)現(xiàn)熔斷隔離的功能炼吴。其作用主要是指在 30
秒內(nèi)請求某一應(yīng)用失敗 3
次本鸣,則認(rèn)為該應(yīng)用宕機(jī),之后會等待 30
秒硅蹦,這期間內(nèi)不會再把新請求發(fā)送到宕機(jī)應(yīng)用荣德,而是直接發(fā)到正常的那一臺。時(shí)間到后再有請求進(jìn)來童芹,則繼續(xù)嘗試連接宕機(jī)應(yīng)用且僅嘗試 1
次涮瞻,如果還是失敗,則繼續(xù)等待 30
秒…...以此循環(huán)假褪,直到恢復(fù)署咽。
# 配置段: upstream, fail_timeout默認(rèn)為10s, max_fails默認(rèn)為1
upstream web_tomcat {
server 127.0.0.1:8081 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8082 max_fails=3 fail_timeout=30s;
}
2.2.5 proxy_connect_timeout
? 用于設(shè)置Nginx向后端服務(wù)器的連接超時(shí)時(shí)間,即為發(fā)起TCP握手等候響應(yīng)的超時(shí)時(shí)間生音。
# 配置段: http宁否、server窒升、location, 默認(rèn)為60s
location / {
proxy_connect_timeout 500s;
proxy_pass http://web_tomcat;
}
2.2.6 proxy_read_timeout
? 連接成功后,等候后端服務(wù)器響應(yīng)時(shí)間家淤,其實(shí)已經(jīng)進(jìn)入后端的排隊(duì)之中等候處理异剥,也可以說是后端服務(wù)器處理請求的 時(shí)間。
# 配置段: http絮重、server、location, 默認(rèn)為60s
location / {
proxy_read_timeout 500s;
proxy_pass http://web_tomcat;
}
2.2.7 proxy_send_timeout
? 用于設(shè)置后端服務(wù)器數(shù)據(jù)回傳時(shí)間歹苦,就是在規(guī)定時(shí)間之內(nèi)后端服務(wù)器必須傳完所有的數(shù)據(jù)青伤。
# 配置段: http、server殴瘦、location, 默認(rèn)為60s
location / {
proxy_send_timeout 500s;
proxy_pass http://web_tomcat;
}
2.2.8 Others
- resolver_timeout:域名解析超時(shí)狠角,默認(rèn)30s。配置段:http蚪腋、server丰歌、location
- lingering_timeout:設(shè)置TCP連接關(guān)閉時(shí)的SO_LINGER延時(shí),默認(rèn)為5s屉凯。配置段:http立帖、server、location
- tcp_nodelay:默認(rèn)情況下悠砚,當(dāng)數(shù)據(jù)發(fā)送時(shí)晓勇,內(nèi)核并不會馬上發(fā)送,可能會等待更多的字節(jié)組成一個(gè)數(shù)據(jù)包灌旧,這樣可以提高 I/O 性能绑咱,但是在每次只發(fā)送很少字節(jié)的業(yè)務(wù)場景中,等待時(shí)間會比較長
注意事項(xiàng):
- 客戶端連接Nginx超時(shí)枢泰,建議5s內(nèi)
- proxy_connect_timeout的值不能超過75s
- 通常client_body_timeout應(yīng)該比keepalive_timeout小
擴(kuò)展:tcp_nodelay與tcp_nopush
- tcp_nodelay:開啟或關(guān)閉Nginx使用TCP_NODELAY選項(xiàng)的功能
- tcp_nopush:開啟或者關(guān)閉Nginx在FreeBSD上使用TCP_NOPUSH套接字選項(xiàng)的功能
# tcp_nodelay配置段: http描融、server、location, 默認(rèn)值為 tcp_nodelay on;
# tcp_nopush配置段: http衡蚂、server窿克、location, 默認(rèn)值為 tcp_nopush off;
http {
tcp_nodelay on;
}
2.3 Gateway Timeout
2.3.1 Zuul Timeout
-
使用Ribbon路由
Zuul的超時(shí)與Ribbon、Hystrix相關(guān)(RibbonRoutingFilter整合了Hystrix和Ribbon)讳窟,此時(shí)Zuul的超時(shí)可以配置如下:# Hystrix,設(shè)置調(diào)用者等待命令執(zhí)行的超時(shí)限制,超過此時(shí)間,HystrixCommand被標(biāo)記為TIMEOUT让歼,并執(zhí)行回退邏輯 hystrix.command.xxx.execution.isolation.thread.timeoutInMilliseconds: 1000 # Ribbon ribbon: read-timeout: 1000 connect-timeout: 1000
-
未使用Ribbo路由(SimpleHostRoutingFilter整合了Apache HttpClient)
zuul.routes.xxx.path: /user/** zuul.routes.xxx.url: http://localhost:8000/ # TCP連接超時(shí)時(shí)間 zuul.host.connect-timeout-millis: 2000 # Socket超時(shí),即數(shù)據(jù)傳輸?shù)某瑫r(shí)時(shí)間 zuul.host.socket-timeout-millis: 10000
2.4 Middleware Timeout
2.4.1 Ribbon Timeout
全局配置:
ribbon:
read-timeout: 60000
connect-timeout: 60000
局部配置:
service-id:
ribbon:
read-timeout: 1000
connect-timeout: 1000
2.4.2 Feign Timeout
? 從Spring Cloud Edgware開始,F(xiàn)eign支持使用屬性配置超時(shí)(對于老版本丽啡,可以寫個(gè)feign.Request.Options
即可):
feign.client.config:
feign-name:
connect-timeout: 5000
read-timeout: 5000
2.4.3 RestTemplate Timeout
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(1000);
factory.setReadTimeout(1000);
return new RestTemplate(factory);
}
2.4.4 Hystrix Timeout
# 默認(rèn)開啟超時(shí)機(jī)制
hystrix.command.default|xxx.execution.timeout.enabled: true
# 是否打開超時(shí)線程中斷, Thread模式有效
hystrix.command.default|xxx.execution.isolation.thread.interruptOnTimeout: true
# 超時(shí)時(shí)間, 默認(rèn)為1秒:
# 1.在THREAD模式下,達(dá)到超時(shí)時(shí)間,可以中斷
# 2.在SEMAPHORE模式下,會等待執(zhí)行完成后,再去判斷是否超時(shí)
hystrix.command.default|xxx.execution.isolation.thread.timeoutInMilliseconds: 1000
2.4.5 Tomcat Timeout
? tomcat對每個(gè)請求的超時(shí)時(shí)間是通過connectionTimeout
參數(shù)設(shè)置的谋右。默認(rèn)的server.xml里的設(shè)置是20秒,如果不設(shè)置這個(gè)參數(shù)代碼里會使用60秒补箍。這個(gè)參數(shù)也會對POST請求有影響改执,但并不是指上傳完的時(shí)間限制啸蜜,而是指兩次數(shù)據(jù)發(fā)送中間的間隔超過connectionTimeout
會被服務(wù)器斷開。
<Connector port="7001" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
? 如果connectionTimeout配置為20000辈挂,這個(gè)配置導(dǎo)致建立一個(gè)socket連接后衬横,如果一直沒有收到客戶端的FIN,也沒有數(shù)據(jù)過來终蒂,那么此連接也必須等到20s后蜂林,才能被超時(shí)釋放。
2.4.6 Dubbo Timeout
? Dubbo協(xié)議超時(shí)實(shí)現(xiàn)使用了Future模式拇泣。ResponseFuture.get()在請求還未處理完或未到超時(shí)前一直是wait狀態(tài)噪叙;響應(yīng)達(dá)到后,設(shè)置請求狀態(tài)霉翔,并進(jìn)行notify喚醒睁蕾。即使用了Object的 await-notify-notifyAll
機(jī)制。
Dubbo消費(fèi)端
- 全局超時(shí)配置
<dubbo:consumer timeout="5000" />
- 指定接口以及特定方法超時(shí)配置
<dubbo:reference interface="com.foo.BarService" timeout="2000"> <dubbo:method name="sayHello" timeout="3000" /> </dubbo:reference>
Dubbo服務(wù)端
- 全局超時(shí)配置
<dubbo:provider timeout="5000" />
- 指定接口以及特定方法超時(shí)配置
<dubbo:provider interface="com.foo.BarService" timeout="2000"> <dubbo:method name="sayHello" timeout="3000" /> </dubbo:provider>
2.5 DB Timeout
以下是應(yīng)用(WAS/BLOC)债朵、連接池(DBCP)子眶、Timeout層級和DBMS直接的關(guān)系圖:
解釋說明:
- statement timeout無法處理網(wǎng)絡(luò)連接失敗時(shí)的超時(shí),它能做的僅僅是限制statement的操作時(shí)間
- 網(wǎng)絡(luò)連接失敗時(shí)的timeout必須交由JDBC來處理
- JDBC的socket timeout會受到操作系統(tǒng)socket timeout設(shè)置的影響
- timeout層級與DBCP是相互獨(dú)立序芦,DBCP負(fù)責(zé)的是數(shù)據(jù)庫連接的創(chuàng)建和管理臭杰,并不干涉timeout的處理
- 在應(yīng)用中調(diào)用DBCP的getConnection()時(shí),你可以設(shè)置獲取數(shù)據(jù)庫連接的超時(shí)時(shí)間芝加,但是這和JDBC的timeout無關(guān)
案例:JDBC連接會在網(wǎng)絡(luò)出錯(cuò)后阻塞30分鐘硅卢,然后又奇跡般恢復(fù),即使并沒有對JDBC的socket timeout進(jìn)行設(shè)置
2.5.1 Transaction Timeout
? 一般存在于框架或應(yīng)用級藏杖,用于設(shè)置是一個(gè)事務(wù)的執(zhí)行總時(shí)間将塑,其中可能包含多個(gè)statement。在Spring中可以使用XML或在源碼中使用@Transactional注解來進(jìn)行設(shè)置蝌麸。
- 1個(gè)statement ~ 0.1s点寥,10w個(gè)statement ~ 1w秒(約7個(gè)小時(shí))
- 1個(gè)statement × 1個(gè)statement執(zhí)行200ms,則transaction timeout至少應(yīng)該設(shè)置為:1100ms(200×5+100)
2.5.2 Statement Timeout
? 用于設(shè)置單個(gè)statement的執(zhí)行超時(shí)時(shí)間来吩,即Driver等待statement執(zhí)行完成敢辩,接收到數(shù)據(jù)的超時(shí)時(shí)間。timeout的值通過調(diào)用JDBC的java.sql.Statement.setQueryTimeout(int timeout) API進(jìn)行設(shè)置弟疆,但更多的是是通過框架來進(jìn)行設(shè)置戚长。
注意:
- statement timeout的具體值需要依據(jù)應(yīng)用本身的特性而定,并
沒有可供推薦的配置
- statement的timeout不是整個(gè)查詢的timeout怠苔,只是statement執(zhí)行完成并拉取數(shù)據(jù)返回的超時(shí)時(shí)間
MySQL JDBC Statement的QueryTimeout處理過程
解釋說明:
- statement創(chuàng)建一個(gè)新的timeout-execution線程用于超時(shí)處理同廉,5.1版本后改為每個(gè)connection分配一個(gè)timeout-execution線程
- 達(dá)到超時(shí)時(shí)間,TimerThread調(diào)用JtdsStatement實(shí)例中的TsdCore.cancel()方法,timeout-execution線程創(chuàng)建一個(gè)和statement配置相同的connection迫肖,向超時(shí)query發(fā)送:
cancel query(KILL QUERY “connectionId”)
2.5.3 JDBC socket timeout
? 用于設(shè)置jdbc I/O socket read and write operations的超時(shí)時(shí)間锅劝,防止因網(wǎng)絡(luò)問題或數(shù)據(jù)庫問題,導(dǎo)致Driver會一直阻塞等待蟆湖。(建議比statement timeout的時(shí)間長)
-
mysql(單位為毫秒)
jdbc:mysql://localhost:3306/ag_admin?useUnicode=true&characterEncoding=UTF8&connectTimeout=60000&socketTimeout=60000
-
pg(單位為秒)
jdbc:postgresql://localhost/test?user=fred&password=secret&&connectTimeout=60&socketTimeout=60
-
oracle
? oracle需要通過oracle.jdbc.ReadTimeout參數(shù)來設(shè)置故爵,連接超時(shí)參數(shù)是oracle.net.CONNECT_TIMEOUT∮缃颍可以通過以下兩種方式進(jìn)行設(shè)置:通過properties設(shè)置
Class.forName("oracle.jdbc.driver.OracleDriver"); Properties props = new Properties() ; props.put( "user" , "test_schema") ; props.put( "password" , "pwd") ; props.put( "oracle.net.CONNECT_TIMEOUT" , "10000000") ; props.put( "oracle.jdbc.ReadTimeout" , "2000" ) ; Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@127.0.0.1:1521:orcl" , props ) ;
通過環(huán)境變量設(shè)置 —— 注意需要在connection連接之前設(shè)置環(huán)境變量
String readTimeout = "10000"; // ms System.setProperty("oracle.jdbc.ReadTimeout", readTimeout); Class.forName("oracle.jdbc.OracleDriver"); Connection conn = DriverManager.getConnection(jdbcUrl, user, pwd);
2.5.4 OS socket timeout
? 這是操作系統(tǒng)級別的socket設(shè)置诬垂,用來檢測壞死socket連接,Linux一般默認(rèn)2小時(shí)饥瓷。如果jdbc socket timeout沒有設(shè)置剥纷,而OS級別的socket timeout有設(shè)置,則使用系統(tǒng)的socket timeout值呢铆。
# 查看OS的keepalive配置信息
sudo sysctl -a|grep keepalive
# 修改OS的keepalive配置信息,并修改以下配置信息
vim /etc/sysctl.conf
# 表示TCP連接在多少秒之后沒有數(shù)據(jù)報(bào)文傳輸時(shí)啟動(dòng)探測報(bào)文(發(fā)送空的報(bào)文),單位為秒(s)
net.ipv4.tcp_keepalive_time = 7200
# 表示前一個(gè)探測報(bào)文和后一個(gè)探測報(bào)文之間的時(shí)間間隔,單位為秒(s)
net.ipv4.tcp_keepalive_intvl = 75
# 表示探測的次數(shù)
net.ipv4.tcp_keepalive_probes = 9
# 讓修改的參數(shù)即時(shí)生效
sysctl -p
總結(jié)
? jdbc的socketTimeout值的設(shè)置要非常小心,不同數(shù)據(jù)庫的jdbc driver設(shè)置不一樣蹲缠,特別是使用不同連接池的話棺克,設(shè)置也可能不盡相同。對于嚴(yán)重依賴數(shù)據(jù)庫操作的服務(wù)來說线定,非常有必要設(shè)置這個(gè)值娜谊,否則萬一網(wǎng)絡(luò)或數(shù)據(jù)庫異常,會導(dǎo)致服務(wù)線程一直阻塞在java.net.SocketInputStream.socketRead0斤讥。
- 如果查詢數(shù)據(jù)多纱皆,則會導(dǎo)致該線程持有的data list不能釋放,相當(dāng)于內(nèi)存泄露芭商,最后導(dǎo)致OOM
- 如果請求數(shù)據(jù)庫操作很多且阻塞住了派草,會導(dǎo)致服務(wù)器可用的woker線程變少,嚴(yán)重則會導(dǎo)致服務(wù)不可用
3 Practice
3.1 Focus
各層組件的超時(shí)時(shí)間铛楣,主要是設(shè)置以下兩個(gè)參數(shù):
- connectTimeout
- socketTimeout
當(dāng)然針對特殊的場景近迁,則可以設(shè)置更詳細(xì)的超時(shí)參數(shù),如:
- readTimeout
- writeTimeout
3.2 Suggest
ajax —— 5s ~ 60s
- 建議全局設(shè)置一個(gè)統(tǒng)一的超時(shí)時(shí)間簸州,如60s
- 從使用的互聯(lián)網(wǎng)產(chǎn)品來看鉴竭,一般網(wǎng)絡(luò)較差時(shí),加載網(wǎng)頁可能需要等待30秒或1分鐘左右后才出現(xiàn)網(wǎng)絡(luò)異常等的情況
- 特殊場景自定義設(shè)置超時(shí)時(shí)間岸浑,從而覆蓋全局超時(shí)時(shí)間
- 如上傳較大文件時(shí)搏存,則可以設(shè)置時(shí)間更長(當(dāng)然太大的文件,則建議單獨(dú)考慮矢洲,如分塊處理等)
- 如實(shí)時(shí)性要求較高的場景璧眠,則可以設(shè)置更短,如5s等
Ngixn
- 建議設(shè)置
keepalived_time
來提高Ngixn支持的并發(fā)能力與復(fù)用HTTP建立的TCP連接,如設(shè)置為5s - 建議設(shè)置
client_body_timeout
和client_header_timeout
蛆橡,用于防止客戶攻擊Dos攻擊舌界,如分別20s、10s - 建議設(shè)置
max_fails
和fail_timeout
泰演,解決每次請求宕機(jī)服務(wù)端時(shí)呻拌,都需要等待超時(shí)問題,如分別為3次睦焕、30s - 建議設(shè)置
proxy_connect_timeout
藐握、proxy_send_timeout
和proxy_read_timeout
參數(shù),用于控制Ngixn轉(zhuǎn)發(fā)到后臺的超時(shí)控制
Node
使用Node作為網(wǎng)關(guān)代理轉(zhuǎn)發(fā)請求時(shí):
- Server —— 60s
- 如果代理層有一定的功能邏輯垃喊,則建議加上Server的處理超時(shí)時(shí)間
- 如果代理層幾乎沒有邏輯猾普,則Server層的超時(shí)可以不配置
- Client
Client用于代理轉(zhuǎn)發(fā),而后端業(yè)務(wù)場景不同本谜,要求也有所不同初家,所以建議設(shè)置較長的默認(rèn)值,并支持請求自定義
Middleware
Ribbon乌助、Zuul(Apache HTTPClient)溜在、Feign、RestTemplate和Netty等他托,都建議必須設(shè)置以下兩個(gè)參數(shù):
- connectTimeout
- socketTimeout
Hystrix
使用Hystrix時(shí)掖肋,建議設(shè)置提交線程后的等待超時(shí)時(shí)間:thread.timeoutInMilliseconds
,默認(rèn)為1000ms
DB
- Transaction Timeout
- connectTimeout
- socketTimeout
- OS socket timeout