1. 程式人生 > >使用Hystrix實現微服務熔斷與降級

使用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