如何处理Dubbo调用超时
一、簡述
同步調用是一種阻塞式的調用方式,即 Consumer 端代碼一直阻塞等待,直到 Provider 端返回為止。dubbo默認的協議是netty, Netty 是 NIO 異步通訊機制,那么服務調用是怎么轉化為同步的呢?Dubbo是阿里開源的RPC框架,因為基于接口開發支持負載均衡、集群容錯、版本控制等特性,因此現在有很多互聯網公司都在使用Dubbo。
1??Dubbo有三個級別的超時設置分別為:
①針對方法設置超時時間
②在服務方設置超時時間
③在調用方設置超時時間
2??一般超時是調用端發生在請求發出后,無法在指定的時間內獲得對應的響應。原因大概有以下幾種情況:
①服務端確實處理比較慢,無法在指定的時間返回結果,調用端就自動返回一個超時的異常響應來結束此次調用。
②服務端如果響應的比較快,但當客戶端 Load 很高,負載壓力很大的時候,會因為客戶端請求發不出去、響應卡在 TCP Buffer 等問題,造成超時。因為客戶端接收到服務端發來的數據或者請求服務端的數據,都會在系統層面排隊,如果系統負載比較高,在內核態的時間占比就會加長,從而造成客戶端獲取到值時已經超時。
③通常是業務處理太慢,可在服務提供方機器上執行:jstack [PID] > jstack.log 分析線程都卡在哪個方法調用上,這里就是慢的原因。如果不能調優性能,請調高 timeout 閾值。
3??排查和解決步驟
①兩邊可能有 GC,檢查服務端和客戶端 GC 日志,耗時很長的 GC,會導致超時。超時的發生很可能意味著調用端或者服務端的資源(CPU、內存或者網絡)出現了瓶頸,需要檢查服務端的問題還是調用端的問題來排除GC抖動等嫌疑。
②檢查服務端的網絡質量,比如重傳率來排除網絡嫌疑。
③借助鏈路跟蹤的分析服務(比如阿里的 ARMS,開源的 OpenTracing 系的實現 Zipkin、SkyWalking 等)來分析下各個點的耗時情況。
4??Dubbo調用超時(client-side timeout)后會有兩種情況:
①客戶端會收到一個TimeoutException異常
②服務端會收到一個警告The timeout response finally returned at xxx
看起來還蠻正常的,但是實際上會有這樣問題:調用超時后服務端還是會繼續執行,該如何處理呢?
?
@Service(version = "1.0") @Slf4j public class DubboDemoServiceImpl implements DubboDemoService {public String sayHi(String name) {try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}String result = "hi: " + name;log.info("Result: {}" , result);return result;} }服務非常簡單,三秒后返回字符串。controller層:
?
@RestController @RequestMapping public class DubboDemoController {@Reference(url = "dubbo://127.0.0.1:22888?timeout=2000", version = "1.0")private DubboDemoService demoService;@GetMappingpublic ResponseEntity<String> sayHi(@RequestParam("name") String name){return ResponseEntity.ok(demoService.sayHi(name));} }連接DubboDemoService服務使用的直連方式dubbo://127.0.0.1:22888?timeout=2000,演示中的超時時間都由url中的timeout指定。
二、Consumer超時處理
前面服務端的sayHi()實現休眠3秒,而連接服務時指定的超時時間是2000ms,那肯定會收到一個TimeoutException異常:
?
There was an unexpected error (type=Internal Server Error, status=500). Invoke remote method timeout. method: sayHi客戶端超時處理比較簡單,既然發生了異常也能捕獲到異常那是該回滾還是不做處理,完全可以由開發者解決。
?
try{return ResponseEntity.ok(demoService.sayHi(name)); }catch (RpcException te){//do something...log.error("consumer", te);return msg; }重點還是解決服務方的超時異常。
三、Provider超時處理
Provider的處理沒有客戶端那樣簡單,因為Provider不會收到異常,而且線程也不會中斷,這樣就會導致Consumer超時數據回滾,而Provider繼續執行最終執行完數據插入成功,數據不一致。
?
?
上面Provider方法休眠3000ms且Consumer的超時是參數是2000ms。調用發生2000ms后就會發生超時,而Provider的sayHi()不會中斷在1000ms后打印hi xx。
?
很明顯要保持數據一致就需要在超時后,將Provider的執行終止或回滾才行,如何做到數據一致性呢?
1??重試機制
Dubbo自身有重試機制,調用超時后會發起重試,Provider端需考慮冪等性。
2??最終一致性
使用補償事務或異步MQ保持最終一致性,需要寫一些與業務無關的代碼來保持數據最終一致性。比如在Provider端加個check方法,檢查是否成功,具體實現還需要結合自身的業務需求來處理。
?
@GetMapping public ResponseEntity<String> sayHi(String name){try{return ResponseEntity.ok(demoService.sayHi(name));}catch (RpcException te){//do something...try{demoService.check(name);}catch (RpcException ignore){}log.error("consumer", te);return msg;} }雖然可以通過添加檢查來驗證業務狀態,但是這個調用執行時間是沒辦法準確預知的,所以這樣簡單的檢測是效果不大,最好還是通過MQ來做這樣的檢測。
3??基于時間回滾
原理比較簡單,在Consumer端調用時設置兩個參數ctime、ttime分別表示調用時間、超時時間,將參數打包發給Provider。Provider收到兩個參數后進行操作,如果執行時間越過ttime則回滾數據,否則正常執行。改造下代碼:
?
public ResponseEntity<String> sayHi(@RequestParam("name") String name){try{RpcContext context = RpcContext.getContext();context.setAttachment("ctime", System.currentTimeMillis() + "");context.setAttachment("ttime", 2000 + "");return ResponseEntity.ok(demoService.sayHi(name));}catch (RpcException te){//do something...log.error("consumer", te);return msg;}}將ctime、ttime兩個參數傳到Provider端處理:
?
public String sayHi(String name) {long curTime = System.currentTimeMillis();String ctime = RpcContext.getContext().getAttachment("ctime");String ttime = RpcContext.getContext().getAttachment("ttime");long ctimeAsLong = Long.parseLong(ctime);long ttimeAsLong = Long.parseLong(ttime);try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}long spent = System.currentTimeMillis() - curTime;if(spent >= (ttimeAsLong - ctimeAsLong - curTime)){throw new RpcException("Server-side timeout.");}String result = "hi: " + name;log.info("Result: {}" , result);return result;}?
?
畫個圖看一下執行的時間線:
?
從上圖在執行完成后,響應返回期間這段時間是計算不出來的,所以這種辦法也不能完全解決Provider超時問題。
四、dubbo中配置的優先級
dubbo作為一個服務治理框架,功能相對比較完善,性能也挺不錯。要知道dubbo中配置是有優先級的,以免出現調優參數設置了卻沒發現效果實際是配置被覆蓋導致這樣的問題。dubbo分為consumer和provider端,在配置各個參數時,其優先級如下:
1、consumer的method配置
2、provider的method配置
3、consumer的reference配置
4、provider的service配置
5、consumer的consumer節點配置
6、provider的provider節點配置
可以看到,方法級的配置優先級高于接口級,consumer的優先級高于provider。同時,在本地參數配置還存在一層優先級:
1、系統參數(-D),如-Ddubbo.protocol.port=20003
2、xml配置
3、property文件
了解了這兩個優先級,調優起來才會更加清晰,省去了一些諸如配置設置了不生效這樣的麻煩。注意,其實dubbo中還可以通過將配置寫入注冊中心的方式覆蓋用戶配置(優先級高于系統參數)。
作者:日常更新
鏈接:https://www.jianshu.com/p/75f7fbe4944f
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
總結
以上是生活随笔為你收集整理的如何处理Dubbo调用超时的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 农商银行营业时间 通常为上午9点到下午5
- 下一篇: 跌停不封板意味着什么