服務雪崩
在多個微服務的系統中,假設有ABC三個服務,並且A服務中調用了B服務,B服務中調用了C服務,組成了一個鏈路,而如果這個鏈路中的某個服務調用響應時間長或者不可用,對A的調用就會佔用越來越多的系統資源,進而會造成系統崩潰。這就是所謂的雪崩。
Hystrix
所以需要一個能夠對故障和延遲進行隔離和管理,以便於單個依賴關係的失敗,不會拖垮整個系統的工具。Hystrix由此誕生,它是一個用於處理分佈式系統的延遲和容錯的開源庫,在分佈式系統中,許多依賴會因爲各種原因導致調用超時或者調用失敗,Hystrix能夠保證一個依賴出問題的情況下,不會導致整體服務出現失敗,避免級聯故障。
服務熔斷
服務熔斷可以看做是家庭用的保險絲,當某個服務出現不可用或者響應超時的情況下,暫停對這個服務的調用。
服務降級
服務降級是從整個系統的負荷情況出發和考慮的,對於某些負荷比較高的情況,爲了預防某個業務出現負荷過載或者響應慢的情況,在其內部暫時捨棄會一些非核心的接口或者數據的請求,而是直接返回一個提前準備好的FallBack錯誤處理信息,用來保證整個系統的穩定性和可用性。
使用Hystrix實現服務降級
接口占用服務器資源的情況
這裏先不將Hystrix引入工程,而是測試一下同一個服務中一個接口訪問量激增導致另一個接口訪問變慢的情況。這裏參考尚硅谷的教程,分爲一個支付模塊,一個訂單模塊,案例中是訂單模塊調用支付模塊。
創建一個不帶Hystrix的支付模塊
服務中主要是兩個方法,一個是正常的方法,功能很簡單,就是返回一個字符串,一個是異常測試方法,也是返回一個字符串,不同的是,裏面進行睡眠3秒。如下。
@Service
public class PaymentHystrixServiceImpl implements PaymentHystrixService{
@Override
public String paymentOk(Integer id) {
return "Normal method -- "+id;
}
@Override
public String paymentError(Integer id) {
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
return "Error method -- "+id;
}
}
Controller層也很簡單,創建兩個接口來調用這兩個方法。
@Controller
public class PaymentHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@RequestMapping("/payment/hystrix/ok/{id}")
@ResponseBody
public String paymentOk(@PathVariable("id")Integer id){
return paymentHystrixService.paymentOk(id);
}
@RequestMapping("/payment/hystrix/error/{id}")
@ResponseBody
public String paymentError(@PathVariable("id")Integer id){
return paymentHystrixService.paymentError(id);
}
}
可以看出,對於異常方法,會出現等待情況,而正常方法應該是立即返回的。現在對異常方法進行壓力測試,這裏用到Apache JMeter工具。
然而事實上測試可以發現,儘管只是對異常方法進行壓力測試,但是正常方法調用還是會出現一定程度的延遲。這就是因爲異常方法佔用了服務器資源(在這裏是Tomcat線程池中的工作線程已經被佔用完畢),導致其他請求也變慢了。
服務降級配置
pom中加入Hystrix的依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
主啓動類配置斷路器註解
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentMain8001 {
public static void main(String[] args){
SpringApplication.run(PaymentMain8001.class,args);
}
}
配置Fallback方法
在超時的方法上設置如下,需要注意的是,fallbackMethod字段對應的方法名和參數列表必須指定的Fallback方法的一致。
@Service
public class PaymentHystrixServiceImpl implements PaymentHystrixService{
@Override
public String paymentOk(Integer id) {
return "Normal method -- ok";
}
@Override
@HystrixCommand(fallbackMethod = "paymentErrorHandler",commandProperties =
{@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")})
public String paymentError(Integer id) {
try {
TimeUnit.SECONDS.sleep(5);
}catch (InterruptedException e){
e.printStackTrace();
}
return "Error method -- error";
}
public String paymentErrorHandler(Integer id){
return "Fallback method -- error";
}
}
當再次訪問異常接口時,因爲Hystrix配置的超時時間是3秒,而方法中睡眠了5秒,所以自然就要執行到Fallback方法。另外值得一提的是,不光是超時,如果是方法裏的拋異常,比如int a = 15/0,也會執行這個方法。這裏測試的是服務端的降級,其實在客戶端也可以配置,並且一般來說,這個配置是放在客戶端的。
客戶端配置降級
由於客戶端需要調用支付模塊,所以需要加入OpenFeign的依賴,其次就是配置OpenFeign和Eureka。
<!-- openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
之後就是Application.yml的配置了。
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://localhost:7001/eureka
feign:
hystrix:
# 開啓降級保護
enabled: true
接下來寫Service層,但是由上面的支付模塊的例子可以看出,Fallback方法和業務方法耦合在同一個類裏面,所以在這裏可以稍微對代碼進行一下優化。那就是用一個專門的類來設置降級方法,這個選項可以在@OpenFeignClient裏配置。如下:
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentOk(@PathVariable("id")Integer id);
@GetMapping("/payment/hystrix/error/{id}")
public String paymentError(@PathVariable("id")Integer id);
}
PaymentFallbackService就是這個類,這個類是繼承於PaymentHystrixService的。
@Component
public class PaymentFallbackService implements PaymentHystrixService{
@Override
public String paymentOk(Integer id) {
return "Fallback_ok";
}
@Override
public String paymentError(Integer id) {
return "Fallback_error";
}
}
在這之後就是控制層,控制層比較簡單。
@RestController
public class OrderHystrixController {
@Resource
private PaymentHystrixService service;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentOk(@PathVariable("id")Integer id){
return service.paymentOk(id);
}
@GetMapping("/consumer/payment/hystrix/error/{id}")
public String paymentError(@PathVariable("id")Integer id){
return service.paymentError(id);
}
}
再進行測試,可以發現執行的降級方法就是PaymentFallbackService中的方法。並且,就算支付模塊是關閉狀態,依舊可以執行。
服務熔斷
服務熔斷就是在達到最大訪問量的時候,直接拒絕訪問,調用服務降級的方法並返回友好提示。當服務正常響應的時候,恢復鏈路。
熔斷配置
在Service類中添加熔斷方法,添加熔斷配置的註解。
//服務熔斷
@HystrixCommand(fallbackMethod = "paymentCircuitBreakerFallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),//是否開啓斷路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),//請求次數
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "1000"),//時間窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60")//失敗率達到多少後跳閘
})
public String paymentCircuitBreaker(@PathVariable("id")Integer id){
if (id<0){
throw new RuntimeException("id 不能爲負數");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"調用成功,流水號:"+serialNumber;
}
public String paymentCircuitBreakerFallback(@PathVariable("id")Integer id){
return "id 不能負數,請稍後再試";
}
熔斷器的打開和關閉,是按照如下步驟來決定的。
1,併發此時是否達到我們指定的閾值
2,錯誤百分比,比如我們配置了60%,那麼如果併發請求中,10次有6次是失敗的,就開啓斷路器
3,上面的條件符合,斷路器改變狀態爲open(開啓)
4,這個服務的斷路器開啓,所有請求無法訪問
5,在我們的時間窗口期,期間,嘗試讓一些請求通過(半開狀態),如果請求還是失敗,證明斷路器還是開啓狀態,服務沒有恢復
如果請求成功了,證明服務已經恢復,斷路器狀態變爲close關閉狀態
現在來測試該方法,可以發現,在我們瘋狂往接口塞爲負數的ID之後,然後停止換成正數之後,發現正數也不正常了,這表示熔斷器起作用了。
熔斷整體流程
1請求進來,首先查詢緩存,如果緩存有,直接返回,如果緩存沒有,-->2
2,查看斷路器是否開啓,如果開啓的,Hystrix直接將請求轉發到降級返回,然後返回。如果斷路器是關閉的,判斷線程池等資源是否已經滿了,如果已經滿了,也會走降級方法。如果資源沒有滿,判斷我們使用的什麼類型的Hystrix,決定調用構造方法還是run方法,然後處理請求,然後Hystrix將本次請求的結果信息彙報給斷路器,因爲斷路器此時可能是開啓的(因爲斷路器開啓也是可以接收請求的)斷路器收到信息,判斷是否符合開啓或關閉斷路器的條件,如果本次請求處理失敗,又會進入降級方法
如果處理成功,判斷處理是否超時,如果超時了,也進入降級方法
最後,沒有超時,則本次請求處理成功,將結果返回給controller層。
Hystrix服務監控--HystrixDashBoard
還是老套路,引入依賴。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
yml中配置端口即可。然後主啓動類需要加上@EnableHystrixDashboard,這裏需要注意一點就是每一個被監控的服務都必須加入actuator依賴。
<!--監控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
此外,還需要在被監控服務中加入如下代碼,原因註釋中有所說明。
/**
* 爲了服務監控來配置,與服務容器本身無關,而是SpringCloud升級後的坑。
* ServletRegistrationBean因爲Springboot的默認路徑不是/hystrix.stream
* 所以只要在自己的項目中配置下面的Servlet就行。
* */
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet servlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(servlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
然後啓動被監控服務,將其url加入到HystrixDashboard中。比如這裏是8001端口,那麼url就是http://localhost:8001/hystrix.stream。開啓監控。
曲線表示流量變化。其他元素意義如下圖所示。
測試HystrixDashboard
還是跟之前一樣,瘋狂網接口塞負數,然後觀察Dashboard的變化。可以發現,到了一定程度,接口就有綠色close狀態變爲紅色open狀態。