先创建一个普通的服务提供者和一个服务消费者,注册中心Eureka,服务调用:openFegin

1.服务提供端

  1.1现有服务端,服务名称为CLOUD-PAYMENT-HYSTRIX-SERVICE

 

1.2依赖 

复制代码
<?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>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

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

    <dependencies>

        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>




        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
        <!--热部署-代码改变自动编译-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <!-- eureka客户端服务  可以作为服务注册到注册中心-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!-- hystrix 服务熔断降级-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

    </dependencies>


</project>
复制代码

1.3它的yml配置文件

复制代码
server:
  port: 8001


spring:
  application:
    name: cloud-payment-hystrix-service  #将会作为注册到注册中心的服务名称

eureka:
  client:
    register-with-eureka: true   #表识向注册中心注册自己
    fetchRegistry: true   #是否从注册中心抓取已有的注册信息,默认为true。单节点无所谓,集群必须要设置为true才能配合ribbon使用负载均衡
    service-url:  #注册中心地址
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版
  instance:
    instance-id: payment8001   #服务显示名称
    prefer-ip-address: true  #是否显示ip
    lease-renewal-interval-in-seconds: 20 #服务项注册中心发送心跳的默认间隔时间,单位为秒(默认是30秒)
    lease-expiration-duration-in-seconds: 80 #注册中心最后一次收到心跳,等待的最长时间,单位是秒,默认90秒


 
复制代码

1.4主启动类

复制代码
package com.atguigu.springcloud;

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

@SpringBootApplication
@EnableEurekaClient  //标识自己是Eureka客户端(也就是一个服务) - 适用于Eureka
public class HystrixPaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixPaymentMain8001.class,args);
    }
}
复制代码

1.5业务类

复制代码
package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;

@RestController
@Slf4j
public class PaymentController {

    @Resource
    private PaymentService paymentService;

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

    @GetMapping(value = "/payment/get1/{id}")
    public String create(@PathVariable("id") Long id){
        return paymentService.paymentInfo_OK(id);
    }


    @GetMapping(value = "/payment/get/{id}")
    public String getPaymentById(@PathVariable("id") Long id) throws InterruptedException {
        return paymentService.paymentInfo_TimeOut(id);
    }
}
复制代码

 

复制代码
package com.atguigu.springcloud.service;

import com.atguigu.springcloud.entities.Payment;


public interface PaymentService {


    public String paymentInfo_OK( Long id);  //读取

    public String paymentInfo_TimeOut( Long id);  //读取
}
复制代码
复制代码
package com.atguigu.springcloud.service.impl;



import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service

public class PaymentServiceImpl implements PaymentService {


    public String paymentInfo_OK( Long id){

        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_OK,id:  "+id+"\t"+"hahaha";

    }

    public String paymentInfo_TimeOut( Long id){
        int timeNumber = 3;
    try {
        TimeUnit.SECONDS.sleep(timeNumber);
    }catch (Exception e) {
        e.printStackTrace();
    }
    return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_TimeOut,id:  "+id+"\t"+"呜呜呜"+" 耗时(秒)"+timeNumber;


    }

}
 
复制代码

2.服务消费端

 

2.1依赖

复制代码
<?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>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

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

    <dependencies>


        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!-- hystrix 服务熔断降级-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

    </dependencies>

</project>
复制代码

2.2yml文件

复制代码
server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版

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

feign:
  client:
    config:
      default:
        #简历连接所用的时间,适用于网络状况正常的情况下,两端连接所需要的时间
        ConnectTimeOut: 5000
        #指建立连接后从服务端读取到可用资源所用的时间
        ReadTimeOut: 10000
复制代码

2.3主启动类

复制代码
package com.atguigu.springcloud;


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

@SpringBootApplication
@EnableFeignClients  //激活FeignClient
public class HyStrixOrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(HyStrixOrderMain80.class,args);
    }

}
复制代码

2.4业务类

复制代码
package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentFeignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderController {

    @Resource
    private PaymentFeignService paymentFeignService;


    @GetMapping("/hystrix/payment/create/{id}")
    public String create(@PathVariable("id") Long id){
        return paymentFeignService.create(id);
    }

    @GetMapping("/hystrix/payment/getPayment/{id}")
    public String getPayment(@PathVariable("id") Long id) throws InterruptedException {
        return paymentFeignService.getPaymentById(id);
    }

}
复制代码

 

