Hystrix服務降級


  服務雪崩:多個微服務之間調用的時候,假設A調用B、C,B、C服務又調用其他服務,這就是所謂的扇出。如果扇出的鏈路上某個微服務調用的時間過長或者不可用,對微服務A的調用就會占用越來越多的資源,從而引起系統崩潰,這就是所謂的"雪崩效應"。

  對於高流量的應用來說,單一的后端依賴可能會導致所有服務器上的所有資源在幾秒內飽和。比失敗更糟糕的是,這些應用程序還可能導致服務之間的延遲增加,備份隊列,線程和其他資源緊張,導致整個系統發生其他的級聯故障。這些都表示需要對故障和延遲進行隔離和關聯,以便單個依賴關系的失敗,不能取消整個應用程序或系統。

1.Hystrix簡介

1.Hystrix是什么

   在布式系統面臨的一個重要問題:應用程序可能有數十個依賴,每個依賴關系不可避免的會出現失敗,比如超時、異常等。Hystrix是一個用於分布式系統的延遲和容錯的開源庫,能夠在一個依賴出問題的情況下,不會導致整體服務失敗,避免級聯故障,以提高分布式系統的彈性。

  "斷路器"本身是一種開關裝置,當某個服務單元發生故障之后,通過斷路器的故障監控(類似熔斷保險絲),向調用方返回一個預期的、可處理的備選響應(FallBack),而不是長時間的等待或者拋出調用方無法處理的異常,這就保證了服務調用方的線程不會被長時間、不必要的占用,從而避免了故障在分布式系統的蔓延乃至雪崩。

  git地址:https://github.com/Netflix/Hystrix

 2.Hystrix的作用以及重要概念

   可以進行服務降級、服務熔斷、接近實時的監控。不過官網上Hystrix已經停止更新。與之對應的還有resilience4j、sentinel。

服務降級(fallback):就是某個服務出現故障了,不讓調用方一直等待,返回一個友好的提示(fallback)。下面情況會發出降級:程序運行異常、超時、服務熔斷觸發服務降級、線程池\信號量打滿也會導致服務降級。

服務熔斷(break):類比保險絲達到最大服務訪問后,直接拒絕訪問,拉閘限電,然后調用服務降級的方法並返回友好提示。通常過程:服務降級-》熔斷-》恢復調用鏈路。

服務限流(flowlimit): 限制接口的訪問次數,嚴禁接口無休止的調用,比如某個接口1秒鍾只能調用200次。自己實現的話可以在Controller層用AOP實現。

2.使用

1. 創建payment支付服務

1.新建項目

 2.修改pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud</artifactId>
        <groupId>cn.qz.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-hystrix-payment8081</artifactId>


    <dependencies>
        <!--hystrix-->
        <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>
        <!--引入自己抽取的工具包-->
        <dependency>
            <groupId>cn.qz.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

3.修改yml

server:
  port: 8081

spring:
  application:
    name: cloud-provider-hystrix-payment

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
      defaultZone: http://localhost:7001/eureka

4.啟動類

package cn.qz.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @Author: qlq
 * @Description
 * @Date: 22:08 2020/10/17
 */
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8081 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8081.class, args);
    }
}

5.業務類:

Service(這里直接用class,不用接口) 

package cn.qz.cloud.service;

import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @Author: qlq
 * @Description
 * @Date: 22:15 2020/10/17
 */
@Service
public class PaymentService {

    /**
     * 正常
     *
     * @param id
     * @return
     */
    public String success(Integer id) {
        return "success,線程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }

    public String timeout(Integer id) {
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "timeout,線程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }
}

Controller

package cn.qz.cloud.controller;

import cn.qz.cloud.service.PaymentService;
import cn.qz.cloud.utils.JSONResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @Author: qlq
 * @Description
 * @Date: 22:22 2020/10/17
 */
@RestController
@Slf4j
@RequestMapping("/hystrix/payment")
public class PaymentController {

    @Resource
    private PaymentService paymentService;

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/success/{id}")
    public JSONResultUtil<String> success(@PathVariable("id") Integer id) {
        String result = paymentService.success(id);
        log.info("*****result: " + result);
        return JSONResultUtil.successWithData(result);
    }

    @GetMapping("/timeout/{id}")
    public JSONResultUtil timeout(@PathVariable("id") Integer id) {
        String result = paymentService.timeout(id);
        log.info("*****result: " + result);
        return JSONResultUtil.successWithData(result);
    }
}

6.測試正常

2.創建訂單服務

1.創建模塊

 2.修改pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud</artifactId>
        <groupId>cn.qz.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-feign-hystrix-order80</artifactId>

    <dependencies>
        <!--hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--引入自己抽取的工具包-->
        <dependency>
            <groupId>cn.qz.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

3.修改yml文件

server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://localhost:7001/eureka/

feign:
  hystrix:
    enabled: true

4.啟動類:

package cn.qz.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @Author: qlq
 * @Description
 * @Date: 14:25 2020/10/18
 */
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class, args);
    }
}

