服务降级 Hystrix断路器-服务降级

一、分布式系统面临的问题:

复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。

二、服务雪崩:

多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓“雪崩效应”。

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的说有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统,所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

三、Hystrix是什么?

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

“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的现成不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

三、Hystrix能干什么?

服务降级

服务熔断

接近实时的监控

四、官方资料:

https://github.com/Netflix/Hystrix/wiki/How-To-Use

五、Hystrix重要概念:

5.1 服务降级:

fallback

不让客户端等待并立刻返回一个友好的提示,哪些情况会触发降级

① 程序运行异常

② 超时

③ 服务熔断触发服务降级

④ 线程池/信号量打满也会导致服务降级

5.2 服务熔断:

 

类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示

服务的降级-》进而熔断-》恢复调用链路

5.3 服务限流:

秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。

六、Hystrix 案例

6.1 构建带熔断(hystrix)支付微服务

6.1.1 新建“cloud-provider-hystrix-payment8001”module模块

6.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>ckfuture-springcloud</artifactId>
        <groupId>com.ckfuture.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-hystrix-payment8001</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>
        <!--引入自定定义的api通用包,可以使用Payment支付实体-->
        <dependency>
            <groupId>com.ckfuture.springcloud</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>

        <!--热部署devtools-->
        <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>

 

6.1.3 建YML

新建“application.yml”文件

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

6.1.4 主启动

新建“PaymentHystrixMain8001” 启动类

package com.ckfuture.springcloud;

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

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class,args);
    }
}

 

6.1.5 业务类

 建立一个PaymentService 类

package com.ckfuture.springcloud.service;

import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {

    public String paymentInfo_ok(Integer id) {

        return Thread.currentThread() + " paymentInfo_ok,id:" + id + "\t";
        // http://i.hrbwmxx.com/wm_fm/#/editPage?templateId=496df2c380a78a86767e7258248a5aaa
    }

    public String paymentInfo_TimeOut(Integer id){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return Thread.currentThread() + " paymentInfo_TimeOut,id:" + id + "\t,耗时3秒";
    }

}

建立“PaymentController”类

package com.ckfuture.springcloud.controller;

import com.ckfuture.springcloud.service.PaymentService;
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.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService paymentService;
    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_ok(id);
        log.info("******result:"+result);
        return result;
    }

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

6.1.6 测试

依次启动 7001、8001

 

http://localhost:8001/payment/hystrix/ok/31

 

 

 http://localhost:8001/payment/hystrix/timeout/31

 

6.2 高并发测试

6.2.1 新建一个消费者“cloud-consumer-feign-hystrix-order80”module

6.2.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>ckfuture-springcloud</artifactId>
        <groupId>com.ckfuture.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-feign-hystrix-order80</artifactId>
    <dependencies>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--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>
        <!--引入自定定义的api通用包,可以使用Payment支付实体-->
        <dependency>
            <groupId>com.ckfuture.springcloud</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>

        <!--热部署devtools-->
        <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>

6.2.3 建立YML

server:
  port: 80
eureka:
  client:
    register-with-eureka: false
    service-url:
      defautZone: http://eureka7001.com:7001/eureka/

6.2.4 建主启动类

package com.ckfuture.springcloud;

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

@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class,args);
    }
}

6.2.5 业务类

创建“PaymentHystrixservice” 接口(面向接口)

package com.ckfuture.springcloud.service;

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;

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixservice {
    
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);

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

创建“OrderHystrixController” 类

package com.ckfuture.springcloud.controller;

import com.ckfuture.springcloud.service.PaymentHystrixservice;
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 OrderHystrixController {
    @Resource
    private PaymentHystrixservice paymentHystrixservice;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_ok(@PathVariable("id") Integer id){
        String result = paymentHystrixservice.paymentInfo_OK(id);
        return result;
    }
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixservice.paymentInfo_TimeOut(id);
        return result;
    }
}

 

6.2.6 测试

 

 http://localhost/consumer/payment/hystrix/ok/31

 

6.3 故障现象和导致原因

6.4 结论

6.5 如何解决

超时导致服务器变慢(转圈):超时不再等待

出错(宕机或程序运行出错):出错要有兜底

6.6 服务降级

6.6.1 降级配置

@HystrixCommand

6.6.2 8001自身问题

 设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,做服务降级fallback


package com.ckfuture.springcloud.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;