复制代码
package com.atguigu.springcloud.service;

import com.atguigu.springcloud.entities.CommonResult;
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;

/**
 * @Classname PaymentFeignService
 * @Description TODO
 * @Date 2021/4/26 0026 下午 4:24
 * @Created by jcc
 */

@Component
@FeignClient(value = "CLOUD-PAYMENT-HYSTRIX-SERVICE")  //value - 服务的名称
public interface PaymentFeignService {

    @GetMapping(value = "/payment/get1/{id}")
    public String create(@PathVariable("id") Long id);


    @GetMapping(value = "/payment/get/{id}")
    public String getPaymentById(@PathVariable("id") Long id) throws InterruptedException;
}
复制代码

3.测试

  上面创建了一个服务提供者和一个服务消费者。现在我们来做测试,先正常访问测试,再使用Jmeter短时间内大量访问,看看效果怎么样。

 3.1 正常访问

  访问http://localhost/hystrix/payment/create/1,立刻返回结果

  访问http://localhost/hystrix/payment/getPayment/1,3秒后返回结果

 通过浏览器访问两个接口,正常返回结果

 

  3.2使用jmeter大量访问的8001服务提供者的两个服务接口,同时再访问客户端80

  使用jmeter开启500个线程每个线程访问100次

  

  500个线程循环访问100次

  

  启动

  

  同时,在浏览器访问http://localhost/hystrix/payment/create/1  和 http://localhost/hystrix/payment/getPayment/1两个接口

  此时,发现请求需等待很长时间才能够返回或者直接就超时了

  

 

   这是因为服务提供者被大量占用,不能及时处理浏览器的请求,导致响应时间变长,就很可能出现超时的现象

4.怎么处理上述的问题

  当8001服务提供者响应超时了或者down机了,调用者80不能一直等着,也不能直接返回一个报错的页面,这里就需要服务降级了

    服务提供者(8001)OK,调用者(80)自己出故障或有自我要求(自己的最大等待时间小于服务提供者的响应时间),自己处理降级

    达到超时不再等待,出错有兜底的处理

   

5.可以在服务提供者端进行配置-服务降级

  我们对public String getPaymentById方法来进行配置,如果我们预期这个接口最多2秒就该返回,否则服务降级

  5.1主启动类加入注解配置@EnableCircuitBreaker

@SpringBootApplication
@EnableEurekaClient  //标识自己是Eureka客户端(也就是一个服务) - 适用于Eureka
@EnableCircuitBreaker
public class HystrixPaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixPaymentMain8001.class,args);
    }
}

 

  5.2在controllrt接口处添加注解@HystrixCommand

   @GetMapping(value = "/payment/get/{id}")
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000") //4秒钟以内就是正常的业务逻辑
    })
    public String getPaymentById(@PathVariable("id") Long id) throws InterruptedException {
        return paymentService.paymentInfo_TimeOut(id);
    }

    fallbackMethod属性是指定超时和报错时调用的兜底方法 。在超时或者出错时会调用

    commandProperties属性中设置超时时间

  5.3兜底方法

 //兜底方法
    public String paymentInfo_TimeOutHandler(Long id){
        return "线程池:"+Thread.currentThread().getName()+"   系统繁忙, 请稍候再试  ,id:  "+id+"\t"+"哭了哇呜";
    }

  5.4测试

  由于方法getPaymentById业务中等待了3秒中,所以时间必然大于2秒,会触发服务降级,调用兜底方法

   

   5.5把超时时间改为4秒后再次测试

 @GetMapping(value = "/payment/get/{id}")
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "4000") //4秒钟以内就是正常的业务逻辑
    })
    public String getPaymentById(@PathVariable("id") Long id) throws InterruptedException {
        return paymentService.paymentInfo_TimeOut(id);
    }

  

 调用成功。以上关键是需要约定好超时时间。

6.也可以在服务调用者进行配置-服务降级

  一般在客户端进行配置,当然,根据业务需求,也可以在服务端进行配置,也可以两端都配置

  6.1主启动类加上注解@EnableHystrix