5.業務類

(1)Service

package cn.qz.cloud.service;

import cn.qz.cloud.utils.JSONResultUtil;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Author: qlq
 * @Description
 * @Date: 14:31 2020/10/18
 */
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService
{
    @GetMapping("/hystrix/payment/success/{id}")
    public JSONResultUtil<String> success(@PathVariable("id") Integer id);

    @GetMapping("/hystrix/payment/timeout/{id}")
    public JSONResultUtil<String> timeout(@PathVariable("id") Integer id);
}

(2)controller 

package cn.qz.cloud.controller;

import cn.qz.cloud.service.PaymentHystrixService;
import cn.qz.cloud.utils.JSONResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: qlq
 * @Description
 * @Date: 14:49 2020/10/18
 */
@RestController
@Slf4j
@RequestMapping("/consumer/hystrix/payment")
public class OrderHystirxController {

    @Autowired
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/success/{id}")
    public JSONResultUtil<String> success(@PathVariable("id") Integer id) {
        return paymentHystrixService.success(id);
    }

    @GetMapping("/timeout/{id}")
    public JSONResultUtil timeout(@PathVariable("id") Integer id) {
        return paymentHystrixService.timeout(id);
    }
}

6.測試:

 3.壓力測試

(1)用jmeter測試2W個並發訪問8081服務的timeout方法。

1)新建線程組:

 2)新建HttpRequest請求

 3)新建listener->view results Tree

4)執行jmeter測試。相當於2W個請求同時去請求timeout接口,8081的tomcat會分配線程組處理這2W個請求。

  可以從8081服務查看日志發現也是用線程池處理timeout請求。

5)訪問正常的success接口報錯。因為沒有可分配的線程來處理success請求。

4.解決上面的超時和報錯(服務降級)

  服務降級可以在服務消費者端進行,也可以在服務提供者進行,一般是在消費者端進行。

主要從下面三個維度處理:

(1)服務提供者8081超時,調用者80不能一直卡死等待,需要有降級

(2)服務提供者8081down機了,調用者80需要有降級

(3)服務提供者8081服務OK,調用者80自己出故障或者有自我要求(自己的等待時間小於服務的處理時間),需要降級。

1.服務提供者8081進行服務降級,超時或者異常之后走自己指定的fallback方法

(1)主啟動類增加注解:

@EnableCircuitBreaker

(2)Service聲明HystrixCommand進行降級:

package cn.qz.cloud.service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @Author: qlq
 * @Description
 * @Date: 22:15 2020/10/17
 */
@Service
public class PaymentService {

    /**
     * 正常
     *
     * @param id
     * @return
     */
    public String success(Integer id) {
        return "success,線程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }

    @HystrixCommand(fallbackMethod = "timeOutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
    })
    public String timeout(Integer id) {
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "timeout,線程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }

    public String timeOutHandler(Integer id) {
        return "線程池:  " + Thread.currentThread().getName() + "  8081系統繁忙或者運行報錯,請稍后再試,id:  " + id;
    }
}

  上面表示超過3s后走 timeOutHandler 降級方法。程序中休眠5s模擬請求耗時五秒。

(3)測試如下:

$ curl -X GET http://localhost:8081/hystrix/payment/timeout/1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   135    0   135    0     0     44      0 --:--:--  0:00:03 --:--:--    44{"success":true,"code":"200","msg":"","data":"線程池:  HystrixTimer-4  8081系統繁忙或者運行報錯,請稍后再試,id:  1"}

