Cloud技术栈

简介

分布式系统面临的问题

复杂分布式体系结构中的应用程序有许多依赖项,每个依赖项在某些时候都不可避免地会失败。如果主机应用程序没有与这些外部故障隔离,那么它有可能被他们拖垮。

例如,对于一个依赖于30个服务的应用程序,每个服务都有99.99%的正常运行时间,你可以期望如下:

99.9930 = 99.7% 可用

也就是说一亿个请求的0.03% = 3000000 会失败

如果一切正常,那么每个月有2个小时服务是不可用的

现实通常是更糟糕

当一切正常时,请求看起来是这样的:

img

当其中有一个系统有延迟时,它可能阻塞整个用户请求:

img

在高流量的情况下,一个后端依赖项的延迟可能导致所有服务器上的所有资源在数秒内饱和(PS:意味着后续再有请求将无法立即提供服务

img

服务雪崩

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

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

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

Hystrix

img

官网https://github.com/Netflix/Hystrix/wiki 停止更新。

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

熔断机制是应对雪崩效应的一种微服务链路保户机制,当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的相应信息。当检测当该节点微服务调用响应正常后恢复调用链路,熔断机制的注解是@HystrixCommand

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

当你使用Hystrix来包装每个依赖项时,上图中所示的架构会发生变化,如下图所示:

每个依赖项相互隔离,当延迟发生时,它会被限制在资源中,并包含回退逻辑,该逻辑决定在依赖项中发生任何类型的故障时应作出何种响应

img

重要概念

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

哪些情况会触发降级

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

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

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

环境代建

新建cloud-provider-hystrix-payment8001,7001服务注册中心方便启动测试成单机版

hystrix一般用于消费端,但是也可以用于服务端

image-20200708160619900

image-20200708160806255

pom

主要引入hystrix依赖,eureka client依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<dependencies>
<!-- hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency><!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.kylin</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</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>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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

image-20200708161547522

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 8001

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

eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka

主启动类

image-20200708161825314

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {

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

service

创建PaymentService接口,编写两个方法一个能正常运行,一个会停止几秒在运行

image-20200708162045623

1
2
3
4
5
6
7
public interface PaymentService {

String paymentInfo_OK(Integer id);

String paymentInfo_TimeOut(Integer id);

}

编写PaymentServiceImpl实现类实现接口

image-20200708162237290

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service
public class PaymentServiceImpl implements PaymentService {


@Override
public String paymentInfo_OK(Integer id) {

return "线程池:" + Thread.currentThread().getName() + " paymentInfo_OK,id:" +id+ "\t" + "哈哈哈~";
}

@Override
public String paymentInfo_TimeOut(Integer id) {

int timeNumber = 3;
try {
//停止timeNumber秒
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + " paymentInfo_TimeOut,id:" +id+ "\t" + "哈哈哈~"+"耗时(秒):"+timeNumber;
}


}

controller

image-20200708162442597

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@RestController
@Slf4j
public class PaymentController {

@Resource
PaymentService paymentService;

@Value("${server.port}")
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;
}
}

测试

启动7001,8001进行测试

image-20200708163029666

访问http://localhost:8001/payment/hystrix/ok/31测试

image-20200708163056478

访问http://localhost:8001/payment/hystrix/timeout/31耗时至少3秒

image-20200708163134322

环境代建成功!

Jmeter压测测试

官网https://jmeter.apache.org/下载解压,bin目录下点击运行ApacheJMeter.jar即可

上述在非高并发情形下,还能勉强满足 我们使用开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务

image-20200708163603052

保存

image-20200708163654201

添加HTTP请求

image-20200708163824687

配置请求路径

image-20200708163954123

点击运行

image-20200708164024611

image-20200708164053262

查看后台,正在有不断的正在进行访问测试

GIF

此时再次访问http://localhost:8001/payment/hystrix/ok/31可正常访问的请求。也会变得慢了起来,以前是毫秒就能完成,现在要2秒左右

image-20200708164254413

为什么会变慢:tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。

上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死

加入服务消费者测试

新建cloud-consumer-feign-hystrix-order80

image-20200708164848315

pom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency><!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.kylin</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

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

image-20200708165414714

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 80


eureka:
client:
register-with-eureka: true #表示不向注册中心注册自己
fetch-registry: true #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka/

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

主启动类

使用到了OpenFeign使用注解开启@EnableFeignClients,这里省略了@EnableEurekaClient

简而言之就是当我们引用了EurekaClient的依赖后,并且我们的两个开关不手动置为false,Spring就会自动帮助我们执行EurekaAutoServiceRegistration类里的start()方法,而注册的动作就是在该方法里完成的。所以,我们的EurekaClient工程,并不需要显式的在SpringBoot的启动类上标注@EnableEurekaClient注解,也可注册到注册中心。

image-20200708165608389

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableFeignClients
//@EnableEurekaClient//我们的两个开关不手动置为false,Spring就会自动帮助我们执行注册
public class OrderHystrixMain80 {
public static void main(String[] args) {

SpringApplication.run(OrderHystrixMain80.class,args);
}
}

service

image-20200708170559955

1
2
3
4
5
6
7
8
9
10
@FeignClient("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

image-20200708170719733

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 OrderHystrixController {

@Resource
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;
}

}

测试

启动80,8001,7001

image-20200708171005744)image-20200708171018943

8001自测访问http://localhost:8001/payment/hystrix/ok/31

image-20200708171048778

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

image-20200708171135220

一切正常

80端http://localhost/consumer/payment/hystrix/ok/80访问正常的速度也十分快

image-20200708171249929

Jmeter测试

image-20200708171349066

80要么出现报错

image-20200708171425679

要么就变得访问十分缓慢

image-20200708171517500

故障现象和导致原因

8001同一层次的其他接口服务被困死,因为tomcat线程里面的工作线程已经被挤占完毕。80此时调用8001,客户端访问响应缓慢,转圈圈

正因为有上述故障或不佳表现,才有我们的降级/容错/限流等技术诞生

解决

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

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

对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级

对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级

对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级

服务降级

通过@HystrixCommand注解的fallbackMethod属性指定降级方法。groupKeycommandKey默认为方法名(当然threadPoolKey不指定时,默认和groupKey一致,所以也是方法名),也可以指定这三个key值,配置文件通过groupKey,commandKey,threadPoolKey使用恰当的配置。commandPropertiesthreadPoolProperties是通过@HystrixProperty的name value键值对进行配置。

服务提供者

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

service

如果再调用的方法上有形参,那么fallback方法也同样需要形参;不然会报 找不到fallback方法异常

image-20200708203808874

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Service
public class PaymentServiceImpl implements PaymentService {

@Override
public String paymentInfo_OK(Integer id) {

return "线程池:" + Thread.currentThread().getName() + " paymentInfo_OK,id:" + id + "\t" + "哈哈哈~";
}

@Override
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String paymentInfo_TimeOut(Integer id) {

int timeNumber = 5;
try {
//停止timeNumber秒
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + " paymentInfo_TimeOut,id:" + id + "\t" + "哈哈哈~" + "耗时(秒):" + timeNumber;
}


public String paymentInfo_TimeOutHandler(Integer id) {
return "线程池:" + Thread.currentThread().getName() + " 系统繁忙或者运行报错,请稍后再试,id:" + "\t" + "/(ㄒoㄒ)/~~";
}


}

主启动类

在启动类上加上@EnableCircuitBreaker,开启断路器功能

image-20200708204143260

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@EnableEurekaClient
//开启断路器功能
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {

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

测试

启动7001,80,访问http://localhost:8001/payment/hystrix/timeout/31

image-20200708204444395

用来单独的线程池来处理,当等待时间超过了3秒调用fallbackMethod

image-20200708204707208

这个异常属于超时异常能被处理,我们修改异常为其他异常看看能不能被处理

image-20200708205142401

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Service
public class PaymentServiceImpl implements PaymentService {

@Override
public String paymentInfo_OK(Integer id) {

return "线程池:" + Thread.currentThread().getName() + " paymentInfo_OK,id:" + id + "\t" + "哈哈哈~";
}

@Override
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String paymentInfo_TimeOut(Integer id) {

int num = 10/0;
/* int timeNumber = 5;
try {
//停止timeNumber秒
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
//return "线程池:" + Thread.currentThread().getName() + " paymentInfo_TimeOut,id:" + id + "\t" + "哈哈哈~" + "耗时(秒):" + timeNumber;
return "线程池:" + Thread.currentThread().getName() + " paymentInfo_TimeOut,id:" + id + "\t" + "哈哈哈~";
}


public String paymentInfo_TimeOutHandler(Integer id) {
return "线程池:" + Thread.currentThread().getName() + " 系统繁忙或者运行报错,请稍后再试,id:" + "\t" + "/(ㄒoㄒ)/~~";
}


}

重启测试访问http://localhost:8001/payment/hystrix/timeout/31

image-20200708205316524

同样出现异常使用fallbackMethod,一进入请求中发生报错

结论:只要当前服务不可用,就会进行服务降级,调用fallbackMethod

服务消费者

进行客户端降级保护,一般服务降级用在客户端

yml

修改80客服端的配置文件,当前我们使用的是Feign做服务调用,开启使用Hystrixfeign.hystrix.enabled=true

image-20200708210252504

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server:
port: 80

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

eureka:
client:
register-with-eureka: true #表示不向注册中心注册自己
fetch-registry: true #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka/



feign:
hystrix:
enabled: true #feign开启hystrix

主启动类

使用@EnableHystrix开启Hystrix

image-20200708210438164

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@EnableFeignClients
//@EnableEurekaClient//我们的两个开关不手动置为false,Spring就会自动帮助我们执行注册
@EnableHystrix//开启Hystrix
public class OrderHystrixMain80 {
public static void main(String[] args) {

SpringApplication.run(OrderHystrixMain80.class,args);
}
}

controller

如果再调用的方法上有形参,那么fallback方法也同样需要形参;不然会报 找不到fallback方法异常

同样使用@HystrixCommand注解

image-20200708210656501

当调用时间超过了1.5秒,运行fallbackMethod方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@RestController
@Slf4j
public class OrderHystrixController {

@Resource
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){
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,/(ㄒoㄒ)/~~";
}

}

测试

启动80 8001 7001http://localhost/consumer/payment/hystrix/timeout/31

出现报错

image-20200708211218085

image-20200708212635227

但是时间确实1秒多钟就进行了服务降级,而不是想象中的1.5秒多出现超时报错。这是为什么了?

原来是Hystrix的配置问题,hystrix.command.default.execution.timeout.enabled ,为false则超时控制有ribbon控制,为true则hystrix超时和ribbon超时都是用,但是谁小谁生效,默认为true。Hystrix的hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds超时时长默认1秒。ribbon的默认超时控制也为1秒,所以现在整个服务的超时时间都为一秒钟

我们修改配置文件,将ribbon和hystrix的超时控制时间修改为10秒

image-20200708212135605

1
2
3
4
5
6
7
8
9
10
11
12
13
14
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 100000 #hystrix超时控制时长
#
##设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接后从服务器读取到可用资源所用的时间
ReadTimeout: 10000
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ConnectTimeout: 10000

重启80,再次访问http://localhost/consumer/payment/hystrix/timeout/31

image-20200708212311097

同样出现了超时报错,进入了fallbackMethod方法。等待时长也是和我们期望的一样至少1.5秒。

为了进一步测试,我们将80的可以等待的超时时长改为10秒

image-20200708212848866

重新启动80,访问http://localhost/consumer/payment/hystrix/timeout/31,成功调用了8001提供的服务,等待时间也至少大于3秒。!

image-20200708212941097

测试成功,但也存在问题,每个业务方法对应一个兜底的方法,代码膨胀,如果每一个方法都要对应一个配置,代码量爆炸。缺少全局的配置

全局服务降级

使用@DefaultProperties注解,配置defaultFallback属性,值为方法名。使用了@HystrixCommand没有配置fallback的就是用全局的,配置了就使用自己的。就近原则

image-20200708214258686

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController {

@Resource
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 = "10000")
})*/
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
int i = 10/0;
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}

public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,/(ㄒoㄒ)/~~";
}


//全局fallback
public String payment_Global_FallbackMethod(){
return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
}


}

测试

重启80,访问http://localhost/consumer/payment/hystrix/timeout/31

全局服务降级配置成功!解决了代码膨胀的问题

image-20200708214407154

通配服务降级

上文我们的fallback方法和业务逻辑混一起,是有点混乱的。

根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,重新新建一个类PaymentFallbackService实现该接口,统一为接口里面的方法进行异常处理。当这个Service的方法出现了异常,而请求中没有配置@HystrixCommand则使用我们这个类中的异常处理方法。

新建PaymentFallbackService类实现PaymentHystrixService,重写方法里是对该方法出现异常的操作。

image-20200708220523482

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class PaymentFallbackService implements PaymentHystrixService {

@Override
public String paymentInfo_OK(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_OK , (┬_┬)";
}

@Override
public String paymentInfo_TimeOut(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_TimeOut , (┬_┬)";
}
}

PaymentHystrixService中的@FeignClient配置fallback属性,值为PaymentFallbackService.class

image-20200708220818691

1
2
3
4
5
6
7
8
9
10
@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);

}