复制代码
package com.atguigu.springcloud;


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

@SpringBootApplication
@EnableFeignClients  //激活FeignClient
@EnableHystrix  //激活Hystrix
public class HyStrixOrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(HyStrixOrderMain80.class,args);
    }

}
复制代码

  6.2在controller对应的接口处加上注解@HystrixCommand

@GetMapping("/hystrix/payment/getPayment/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",
            commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value= "2000")//2秒钟以内就是正常的业务逻辑
            })
    public String getPayment(@PathVariable("id") Long id) throws InterruptedException {
        return paymentFeignService.getPaymentById(id);
    }

  6.3兜底方法

//兜底方法
    public String paymentTimeOutFallbackMethod (@PathVariable("id") Long id) throws InterruptedException{
        return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
    }

  6.4测试

  

   6.5设置默认的兜底方法

   6.5.1在controller类上加上注解@DefaultProperties

@RestController
@Slf4j
@DefaultProperties(defaultFallback = "paymentTimeOutFallbackMethodHander")
public class OrderController {

    6.5.2创建兜底方法-不要参数

//兜底方法 由于是通用的,参数不能保证各个地方的参数一致,所以不能有参数
    public String paymentTimeOutFallbackMethodHander (){
        return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
    }

 

    6.5.3在接口上加入注解@HystrixCommand,不需要fallbackMethod属性

 @GetMapping("/hystrix/payment/getPayment/{id}")
    @HystrixCommand(
            commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value= "2000")//15秒钟以内就是正常的业务逻辑
            })
    public String getPayment(@PathVariable("id") Long id) throws InterruptedException {
        return paymentFeignService.getPaymentById(id);
    }

    6.5.5测试

     

   6.6这样子配置还存在一个问题,兜底方法和业务逻辑是在一起的,比较混乱

  6.6.1我们调用8001的服务靠的是接口PaymentFeignService,下面是这个接口

复制代码
package com.atguigu.springcloud.service;

import com.atguigu.springcloud.entities.CommonResult;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
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;

/**
 * @Classname PaymentFeignService
 * @Description TODO
 * @Date 2021/4/26 0026 下午 4:24
 * @Created by jcc
 */

@Component
@FeignClient(value = "CLOUD-PAYMENT-HYSTRIX-SERVICE")  //value - 服务的名称
public interface PaymentFeignService {

    @GetMapping(value = "/payment/get1/{id}")
    public String create(@PathVariable("id") Long id);


    @GetMapping(value = "/payment/get/{id}")
    public String getPaymentById(@PathVariable("id") Long id) throws InterruptedException;


}
复制代码

    6.6.2我们写一个实现类,实现这个接口,作为这个接口的每一个接口的兜底方法,@Component注解

复制代码
package com.atguigu.springcloud.service;

import org.springframework.stereotype.Component;

/**
 * @Classname PaymentFeignServiceImpl
 * @Description TODO
 * @Date 2021/4/29 0029 下午 4:58
 * @Created by jcc
 */
@Component
public class PaymentFeignServiceImpl implements PaymentFeignService{
    @Override
    public String create(Long id) {
        return "PaymentFeignServiceImpl的兜底方法1create";
    }

    @Override
    public String getPaymentById(Long id) throws InterruptedException {
        return "PaymentFeignServiceImpl的兜底方法2getPaymentById";
    }
}
复制代码

  6.6.3在接口PaymentFeignService的注解@FeignClient加入一个属性fallback = PaymentFeignServiceImpl.class

   fallback的值就是上面的实现类

复制代码
package com.atguigu.springcloud.service;

import com.atguigu.springcloud.entities.CommonResult;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
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;

/**
 * @Classname PaymentFeignService
 * @Description TODO
 * @Date 2021/4/26 0026 下午 4:24
 * @Created by jcc
 */

@Component
@FeignClient(value = "CLOUD-PAYMENT-HYSTRIX-SERVICE",fallback = PaymentFeignServiceImpl.class)  //value - 服务的名称
public interface PaymentFeignService {

    @GetMapping(value = "/payment/get1/{id}")
    public String create(@PathVariable("id") Long id);


