1. 前言(以下的springcloud版本是Dalston.RC1)
以下的springcloud版本是Dalston.RC1
Springcloud框架中,超時時間的設置通常有三個層面:
- zuul網關
#默認1000
zuul.host.socket-timeout-millis=2000
#默認2000
zuul.host.connect-timeout-millis=4000
- 1
- 2
- 3
- 4
- ribbon
ribbon: OkToRetryOnAllOperations: false #對所有操作請求都進行重試,默認false ReadTimeout: 5000 #負載均衡超時時間,默認值5000 ConnectTimeout: 3000 #ribbon請求連接的超時時間,默認值2000 MaxAutoRetries: 0 #對當前實例的重試次數,默認0 MaxAutoRetriesNextServer: 1 #對切換實例的重試次數,默認1
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 熔斷器Hystrix
hystrix: command: default: #default全局有效,service id指定應用有效 execution: timeout: #如果enabled設置為false,則請求超時交給ribbon控制,為true,則超時作為熔斷根據 enabled: true isolation: thread: timeoutInMilliseconds: 1000 #斷路器超時時間,默認1000ms feign.hystrix.enabled: true
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
2.測試各個配置的效果
這里我開了一個Eureka服務中心
開了兩個個服務eureka-client,端口分別為8087
和8088
,進行負載均衡
開了一個服務eureka-feign去調用eureka-client的方法,模擬eureka-client處理時間過長的時候出現的情況
eureka-client的方法:
/** * 測試重試時間 * * @return */ @RequestMapping("/timeOut") public String timeOut(@RequestParam int mills) { log.info("[client服務-{}] [timeOut方法]收到請求,阻塞{}ms", port, mills); try { Thread.sleep(mills); } catch (InterruptedException e) { e.printStackTrace(); } log.info("[client服務-{}] [timeOut]返回請求",port); return String.format("client服務-%s 請求ok!!!", port); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
eureka-feign調用client的方法,通過傳參數mills
來控制client線程休眠的時間
/** * 測試重試時間 * @return */ @RequestMapping("/timeOut") public String timeOut(@RequestParam int mills){ log.info("開始調用"); return feignService.timeOut( mills ); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
service:
/** * 測試springcloud的超時機制 * @param mills * @return */ @RequestMapping(value = "/timeOut",method = RequestMethod.GET) String timeOut(@RequestParam(value = "mills") int mills);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
熔斷方法:
@Override public String timeOut(int mills) { System.out.println("熔斷"); return "熔斷了"; }
- 1
- 2
- 3
- 4
- 5
測試1
ribbon: OkToRetryOnAllOperations: false #對所有操作請求都進行重試,默認false ReadTimeout: 1000 #負載均衡超時時間,默認值5000 ConnectTimeout: 3000 #ribbon請求連接的超時時間,默認值2000 MaxAutoRetries: 0 #對當前實例的重試次數,默認0 MaxAutoRetriesNextServer: 1 #對切換實例的重試次數,默認1 hystrix: command: default: #default全局有效,service id指定應用有效 execution: timeout: #如果enabled設置為false,則請求超時交給ribbon控制,為true,則超時作為熔斷根據 enabled: true isolation: thread: timeoutInMilliseconds: 5000 #斷路器超時時間,默認1000ms
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 測試 900ms
請求正常.
- 測試 2000ms
熔斷
接着測試4000ms, 6000都熔斷了
測試2
更換兩個超時時間:
ReadTimeout: 3000 #負載均衡超時時間,默認值5000
ConnectTimeout: 1000 #ribbon請求連接的超時時間,默認值2000
- 1
- 2
ribbon: OkToRetryOnAllOperations: false #對所有操作請求都進行重試,默認false ReadTimeout: 3000 #負載均衡超時時間,默認值5000 ConnectTimeout: 1000 #ribbon請求連接的超時時間,默認值2000 MaxAutoRetries: 0 #對當前實例的重試次數,默認0 MaxAutoRetriesNextServer: 1 #對切換實例的重試次數,默認1 hystrix: command: default: #default全局有效,service id指定應用有效 execution: timeout: #如果enabled設置為false,則請求超時交給ribbon控制,為true,則超時作為熔斷根據 enabled: true isolation: thread: timeoutInMilliseconds: 5000 #斷路器超時時間,默認1000ms
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
測試2000ms:
成功了
調用4000ms
熔斷了
測試6000ms也是熔斷
可見ReadTimeout
和ConnectTimeout
,當調用某個服務等待時間過長的時候, 對超時報錯/熔斷生效的是ReadTimeout
,ConnectTimeout
則表示連接服務的時間,一般不用配置太久,1~2秒左右就可以了
測試3
現在來測試ReadTimeout
和timeoutInMilliseconds
誰起作用
測試2中的配置如下:
ReadTimeout: 3000 #負載均衡超時時間,默認值5000
ConnectTimeout: 1000 #ribbon請求連接的超時時間,默認值2000
timeoutInMilliseconds: 5000 #斷路器超時時間,默認1000ms
- 1
- 2
- 3
在4000ms熔斷了,2000ms正常,說明是ReadTimeout
生效, 現在換成:
ReadTimeout: 5000 #負載均衡超時時間,默認值5000
ConnectTimeout: 1000 #ribbon請求連接的超時時間,默認值2000
timeoutInMilliseconds: 3000 #斷路器超時時間,默認1000ms
- 1
- 2
- 3
- 4
ribbon: OkToRetryOnAllOperations: false #對所有操作請求都進行重試,默認false ReadTimeout: 5000 #負載均衡超時時間,默認值5000 ConnectTimeout: 1000 #ribbon請求連接的超時時間,默認值2000 MaxAutoRetries: 0 #對當前實例的重試次數,默認0 MaxAutoRetriesNextServer: 1 #對切換實例的重試次數,默認1 hystrix: command: default: #default全局有效,service id指定應用有效 execution: timeout: #是否開啟超時熔斷 enabled: true isolation: thread: timeoutInMilliseconds: 3000 #斷路器超時時間,默認1000ms feign.hystrix.enabled: true
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
2000ms 正常
4000ms 熔斷
說明熔斷器timeoutInMilliseconds: 3000
起作用了
測試4
這里再測一個配置:
這個enable如果為false, 則表示熔斷器不根據自己配置的超時時間進行熔斷,這樣的話就會收到ribbon的ReadTimeout
配置的影響了,超過這個時間,eureka-feign會拋出timeout
的異常,這個時候熔斷器就會因為這個異常而進行熔斷
hystrix:
command:
default: #default全局有效,service id指定應用有效
execution:
timeout:
#是否開啟超時熔斷
enabled: false
- 1
- 2
- 3
- 4
- 5
- 6
- 7
測試4000ms 正常
測試6000ms 熔斷. 此處是因為ribbon的ReadTimeout: 5000
3.總結
由上面的測試可以得出:
- 如果
hystrix.command.default.execution.timeout.enabled
為true,則會有兩個執行方法超時的配置,一個就是ribbon的ReadTimeout
,一個就是熔斷器hystrix的timeoutInMilliseconds
, 此時誰的值小誰生效 - 如果
hystrix.command.default.execution.timeout.enabled
為false,則熔斷器不進行超時熔斷,而是根據ribbon的ReadTimeout
拋出的異常而熔斷,也就是取決於ribbon - ribbon的
ConnectTimeout
,配置的是請求服務的超時時間,除非服務找不到,或者網絡原因,這個時間才會生效 - ribbon還有
MaxAutoRetries
對當前實例的重試次數,MaxAutoRetriesNextServer
對切換實例的重試次數, 如果ribbon的ReadTimeout
超時,或者ConnectTimeout
連接超時,會進行重試操作 - 由於ribbon的重試機制,通常熔斷的超時時間需要配置的比
ReadTimeout
長,ReadTimeout
比ConnectTimeout
長,否則還未重試,就熔斷了 - 為了確保重試機制的正常運作,理論上(以實際情況為准)建議hystrix的超時時間為:
(1 + MaxAutoRetries + MaxAutoRetriesNextServer) * ReadTimeout
ribbon: OkToRetryOnAllOperations: false #對所有操作請求都進行重試,默認false ReadTimeout: 10000 #負載均衡超時時間,默認值5000 ConnectTimeout: 2000 #ribbon請求連接的超時時間,默認值2000 MaxAutoRetries: 0 #對當前實例的重試次數,默認0 MaxAutoRetriesNextServer: 1 #對切換實例的重試次數,默認1 hystrix: command: default: #default全局有效,service id指定應用有效 execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 20000 #斷路器超時時間,默認1000ms
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
4.微服務優化
4.1 什么是hystrix
我們先來看這么一個圖,假如訂單服務需要調用積分服務,庫存服務,倉儲服務,訂單服務的線程池有100個線程,這個時候積分服務突然掛了.這時候同時有大量的請求來訪問訂單服務,最終的結果是這100個線程都會卡在積分服務這里,這時候訂單服務也沒有多余的線程處理請求了,所以訂單服務也差不多掛了.
這就是微服務中的服務雪崩問題.
而這時你會發現,如果我只是看看這個商品還有多少庫存,那么訂單服務就只需要調用庫存服務就可以了,並不受積分服務的影響.
這個時候就是Hystrix的時刻了,Hystrix是隔離、熔斷以及降級的一個框架。
Hystrix的特點,就是針對不同的服務,會搞很多個小小的線程池,比如訂單服務請求庫存服務是一個單獨的線程池,請求積分服務是一個單獨的線程池. 這樣雖然積分服務的線程池全部卡住了,但是不影響庫存服務的調用.
4.2 服務降級和熔斷
現在庫存服務的問題解決了,積分服務還沒解決啊. 比如積分服務網絡很差,訂單服務要一直傻等積分服務響應嗎?單單看一個請求,用戶等個幾秒可能還沒什么,如果100個線程都卡住幾秒,后面的請求全部得不到處理.
所以我們可以讓Hystrix在一定時間后主動返回,不再等待,這就是熔斷.
降級,顧名思義,就是將不重要或者不緊急的任務,延遲處理,或者暫不處理.比如上面的超時熔斷,熔斷了怎么辦?獲取不到用戶的積分,直接給用戶提示網絡繁忙,請稍后再試,就是一種延遲處理. 比如秒殺活動,為了防止並發量太大,通常會采取限流措施,降級后的處理方案可以是:排隊頁面(將用戶導流到排隊頁面等一會重試)、無貨(直接告知用戶沒貨了)、錯誤頁(如活動太火爆了,稍后重試)。
4.3 微服務優化
了解了Hystrix的特性和超時效果,再看看下面這個圖,服務A調用服務B和服務C,服務C沒有太復雜的邏輯處理,300毫秒內就處理返回了,服務B邏輯復雜,Sql語句就長達上百行,經常要卡個5,6秒返回,在大量請求調用到服務B的時候,服務A調用服務B的hystrix線程池已經不堪重負,全部卡住
這里的話,首先考慮的就是服務B的優化,優化SQL,加索引,加緩存, 優化流程,同步改異步,總之縮短響應時間
一個接口,理論的最佳響應速度應該在200ms以內,或者慢點的接口就幾百毫秒。
a. 如何設置Hystrix線程池大小
Hystrix線程池大小默認為10
hystrix: threadpool: default: coreSize: 10
- 1
- 2
- 3
- 4
每秒請求數 = 1/響應時長(單位s) * 線程數 = 線程數 / 響應時長(單位s)
也就是
線程數 = 每秒請求數 * 響應時長(單位s) + (緩沖線程數)
標准一點的公式就是QPS * 99% cost + redundancy count
比如一台服務, 平均每秒大概收到20個請求,每個請求平均響應時長估計在500ms,
線程數 = 20 * 500 / 1000 = 10
為了應對峰值高並發,加上緩沖線程,比如這里為了好計算設為5,就是 10 + 5 = 15個線程
b. 如何設置超時時間
還拿上面的例子,比如已經配置了總線程是12個,每秒大概20個請求,那么極限情況,每個線程都飽和工作,也就是每個線程一秒內處理的請求為 20 / 15 = ≈ 1.3個 , 那每個請求的最大能接受的時間就是 1000 / 1.3 ≈ 769ms ,往下取小值700ms.
實際情況中,超時時間一般設為比99.5%平均時間略高即可,然后再根據這個時間推算線程池大小