Hystrix熔断器(Hystrix官宣,停更进维)

为什么要用Hystrix

在复杂分布式体系结构中的应用程序中,我们程序中有很多依赖,加入其中一个依赖坏了,其他的也会受到影响,很容易照成服务器雪崩

雪崩:服务雪崩效应产生与服务堆积在同一个线程池中,因为所有的请求都是同一个线程池进行处理,这时候如果在高并发情况下,所有的请求全部访问同一个接口,
这时候可能会导致其他服务没有线程进行接受请求,这就是服务雪崩效应效应。

Hystrix是什么

Hystrix是一 个用于处理分布式系统的延迟和容错的开源库, 在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,
Hystrix能够保证在一 个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

官方资料:https://github.com/Netflix/hystrix/wiki

主要功能

服务降级

服务降级是什么?

服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback

比如说我们在访问一个网站很就都没有给出明确的反应,那么就会提示出 “服务器繁忙” 等等…… 这就是服务降级的一种方式

什么情况下,或者说什么什么需要服务降级?

1.服务出现运行时异常

2.访问超时

3.服务熔断触发服务降级

4.线程池/信号量也会导致服务降级

使用案例

新建cloud-provider-hystrix-payment8001

  • pom
<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-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.example</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.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>
  • yaml
server:
  port: 8001

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
  • 主启动类 PaymentHystrixMain8001
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class,args);

    }
}
  • 编写service 这里是为了展示效果,所以就不写接口了,直接写PaymentHystrixService
@Service
public class PaymentHystrixService {
    /**
     * 正常访问
     *
     * @param id
     * @return
     */
    public String paymentInfo_OK(Integer id) {
        return "线程池:" + Thread.currentThread().getName() + " paymentInfo_OK,id:" + id + "\t" + "O(∩_∩)O哈哈~";
    }

    /**
     * 超时访问
     *
     * @param id
     * @return
     */
    public String paymentInfo_TimeOut(Integer id) {
        int timeNumber = 3;
        try {
            // 暂停3秒钟
            TimeUnit.SECONDS.sleep(timeNumber);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:" + Thread.currentThread().getName() + " paymentInfo_TimeOut,id:" + id + "\t" +
                "O(∩_∩)O哈哈~  耗时(秒)" + timeNumber;
    }

}
  • controller层 OrderHystrixController
@RestController
@Slf4j
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService paymentHystrixService;
    @Value("${server.port}")
    private String servicePort;

    /**
     * 正常访问
     *
     * @param id
     * @return
     */
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id) {
        String result = paymentHystrixService.paymentInfo_OK(id);
        log.info("*****result:" + result);
        return result;
    }

    /**
     * 超时访问
     *
     * @param id
     * @return
     */
    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        log.info("*****result:" + result);
        return result;

    }
}

测试 是否能正常访问

加入服务消费者模块cloud-consumer-feign-hystrix-order80

  • pom

    <dependencies>
        <!--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-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>cloud-api-common</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.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>
    
  • yaml

    server:
      port: 80
    eureka:
      client:
        register-with-eureka: false
        fetch-registry: true
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
    
  • 主启动类

    @SpringBootApplication
    @EnableFeignClients
    @EnableEurekaClient
    public class OrderFeignMain80 {
        public static void main(String[] args) {
            SpringApplication.run(OrderFeignMain80.class,args);
        }
    }
    
  • 编写业务类serviceFeign接口OrderController

    @Component
    @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
    public interface PaymentHystrixService {
    
    
        /**
         * 正常访问
         *
         * @param id
         * @return
         */
        @GetMapping("/payment/hystrix/ok/{id}")
        String paymentInfo_OK(@PathVariable("id") Integer id);
    
        /**
         * 超时访问
         *
         * @param id
         * @return
         */
        @GetMapping("/payment/hystrix/timeout/{id}")
        String paymentInfo_TimeOut(@PathVariable("id") Integer id);
    }
    
  • controller

    @RestController
    @Slf4j
    public class OrderController {
        @Resource
        private PaymentHystrixService paymentHystrixService;
    
        /**
         * 正常访问
         *
         * @param id
         * @return
         */
        @GetMapping("/consumer/payment/hystrix/ok/{id}")
        String paymentInfo_OK(@PathVariable("id") Integer id){
            return paymentHystrixService.paymentInfo_OK(id);
        }
    
        /**
         * 超时访问
         *
         * @param id
         * @return
         */
        @GetMapping("/consumer/payment/hystrix/timeout/{id}")
        String paymentInfo_TimeOut(@PathVariable("id") Integer id){
            return paymentHystrixService.paymentInfo_TimeOut(id);
        }
    
    }
    

    准备工作弄好了就开始做一个压力测试2w个线程压8001

    然后在去访问80,发现本来访问秒回的,现在也需要等待了,甚至可能发生访问超时 的错误! 所以这是后就需要用服务降级

    如何解决?解决的要求

    访问超时不在等待,出现报错要有兜底方案

    使用服务降级

    主启动上开启支持@EnableHystrix,

    yml 中配置

    feign:
      hystrix:
        enabled: true
    

    在controller中加入注解以及兜底方法

    //fallbackMethod:调用那个方法
    //commandProperties配置属性(如:等待多少秒,单位……)
    @HystrixCommand(fallbackMethod ="paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })
    
    //兜底方法,也就是等待超时或者出现服务报错时调用的方法
     public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
            return "我是消费者80,对方支付系统繁忙请10秒种后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
        }
    

    弄完之后测试

    访问:http://localhost/consumer/payment/hystrix/timeout/44

    因为我访问的这个方法中本来就让他线程睡眠了3秒,刚刚到等待的时间所以直接就返回了image-20200327202108258

    虽然我们这样写可以达到服务降级的能力,但是发现如果很多个方法都要达到这个效果,会出现代码的耦合高,代码的冗余,所以这也不是很理想的实现方式,所以下面用一种没有给方法特定的定制,就按默认的走