    @GetMapping(value = "/payment/get/{id}")
    public String getPaymentById(@PathVariable("id") Long id) throws InterruptedException;


}
复制代码

   6.6.4在yml加入配置 

feign:

      hystrix:
        enabled: true   
复制代码
server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版

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

feign:
  client:
    config:
      default:
        #简历连接所用的时间,适用于网络状况正常的情况下,两端连接所需要的时间
        ConnectTimeOut: 15000
        #指建立连接后从服务端读取到可用资源所用的时间
        ReadTimeOut: 15000
  hystrix:
    enabled: true
复制代码

  6.6.5业务类

@GetMapping("/hystrix/payment/getPayment/{id}")
    @HystrixCommand(
            commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value= "2000")//15秒钟以内就是正常的业务逻辑
            })
    public String getPayment(@PathVariable("id") Long id) throws InterruptedException {
        return paymentFeignService.getPaymentById(id);
    }

   6.6.6调用

  

  6.6.7问题

@GetMapping("/hystrix/payment/getPayment/{id}")
    @HystrixCommand(
            commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value= "5000")//5秒钟以内就是正常的业务逻辑
            })
    public String getPayment(@PathVariable("id") Long id) throws InterruptedException {
        return paymentFeignService.getPaymentById(id);
    }

    当把超时时间改为5秒时,再次访问,还是超时了。原因是因为我们上面配置了 feign.hystrix.enabled=true,hystrix的默认超时时间是1秒,它这里用的是默认的时间1秒而不是我们配置的5秒。

    百度了一下说需要加入配置,下面的配置试过了,发现超时还是1秒,为什么?????求大佬告知!!!

hystrix:
  command.default.execution.isolation.thread.timeoutInMilliseconds: 6000
hystrix:
  command:
    test:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000

 

7.服务熔断

  大神论文 https://martinfowler.com/bliki/CircuitBreaker.html

  服务熔断是针对服务接口的健康状态来进行一个监控。若发现这个 服务接口在某段时间内的访问数达到一个数量,失败率也达到阀值,此时,断路器打开,每次都调用服务降级fallback。过一段时间后,才尝试释放一个请求正常执行,若成功,断路器关闭,服务恢复正常。否则等一段时间再次尝试。

  服务熔断针对的是服务提供者

 测试实例

  7.1服务提供者8001处新添加一个服务接口

  7.1.1controller

 @GetMapping(value = "/payment/rd/get/{id}")
    public String getPaymentRDById(@PathVariable("id") Long id) throws InterruptedException {
        return paymentService.getPaymentRDById(id);
    }

 

  7.1.2service

String getPaymentRDById(Long id);

 

复制代码
@Override
    @HystrixCommand(fallbackMethod = "getPaymentRDByIdFallback",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"),//失败率达到多少后跳闸
            })
    //整个配置的意思是:在10秒内,请求数大于等于10,且失败率大于等于60%就触发熔断
    public String getPaymentRDById(Long id) {

        if(id < 0){
            int i = 1 / 0;  //故意出错
        }
        return "熔断测试方法getPaymentRDById";
    }
复制代码
 public String getPaymentRDByIdFallback(Long id) {
        return "熔断测试方法getPaymentRDById的兜底方法";
    }

  关于commandProperties下几个注解的说明

  circuitBreaker.enabled:表示开机熔断机制

  circuitBreaker.requestVolumeThreshold:表示一段时间内请求至少需要达到多少才可能触发熔断机制

    circuitBreaker.sleepWindowInMilliseconds:时间范围,也就是上面的[一段时间内]

  circuitBreaker.errorThresholdPercentage:请求的失败率

  上面的配置表示:在10秒内,请求数大于等于10,且失败率大于等于60%就触发熔断

  7.2在80端去调用这个服务接口

  7.2.1controller

 @GetMapping("/hystrix/payment/rd/{id}")
    public String rd(@PathVariable("id") Long id){
        return paymentFeignService.getPaymentRDById(id);
    }

 

  7.2.2service

