文章目录
服务降级
Hystrix
服务降级
创建带降级机制的pay模块
创建带降级的order模块
配置服务降级
修改pay模块
修改order模块
重复代码的问题
代码耦合度的问题
服务熔断
继续修改pay模块
总结补充
Hystrix所有可配置的属性
熔断整体流程
服务监控
HystrixDashboard
服务降级
首先需要了解一个概念,服务雪崩:
多个微服务之间调用时,假设微服务 A 调用微服务 B 和微服务 C,微服务 B 和微服务 C 又调用其它的微服务,这就是所谓的”扇出“。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务 A 的调用就会占用越来越多的系统资源,进而导致系统”雪崩效应“。
Hystrix
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,它能够保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障(雪崩)。
主要作用
服务降级:比如当某个服务繁忙,不能让客户端的请求一直等待,应该立刻返回给客户端一个备选方案。
服务熔断:当某个服务出现问题,卡死了,不能让用户一直等待,需要关闭所有对此服务的访问然后调用服务降级。
服务限流:比如秒杀场景,不能访问用户瞬间都访问服务器,限制一次只可以有多少请求。
接近实时的监控
Hystrix
服务降级
比如当某个服务繁忙,不能让客户端的请求一直等待,应该立刻返回给客户端一个备选方案。
出现降级的情况:
程序运行异常
超时
发生服务熔断
线程池/信号量打满
创建带降级机制的pay模块
1 名称: cloud-provider-hystrix-payment8001
2 pom文件
<!-- hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
3 配置文件,为了减少资源占用,这里使用单机版 Eureka
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
4 主启动类
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
5 service,节约时间不写接口,直接上实现类
@Service
public class PaymentService {
/*
正确的方法
*/
public String paymentInfo_OK(Integer id){
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,id:"+id+"\t"+"???";
}
/*
会超时报错的方法
*/
public String paymentInfo_TimeOut(Integer id){
int timeNumber = 5;
try{
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_TimeOut,id:"+id+"\t"+"!!!";
}
}
6 Controller
@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_OK(id);
log.info("****result:"+result);
return result;
}
@GetMapping("/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_TimeOut(id);
log.info("****result:"+result);
return result;
}
}
7 启动项目,并发度低的情况下可以使用,我们使用 JMeter 并发2w个请求TimeOut接口,然后访问OK接口会发现也需要进行等待。这就是因为被压测的方法它占用了服务器大部分资源, 导致其他请求也变慢了。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200909144034529.png#pic_center)
1 创建带降级的order模块
名字: cloud-consumer-feign-hystrix-order80
2 pom
<!-- 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>
3 配置文件
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka
4 主启动类
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80 {
public static void main(String[] args){
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
5 远程调用pay模块的接口
@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);
}
6 controller:
@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
}
7 测试,启动order模块,再次压测2万并发pay模块的timeout接口,发现order访问也变慢了,没有配置Ribbon超时参数还会调用报错。
image-20200712180843334
原因
出现这种现象的原因是,8001的tomcat容器中线程池的工作线程被timeout接口挤占完毕,80此时调用8001中的ok接口就无法得到处理。
解决
8001超时/宕机:80不能一直等待,需要进行服务降级
8001没问题:80自己出故障或需要自己的等待时间小于服务提供者,则需要自己处理降级
配置服务降级
修改pay模块
1 为service会延迟的方法添加 @HystrixCommand 注解
/*
会超时报错的方法
*/
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})//设置调用超时时间的峰值,超时则会调用设置的paymentInfo_TimeOutHandler方法
public String paymentInfo_TimeOut(Integer id){
int timeNumber = 5;
//int age = 10/0;
//其他异常也会触发服务降级
try{
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_TimeOut,id:"+id+"\t"+"!!!";
}
public String paymentInfo_TimeOutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName()+" 系统繁忙,请稍后再试,id:"+id+"\t"+"。。。";
}
2 动类上,添加激活hystrix的注解@EnableCircuitBreaker
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args){
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
3 访问8001的timeout接口,可以看到触发了降级
修改order模块
一般服务降级,都是放在客户端。此外,对于@HystrixCommand内的修改,建议重启微服务。
1 修改配置文件,feign开启hystrix支持
feign:
hystrix:
enabled: true
2 主启动类添加@EnableHystrix,启用hystrix
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
public static void main(String[] args){
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
3 修改controller【pay模块timeout改为3s,限制5s】
@GetMapping("/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",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 paymentInfo_TimeOutHandler(Integer id){
return "本80系统繁忙,请稍后再试,id:"+id+"\t"+"。。。";
}
4 测试,order访问pay需要3s,而自己只运行1.5s,所以会在order中降级
5 目前两个降级模块出现的问题
降级方法与业务方法写在了一块,耦合度高
每个业务方法都写了一个降级方法,重复代码多
重复代码的问题
解决:
配置一个全局的降级方法,所有方法都可以走这个降级方法,至于某些特殊创建,再单独创建方法。
1 创建一个全局方法
//全局降级方法
public String paymentInfo_Global_FallbackMethod(){
return "Global异常处理信息,请稍后再试,。。。";
}
2 使用@DefaultProperties注解指定其为全局降级方法(默认降级方法)
@RestController
@Slf4j
@RequestMapping("/consumer")
@DefaultProperties(defaultFallback = "paymentInfo_Global_FallbackMethod")
public class OrderHystrixController {
...
}
3 业务方法不指定具体降级方法,就会使用默认降级方法
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
4 测试
代码耦合度的问题
解决:
1 修改order模块,这里开始,pay模块就不服务降级了,服务降级写在order模块即可。
PaymentHystrixService接口是远程调用pay模块的,我们这里创建一个类实现改接口进行统一降级处理。
@Component
public class PaymentFallbackService implements PaymentHystrixService{
@Override
public String paymentInfo_OK(@PathVariable("id") Integer id) {
return "----PaymentFallbackService fall back-paymentInfo_OK,...";
}
@Override
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
return "----PaymentFallbackService fall back-paymentInfo_TimeOut,...";
}
}
2 确保配置文件中开启了feign的hystrix支持,最后让PaymentHystrixService的实现类生效,修改接口的@FeignClient注解
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
3 启动测试,启动order和pay正常访问OK接口
服务熔断
当某个服务出现问题:
需要关闭所有对此服务的访问【断路器】,然后调用服务降级。
当检测到该服务响应正常后,恢复调用链路。
继续修改pay模块
1修改Payservice接口,添加服务熔断相关的方法
//服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enable",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();//hutool工具
return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
}
public String paymentCircuitBreaker_fallback(Integer id){
return "id:"+id+",不能为负数,请稍后再试,。。。";
}
配置服务降级方法就不说了,对于配置的参数表示:
1 如果并发超过10个或者10个并发中失败了6个,就会开启断路器
2 在时间窗口期10秒之内会尝试请求,如果请求成功就会关闭断路器
2 修改controller,添加一个测试方法
//服务熔断
@GetMapping("/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result:"+result);
return result;
}
3 测试结果:
总结补充
Hystrix所有可配置的属性
此外,所有的参数配置可以查看HystrixCommandProperties类当中的成员变量。
服务监控
HystrixDashboard
HystrixDashboard是Hystrix提供的准实时调用监控,它记录了所有Hystrix发起的请求的执行信息,并以图形化的形式显示。
HystrixDashboard的使用
1 创建项目,名称为cloud-consumer-hystrix-dashboard9001
2 pom文件
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
3 配置文件
server:
port: 9001
4 主启动类
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboarMain9001 {
public static void main(String[] args){
SpringApplication.run(HystrixDashboarMain9001.class,args);
}
}
5 修改所有pay模块(8001,8002,8003…)添加一个pom依赖,我们之前都配过了
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
6 启动9001即可,访问: http://localhost:9001/hystrix
7 此时仅仅是可以访问HystrixDashboard,并不代表已经监控了8001,8002。如果要监控,还需要配置(8001为例):
8001的主启动类添加
/**
* 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
* ServletRegistrationBean因为SpringBoot的默认路径不是 “/hystrix.stream"
* 只要在自己的项目里配置上下的servlet就可以了
*/
@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;
}
8 到此,可以启动服务启动7001,8001,9001,然后在web界面,指定9001要监控8001:
在这里插入图片描述