0%

【SpringCloud】服务降级:Hystrix

Hystrix实现服务降级,服务熔断介绍


1 Hystrix简介

1.1 介绍

  • Hystrix是一个用于处理分布式系统的延迟和容错的开源库
  • Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
  • “断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的,可处理的备选相应(FallBack),而不是长时间的等待或抛出调用方法无法处理的异常,这样就保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩

1.2 功能

  • 服务降级
  • 服务熔断
  • 接近实时的监控

1.3 资料


2 Hystrix重要概念

2.1 服务降级(fallback)

  • 如果当前对方服务不可用,给出一个解决方法来
  • 类似我们的if-else判断语句
  • 举例:”服务器繁忙,请稍后再试!”,不让客户端等待并立刻返回一个友好提示(fallback)
  • 发生降级的情况;
    • (1)程序运行异常
    • (2)超时
    • (3)服务熔断触发服务降级
    • (4)线程池 / 信号量打满导致服务降级

2.2 服务熔断(break)

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

2.3 服务限流(flowlimit)

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

3 Hystrix使用

3.1 服务端简单搭建环境

  • (1) 创建cloud-provider-hystrix-payment8001项目
  • (2)依赖导入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!-- 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>
    <!-- web -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  • (3)yaml配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    server:
    port: 8001 #端口

    spring:
    application:
    name: cloud-provider-hystrix-payment #服务名称

    eureka:
    instance:
    instance-id: hystrix-payment8001 # eureka中服务名称
    prefer-ip-address: true # 访问路径是否可以显示IP地址
    client:
    register-with-eureka: true # 是否注册进eureka服务端
    fetch-registry: true # 是否从eureka server从获取已有的注册信息,默认true。单节点无所谓,集群的话需要
    service-url:
    defaultZone: http://localhost:7001/eureka
  • (4)主启动类
    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableEurekaClient //表示此服务器是eureka client
    public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
    SpringApplication.run(PaymentHystrixMain8001.class, args);
    }
    }
  • (5)Serviec层
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Service
    public class PaymentService {

    //模拟服务正常
    public String paymentInfo_OK(Integer id) {
    return "线程池:" + Thread.currentThread().getName() + "\n-- OK --\nid:" + id;
    }

    //模拟服务异常:超时
    public String paymentInfo_Timeout(Integer id) {
    try { //沉睡3秒
    TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    return "线程池:" + Thread.currentThread().getName() + "\n-- Timeout --\nid:" + id;
    }
    }
  • (6)Controller层
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @RestController
    public class PaymentController {

    @Resource
    PaymentService paymentService;

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

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String getTimeInfo(@PathVariable("id") Integer id) {
    return paymentService.paymentInfo_Timeout(id);
    }
    }

3.2 客户端简单环境搭建

  • (1)创建项目cloud-consumer-openfeign-hystrix-order
  • (2)依赖引入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!-- 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>
    <!-- openfeign:内整合了ribbon -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!-- web -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  • (3)yaml配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    spring:
    application:
    name: cloud-consumer-openfeign-hystrix-order

    eureka:
    client:
    register-with-eureka: true # 是否注册进eureka服务端
    fetch-registry: true # 是否从eureka server从获取已有的注册信息,默认true。单节点无所谓,集群的话需要
    service-url:
    defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka/
  • (4)主启动类

    1
    2
    3
    4
    5
    6
    7
    8
    @SpringBootApplication
    @EnableEurekaClient
    @EnableFeignClients //表示自己是OpenFeign的客户端
    public class OrderOpenFeignHystrixMain80 {
    public static void main(String[] args) {
    SpringApplication.run(OrderOpenFeignHystrixMain80.class, args);
    }
    }
  • (5)feign接口
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Component
    @FeignClient(value = "cloud-provider-hystrix-payment") //对应服务器的名词
    public interface PaymentFeignClient {

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

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String getTimeInfo(@PathVariable("id") Integer id);
    }
  • (6)Controller
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @RestController
    public class OrderFeignController {

    @Autowired
    PaymentFeignClient paymentFeignClient;

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

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

    }

3.2 服务降级配置

  • (1)解决服务端超时
    • 服务端设置自身调用超时时间峰值,峰值内正常运行
    • 超过峰值,需要有兜底的方法进行处理,作为服务降级fallback
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//模拟服务异常:超时
@HystrixCommand(fallbackMethod = "timeoutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") //设置超时时间为3秒
}) //出现异常时,调用处理方法
public String paymentInfo_Timeout(Integer id) {
try { //沉睡3秒
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + "\n-- Timeout --\nid:" + id;
}

//异常处理方法
public String timeoutHandler(Integer id) { //注意参数要与方法一致,才能被成功调用
return "o(╥﹏╥)o!服务器繁忙,请稍后再试!";
}
1
2
3
4
5
6
7
8
9
//主启动类
@SpringBootApplication
@EnableEurekaClient //表示此服务器是eureka client
@EnableCircuitBreaker //使用hystrix
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
  • (2)客户端配置
1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "timeoutHandler" , commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
public String getTimeInfo(@PathVariable("id") Integer id) {
return paymentFeignClient.getTimeInfo(id);
}

public String timeoutHandler(Integer id) {
return "这里是客户端80!服务端正在忙,请稍后再来!o(╥﹏╥)o";
}
1
2
3
4
5
6
7
8
9
10
//主启动类
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients //表示自己是OpenFeign的客户端
@EnableCircuitBreaker //使用hystrix
public class OrderOpenFeignHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderOpenFeignHystrixMain80.class, args);
}
}