  可以看到是Hystrix相關的線程池在處理請求。

(4)修改timeout方法,模擬程序報錯:

    public String timeout(Integer id) {
        int i = 10 / 0;
//        try {
//            TimeUnit.SECONDS.sleep(5);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

        return "timeout,線程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }

發現程序也是走的  timeOutHandler 方法,可以滿足實際中的需求。

(5)還原方法,認為5秒鍾是正常請求,線程休眠3s模擬實際處理耗時3s

    @HystrixCommand(fallbackMethod = "timeOutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")
    })
    public String timeout(Integer id) {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "timeout,線程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }

2.服務消費者端80進行服務降級

(1)修改OrderHystirxController

package cn.qz.cloud.controller;

import cn.qz.cloud.service.PaymentHystrixService;
import cn.qz.cloud.utils.JSONResultUtil;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: qlq
 * @Description
 * @Date: 14:49 2020/10/18
 */
@RestController
@Slf4j
@RequestMapping("/consumer/hystrix/payment")
public class OrderHystirxController {

    @Autowired
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/success/{id}")
    public JSONResultUtil<String> success(@PathVariable("id") Integer id) {
        return paymentHystrixService.success(id);
    }

    @GetMapping("/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
    })
    public JSONResultUtil timeout(@PathVariable("id") Integer id) {
        return paymentHystrixService.timeout(id);
    }

    public JSONResultUtil paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
        return JSONResultUtil.successWithData("消費者80,paymentTimeOutFallbackMethod, 線程池:  " + Thread.currentThread().getName() + "  8081系統繁忙或者運行報錯,請稍后再試,id:  " + id);
    }
}

(2)測試:(調用服務提供者的timeout接口走的是paymentTimeOutFallbackMethod方法)

$ curl http://localhost/consumer/hystrix/payment/timeout/1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   199    0   199    0     0    187      0 --:--:--  0:00:01 --:--:--   193{"success":true,"code":"200","msg":"","data":"消費者80,paymentTimeOutFallbackMethod, 線程池:  hystrix-OrderHystirxController-2  8081系統繁忙或者運行報錯,請稍后再試,id:  1"}

3.上面降級存在的問題:

(1)每個方法配置一個fallback降級方法,代碼膨脹

(2)降級方法和業務邏輯混在一起,代碼混亂

解決辦法:

(1)defaultFallback解決上面問題一,實現默認降級處理

controller方法增加全局默認的降級處理,如下:

package cn.qz.cloud.controller;

import cn.qz.cloud.service.PaymentHystrixService;
import cn.qz.cloud.utils.JSONResultUtil;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: qlq
 * @Description
 * @Date: 14:49 2020/10/18
 */
@RestController
@Slf4j
@RequestMapping("/consumer/hystrix/payment")
@DefaultProperties(defaultFallback = "paymentGlobalFallbackMethod")
public class OrderHystirxController {

    @Autowired
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/success/{id}")
    public JSONResultUtil<String> success(@PathVariable("id") Integer id) {
        return paymentHystrixService.success(id);
    }

    @GetMapping("/timeout/{id}")
//    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
//            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
//    })
    // 采用默認的全局fallback
    @HystrixCommand
    public JSONResultUtil timeout(@PathVariable("id") Integer id) {
        int i = 1 / 0;
        return paymentHystrixService.timeout(id);
    }

    public JSONResultUtil paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
        return JSONResultUtil.successWithData("消費者80,paymentTimeOutFallbackMethod, 線程池:  " + Thread.currentThread().getName() + "  8081系統繁忙或者運行報錯,請稍后再試,id:  " + id);
    }

    // 下面是全局fallback方法
    public JSONResultUtil paymentGlobalFallbackMethod() {
        return JSONResultUtil.successWithData("消費者80全局服務降級,paymentGlobalFallbackMethod, 線程池:  " + Thread.currentThread().getName());
    }
}

  定義了defaultFallback的只需要在方法聲明HystrixCommand注解出錯或調用的服務超時即可調用paymentGlobalFallbackMethod全局降級方法。也可以對方法單獨設置,單獨設置的會優先取方法上設置的降級方法。如果沒有聲明 HystrixCommand注解,不會進行服務的降級,報錯和超時都會直接走error。

