断路器Hystrix与Turbine集群监控-Spring Cloud学习第三天(非原创)
文章大纲
一、Hystrix基础介绍
二、断路器Hystrix简单使用
三、自定义Hystrix请求命令
四、Hystrix的服务降级与异常处理
五、Hystrix的请求缓存与请求合并
六、Hystrix仪表盘与Turbine集群监控
七、项目源码与参考资料下载
八、参考文章
一、Hystrix基础介绍
1. Hystrix简介
一个用户管理项目,里边就三个功能:用户注册、用户登录、用户详情浏览。按照传统的软件开发方式直接创建一个Web项目,分分钟就把这三个功能开发出来了,但是我现在想使用微服务+服务治理的方式来开发:首先我将这个项目拆分为四个微服务,四个微服务各建一个模块,分别是用户注册模块、用户登录模块、用户详情浏览模块和数据库操作模块,这四个模块通过内部服务治理互相调用。但是现在存在一个问题,这四个模块通过服务注册与订阅的方式互相依赖,如果一个模块出现故障会导致依赖它的模块也发生故障从而发生故障蔓延,进而导致整个服务的瘫痪。比如说这里的登录模块依赖于数据库模块,如果数据库模块发生故障,那么当登录模块去调用数据库模块的时候可能得不到响应,这个调用的线程被挂起,如果处于高并发的环境下,就会导致登录模块也崩溃。当一个系统划分的模块越多,这种故障发生的频率就会越高,对于这个问题,Spring Cloud中最重要的解决方案就是断路。
2. 何时需要保护
对于一个系统而言,它往往承担着2层角色,服务提供者与服务消费者。对于服务消费者而言最大的痛苦就是如何“明哲保身”,做过网关项目的同学肯定感同身受
上面是一个常见的系统依赖关系,底层的依赖往往很多,通信协议包括 socket、HTTP、Dubbo、WebService等等。当通信层发生网络抖动以及所依赖的系统发生业务响应异常时,我们业务本身所提供的服务能力也直接会受到影响。
这种效果传递下去就很有可能造成雪崩效应,即整个业务联调发生异常,比如业务整体超时,或者订单数据不一致。
那么核心问题就来了,如何检测业务处于异常状态?
成功率!成功率直接反映了业务的数据流转状态,是最直接的业务表现。
当然,也可以根据超时时间做判断,比如 Sentinel 的实现。其实这里概念上可以做一个转化,用时间做超时控制,超时=失败,这依然是一个成功率的概念。
3. 如何保护
如同豪猪一样,“刺”就是他的保护工具,所有的攻击都会被刺无情的怼回去。在 Hystrix 的实现中,这就出现了“熔断器”的概念,即当前的系统是否处于需要保护的状态。当熔断器处于开启的状态时,所有的请求都不会真正的走之前的业务逻辑,而是直接返回一个约定的信息,即 FallBack。通过这种快速失败原则保护我们的系统。 但是,系统不应该永远处于“有刺”的状态,当危险过后需要恢复正常。于是对熔断器的核心操作就是如下几个功能:如果成功率过低,就打开熔断器,阻止正常业务,随着时间的流动,熔断器处于半打开状态,尝试性放入一笔请求,熔断器的核心 API 如下图:
4. 限流、熔断、隔离、降级
这四个概念是我们谈起微服务会经常谈到的概念,这里我们讨论的是 Hystrix 的实现方式。
4.1 限流
这里的限流与 Guava 的 RateLimiter 的限流差异比较大,一个是为了“保护自我”,一个是“保护下游”
当对服务进行限流时,超过的流量将直接 Fallback,即熔断。而 RateLimiter 关心的其实是“流量整形”,将不规整流量在一定速度内规整。
4.2 熔断
当我的应用无法提供服务时,我要对上游请求熔断,避免上游把我压垮,当我的下游依赖成功率过低时,我要对下游请求熔断,避免下游把我拖垮。
4.3 降级
降级与熔断紧密相关,熔断后业务如何表现,约定一个快速失败的 Fallback,即为服务降级。
4.3 隔离
业务之间不可互相影响,不同业务需要有独立的运行空间,最彻底的,可以采用物理隔离,不同的机器部,次之,采用进程隔离,一个机器多个 Tomcat,次之,请求隔离,由于 Hystrix 框架所属的层级为代码层,所以实现的是请求隔离,线程池或信号量。
二、断路器Hystrix简单使用
1. 开始前准备
在之前的文章中我们已经成功的搭建出服务注册中心、服务提供者和服务消费者三个微服务,本文的案例我们依然在这三个案例的基础上来实现。
首先我们分别启动服务注册中心,再启动两个服务提供者的实例,端口号分别是8080和8081,然后再启动一个服务消费者,服务消费者的端口号为9000,这几个都启动成功之后,我们访问http://localhost:9000/ribbon-consumer
这个地址,可以看到如下效果:
此时我们关闭掉任意一个服务提供者,再去访问这个地址,会看到如下效果:
2. Hystrix引入
下列的所有操作都是在ribbon-consumer项目中进行
2.1 服务消费者中加入断路器
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
2.2 修改服务消费者启动入口类
引入hystrix之后,我们需要在入口类上通过@EnableCircuitBreaker开启断路器功能,如下:
@EnableCircuitBreaker
@SpringBootApplication
@EnableDiscoveryClient
public class RibbonConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonConsumerApplication.class, args);
}
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
我们也可以使用一个名为@SpringBootApplication的注解代替这三个注解,@SpringBootApplication注解的定义如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}
实际上就是这三个注解的一个整合
2.3 修改Controller
我们创建一个HelloService类,如下:
@Service
public class HelloService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "error")
public String hello() {
ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class);
return responseEntity.getBody();
}
public String error() {
return "error";
}
}
关于这个HelloService类我说如下几点:
(1)RestTemplate执行网络请求的操作我们放在HelloService中来完成。
(2)error方法是一个请求失败时回调的方法。
(3)在hello方法上通过@HystrixCommand注解来指定请求失败时回调的方法。
最后我们将ConsumerController的逻辑修改成下面这样:
@RestController
public class ConsumerController {
@Autowired
private HelloService helloService;
@RequestMapping(value = "/ribbon-consumer",method = RequestMethod.GET)
public String helloController() {
return helloService.hello();
}
}
此时我们就开启了断路器功能
2.4 启动项目并测试断路器
启动项目后,我们的访问是正常的
现在我们将其中一个服务提供者干掉
重新访问http://localhost:9000/ribbon-consumer
OK,小伙伴们看到,此时如果服务调用失败,就会调用失败的那个回调方法。事实上,不仅仅是服务提供者被关闭时我们需要断路器,如果请求超时也会触发熔断请求,调用回调方法返回数据。
三、自定义Hystrix请求命令
1. 简介
在上面内容中,我们介绍了断路器Hystrix的一个简单使用,主要是通过注解来实现断路器的功能的,不过对于Hystrix的使用,除了注解,我们也可以使用继承类的方式来实现,本文我们就来看看另一种Hystrix的使用方式。
2. 自定义断路器Hystrix
我们除了使用@HystrixCommand注解,也可以自定义类继承自HystrixCommand,创建BookCommand.java
package test.custom;
import com.netflix.hystrix.HystrixCommand;
import org.springframework.web.client.RestTemplate;
import java.awt.print.Book;
/**
* 自定义断路器Hystrix
*
* 在BookCommand中注入RestTemplate,然后重写两个方法:一个是getFallback,这个方法将在服务调用失败时回调;
* 另一个是run方法,执行请求时调用。构造方法的第一个参数主要用来保存一些分组信息。
*/
public class BookCommand extends HystrixCommand<String> {
private RestTemplate restTemplate;
@Override
protected String getFallback() {
return "测试出问题了";
}
public BookCommand(Setter setter, RestTemplate restTemplate) {
super(setter);
this.restTemplate = restTemplate;
}
@Override
protected String run() throws Exception {
return restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
}
}
在BookCommand中注入RestTemplate,然后重写两个方法:一个是getFallback,这个方法将在服务调用失败时回调;另一个是run方法,执行请求时调用。构造方法的第一个参数主要用来保存一些分组信息。
3. 同步调用和异步调用
当BookCommand创建成功之后,我们就可以在我们的Controller中调用它了,创建BookCommandController.java
package test.controller;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import test.custom.BookCommand;
import java.awt.print.Book;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* 测试自定义断路器Hystrix
*/
@RestController
public class BookCommandController {
@Autowired
RestTemplate restTemplate;
@RequestMapping(value = "/test1",method = RequestMethod.GET)
public String test1() throws ExecutionException, InterruptedException {
// 1.获取到BookCommand对象之后,我们有两种方式来执行请求,一种是调用execute方法发起一个同步请求,另一种是调用queue方法发起一个异步请求。
// 2.同步请求中可以直接返回请求结果。
// 3.异步请求中我们需要通过get方法来获取请求结果,在调用get方法的时候也可以传入超时时长。
BookCommand bookCommand = new BookCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")), restTemplate);
//同步调用
//Book book1 = bookCommand.execute();
//异步调用
Future<String> queue = bookCommand.queue();
String string = queue.get();
return string;
}
}
温馨提示:
(1)获取到BookCommand对象之后,我们有两种方式来执行请求,一种是调用execute方法发起一个同步请求,另一种是调用queue方法发起一个异步请求。
(2)同步请求中可以直接返回请求结果。
(3)异步请求中我们需要通过get方法来获取请求结果,在调用get方法的时候也可以传入超时时长。
4. 项目运行与访问
保持原本项目启动情况,即关闭一个服务提供者,之后重新运行ribbon-cunsumer,再访问http://localhost:9000/test1,出现以下结果
四、Hystrix的服务降级与异常处理
1. 服务降级
fallbackMethod所描述的函数实际上就是一个备胎,用来实现服务的降级处理,在注解中我们可以通过fallbackMethod属性来指定降级处理的方法名称,在自定义Hystrix请求命令时我们可以通过重写getFallback函数来处理服务降级之后的逻辑。使用注解来定义服务降级逻辑时,服务降级函数和@HystrixCommand注解要处于同一个类中,同时,服务降级函数在执行过程中也有可能发生异常,所以也可以给服务降级函数添加‘备胎’
2. 异常处理
我们在调用服务提供者时有可能会抛异常,默认情况下方法抛了异常会自动进行服务降级,交给服务降级中的方法去处理,在自定义Hystrix请求命令的方式下,我们可以在getFallback方法中调用getExecutionException方法来获取抛出的异常,举个简单的例子:
package test.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import test.service.HelloService;
import test.service.HelloServiceImpl;
@RestController
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
// @RequestMapping(value = "/ribbon-consumer",method = RequestMethod.GET)
// public String helloController() {
// return restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
// }
@Autowired
private HelloService helloService;
//测试短路访问
@RequestMapping(value = "/ribbon-consumer",method = RequestMethod.GET)
public String helloController() {
return helloService.hello();
}
}
重新启动ribbon-cunsumer项目,此时访问http://localhost:9000/test1,控制台显示结果如下:
五、Hystrix的请求缓存与请求合并
1. 请求缓存
高并发环境下如果能处理好缓存就可以有效的减小服务器的压力,Java中有许多非常好用的缓存工具,比如Redis、EHCache等,当然在Spring Cloud的Hystrix中也提供了请求缓存的功能,我们可以通过一个注解或者一个方法来开启缓存,进而减轻高并发环境下系统的压力。OK,本文我们就来看看Hystrix中请求缓存的使用。
请求缓存具体内容可参考https://mp.weixin.qq.com/s/YpWODLrwzFXUQRtIAHLF3Q?
2. 请求合并
我们将一个项目拆分成很多个独立的模块,这些独立的模块通过远程调用来互相配合工作,但是,在高并发情况下,通信次数的增加会导致总的通信时间增加,同时,线程池的资源也是有限的,高并发环境会导致有大量的线程处于等待状态,进而导致响应延迟,为了解决这些问题,我们需要来了解Hystrix的请求合并。
请求合并具体内容可参考https://mp.weixin.qq.com/s/0QSKVLaDjBAscRaeccaXuA?
六、Hystrix仪表盘与Turbine集群监控
1. 简介
Hystrix仪表盘,就像汽车的仪表盘实时显示汽车的各项数据一样,Hystrix仪表盘主要用来监控Hystrix的实时运行状态,通过它我们可以看到Hystrix的各项指标信息,从而快速发现系统中存在的问题进而解决它,OK,本文我们就来看看Hystrix仪表盘要怎么使用。
2. 创建最基本的Hystrix项目
2.1 新建springboot项目,项目名称为hystrix-dashboard
创建后项目结构如下:
2.2 pom.xml文件添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wxc</groupId>
<artifactId>hystrix-dashboard</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR3</version>
<relativePath/>
</parent>
<dependencies>
<!-- 其他默认依赖 -->
<!-- 我们需要添加的依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
2.3 创建项目启动类
com.wxc.test包下新建HystrixDashboardApplication.java
package com.wxc.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}
2.4 项目启动与访问
浏览器输入http://localhost:2001/进行访问,访问成功后结果如下:
三个参数的含义我已在图中标注出来了。
OK,现在我们的仪表盘工程已经创建成功了,但是还不能用来监控某一个服务,要监控某一个服务,需要该服务提供一个/hystrix.stream接口,so,我们需要对我们的服务消费者工程稍加改造。
3.改造要监控的服务
3.1 改造服务消费者工程ribbon-consumer
在pom.xml文件中添加以下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
在确保服务注册中心和服务提供者开启情况下,在服务消费者工程的入口类上添加@EnableCircuitBreaker注解,表示开启断路器功能。此时项目启动情况如下:
这个信息表明我们的consumer工程目前已经具备了/hystrix.stream接口,我们可以直接访问这个接口了。但是这里有一个细节需要小伙伴们注意:要访问/hystrix.stream接口,得先访问consumer工程中的任意一个其他接口,否则如果直接访问/hystrix.stream接口的话,会打印出一连串的ping: ping: …。 OK,我先访问consumer中的任意一个其他接口,然后在访问/hystrix.stream接口,访问地址如下:http://localhost:9000/hystrix.stream,访问结果如下:
3.2 hystrix-dashboard服务监控
在hystrix仪表盘中输入监控地址,如下:
然后点击Monitor Stream按钮,我们就可以看到监控画面了,如下:
3.3 监控参数详解
4. Turbine集群监控
OK,上文我们看了一个监控单体应用的例子,在实际应用中,我们要监控的应用往往是一个集群,这个时候我们就得采取Turbine集群监控了。Turbine有一个重要的功能就是汇聚监控信息,并将汇聚到的监控信息提供给Hystrix Dashboard来集中展示和监控。那我们就来看看Turbine集群监控如何使用。
搭建监控环境
监控环境的搭建也是分为四个步骤:
第一步:创建一个普通的Spring Boot工程
第一步创建一个名叫turbine的普通Spring Boot工程。
第二步:添加依赖
工程创建完成之后,我们需要添加一个依赖,如下:
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR3</version>
<relativePath/>
</parent>
<dependencies>
<!-- 其他默认的依赖 -->
<!-- 我们要添加的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine</artifactId>
</dependency>
</dependencies>
第三步:添加注解
在入口类上添加@EnableTurbine注解表示开启Turbine,如下:
@SpringBootApplication
@EnableDiscoveryClient
@EnableTurbine
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}
第四步:修改配置
在application.properties配置文件中加入eureka和turbine的相关配置,如下:
spring.application.name=turbine
server.port=2002
management.port=2003
eureka.client.service-url.defaultZone=http://localhost:1111/eureka/
turbine.app-config=ribbon-consumer
turbine.cluster-name-expression="default"
turbine.combine-host-port=true
关于这个配置文件,我说如下几点:
1.turbine.app-config=ribbon-consumer指定了要监控的应用名字为ribbon-consumer
2.turbine.cluster-name-expression=”default”,表示集群的名字为default
3.turbine.combine-host-port=true表示同一主机上的服务通过host和port的组合来进行区分,默认情况下是使用host来区分,这样会使本地调试有问题
查看监控图
OK,监控服务创建成功之后,我们再次依次启动eureka-server、provider和consumer,其中consumer启动两个实例,两个实例的端口不一致,再分别启动hystrix-dashboard和turbine,然后在hystrix监控地址栏输入如下地址(监控之前要记得先访问一下服务中的任意一个接口):http://localhost:2002/turbine.stream,访问结果如下:
七、项目源码与参考资料下载
链接:https://pan.baidu.com/s/120z7NZZ_-Z_BHySQHHazZw
提取码:flic