springcloud之Hystrix

1、Hystrix概述

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

1.1、服务降级

当出现以下情况,服务器忙,不让客户端等待并立刻返回一个友好提示,fallback

  • 程序运行异常
  • 超时
  • 服务熔断触发服务降级
  • 线程池/信号量打满

1.2、服务熔断

当服务器请求超过最大数,直接拒绝访问,然后调用服务降级方法并返回一个友好提示

1.3、服务限流

秒杀高并发等操作,严禁拥挤,排队有序进行

2、模拟高并发场景下异常

2.1、服务端-项目构建

项目结构
在这里插入图片描述

启动类

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

yml文件

server:
  port: 8001

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

eureka:
  client:
    register-with-eureka: true #是否将自己注册进eurekaServer默认true
    fetch-registry: true #是否从eurekaServer抓取已有的注册信息,默认true。集群必须设置true才能配合Ribbon使用负载均衡
    service-url:
      #设置与Eureka Server交互的地址查询服务和注册服务
      defaultZone: http://eureka7001.com:7001/eureka
      #集群版
#      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

两个service方法,一个正常返回,另外一个睡眠3秒钟返回

@Service
public class PaymentService {
    //正常访问返回的方法
    public String paymentInfo_OK(Integer id){
        return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK, id: "+id+" ==》正常返回";
    }
    //耗时等待返回的方法
    public String paymentInfo_TimeOut(Integer id) throws InterruptedException {
        int timeOut = 3;
        TimeUnit.SECONDS.sleep(timeOut);
        return "线程池:"+Thread.currentThread().getName()+" paymentInfo_TimeOut, id: "+id+" ==》耗时等待"+timeOut+"秒";
    }
}

controller方法

@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("paymentInfo_OK 响应结果:"+result);
        return result;
    }

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) throws InterruptedException {
        String result = paymentService.paymentInfo_TimeOut(id);
        log.info("paymentInfo_TimeOut 响应结果:"+result);
        return result;
    }

}

启动Eureka,和以上服务
在这里插入图片描述

访问正常返回方法(立刻返回)
在这里插入图片描述
访问睡眠3秒钟再返回方法(转圈等待3秒)
在这里插入图片描述

2.2、Jmeter高并发访问

2000个并发访问paymentInfo_TimeOut,每次请求都需要睡眠3秒钟才能返回
在这里插入图片描述
在这里插入图片描述
后台一直在请求

在这里插入图片描述

此时访问paymentInfo_OK方法(以前立刻秒回)也在转圈等待
在这里插入图片描述

结论:20000个请求一直发送到paymentInfo_TimeOut,tomcat工作线程被使用完了,没有多余的线程来处理paymentInfo_OK,此时访问paymentInfo_OK也需要等3秒钟(等其中访问paymentInfo_TimeOut的线程执行完)。

2.3、消费端-项目构建

在这里插入图片描述
启动类

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

yml文件

server:
  port: 80

eureka:
  client:
    #是否将自己注册进eurekaServer默认true
    register-with-eureka: false
    service-url:
      #设置与Eureka Server交互的地址查询服务和注册服务
      defaultZone: http://localhost:7001/eureka
      #集群版
#      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
ribbon:
  #指建立连接后从服务器读取到可用资源所用的时间
  # openFeign-ribbon,客户端一般默认等待1秒钟
  ReadTimeout: 3000

利用Feign调用客户端微服务方法

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

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

controller方法

@RestController
@Slf4j
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService PaymentHystrixService;

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

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

当服务端20000个并发访问paymentInfo_TimeOut,此时消费者发送请求,无论哪里方法,都需要等待3秒。服务端发送请求出现的问题一致。

3、服务降级的处理方式

服务降级:当消费端发送请求到客户端,客户端出现超时、异常、宕机,此时需要有兜底处理方式,不至于整个服务崩溃。

  • 服务出现问题,则将请求转到默认处理返回方法(友好提示)
  • 客户端和消费端都可以做,一般放在消费端

3.1、消费端设置服务降级

启动类
在这里插入图片描述
yml
在这里插入图片描述

Controller业务层

@RestController
@Slf4j
@DefaultProperties(defaultFallback = "paymentGlobalFallbackMethod")
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService PaymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    @HystrixCommand
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        int i = 10/0;
        return PaymentHystrixService.paymentInfo_OK(id);
    }

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            //超过2秒及服务降级
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")
    })
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        return PaymentHystrixService.paymentInfo_TimeOut(id);
    }
    //此方法为paymentInfo_TimeOut方法的专属降级方法
    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
        return "对方支付系统繁忙,请稍后再试  ==>paymentInfo_TimeOut方法专属降级方法";
    }

    //全局公共的降级方法
    public String paymentGlobalFallbackMethod(){
        return "对方支付系统繁忙,请稍后再试  ==>全局公共降级方法";
    }
}