測試如下:

$ curl http://localhost/consumer/hystrix/payment/timeout/1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   155    0   155    0     0   3297      0 --:--:-- --:--:-- --:--:--  4843{"success":true,"code":"200","msg":"","data":"消費者80全局服務降級,paymentGlobalFallbackMethod, 線程池:  hystrix-OrderHystirxController-2"}

(2)通配服務降級FeignFallback:解決上面問題2,相當於每個方法fallback和業務分離

修改PaymentHystrixService增加 fallback屬性

package cn.qz.cloud.service;

import cn.qz.cloud.utils.JSONResultUtil;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Author: qlq
 * @Description
 * @Date: 14:31 2020/10/18
 */
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
    @GetMapping("/hystrix/payment/success/{id}")
    public JSONResultUtil<String> success(@PathVariable("id") Integer id);

    @GetMapping("/hystrix/payment/timeout/{id}")
    public JSONResultUtil<String> timeout(@PathVariable("id") Integer id);
}

增加PaymentFallbackService類:相當於處理上面接口中對應方法的fallback

package cn.qz.cloud.service;

import cn.qz.cloud.utils.JSONResultUtil;
import org.springframework.stereotype.Component;

/**
 * @Author: qlq
 * @Description
 * @Date: 20:57 2020/10/18
 */
@Component
public class PaymentFallbackService implements PaymentHystrixService {
    @Override
    public JSONResultUtil<String> success(Integer id) {
        return JSONResultUtil.successWithData("PaymentFallbackService fallback,success 方法, threadName: " + Thread.currentThread().getName() + "\tid: " + id);
    }

    @Override
    public JSONResultUtil<String> timeout(Integer id) {
        return JSONResultUtil.successWithData("PaymentFallbackService fallback,timeout 方法, threadName: " + Thread.currentThread().getName() + "\tid: " + id);
    }
}

測試:

$ curl http://localhost/consumer/hystrix/payment/timeout/1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   129    0   129    0     0     49      0 --:--:--  0:00:02 --:--:--    50{"success":true,"code":"200","msg":"","data":"PaymentFallbackService fallback,timeout 方法, threadName: HystrixTimer-1\tid: 1"}

 

5.服務熔斷

  熔斷機制是應對雪崩效應的一種微服務鏈路保護機制,當扇出鏈路的某個服務出錯不可用或響應時間太長時,會進行服務的降級,進而熔斷該節點微服務的調用,快速返回錯誤的響應信息。當檢測到該節點微服務調用響應正常后,恢復調用鏈路。

  在SpringCloud框架里,熔斷機制通過Hystrix實現。Hystrix會監控微服務調用的狀況,當失敗的調用達到一定的閾值,缺省是5s內20次調用失敗,就會啟動熔斷機制。熔斷機制的注解是@HystrixCommand。

參考:https://martinfowler.com/bliki/CircuitBreaker.html

斷路器的3種狀態:

  關閉 - 當一切正常時,斷路器保持閉合狀態,所有調用都能訪問到服務。當故障數超過預定閾值時,斷路器跳閘,並進入打開狀態。
  打開 - 斷路器在不執行該服務的情況下為調用返回錯誤。
  半開 - 超時后,斷路器切換到半開狀態,以測試問題是否仍然存在。如果在這種半開狀態下單個調用失敗,則斷路器再次打開。如果成功,則斷路器重置回正常關閉狀態。補充一下鏈路回復的過程:斷路器開啟一段時間之后(默認5s),這個時候斷路器是半開狀態,會讓其中一個請求進行處理,如果成功則關閉斷路器,若失敗,繼續開啟斷路器。

支付服務設置服務熔斷:

(1)修改PaymentService增加熔斷設置

package cn.qz.cloud.service;

import cn.hutool.core.util.IdUtil;
import cn.qz.cloud.utils.JSONResultUtil;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @Author: qlq
 * @Description
 * @Date: 22:15 2020/10/17
 */
@Service
public class PaymentService {

