在基于Spring Cloud的微服務(wù)架構(gòu)體系下,按照系統(tǒng)功能邊界的不同劃分,原先大而全的系統(tǒng)會被拆分為多個不同的微服務(wù),而相應(yīng)的微服務(wù)會提供一組功能關(guān)聯(lián)的服務(wù)接口,并向系統(tǒng)中的其他微服務(wù)提供服務(wù)。在正常情況下,各個微服務(wù)之間功能上相互解耦,從軟件的設(shè)計上來講會呈現(xiàn)出一個比較合理的狀態(tài),但是從調(diào)用鏈路上來看,這種拆分實際上也是拉長了外部服務(wù)請求的調(diào)用鏈路。
創(chuàng)新互聯(lián)公司堅持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:網(wǎng)站制作、網(wǎng)站設(shè)計、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時代的武強網(wǎng)站設(shè)計、移動媒體設(shè)計的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
舉個例子,在創(chuàng)業(yè)公司的早期,考慮到研發(fā)維護成本,系統(tǒng)架構(gòu)設(shè)計很簡單,從軟件結(jié)構(gòu)上看,就是一個api服務(wù)面向app端,一個service端面向后臺功能。以用戶購物場景舉例,雖然這個過程邏輯上會經(jīng)歷商品、下單、支付、物流、庫存等復(fù)雜邏輯的處理,但是因為這些邏輯都耦合在一個系統(tǒng)中,所以從用戶的app到后臺服務(wù),服務(wù)的調(diào)用鏈路并不算太長。即便在這樣的情況下,還是會存在如果請求量突然劇增,服務(wù)端的業(yè)務(wù)處理線程池被塞滿,整個后臺系統(tǒng)的數(shù)據(jù)庫連接資源、緩存資源全部被耗盡,從而導(dǎo)致整個服務(wù)不可用的情況。
而隨著公司的逐步發(fā)展,業(yè)務(wù)請求量與日劇增,為了提高整個系統(tǒng)的吞吐量及可用性,我們采用了微服務(wù)架構(gòu)的設(shè)計,將原先的系統(tǒng)拆分成了商品、訂單、支付、物流、庫存等多個微服務(wù),而這些服務(wù)之間通過網(wǎng)絡(luò)進行通信(以Spring Cloud來說就是通過我們前面說到的FeignClient進行服務(wù)發(fā)現(xiàn)后,以HTTP的方式進行網(wǎng)絡(luò)調(diào)用),形成了一次購物請求,會經(jīng)歷app端調(diào)用商品微服務(wù)進行瀏覽,選中商品后由商品中心調(diào)用訂單中心進行下單,然后訂單中心調(diào)用支付系統(tǒng)進行付款,付款成功后,訂單中心再調(diào)用物流中心進行發(fā)貨,與此同時,物流中心調(diào)用庫存系統(tǒng)進行庫存減少的漫長調(diào)用鏈路。
這個流程看起來就有點長了,為了方便大家理解,還是來張圖:
如上圖所示,在系統(tǒng)微服務(wù)化后,雖然此時每個微服務(wù)都擁有獨立的進程資源、業(yè)務(wù)線程池以及單獨的數(shù)據(jù)庫,整體的系統(tǒng)吞吐量比以前高了很多,并且每個微服務(wù)也都是集群部署。但是因為整個調(diào)用的網(wǎng)絡(luò)鏈路是非常長的,如果此時發(fā)生局部網(wǎng)絡(luò)或者部分微服服務(wù)故障的話,依然可能會導(dǎo)致整個微服務(wù)系統(tǒng)的癱瘓。
舉個例子,假設(shè)此時物流服務(wù)發(fā)生了宕機,但是前面的微服務(wù)都不知道,因為整個鏈路調(diào)用都是同步的,所以此時訂單服務(wù)調(diào)用物流微服務(wù)的時候會出現(xiàn)部分線程阻塞直至超時異常,同理調(diào)用物流的微服務(wù)的訂單服務(wù)的那個線程也會出現(xiàn)阻塞,假如此時業(yè)務(wù)請求并發(fā)量非常高,因為線程阻塞時間過長,那么很快訂單微服務(wù)及物流微服務(wù)的業(yè)務(wù)線程數(shù)資源就會被耗盡,此時用戶App端就會出現(xiàn)不僅購物功能無法使用,就連商品瀏覽也不行了,而此時請求量繼續(xù)增加,情況就會更加惡化,如果業(yè)務(wù)線程池使用的是***隊列(線程池請求隊列),最終還會導(dǎo)致系統(tǒng)內(nèi)存溢出,此時系統(tǒng)要想自動恢復(fù)可能就比較困難了,糟糕的情況就是服務(wù)持續(xù)不可用,而最終可能只能采用重啟整個系統(tǒng)的高昂成本來臨時解決下,而這也還不能最終解決問題,因為重啟后情況依然可能會發(fā)生(如果并沒有排查及解決掉物流微服務(wù)故障原因的話)。
從上面的例子看,一個微服務(wù)的故障居然能導(dǎo)致整個系統(tǒng)的崩潰,而我們希望的情況是如果發(fā)現(xiàn)物流微服務(wù)持續(xù)故障的話,此時訂單微服務(wù)應(yīng)該是可以感知到,并根據(jù)一定的機制進行容錯,即訂單微服務(wù)在知道物流微服務(wù)異常的情況下,就暫時先不要把請求發(fā)送到物流微服務(wù)了,給物流微服務(wù)先限流,而在自身本地邏輯中采取一個默認(rèn)容錯邏輯進行熔斷后立刻返回App調(diào)用端,例如,可以先將需要發(fā)送的消息緩存,待物流微服務(wù)恢復(fù)后再重新發(fā)送。這樣的話,故障的物流微服務(wù)也就不會導(dǎo)致訂單服務(wù)因為同步調(diào)用鏈路超時過長而出現(xiàn)級聯(lián)故障了。
那么在Spring Cloud微服務(wù)設(shè)計中如何才能實現(xiàn)這樣的機制呢?這里涉及到幾個問題:
以上這些問題,就是本章要講述的如何在Spring Cloud微服務(wù)設(shè)計中實現(xiàn)服務(wù)熔斷限流的內(nèi)容了!而這一點對于并發(fā)量非常高的情況下,實現(xiàn)微服務(wù)的可用性是很重要的一個方面。
在Spring Cloud微服務(wù)設(shè)計中需要通過集成Hystrix框架來實現(xiàn)微服務(wù)間的熔斷保護機制,Hystrix框架會通過監(jiān)控微服務(wù)之間的調(diào)用情況,來決定是否啟動熔斷保護。那么接下來,就讓我們一起來看下如何在Spring Cloud項目中通過集成Hystrix框架來實現(xiàn)熔斷機制吧!
引入依賴
要Spring Cloud中使用Hystrix框架,需要引入Hystrix框架的starter依賴包,如下:
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
通過引入此stater依賴包,我們就可以基于Spring Boot框架的特性,實現(xiàn)對Hystrix框架的開箱即用了。
注解開啟熔斷器
在Spring Cloud微服務(wù)中啟用熔斷器,需要在微服務(wù)的Application主程序上添加org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker注解,如:
@EnableDiscoveryClient
@EnableCircuitBreaker
@SpringBootApplication
@EnableFeignClients(basePackageClasses = {PaymentClient.class})
@EnableScheduling
public class Goods {
public static void main(String[] args) {
SpringApplication.run(Goods.class, args);
}
}
通過這樣一個簡單的注解配置,此時微服務(wù)就開啟了基于Hystrix的斷路器功能。需要說明的是,在某個微服務(wù)中開啟斷路器,實現(xiàn)的是該微服務(wù)對其下游微服務(wù)的熔斷功能,而不是該微服務(wù)對其上游調(diào)用的熔斷,這一點大家不要混淆了,因為在Spring Cloud的微服務(wù)體系下,熔斷的實現(xiàn)是基于Hystrix本地庫來實現(xiàn)的,本質(zhì)上是客戶端熔斷,而不是服務(wù)端的熔斷。相比較于最近談?wù)摫容^多的基于Service Mesh的限流熔斷功能而言,基于客戶端的熔斷從應(yīng)用的形態(tài)上看,是與微服務(wù)本身融合在一起的,而不是獨立的服務(wù)。
FeignClient開啟Hystrix
在微服務(wù)中開啟斷路器后,并不表示就可以立刻使用了,在前面的章節(jié)中我們講過,在Spring Cloud微服務(wù)體系中,微服務(wù)之間的通信交互需要通過使用FeignClient來進行,而默認(rèn)情況下FeignClient中默認(rèn)情況下是禁用Hystrix的,所以如果需要在微服務(wù)中啟用Hystrix的熔斷功能,則需要通過配置手動開啟Hystrix功能,這樣FeignClient客戶端在微服務(wù)之間進行通訊調(diào)用時,才能在感知到微服務(wù)異常的情況下,將錯誤指標(biāo)信息反饋給Hystrix框架,從而Hystrix才能根據(jù)自身邏輯對熔斷器的狀態(tài)進行啟停(關(guān)于Hystrix的具體運行原理,我們在后面的章節(jié)中進行介紹)。
以下是我們在項目的bootstrap.yml文件中,開啟FeignClient對Hystrix支持的配置:
feign:
hystrix:
enabled: true
Spring Cloud中微服務(wù)之間的服務(wù)調(diào)用是基于FeignClient的,在實際的工程實踐中,我們一般會單獨將微服務(wù)的FeignClient調(diào)用端代碼進行抽離,并以SDK jar包依賴的形式進行發(fā)布。一般情況下,可以每個微服務(wù)都抽離一個FeignClient工程代碼,這樣更加清晰;如果覺得太過于麻煩,也可以把多個不同微服務(wù)的FeignClient客戶端代碼耦合在一起,所有的微服務(wù)依賴這一個SDK也可以,只是后期如果微服務(wù)的數(shù)量比較多,并且維護團隊比較分散的話,這樣也會導(dǎo)致一個很臃腫的項目出現(xiàn),維護升級更加麻煩而已,大家可以根據(jù)團隊的實際情況進行規(guī)劃。
我們在前面講述過基于Spring Cloud的微服務(wù)的熔斷機制,實際上是基于Hystrix框架的客戶端熔斷機制,也就是說上游微服務(wù)在通過FeignClient調(diào)用下游微服務(wù)的時候,如果感知到下游微服務(wù)調(diào)用異常需要向上向Hystrix框架反饋異常,如果Hystrix框架計算異常指標(biāo)達到了閥值就會開啟熔斷器。而之后FeignClient客戶端針對該下游微服務(wù)的調(diào)用,就需要被Hystrix熔斷后回調(diào)一個相應(yīng)的本地降級處理方法,從而實現(xiàn)服務(wù)降級。
而FeignClient從代碼的角度已經(jīng)支持了這樣的設(shè)計,我們在通過@FeignClient注解編寫微服務(wù)的客戶端調(diào)用代碼時,就可以通過指定相應(yīng)的Fallback類來處理服務(wù)被熔斷后的降級邏輯。下面我們就以本文舉例的項目示例,來編寫訂單微服務(wù)的FeignClient客戶端SDK代碼:order-client。
代碼示例:
@FeignClient(value = "order", configuration = OrderClientConfiguration.class, fallback = OrderClientFallback.class)
public interface OrderClient {
// 查詢購物訂單扣費狀態(tài)(內(nèi))
@RequestMapping(value = "/order/queryOrderCost", method = RequestMethod.GET)
QeuryOrderCostResVo queryOrderCost(@RequestParam(value = "orderId") String orderId) throws InternalApiException;
}
根據(jù)訂單微服務(wù)中的服務(wù)接口定義,我們通過@FeignClient注解定義了一個OrderClient.class類,該類聲明了微服務(wù)的接口定義,假設(shè)這里訂單微服務(wù)提供了一個訂單查詢接口(一個微服務(wù)一般情況下會有多個服務(wù)接口,這里舉一個接口只是為了好舉例)。
我們可以看到在@FeignClient注解的屬性中,有一個fallback屬性,這個屬性指定了一個服務(wù)降級的配置類OrderClientFallback.class。這樣,就可以在該類中實現(xiàn)微服務(wù)對應(yīng)方法的降級邏輯了:
public class OrderClientFallback implements OrderClient {
@Override
public OrderCostDetailVo orderCost(String orderId, long userId, String busiId, String orderType, int duration,
int bikeType, String bikeNo, String countryName, int cityId, int orderCost, String currency, int strategyId,
String tradeTime) {
return new OrderCostDetailVo();
}
}
可以看到降級處理類實際上是OrderClient的一個實現(xiàn)類,所以在這里每個微服務(wù)的接口都會被強制要求實現(xiàn)相應(yīng)的熔斷降級代碼。而具體的降級邏輯,則可以根據(jù)服務(wù)的具體情況進行編寫,如這里是返回一個空的消息對象。
以上模式就是在Spring Cloud中通過FeignClient調(diào)用時,在開啟Hystrix熔斷功能后的基本處理套路了。接下來,我們通過具體的測試效果,來看下熔斷器功能的生效情況:
1、在微服務(wù)goods中引入order微服務(wù)的FeignClient客戶端SDK
com.wudimanong.client
order-client
1.0.0
2、為了觀測,我們需要開啟HystrixDashboard
引入HystrixDashboard依賴:
org.springframework.cloud
spring-cloud-starter-hystrix-dashboard
在應(yīng)用程序主類中開啟HystrixDashboard注解:
@EnableHystrixDashboard
@EnableDiscoveryClient
@EnableCircuitBreaker
@SpringBootApplication
@EnableFeignClients
public class GoodsApplication {
public static void main(String[] args) {
SpringApplication.run(GoodsApplication.class, args);
}
}
3、此時通過訪問HystrixDashboard控制臺就可以看到監(jiān)控指標(biāo)信息了
我們假設(shè)goods調(diào)用order服務(wù)正常情況下Ciruit是close狀態(tài)的,如果此時斷掉order服務(wù),然后多刷幾次goods調(diào)用請求,此時,我們就發(fā)現(xiàn)關(guān)于order服務(wù)的熔斷開關(guān)被打開了。
然后我們恢復(fù)order服務(wù),然后再多刷幾次調(diào)用接口,就會發(fā)現(xiàn)Ciruit就會被關(guān)閉了。
通過上面的配置,我們就基本完成了Spring Cloud項目中關(guān)于Hystrix熔斷器的配置了。