4 问题出现与解决

4.1 问题

  • (1)如果每一个业务方法都要有一个服务降级的方法,会导致代码膨胀
  • (2)业务方法与服务降级方法写在了一起,会十分的不舒服,不适合维护

4.2 代码膨胀解决

  • 在类头添加注解@DefaultProperties(defaultFallback = "")
  • 会自动对添加了@HystrixCommand的注解默认使用此服务降级方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
@DefaultProperties(defaultFallback = "globalHandler")
public class OrderFeignController {

@Autowired
PaymentFeignClient paymentFeignClient;

@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand //直接用注解,不用配置参数
public String getTimeInfo(@PathVariable("id") Integer id) {
return paymentFeignClient.getTimeInfo(id);
}

public String globalHandler() { //注意全局方法不能有参数
return "这里是客户端80!现在全局方法进行信息返回:服务端正在忙,请稍后再来!o(╥﹏╥)o";
}

}

4.3 业务方法降级方法分离

  • 我们可以通过使用feign对hystrix方法的支持,来实现分离
  • 在feign接口头上的注解新增属性:@FeignClient(value = "", fallback = ""),参数为一个类
1
2
3
4
5
6
7
8
9
10
11
12
@Component //记得能够被扫描到
public class paymentClientFallback implements PaymentFeignClient{
@Override
public String getOKInfo(Integer id) { //要服务降级时,会寻找同名方法来调用,所以直接实现接口最好
return "这里是OK端的返回信息哦!";
}

@Override
public String getTimeInfo(Integer id) {
return "这里是Timeout端的返回信息哦!";
}
}
1
2
3
4
5
6
7
8
9
10
@Component
@FeignClient(value = "cloud-provider-hystrix-payment", fallback = paymentClientFallback.class) //fallback参数为对应自己的实现类
public interface PaymentFeignClient {

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

@GetMapping("/payment/hystrix/timeout/{id}")
public String getTimeInfo(@PathVariable("id") Integer id);
}
1
2
3
feign:
hystrix:
enabled: true #yaml配置文件开启feign对hystrix的支持

5 服务熔断

5.1 服务熔断理论

  • closed –> open:达到服务器极限,开启熔断
  • open –> half open:一段时间后,尝试取消熔断
  • half open –> closed:没有异常,关闭熔断

5.2 配置服务熔断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//paymentService
@HystrixCommand(fallbackMethod = "circuitBreaker_handler", commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"), //是否开启断路器(熔断)
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), //请求次数
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), //失败率
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000") //沉睡时间
//以上配置意思为:开启熔断服务;10次请求的失败率达到60%,就开启服务熔断;在一定的沉睡时间后,尝试让一个请求进来,如果成功,就关闭服务熔断
})
public String circuitBreaker(Integer id) {
if(id < 0) { //当传参为负数时报错
throw new RuntimeException("不能负数");
}
return "方法调用成功哦!(*^▽^*)*!";
}

public String circuitBreaker_handler(Integer id) {
return "这里是服务熔断提醒你:id不能负数哦!";
}
  • 测试:自己配一个对应的Controller接口,用负数进行错误请求达到6次以上,再用正数进行正确请求,会发现无法进行正确请求,因为服务器处于熔断状态。10秒后再进行请求,发现可以正常请求,服务器处理熔断关闭阶段

6 服务监控HystrixDashboard

6.1 介绍

  • Hystrix提供了准实时的调用监控(Hystrix Dashborad)。Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户。

6.2 搭建HystrixDashboard

  • (1)创建一个新工程cloud-provider-hystrix-dashboard
  • (2)依赖引入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!-- hystrix-dashboard -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    </dependency>
    <!--web-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  • (3)yaml配置文件
    1
    2
    server:
    port: 9001
  • (4)主启动类
    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableHystrixDashboard //开启HystrixDashboard
    public class HystrixDashboardMain9001 {
    public static void main(String[] args) {
    SpringApplication.run(HystrixDashboardMain9001.class, args);
    }
    }

6.3 服务监控测试

  • 以下例子在cloud-provider-hystrix-payment8001来实现

  • (1)引入依赖

    1
    2
    3
    4
    5
    <!--监控-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
  • (2)修改yaml配置文件
    1
    2
    3
    4
    5
    management:
    endpoints:
    web:
    exposure:
    include: 'hystrix.stream' #暴露hystrix监控端点
  • (3)启动服务器并返回hystrix dashboard