    /**
     * 正常
     *
     * @param id
     * @return
     */
    public String success(Integer id) {
        return "success,線程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }

    @HystrixCommand(fallbackMethod = "timeOutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")
    })
    public String timeout(Integer id) {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "timeout,線程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }

    public String timeOutHandler(Integer id) {
        return "線程池:  " + Thread.currentThread().getName() + "  8081系統繁忙或者運行報錯,請稍后再試,id:  " + id;
    }

    //=====服務熔斷
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),// 是否開啟斷路器
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),// 請求次數
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 時間窗口期
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),// 失敗率達到多少后跳閘
    })
    public JSONResultUtil<String> circuit(Integer id) {
        if (id < 0) {
            throw new RuntimeException("******id 不能負數");
        }

        String serialNumber = IdUtil.simpleUUID();
        return JSONResultUtil.successWithData(Thread.currentThread().getName() + " 調用成功,id: " + id + " ,流水號: " + serialNumber);
    }

    public JSONResultUtil<String> paymentCircuitBreaker_fallback(Integer id) {
        return JSONResultUtil.successWithData("paymentCircuitBreaker_fallback 降級處理, " + Thread.currentThread().getName() + " 調用失敗, id 不能負數, id: " + id);
    }
}

  需要注意方法上面三個重要的參數。關於更詳細的配置,可以參考類:HystrixCommandProperties

快照時間窗:斷路器確定是否打開統計一些請求和錯誤數據,而統計的時間范圍就是快照時間窗,默認為最近的10秒。

請求次數:在快照時間窗內必須達到請求次數才有資格熔斷,默認為20.如果達不到總次數,即使全部失敗也不會開啟斷路器。

錯誤百分比閾值:默認是50,就是一半失敗的情況下會開啟斷路。

(2)controller增加方法:

package cn.qz.cloud.controller;

import cn.qz.cloud.service.PaymentService;
import cn.qz.cloud.utils.JSONResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @Author: qlq
 * @Description
 * @Date: 22:22 2020/10/17
 */
@RestController
@Slf4j
@RequestMapping("/hystrix/payment")
public class PaymentController {

    @Resource
    private PaymentService paymentService;

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/success/{id}")
    public JSONResultUtil<String> success(@PathVariable("id") Integer id) {
        String result = paymentService.success(id);
        log.info("*****result: " + result);
        return JSONResultUtil.successWithData(result);
    }

    @GetMapping("/timeout/{id}")
    public JSONResultUtil timeout(@PathVariable("id") Integer id) {
        String result = paymentService.timeout(id);
        log.info("*****result: " + result);
        return JSONResultUtil.successWithData(result);
    }

    //====服務熔斷
    @GetMapping("/circuit/{id}")
    public JSONResultUtil<String> circuit(@PathVariable("id") Integer id) {
        JSONResultUtil<String> result = paymentService.circuit(id);
        log.info("****result: " + result);
        return result;
    }
}

(3)測試:

-先測試成功:

liqiang@root MINGW64 ~/Desktop
$ curl http://localhost:8081/hystrix/payment/circuit/5
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   139    0   139    0     0   4483      0 --:--:-- --:--:-- --:--:--  8687{"success":true,"code":"200","msg":"","data":"hystrix-PaymentService-10 調用成功,id: 5 ,流水號: 2f5966b1f6cd469ab8b05ef83067c156"}

liqiang@root MINGW64 ~/Desktop
$ curl http://localhost:8081/hystrix/payment/circuit/-5
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   158    0   158    0     0   3361      0 --:--:-- --:--:-- --:--:--  9875{"success":true,"code":"200","msg":"","data":"paymentCircuitBreaker_fallback 降級處理, hystrix-PaymentService-10 調用失敗, id 不能負數, id: -5"}

liqiang@root MINGW64 ~/Desktop
$ curl http://localhost:8081/hystrix/payment/circuit/5
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   139    0   139    0     0   4483      0 --:--:-- --:--:-- --:--:--  8687{"success":true,"code":"200","msg":"","data":"hystrix-PaymentService-10 調用成功,id: 5 ,流水號: a528efccc26444cdac3219328616b491"}

-多調用幾次http://localhost:8081/hystrix/payment/circuit/-5連接,使得10秒鍾達到10次請求,並且每次都是失敗。斷路器會自動開啟,過段時間又會關閉斷路器,如下:

  可以看到即使調用成功的ID,也是走的降級的方法。待自動關閉斷路器之后又自動進行鏈路恢復。