消费者调用paymentInfo_OK方法(有10/0异常)
后台无报错信息,相当于直接跳转方法了
在这里插入图片描述
消费者调用paymentInfo_TimeOut方法(睡眠3秒,2秒超时)
在这里插入图片描述
结论和注意点:

  • @DefaultProperties注解为全局降级方法,对于添加@HystrixCommand注解的方法会走全局公共降级方法
  • @HystrixCommand(fallbackMethod = “xxx” )则为当前方法设置专属的降级方法
  • 注意事项1:设置专属降级方法时候,其降级方法入参必须与注解方法一致,否则报错找不到指定参数的降级方法
  • 注意事项2:如果代码中有异常,比如10/0,不会抛错,只会走降级方法

3.2、消费端解耦式设置服务降级(不在Controller业务层)

业务层

@RestController
@Slf4j
//@DefaultProperties(defaultFallback = "paymentGlobalFallbackMethod")
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService PaymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        int i = 10/0;
        return PaymentHystrixService.paymentInfo_OK(id);
    }

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

为Feigin添加fallback类,此类实现本接口,每个实现类即为调用服务的降级方法

@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
    @GetMapping("/payment/hystrix/ok/{id}")
    String paymentInfo_OK(@PathVariable("id") Integer id);

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

降级方法

@Component
public class PaymentFallbackService implements PaymentHystrixService {
    @Override
    public String paymentInfo_OK(Integer id) {
        return "PaymentFallbackService 的 paymentInfo_OK的降级方法";
    }

    @Override
    public String paymentInfo_TimeOut(Integer id) {
        return "PaymentFallbackService 的 paymentInfo_TimeOut的降级方法";
    }
}

访问paymentInfo_OK方法,消费者中有10/0

在这里插入图片描述
访问paymentInfo_TimeOut方法,超时
在这里插入图片描述

3.3、两种方式的区别

  • 在Controller层添加降级方法相当于为消费端和服务端所有代码添加降级方法,无论哪里出现错误,都会进入降级方法
  • 利用feign设置fallback降级方法,相当于为服务端添加降级方法,只有服务端出现异常才会进入降级方法,消费端的异常会将错误抛出,并不会进入降级方法

4、服务熔断的处理方式

这里模拟服务端的处理情况

service服务

@Service
public class PaymentService {
    //服务熔断
    @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 String paymentCircuitBreaker(Integer id){
        if (id < 0){
            throw new RuntimeException("*****id 不能负数");
        }
        String serialNumber = IdUtil.simpleUUID();
        return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
    }

    public String paymentCircuitBreaker_fallback(Integer id){
        return "id 不能负数,请稍候再试,(┬_┬)/~~     id: " +id;
    }
}

controller业务层

@RestController
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService paymentService;
    
    //===服务熔断
    @GetMapping("/payment/circuit/{id}")
    public String paymentCircuitBreaker(@PathVariable("id") Integer id){
        String result = paymentService.paymentCircuitBreaker(id);
         log.info("*******result:"+result);
        return result;
    }
}
  1. 单次请求传入正数,服务正常返回
    在这里插入图片描述

  2. 单次请求传入负数,走服务降级方法
    在这里插入图片描述

  3. 多次请求传入负数,一直走服务降级方法,此时服务熔断打开,传入正数访问。
    在这里插入图片描述
    正数也走服务降级方法,因为此时服务熔断打开,微服务不可访问

4.1、服务熔断类型

  • 熔断打开:请求不再进行调用当前服务,内置时钟(平均故障处理时间),当打开后达到所设时钟则进入半熔断状态
  • 熔断关闭:熔断关闭不会对服务进行熔断
  • 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断

4.2、断路器开启和关闭的条件

在这里插入图片描述

开启条件

  • 当满足一定的阀值(默认10秒内超过20个请求)–代码设置10秒内超过10个请求
  • 当失败率达到一定的时候(默认10秒内超过50%的请求失败)–代码设置10秒内超过60%的请求失败

关闭条件

  • 当开启的时候,所有请求不会进行转发
  • 开启以后,一段时间之后(默认5秒),这个时候断路器是半开状态,会让其中一个请求进行转发,如果成功,断路器会关闭,若失败,继续开启。–代码默认10秒
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冬天vs不冷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值