@DefaultProperties

我们可以通过这个注解实现通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量

具体怎么弄

  • 首先修改80 的controller 头部加上 @DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")

  • 方法上加上@HystrixCommand代表这个方法使用服务降级

  • 最后写上全局fallback

  • 测试

    ps:如果你需要某个方法特定的返回,那么就可以用@HystrixCommand来定制

解决业务逻辑和fallback的和在一起

但是我们还有一个问题还没有解决,把fallback方法和业务方法写在一起,还是不行所以下面解决这个问题

  • 实现PaymentHystrixService这个接口类,实现方法中写上你业务报错要返回的东西
  • 之后在业务接口上@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = paymentHystrixServiceFallback.class)

服务熔断

服务熔断是什么

*熔断*这一概念来源于电子工程中的断路器(Circuit Breaker)

在分布式系统中,当我们的的业务出错或者访问超时,这样一直访问就会拉低其他服务的效率,互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。

这种牺牲局部,保全整体的措施就叫做熔断。

相关论文 https://martinfowler.com/bliki/CircuitBreaker.html

实操

修改cloud-provider-hystrix-payment8001

在PaymentHystrixService中添加一个方法业务:

//====服务熔断
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
    if (id < 0) {
        throw new RuntimeException("*****id不能是负数");
    }
    String serialNumber = IdUtil.simpleUUID();
    return Thread.currentThread().getName() + "\t" + "调用成功,流水号:" + serialNumber;
}
/**
 * 服务熔断
 * http://localhost:8001/payment/circuit/32
 * @param id
 * @return
 */
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
    String result = paymentHystrixService.paymentCircuitBreaker(id);
    log.info("***result:" + result);
    return result;
}

cloud-consumer-feign-hystrix-order80修改

PaymentHystrixService

/**
 * 服务熔断
 * @param id
 * @return
 */
@GetMapping("/payment/circuit/{id}")
String paymentCircuitBreaker(@PathVariable("id") Integer id);

paymentHystrixServiceFallback

@Override
public String paymentCircuitBreaker(Integer id) {
    return "id 不能负数,请稍后重试,o(╥﹏╥)o id:" + id;
}

controller

/**
 * 在10秒窗口期中10次请求有6次是请求失败的,断路器将起作用
 * @param id
 * @return
 */
@HystrixCommand(
        fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
        @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),// 是否开启断路器
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5"),// 请求次数
        @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),// 时间窗口期/时间范文
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")// 失败率达到多少后跳闸
})
@GetMapping("/consumer/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
    return  paymentHystrixService.paymentCircuitBreaker(id);
}

测试:

发现你输入负整数,满足了他的熔断要求之后,马上访问正整数id也是会是错的,所以他是熔断之后不会马上回复,而是过段时间才好。

服务监控hystrixDashboard

我们知道Hystrix除了服务降级和服务熔断之外还可以提供准实时监控功能,它会持续记录通过Hystrix发起的请求信息,简单来说就是会将服务的请求调用情况以可视化的形式展现出来,方便维护微服务。

【示例】

新建cloud-consumer-hystrix-dashboard9001

pom

<description>hystrix监控</description>


<dependencies>
    <!--hystrix dashboard-->
    <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.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>

yml

server:
  port: 9001

主启动

@SpringBootApplication
@EnableHystrixDashboard//开启HystrixDashboard
public class DashoardHystrixMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(DashoardHystrixMain9001.class,args);
    }
}

访问http://localhost:9001/hystrix

image-20200328160510626

看到这个说明配置成功了,之后一步就是给你要监控的服务

pom

加上

<!--hystrix dashboard-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!--hystrix-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</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>

主启动上开启@EnableHystrixDashboard

注意:新版本Hystrix需要在服务主启动中指定监控路径

没有加会出现:Unable to connect to Command Metric Stream.的红色字

/**
 * 此配置是为了服务监控而配置,与服务容错本身无观,springCloud 升级之后的坑
 * ServletRegistrationBean因为springboot的默认路径不是/hystrix.stream
 * 只要在自己的项目中配置上下面的servlet即可
 * @return
 */
@Bean
public ServletRegistrationBean getServlet(){
    HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
    ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(streamServlet);
    registrationBean.setLoadOnStartup(1);
    registrationBean.addUrlMappings("/hystrix.stream");
    registrationBean.setName("HystrixMetricsStreamServlet");
    return registrationBean;
}

之后测试image-20200328161104436

点击(记得一进去你要多查询几遍服务地址,)

image-20200328161249983

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

只有不断的努力才会有更大的惊喜等着你去发现!!