liqiang@root MINGW64 ~/Desktop
$ curl http://localhost:8081/hystrix/payment/circuit/5
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   152    0   152    0     0   4903      0 --:--:-- --:--:-- --:--:-- 10133{"success":true,"code":"200","msg":"","data":"paymentCircuitBreaker_fallback 降級處理, http-nio-8081-exec-3 調用失敗, id 不能負數, id: 5"}

liqiang@root MINGW64 ~/Desktop
$ curl http://localhost:8081/hystrix/payment/circuit/5
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   152    0   152    0     0   4903      0 --:--:-- --:--:-- --:--:--  148k{"success":true,"code":"200","msg":"","data":"paymentCircuitBreaker_fallback 降級處理, http-nio-8081-exec-4 調用失敗, id 不能負數, id: 5"}

liqiang@root MINGW64 ~/Desktop
$ curl http://localhost:8081/hystrix/payment/circuit/5
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   139    0   139    0     0   2957      0 --:--:-- --:--:-- --:--:--  8687{"success":true,"code":"200","msg":"","data":"hystrix-PaymentService-10 調用成功,id: 5 ,流水號: c6438748e5a44ca9a619161c84a8296b"}

 

6.服務限流

  暫時不做研究。之后研究alibaba的sentinel。

7.hystrix工作流程

注意分為如下步驟:

1.Construct a HystrixCommand or HystrixObservableCommand Object
2.Execute the Command
3.Is the Response Cached?
4.Is the Circuit Open?
5.Is the Thread Pool/Queue/Semaphore Full?
6.HystrixObservableCommand.construct() or HystrixCommand.run()
7.Calculate Circuit Health
8.Get the Fallback
9.Return the Successful Response

 補充:Hystrix的資源隔離策略

1.為什么需要資源隔離:

  例如,我們容器(Tomcat)配置的線程個數為1000,服務A-服務B,其中服務A的並發量非常的大,需要500個線程來執行,此時,服務A又掛了,那么這500個線程很可能就夯死了,那么剩下的服務,總共可用的線程為500個,隨着並發量的增大,剩余服務掛掉的風險就會越來越大,最后導致整個系統的所有服務都不可用,直到系統宕機。這就是服務的雪崩效應。

  Hystrix就是用來做資源隔離的,比如說,當客戶端向服務端發送請求時,給服務A分配了10個線程,只要超過了這個並發量就走降級服務,就算服務A掛了,最多也就導致服務A不可用,容器的10個線程不可用了,但是不會影響系統中的其他服務。

2.Hystrix的資源隔離策略有兩種,分別為:線程池和信號量。

(1)線程池隔離模式:使用一個線程池來存儲當前的請求,線程池對請求作處理,設置任務返回處理超時時間,堆積的請求堆積入線程池隊列。這種方式需要為每個依賴的服務申請線程池,有一定的資源消耗,好處是可以應對突發流量(流量洪峰來臨時,處理不完可將數據存儲到線程池隊里慢慢處理)。

(2)信號量隔離模式:使用一個原子計數器(或信號量)來記錄當前有多少個線程在運行,請求來先判斷計數器的數值,若超過設置的最大線程個數則丟棄改類型的新請求,若不超過則執行計數操作請求來計數器+1,請求返回計數器-1。這種方式是嚴格的控制線程且立即返回模式,無法應對突發流量(流量洪峰來臨時,處理的線程超過數量,其他的請求會直接返回,不繼續去請求依賴的服務)

3.可以切換hystrix的資源隔離方式,默認是線程池模式。可以對某個方法單獨切換,也可以切換全局的,切換全局的如下:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1000
          strategy: SEMAPHORE  # 信號量隔離
#          strategy: THREAD  # 線程池
          semaphore:
            maxConcurrentRequests: 100  # 最大信號量上限

(1) 默認是線程池模式,HystrixCommand 降級以及熔斷方法完全采用hystrix的線程池
(2) 設置信號量模式: 會使用tomcat的線程池,可以通過信號量的多少控制並發量。

 

  參考:https://github.com/Netflix/Hystrix/wiki/How-it-Works

3.Hystrixdashboard實現服務監控

  Hystrix提供了對於微服務調用狀態的監控信息,但是需要結合spring-boot-actuator模塊一起使用。Hystrix Dashboard是Hystrix的一個組件,Hystrix Dashboard提供一個斷路器的監控面板,可以使我們更好的監控服務和集群的狀態。

