0%

SpringCloud基础大全-服务降级

一、Hystrix

1.1 服务雪崩

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

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

  • 服务降级:
    • 超时导致服务器变慢—->超时不再等待—->服务降级
    • 出错(宕机或程序运行出错)—->出错有兜底—->服务降级
  • 服务熔断:
  • 服务限流:

1.2 Hystrix概念

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

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

Hystrix的作用:

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

1.3 服务端实现

1.3.1 依赖配置

  • 创建项目cloud-provider-hystrix-payment8001
  • 引入Hystrix依赖
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
  • 配置application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 8001

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

eureka:
client:
register-with-eureka: true #false表示不向注册中心注册自己。
fetch-registry: true #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://eureka7001.com:7001/eureka/

1.3.2 创建业务类

  • 创建启动类
1
2
3
4
5
6
7
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
  • 创建service层
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()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~";
}

// 模拟超时
public String paymentInfo_TimeOut(Integer id){
int timeNumber=3;
try{
TimeUnit.SECONDS.sleep(timeNumber); // 等待3秒
}catch(InterruptedException e){
e.printStackTrace();
}
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时"+timeNumber+"秒钟";
}
}
  • 创建controller层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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_OK(id);
log.info("*********result:",result);
return result;
}
}

1.3.3 启动流程

注意启动时先将eureka服务注册中心修改为单机版,主要是为了简单

  • 启动eureka7001
  • 启动cloud-provider-hystrix-payment8001
  • 访问接口

1.4 高并发测试

1.5 客户端实现

  • 客户端和服务端的创建都是类似的
  • 创建工程cloud-consumer-feign-hystrix-order8080
  • 配置yml
1
2
3
4
5
6
7
8
9
server:
port: 8080

eureka:
client:
register-with-eureka: false #false表示不向注册中心注册自己。
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://eureka7001.com:7001/eureka/
  • 启动类OrderHystrixMain8080
1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain8080 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain8080.class,args);
}

}
  • 业务接口PaymentHystrixService
1
2
3
4
5
6
7
8
9
@Service
@FeignClient(value = "CLOUD-PAYMENT-HYSTRIX-SERVICE")
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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;
}
}

1.6 服务降级

  • 服务降级可以配置在服务端,也可以配置客户端,一般会配置在客户端

  • 主启动类使用@EnableCircuitBreaker开启降级,并添加@EnableHystrix开启Hystrix

  • 方法上使用@HystrixCommand注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
@Slf4j
public class OrderHystrixController {
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties={
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
})
public String paymentInfo_TimeOut(Integer id){
int timeNumber=5;
try{
TimeUnit.SECONDS.sleep(timeNumber); // 等待3秒
}catch(InterruptedException e){
e.printStackTrace();
}
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时"+timeNumber+"秒钟";
}

public String paymentInfo_TimeOutHandler(Integer id){
return "接口调用异常:\t"+"当前线程名称\t"+Thread.currentThread().getName();
}
}
  • yml配置
1
2
3
feign:
hystrix:
enable: true
  • 在接口类上添加注解@DefaultProperties启动默认服务降级方法配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@DefaultProperties(defaultFallback = "paymentInfo_TimeOutHandler")
public class OrderHystrixController{
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand // HystrixCommand还是要在需要降级的请求方法上加上
public String paymentInfo_TimeOut(Integer id){
int timeNumber=5;
try{
TimeUnit.SECONDS.sleep(timeNumber); // 等待3秒
}catch(InterruptedException e){
e.printStackTrace();
}
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时"+timeNumber+"秒钟";
}

public String paymentInfo_TimeOutHandler(Integer id){
return "接口调用异常:\t"+"当前线程名称\t"+Thread.currentThread().getName();
}
}

1.7 服务降级解耦

  • 因为目前兜底方法和接口方法写在了同一个接口类中,这样如果为每一个接口方法添加一个兜底方法就会导致代码冗余
  • 那么我们就可以考虑新建一个类用来实现业务接口,将兜底方法都写在这个类中,这样就解决了代码的冗余
  • 接下来的服务降级都配置在客户端,创建PaymentFallbackService实现业务接口
1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class PaymentFallbackService implements PaymentHystrixService{
@Override
public String paymentInfo_OK(Integer id){
return "------PaymentHystrixService fall back-paymentInfo_OK";
}

@Override
public String paymentInfo_TimeOut(Integer id){
return "------PaymentHystrixService fall back-paymentInfo_TimeOut";
}
}
  • 在业务类上的@FeignClient上添加fallback属性,将上面的实现类注册进Feign中
1
2
3
4
5
6
7
8
9
@Service
@FeignClient(value = "CLOUD-PAYMENT-HYSTRIX-SERVICE" 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);
}