@GetMapping(value = "/payment/rd/get/{id}")
    public String getPaymentRDById(@PathVariable("id") Long id);

 

  7.3测试

    http://localhost/hystrix/payment/rd/1

    当id传正数时,正常返回

    

    http://localhost/hystrix/payment/rd/-1

    当id传负数时,服务接口报错,调用失败,服务降级,调用fallback方法

 

    

     这都符合我们的预期。

    现在我们调用http://localhost/hystrix/payment/rd/-1,id为负数,在10秒内大量访问(超过10次)

    每次都是返回的fallback的方法的返回值

    

     马上访问http://localhost/hystrix/payment/rd/1,id为正数,此时我们发现,返回的还是fallback的方法的返回值,说明还是调用的服务降级的fallback方法。也就是说,断路器已经打开了

  等待一段时间后,在次访问http://localhost/hystrix/payment/rd/1,发现恢复正常了

  

 8.HystrixCommand参数解析(转https://www.cnblogs.com/zhenbianshu/p/9630167.html)

HystrixCommand


配置方式

我们的配置都是基于 HystrixCommand 的,我们通过在方法上添加 @HystrixCommand 注解并配置注解的参数来实现配置,但有的时候一个类里面会有多个 Hystrix 方法,每个方法都是类似配置的话会冗余很多代码,这时候我们可以在类上使用 @DefaultProperties 注解来给整个类的 Hystrix 方法设置一个默认值。

配置项

下面是 HystrixCommand 支持的参数,除了 commandKey/observableExecutionMode/fallbackMethod 外,都可以使用 @DefaultProperties 配置默认值。

  • commandKey:用来标识一个 Hystrix 命令,默认会取被注解的方法名。需要注意:Hystrix 里同一个键的唯一标识并不包括 groupKey,建议取一个独一二无的名字,防止多个方法之间因为键重复而互相影响。

  • groupKey:一组 Hystrix 命令的集合, 用来统计、报告,默认取类名,可不配置。

  • threadPoolKey:用来标识一个线程池,如果没设置的话会取 groupKey,很多情况下都是同一个类内的方法在共用同一个线程池,如果两个共用同一线程池的方法上配置了同样的属性,在第一个方法被执行后线程池的属性就固定了,所以属性会以第一个被执行的方法上的配置为准。

  • commandProperties:与此命令相关的属性。

  • threadPoolProperties:与线程池相关的属性,

  • observableExecutionMode:当 Hystrix 命令被包装成 RxJava 的 Observer 异步执行时,此配置指定了 Observable 被执行的模式,默认是 ObservableExecutionMode.EAGER,Observable 会在被创建后立刻执行,而 ObservableExecutionMode.EAGER模式下,则会产生一个 Observable 被 subscribe 后执行。我们常见的命令都是同步执行的,此配置项可以不配置。

  • ignoreExceptions:默认 Hystrix 在执行方法时捕获到异常时执行回退,并统计失败率以修改熔断器的状态,而被忽略的异常则会直接抛到外层,不会执行回退方法,也不会影响熔断器的状态。

  • raiseHystrixExceptions:当配置项包括 HystrixRuntimeException 时,所有的未被忽略的异常都会被包装成 HystrixRuntimeException,配置其他种类的异常好像并没有什么影响。

  • fallbackMethod:方法执行时熔断、错误、超时时会执行的回退方法,需要保持此方法与 Hystrix 方法的签名和返回值一致。

  • defaultFallback:默认回退方法,当配置 fallbackMethod 项时此项没有意义,另外,默认回退方法不能有参数,返回值要与 Hystrix方法的返回值相同。

commandProperties


配置方式

Hystrix 的命令属性是由 @HystrixProperty 注解数组构成的,HystrixProperty 由 name 和 value 两个属性,数据类型都是字符串。

以下将所有的命令属性分组来介绍。

线程隔离(Isolation)

  • execution.isolation.strategy: 配置请求隔离的方式,有 threadPool(线程池,默认)和 semaphore(信号量)两种,信号量方式高效但配置不灵活,我们一般采用 Java 里常用的线程池方式。

  • execution.timeout.enabled:是否给方法执行设置超时,默认为 true。

  • execution.isolation.thread.timeoutInMilliseconds:方法执行超时时间,默认值是 1000,即 1秒,此值根据业务场景配置。

  • execution.isolation.thread.interruptOnTimeoutexecution.isolation.thread.interruptOnCancel:是否在方法执行超时/被取消时中断方法。需要注意在 JVM 中我们无法强制中断一个线程,如果 Hystrix 方法里没有处理中断信号的逻辑,那么中断会被忽略。

  • execution.isolation.semaphore.maxConcurrentRequests:默认值是 10,此配置项要在 execution.isolation.strategy 配置为 semaphore 时才会生效,它指定了一个 Hystrix 方法使用信号量隔离时的最大并发数,超过此并发数的请求会被拒绝。信号量隔离的配置就这么一个,也是前文说信号量隔离配置不灵活的原因。

统计器(Metrics)

滑动窗口: Hystrix 的统计器是由滑动窗口来实现的,我们可以这么来理解滑动窗口:一位乘客坐在正在行驶的列车的靠窗座位上,列车行驶的公路两侧种着一排挺拔的白杨树,随着列车的前进,路边的白杨树迅速从窗口滑过,我们用每棵树来代表一个请求,用列车的行驶代表时间的流逝,那么,列车上的这个窗口就是一个典型的滑动窗口,这个乘客能通过窗口看到的白杨树就是 Hystrix 要统计的数据。

: bucket 是 Hystrix 统计滑动窗口数据时的最小单位。同样类比列车窗口,在列车速度非常快时,如果每掠过一棵树就统计一次窗口内树的数据,显然开销非常大,如果乘客将窗口分成十分,列车前进行时每掠过窗口的十分之一就统计一次数据,开销就完全可以接受了。 Hystrix 的 bucket (桶)也就是窗口 N分之一 的概念。

  • metrics.rollingStats.timeInMilliseconds:此配置项指定了窗口的大小,单位是 ms,默认值是 1000,即一个滑动窗口默认统计的是 1s 内的请求数据。

  • metrics.healthSnapshot.intervalInMilliseconds:它指定了健康数据统计器(影响 Hystrix 熔断)中每个桶的大小,默认是 500ms,在进行统计时,Hystrix 通过 metrics.rollingStats.timeInMilliseconds / metrics.healthSnapshot.intervalInMilliseconds 计算出桶数,在窗口滑动时,每滑过一个桶的时间间隔时就统计一次当前窗口内请求的失败率。

  • metrics.rollingStats.numBuckets:Hystrix 会将命令执行的结果类型都统计汇总到一块,给上层应用使用或生成统计图表,此配置项即指定了,生成统计数据流时滑动窗口应该拆分的桶数。此配置项最易跟上面的 metrics.healthSnapshot.intervalInMilliseconds 搞混,认为此项影响健康数据流的桶数。 此项默认是 10,并且需要保持此值能被 metrics.rollingStats.timeInMilliseconds 整除。

  • metrics.rollingPercentile.enabled:是否统计方法响应时间百分比,默认为 true 时,Hystrix 会统计方法执行的 1%,10%,50%,90%,99% 等比例请求的平均耗时用以生成统计图表。

  • metrics.rollingPercentile.timeInMilliseconds:统计响应时间百分比时的窗口大小,默认为 60000,即一分钟。

  • metrics.rollingPercentile.numBuckets:统计响应时间百分比时滑动窗口要划分的桶用,默认为6,需要保持能被metrics.rollingPercentile.timeInMilliseconds 整除。

  • metrics.rollingPercentile.bucketSize:统计响应时间百分比时,每个滑动窗口的桶内要保留的请求数,桶内的请求超出这个值后,会覆盖最前面保存的数据。默认值为 100,在统计响应百分比配置全为默认的情况下,每个桶的时间长度为 10s = 60000ms / 6,但这 10s 内只保留最近的 100 条请求的数据。

熔断器(Circuit Breaker)

  • circuitBreaker.enabled:是否启用熔断器,默认为 true;

  • circuitBreaker.forceOpencircuitBreaker.forceClosed:是否强制启用/关闭熔断器,强制启用关闭都想不到什么应用的场景,保持默认值,不配置即可。

  • circuitBreaker.requestVolumeThreshold:启用熔断器功能窗口时间内的最小请求数。试想如果没有这么一个限制,我们配置了 50% 的请求失败会打开熔断器,窗口时间内只有 3 条请求,恰巧两条都失败了,那么熔断器就被打开了,5s 内的请求都被快速失败。此配置项的值需要根据接口的 QPS 进行计算,值太小会有误打开熔断器的可能,值太大超出了时间窗口内的总请求数,则熔断永远也不会被触发。建议设置为 QPS * 窗口秒数 * 60%

  • circuitBreaker.errorThresholdPercentage:在通过滑动窗口获取到当前时间段内 Hystrix 方法执行的失败率后,就需要根据此配置来判断是否要将熔断器打开了。 此配置项默认值是 50,即窗口时间内超过 50% 的请求失败后会打开熔断器将后续请求快速失败。

  • circuitBreaker.sleepWindowInMilliseconds:熔断器打开后,所有的请求都会快速失败,但何时服务恢复正常就是下一个要面对的问题。熔断器打开时,Hystrix 会在经过一段时间后就放行一条请求,如果这条请求执行成功了,说明此时服务很可能已经恢复了正常,那么会将熔断器关闭,如果此请求执行失败,则认为服务依然不可用,熔断器继续保持打开状态。此配置项指定了熔断器打开后经过多长时间允许一次请求尝试执行,默认值是 5000。

其他(Context/Fallback)

  • requestCache.enabled:是否启用请求结果缓存。默认是 true,但它并不意味着我们的每个请求都会被缓存。缓存请求结果和从缓存中获取结果都需要我们配置 cacheKey,并且在方法上使用 @CacheResult 注解声明一个缓存上下文。

  • requestLog.enabled:是否启用请求日志,默认为 true。

  • fallback.enabled:是否启用方法回退,默认为 true 即可。

  • fallback.isolation.semaphore.maxConcurrentRequests:回退方法执行时的最大并发数,默认是10,如果大量请求的回退方法被执行时,超出此并发数的请求会抛出 REJECTED_SEMAPHORE_FALLBACK 异常。

threadPoolProperties


配置方式

线程池的配置也是由 HystrixProperty 数组构成,配置方式与命令属性一致。

配置项

  • coreSize:核心线程池的大小,默认值是 10,一般根据 QPS * 99% cost + redundancy count 计算得出。

  • allowMaximumSizeToDivergeFromCoreSize:是否允许线程池扩展到最大线程池数量,默认为 false;

  • maximumSize:线程池中线程的最大数量,默认值是 10,此配置项单独配置时并不会生效,需要启用 allowMaximumSizeToDivergeFromCoreSize 项。

  • maxQueueSize:作业队列的最大值,默认值为 -1,设置为此值时,队列会使用 SynchronousQueue,此时其 size 为0,Hystrix 不会向队列内存放作业。如果此值设置为一个正的 int 型,队列会使用一个固定 size 的 LinkedBlockingQueue,此时在核心线程池内的线程都在忙碌时,会将作业暂时存放在此队列内,但超出此队列的请求依然会被拒绝。

  • queueSizeRejectionThreshold:由于 maxQueueSize 值在线程池被创建后就固定了大小,如果需要动态修改队列长度的话可以设置此值,即使队列未满,队列内作业达到此值时同样会拒绝请求。此值默认是 5,所以有时候只设置了 maxQueueSize 也不会起作用。

  • keepAliveTimeMinutes:由上面的 maximumSize,我们知道,线程池内核心线程数目都在忙碌,再有新的请求到达时,线程池容量可以被扩充为到最大数量,等到线程池空闲后,多于核心数量的线程还会被回收,此值指定了线程被回收前的存活时间,默认为 2,即两分钟。

工作方式

Hystrix 内线程池的使用是基于 Java 内置线程池的简单包装,通常有以下三种状态:

  • 如果请求量少,达不到 coreSize,通常会使用核心线程来执行任务。
  • 如果设置了 maxQueueSize,当请求数超过了 coreSize, 通常会把请求放到 queue 里,待核心线程有空闲时消费。
  • 如果 queue 长度无法存储请求,则会创建新线程执行直到达到 maximumSize 最大线程数,多出核心线程数的线程会在空闲时回收。