1.新建監控微服務

1.新建模塊cloud-consumer-hystrix-dashboard9001

 2.完善pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud</artifactId>
        <groupId>cn.qz.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-hystrix-dashboard9001</artifactId>

    <dependencies>
        <!--引入自己抽取的工具包-->
        <dependency>
            <groupId>cn.qz.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <!--監控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</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>
        <!--熱部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

3.修改yml

server:
  port: 9001

4.啟動類:

package cn.qz.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@SpringBootApplication
// 開啟儀表盤監控注解
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {

    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardMain9001.class, args);
    }
}

2.修改原來的cloud-provider-hystrix-payment8081服務:

注意pom需要加上:

        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

修改主啟動類:(必須增加下面的getServlet配置,否則報錯連接不到)

package cn.qz.cloud;

import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;

/**
 * @Author: qlq
 * @Description
 * @Date: 22:08 2020/10/17
 */
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
@EnableHystrix
public class PaymentHystrixMain8081 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8081.class, args);
    }

    /**
     * 此配置是為了服務監控而配置,與服務容錯本身無關,springcloud升級后的坑
     * ServletRegistrationBean因為SpringBoot的默認路徑不是 “/hystrix.stream"
     * 只要在自己的項目里配置上下的servlet就可以了
     */
    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }
}

修改后啟動服務

3.啟動hystrixdashboard服務

(1)訪問首頁如下:

 (2)多次訪問http://localhost:8081/hystrix/payment/circuit/-1,使其斷路器打開

(3)輸入以下地址查看:http://localhost:8081/hystrix.stream

 (4)進入monitor

(5)上面測試的效果不是很明顯,可以用jmeter批量進行測試

補充:可以設置hystrix默認執行時長,超時進行降級處理,這里需要注意下Ribbon鏈接時長和等待請求處理時長的影響

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1000

這些配置的屬性都可以從類HystrixPropertiesManager、 HystrixCommandProperties 中查看。

補充:如果通過feign調用服務沒有進行服務的降級。比如A服務調B服務,B服務拋出除0異常,A服務報錯如下:

會報feign相關的錯誤:

2020-11-26 14:54:17.603 ERROR 27404 --- [o-auto-1-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.netflix.hystrix.exception.HystrixRuntimeException: PaymentHystrixService#error() failed and no fallback available.] with root cause

feign.FeignException$InternalServerError: status 500 reading PaymentHystrixService#error()
    at feign.FeignException.serverErrorStatus(FeignException.java:195) ~[feign-core-10.4.0.jar:na]
    at feign.FeignException.errorStatus(FeignException.java:144) ~[feign-core-10.4.0.jar:na]
    at feign.FeignException.errorStatus(FeignException.java:133) ~[feign-core-10.4.0.jar:na]
    at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:92) ~[feign-core-10.4.0.jar:na]
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:151) ~[feign-core-10.4.0.jar:na]
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:80) ~[feign-core-10.4.0.jar:na]
    at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:109) ~[feign-hystrix-10.4.0.jar:na]
    at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302) ~[hystrix-core-1.5.18.jar:1.5.18]
    at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298) ~[hystrix-core-1.5.18.jar:1.5.18]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.Observable.unsafeSubscribe(Observable.java:10327) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:51) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.Observable.unsafeSubscribe(Observable.java:10327) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.Observable.unsafeSubscribe(Observable.java:10327) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OperatorSubscribeOn$SubscribeOnSubscriber.call(OperatorSubscribeOn.java:100) ~[rxjava-1.3.8.jar:1.3.8]
    at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:56) ~[hystrix-core-1.5.18.jar:1.5.18]
    at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:47) ~[hystrix-core-1.5.18.jar:1.5.18]
    at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction.call(HystrixContexSchedulerAction.java:69) ~[hystrix-core-1.5.18.jar:1.5.18]
    at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) ~[rxjava-1.3.8.jar:1.3.8]
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[na:1.8.0_171]
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_171]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_171]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_171]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_171]

補充:另外每個應用可以通過全局異常攔截器進行規避一些錯誤,攔截到錯誤之后將錯誤信息返回給上個服務。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM