聊聊微服务的隔离和熔断
今天來聊一聊微服務的隔離和熔斷是怎么做的, 如果你的項目沒有用微服務,不要走開,可以看看對一個問題的解決思路。
假設Tomcat線程池有100個線程, 每次有新的用戶請求過來,Tomcat就會從中找出一個空閑的線程去執行, 拋開那些瑣碎的小細節,這些請求其實非常簡單, 無非就是這么幾件事:?
1. 根據用戶ID調用用戶服務, 獲取用戶對象。?
2. 獲取該用戶的推薦商品?
3. 獲取該用戶的積分。?
4. 把這些信息組合起來,返回給瀏覽器。 ?
有意思的是前三件事情全是HTTP調用,需要調用某個地方的所謂“微服務”。?
有一次,線程A去執行幾個邏輯,等它調用“推薦服務”的時候,“推薦服務”遲遲沒有返回,線程A也許很高興, 終于可以休息了!?
新的用戶請求源源不斷地到來,線程池中越來越多的線程都在等待推薦服務返回。 ?
很快,100個線程全部用光,Tomcat只好掛出一個牌子: “系統繁忙,暫停營業。” ?
總之, 一個服務的出錯竟然導致了整個Tomcat不可用,實在是難以忍受。?
也許你會和運維商量一下,來個簡單粗暴的辦法: 給Tomcat線程池在增加100個線程兄弟, 可是這不能解決問題, 在高并發的情況下, 只要那些遠程的微服務有一個阻塞,無論多少線程,很快就會被用光。?
于是,你只好重啟Tomcat,毀滅這個可愛的世界,但是重啟后問題還是有可能發生。?
?
隔離
怎么把一個微服務的故障給隔離起來呢?讓他們互不影響呢??
Netflix的程序員們想了一個點子, 對每個微服務,都分配一個線程池,像這樣:?
比如說調用“推薦服務”的時候,就會從“推薦服務線程池” (假設有5個線程)中找到一個線程執行。如果這個HTTP系統調用遲遲沒有返回,那這個線程就會一直等待,新的請求就需用使用池中別的線程。 ?
如果5個線程都用光了,會發生什么情況??
這很簡單, 可以簡單地認為這個服務不可用了!馬上返回,絕不等待。?
這些新的線程池,是一種隔離的手段, 一個微服務一旦出了問題,很快就會被識別出來。 ??
?
熔斷器
但是上面這種方案,還是有一定的問題,如果這個推薦服務已經不可用了,還不斷地嘗試去調用,那肯定是一種浪費。
所以Netflix的程序員又想了一個辦法:使用熔斷器(也叫斷路器),注意:當這個熔斷器關閉的時候,外面的請求可以直接調用,如果打開,就把外界的請求給阻斷了。 ?
具體的做法是:系統會檢測請求失敗的比率(失敗數/總請求數), 一旦這個比率達到一個閾值的時候,熔斷器就開啟, 直接拒絕執行用戶請求。然后休眠一段時間,嘗試放過一部分流量(比如一個請求),如果調用成功,熔斷器閉合,恢復到正常狀態,否則繼續進行休眠周期。?
?
API
現在有了新的線程池,對程序員來講,該如何使用呢? 原來是這么做的: ?
UserService?service?=?...?獲得用戶服務... User?user?=?service.getUser(userID);現在,為了利用新的線程池, 需要做一層封裝:??
UserService?service?=?...?獲得用戶服務... UserServiceCmd??cmd?=?new?UserServiceCmd(service,?userID); User?user?=?cmd.execute();看到沒有??UserService 被封裝了一層, 放到了一個UserServiceCmd中去執行。?
這個Command代碼是這個樣子的:?
public?class?UserServiceCmd????extends?HystrixCommand<User>?{private?UserService?userService?=?null;private?String?userID?=?null;……public?UserServiceCmd(UserService?userService,String?userID)?{……this.userService?=?userService;this.userID?=?userID;}@Overrideprotected?User?run(){????????return?userService.getUser(userID);????????}@Overrideprotected?User?getFallback()?{????????return?annonymousUser;} }看起來非常簡單吧, 可是背后的魔法是什么呢??
實際上,在這個UserServiceCmd執行的時候,會使用另外一個線程池的線程去調用那個run()方法。?
(注:這是一種同步調用,實際上還可以異步調用)?
線程池的維護是在HystrixCommand這個父類中(命令模式),不需要程序員處理,程序員只需要告訴它: 我需要幾個線程,就可以了。?
眼光敏銳的你也許已經猜到,這里還采用了設計模式模板方法!?
HystrixCommand它定義了一個抽象的方法:?run(), 這個方法需要程序員去實現(例如前面的UserServiceCmd ?), 父類的的execute方法會調用程序員寫的run()方法。
你也許還會注意到,還有一個叫做getFallback()的方法,這是干嘛用的??
其實前面的例子中我們只說道了線程池耗盡的時候,直接返回。 但是大部分情況下總得返回一點兒東西吧,比如UserServiceCmd,我們也許可以返回一個匿名的用戶給調用方。
這就是所謂的撤退,退卻(Fallback)邏輯。?
當然,這個邏輯也可以用在熔斷器開啟,調用失敗,超時等情況下。?
一個粗略的、大致的流程圖是這樣的:?
Netflix把這些功能(當然,這里只是概要介紹,還有很多其他功能)給組裝起來,形成了一個開源的庫,叫做Hystrix,就是豪豬,渾身是刺,自我保護,還是挺貼切的。?
?
后記
剛寫完這個文章,就得到了一個”悲慘“的消息: Hystrix不再開發新功能,將進入維護模式。 考慮到Hystrix巨大的使用量,學習它還是非常有價值的。 ?
Netflix推薦大家轉向Resilience4j,看來又有新的玩具可以研究下了,興奮!?
這是個相對新的項目,影響力和使用量現在還不能和Hystrix相比。?
Resilience4j全面擁抱了Java 8和函數式編程, 他的核心功能包括:斷路器,限速,隔離(不再支持線程池),自動重試,響應的緩存, 看,核心的功能還是類似的, resilience4j能發展到什么程度,我們拭目以待吧。
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的聊聊微服务的隔离和熔断的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 写了 30 万行基础设施代码后,我们得出
- 下一篇: 人工智能会终结就业吗?