使用Hystrix實現微服務熔斷與降級
在實際專案中,微服務之間的相互呼叫可能會遇上網路抖動、延遲、超時等一些列問題,如果不加以處理,可能引發更加嚴重的問題,如一開始的使用者服務不可用導致呼叫此服務的售票服務受阻,導致佔用了很多系統資源,因而導致呼叫售票服務的支付服務也掛掉,這期間又導致了資源佔用無法釋放,持續滾雪球導致整個系統都宕掉,也是極有可能的。這個時候需要一種適當的容錯機制,從上面來看,這種容錯至少需要這個幾個功能點:
1、請求超時:為每個請求設定超時時間,避免系統資源的持續性無效佔用;
2、斷路器模式:當某個服務不可用並達到一定的失敗率(預設5秒內20次),快速跳閘,請求快速返回,並且後續進來的呼叫此服務的請求不再繼續呼叫不可用的服務;
Hystrix實現了超時機制及斷電模式,它是Netflix開源的延遲和容錯工具類庫。它實現的功能點主要如下:
1、包裹請求:請求包裹在HystrixCommand中,並在獨立執行緒中執行;
2、斷路器機制:當某個服務不可用率到達閾值就跳閘,一段時間內禁止後續請求該服務(熔斷);
3、回退機制:當服務間呼叫遇上網路延遲、超時、失敗或已經跳閘,直接放回自定義方法中預設值(降級);
4、監控:Hystrix會實時的監控請求的成功、失敗、超時等服務執行指標
5、資源隔離:Hystrix為每個依賴維護了一個小型的訊號量,如果訊號量滿了則進來的請求直接拒絕,不需要排隊等待;
Hystrix核心點:
1、Hystrix的容錯性關鍵點在於把各個微服務之間的RPC呼叫包裹在了HystrixCommand中,這樣每次呼叫就是都是單獨的執行緒執行
2、Hystrix分執行緒隔離和訊號量隔離,HystrixCommand就是執行緒隔離(預設使用),因為執行緒隔相對訊號量而言較耗資源,所以可在高負載時候選用
實現Hystrix HelloWorld
修改之前ticket-consumer-ribbon專案的ArtifactId為ticket-consumer-ribbon-hystrix,pom中新增Hystrix依賴:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency>
啟動類頭部加上@EnableHystrix註解,開啟Hystrix;
修改TicketController類方法方法頭部加上:
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.simons.cn.util.CommonEnum;
import com.simons.cn.util.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
@Slf4j
@Controller
public class TicketController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@HystrixCommand(fallbackMethod = "purchaseTicketFallBack", commandProperties = {
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"), //滑動視窗大小
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000"), //過多久再次檢測是否開啟熔斷器
@HystrixProperty(name ="circuitBreaker.errorThresholdPercentage",value = "50") //錯誤率
})
@RequestMapping("/ticketpurchase")
@ResponseBody
public CommonResult purchaseTicket(@RequestParam(required = false, value = "name") String name) {
CommonResult result = restTemplate.getForObject("http://user-provider/getuserinfo?name=" + name, CommonResult.class);
return result;
}
/**
* 預設回退方法(此處fallback屬性實現的功能效果即降級)
*
* @return
*/
public CommonResult purchaseTicketFallBack(String name) {
return CommonResult.success(CommonEnum.FAIL.getCode(), CommonEnum.FAIL.getMessage(), null);
}
@GetMapping("/loginfo")
public void loginfo() {
ServiceInstance serviceInstance = loadBalancerClient.choose("user-provider");
log.info("host=" + serviceInstance.getHost() + ",port=" + serviceInstance.getPort() + ",serviceid=" + serviceInstance.getServiceId());
}
}
測試:
啟動多個user-provider-eureka專案服務;
啟動discovery-eureka專案服務;
啟動ticket-consumer-ribbon-hystrix專案服務;
{
"code": "0",
"message": "success",
"data":[{
"id": 125,
"name": "jack",
"role": "system"
}]
}
{
"code": "1",
"message": "system error",
"data": null
}
可以看到,預設的回退方法生效了。我們可以利用SpringBoot的Actuator來檢查下Hystrix狀態,訪問:http://localhost:9000/health,效果如下
Hystrix提供了這麼幾個關鍵引數:
circuitBreaker.requestVolumeThreshold 滑動視窗大小
circuitBreaker.sleepWindowInMilliseconds 過多久斷路器再次檢測是否開啟
circuitBreaker.errorThresholdPercentage 錯誤率
上面controller中的配置組合起來的意思就是,每20個請求中,有50%的失敗就會開啟斷路器,此時後續的請求將不再呼叫此服務,5s鍾之後重新檢測開啟還是關閉斷路器。更多引數配置請參考https://github.com/Netflix/Hystrix/wiki/Configuration
在Feign中使用Hystrix
參考之前的ticket-consumer-feign專案,在Feign介面UserFeignService中新增
import com.simons.cn.util.CommonResult;
import com.simons.cn.util.UserFeignFallBackReason;
import com.simons.cn.util.UserFeignServiceFallBack;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "user-provider",fallback = UserFeignServiceFallBack.class,fallbackFactory = UserFeignFallBackReason.class)
public interface UserFeignService {
@RequestMapping(value = "/getuserinfo",method = RequestMethod.GET)
CommonResult getUserByName(@RequestParam(required = false,value = "name") String name);
}
fallback屬性值為UserFeignServiceFallBack類需實現自定義Feign介面(此處fallback屬性實現的功能效果即降級)
import com.simons.cn.UserFeignService;
/**
* 在Feign中使用Hystrix,需要實現自定義Feign介面
*/
public class UserFeignServiceFallBack implements UserFeignService {
@Override
public CommonResult getUserByName(String name) {
return CommonResult.success(CommonEnum.FAIL.getCode(), CommonEnum.FAIL.getMessage(), null);
}
}
同時使用fallbackFactory屬性定義實現feign介面的UserFeignFallBackReason類來獲取回退異常原因
import com.simons.cn.UserFeignService;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 通過實現FallBackFactory介面的類來列印服務呼叫回退的原因
*/
@Slf4j
@Component
public class UserFeignFallBackReason implements FallbackFactory {
@Override
public UserFeignService create(final Throwable throwable) {
return new UserFeignService() {
@Override
public CommonResult getUserByName(String name) {
log.error("UserFeignService fallback reason was:"+throwable);
return CommonResult.success(CommonEnum.FAIL.getCode(), CommonEnum.FAIL.getMessage(), null);
}
};
}
}
在Feign中禁用Hystrix
因為SpringCloud預設為Feign整合了Hystrix,也就是說,Feign也預設會使用HystrixCommand包裹所有請求,但生產環境中不一定就需要Hystrix,這個時候就需要去掉這個限制(包裹請求)
application.yml檔案中新增如下配置:
feign:
hystrix:
enabled: false