@Service
public class PaymentService {

public String paymentInfo_ok(Integer id) {

return Thread.currentThread() + " paymentInfo_ok,id:" + id + "\t";
}

@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public String paymentInfo_TimeOut(Integer id){
try {
int timeNumber = 5;
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Thread.currentThread() + " paymentInfo_TimeOut,id:" + id + "\t,耗时3秒";
}
//兜底方法
public String paymentInfo_TimeOutHandler(Integer id){

return Thread.currentThread() + " 系统繁忙,请稍后再试,id:" + id + "\t┭┮﹏┭┮";
}
}

6.6.3 主启动类激活

添加新注解@EnableCircuitBreaker

package com.ckfuture.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class,args);
    }
}

6.6.4 8001本身服务测试

启动8001服务。浏览器输入:http://localhost:8001/payment/hystrix/timeout/31

 

 6.6.5 80调用的服务降级

80订单为服务,也可以更好的保护自己。

注意:我们自己配置过的热部署方式对java代码的改动明显,但对@HystrixCommand内属性的修改建议重启微服务。

①修改yml

server:
  port: 80
spring:
  application:
    name: cloud-order-service
eureka:
  client:
    #表示是否将自己注册进EurekaServer,默认true
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置true才能配合ribbon使用负载均衡。
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka #单机版
      #defaultZone: http://eurreka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版
feign:
  hystrix:
    enabled: true

 

 ②主启动添加注解@EnableHystrix

package com.ckfuture.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
@EnableHystrix
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class,args);
    }
}

③ 业务类

package com.ckfuture.springcloud.controller;

import com.ckfuture.springcloud.service.PaymentHystrixservice;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
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 OrderHystrixController {
    @Resource
    private PaymentHystrixservice paymentHystrixservice;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_ok(@PathVariable("id") Integer id){
        String result = paymentHystrixservice.paymentInfo_OK(id);
        return result;
    }
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "1500")
    })
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixservice.paymentInfo_TimeOut(id);
        return result;
    }
    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
        String result = "我是消费者80,对反支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,┭┮﹏┭┮";
        return result;
    }
    
}

 6.6.6 全局通用的fallback

目前问题:每个业务方法对应一个兜底的方法,代码膨胀,定义一个全局通用的fallback、

解决方法:再类头上添加注解@DefaultProperties(defaultFallback=" 方法")

此类中的方法上没有指定的@HystrixComand(fallbackMethod=" 方法"),则找通用的fallback方法

① 写一个通用的服务降级的方法

 //下面是全局fallback方法
    public String payment_Global_FallbackMethod(){
        return "Global 异常处理信息,请稍后再试";
    }

② 在类头上添加注解@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")

③ 在方法上添加服务降级的注解 @HystrixCommand

package com.ckfuture.springcloud.controller;

import com.ckfuture.springcloud.service.PaymentHystrixservice;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
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
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController {
    @Resource
    private PaymentHystrixservice paymentHystrixservice;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_ok(@PathVariable("id") Integer id){
        String result = paymentHystrixservice.paymentInfo_OK(id);
        return result;
    }
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
  /*  @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "1500")
    })*/
    @HystrixCommand
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixservice.paymentInfo_TimeOut(id);
        return result;
    }
    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
        String result = "我是消费者80,对反支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,┭┮﹏┭┮";
        return result;
    }

    //下面是全局fallback方法
    public String payment_Global_FallbackMethod(){
        return "Global 异常处理信息,请稍后再试";
    }
}

 6.6.7 服务降级fallback方法和业务代码分开

目前问题:在一个类中既有业务代码又有服务降级的方法,代码太混乱了。

解决办法:

①新建一个类(PaymentFallbackService)实现PaymentHystrixservice接口,统一为接口里面的方法进行异常处理。


package com.ckfuture.springcloud.service.Impl;

import com.ckfuture.springcloud.service.PaymentHystrixservice;
import org.springframework.stereotype.Component;

@Component
public class PaymentFallbackService implements PaymentHystrixservice {
@Override
public String paymentInfo_OK(Integer id) {
return "----PaymentFallbackService fall back-paymentInfo_ok,/(ㄒoㄒ)/~~";
}

@Override
public String paymentInfo_TimeOut(Integer id) {
return "----PaymentFallbackService fall back-paymentInfo_TimeOut,/(ㄒoㄒ)/~~";
}
}

②改YML

在yml中开启hystrix

feign:
  hystrix:
    enabled: true

③改PaymentHystrixservice接口

package com.ckfuture.springcloud.service;

import com.ckfuture.springcloud.service.Impl.PaymentFallbackService;
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;

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixservice {

    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);

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

 

 ④测试

依次开启 7001、8001、80

 

 关闭8001服务,模拟服务宕机。再次请求

 

posted @ 2021-11-01 19:50  创客未来  阅读(70)  评论(0编辑  收藏  举报