测试

重新启动80测试,访问http://localhost/consumer/payment/hystrix/ok/31因为改请求没有配置@HystrixCommand,所以当Service访问服务出现异常时,则会掉用fallback类中的对应方法

image-20200708220902296

目前一切正常

image-20200708221055195

当我们把8001服务给关闭后,Service访问服务出现异常时,则会掉用fallback类中的对应方法。

image-20200708221142914

再次访问,服务降级配置成功!

image-20200708221202492

服务熔断

熔断机制是应对雪崩效应的一种微服务链路保护机制,

当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回”错误”的响应信息。当检测到该节点微服务响应正常后恢复调用链路,在SpringCloud框架机制通过Hystrix实现,Hystrix会监控微服务见调用的状况,当失败的调用到一个阈值,缺省是5秒内20次调用失败就会启动熔断机制,熔断机制的注解是@HystrixCommand

修改cloud-provider-hystrix-payment8001

Servcie

image-20200709091848586

1
2
3
4
5
6
7
8
public interface PaymentService {

String paymentInfo_OK(Integer id);

String paymentInfo_TimeOut(Integer id);

String paymentCircuitBreaker(@PathVariable("id") Integer id);
}

image-20200709092004082

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//服务熔断
@Override
@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(@PathVariable("id") Integer id){
if (id < 0){
throw new RuntimeException("*****id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();

return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
}

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

circuitBreaker.enabled:是否启用熔断器,默认为 true;

circuitBreaker.forceOpencircuitBreaker.forceClosed:是否强制启用/关闭熔断器,强制启用关闭都想不到什么应用的场景,保持默认值,不配置即可。

circuitBreaker.requestVolumeThreshold:启用熔断器功能窗口时间内的最小请求数。试想如果没有这么一个限制,我们配置了 50% 的请求失败会打开熔断器,窗口时间内只有 3 条请求,恰巧两条都失败了,那么熔断器就被打开了,5s 内的请求都被快速失败。此配置项的值需要根据接口的 QPS 进行计算,值太小会有误打开熔断器的可能,值太大超出了时间窗口内的总请求数,则熔断永远也不会被触发。建议设置为 QPS * 窗口秒数 * 60%

circuitBreaker.errorThresholdPercentage:在通过滑动窗口获取到当前时间段内 Hystrix 方法执行的失败率后,就需要根据此配置来判断是否要将熔断器打开了。 此配置项默认值是 50,即窗口时间内超过 50% 的请求失败后会打开熔断器将后续请求快速失败。

circuitBreaker.sleepWindowInMilliseconds:熔断器打开后,所有的请求都会快速失败,但何时服务恢复正常就是下一个要面对的问题。熔断器打开时,Hystrix 会在经过一段时间后就放行一条请求,如果这条请求执行成功了,说明此时服务很可能已经恢复了正常,那么会将熔断器关闭,如果此请求执行失败,则认为服务依然不可用,熔断器继续保持打开状态。此配置项指定了熔断器打开后经过多长时间允许一次请求尝试执行,默认值是 5000。

controller

image-20200709092205633

1
2
3
4
5
6
7
//===服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("*******result:"+result);
return result;
}

测试

启动8001 7001http://localhost:8001/payment/circuit/31

image-20200709092808186

访问http://localhost:8001/payment/circuit/-31

image-20200709092838761

多次访问负数id后,导致服务熔断,此时访问正数id也会调用fallbackmethod

image-20200709092910088

过了一段时间后发送一个正确请求正数id,服务熔断关闭

image-20200709093025231

工作流程

官网https://github.com/Netflix/Hystrix/wiki/How-it-Works

img

image-20200709093232873

服务监控HystrixDashboard

Hystrix还提供了实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求,多少成功,多少失败等。

Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。

搭建

新建cloud-consumer-hystrix-dashboard9001

image-20200709093556464

pom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<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

只需要配置好端口号为9001

image-20200709093811984

1
2
server:
port: 9001

主启动类

使用@EnableHystrixDashboard开启Hystrix图形界面

image-20200709094022219

监控配置

要想监控微服务的提供类,都需要导入监控依赖配置, 我们之前都配置好了

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

测试

启动9001,访问http://localhost:9001/hystrix,搭建成功

image-20200709094506892

实战

启动cloud-consumer-hystrix-dashboard9001该微服务后续将监控微服务8001

修改cloud-provider-hystrix-payment8001

主启动类

image-20200709094826728

1
2
3
4
5
6
7
8
9
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}

新版本Hystrix需要在主启动类MainAppHystrix8001中指定监控路径,否则会报错Unable to connect to Command Metric Stream

测试

启动8001,在监控界面填写数据http://localhost:8001/hystrix.stream

image-20200709095415004

点击按钮监控8001

image-20200709095623432

分别进行访问错误和正确的测试

GIF958

image-20200709100133863

image-20200709100142509