Hystrix

Hystrix

Spring Cloud构建微服务架构:Hystrix监控数据聚合【Dalston版】

Spring Cloud程序猿DD 发表了文章 • 0 个评论 • 320 次浏览 • 2017-07-24 22:57 • 来自相关话题

上一篇我们介绍了使用Hystrix Dashboard来展示Hystrix用于熔断的各项度量指标。通过Hystrix Dashboard,我们可以方便的查看服务实例的综合情况,比如:服务调用次数、服务调用延迟等。但是仅通过Hystrix Dashboard我们只能实现对服务当个实例的数据展现,在生产环境我们的服务是肯定需要做高可用的,那么对于多实例的情况,我们就需要将这些度量指标数据进行聚合。下面,在本篇中,我们就来介绍一下另外一个工具:Turbine。
 
准备工作

在开始使用Turbine之前,我们先回顾一下上一篇中实现的架构,如下图所示:





 
其中,我们构建的内容包括:
 
eureka-server:服务注册中心eureka-client:服务提供者eureka-consumer-ribbon-hystrix:使用ribbon和hystrix实现的服务消费者hystrix-dashboard:用于展示eureka-consumer-ribbon-hystrix服务的Hystrix数据
 
动手试一试

下面,我们将在上述架构基础上,引入Turbine来对服务的Hystrix数据进行聚合展示。这里我们将分别介绍两种聚合方式。

通过HTTP收集聚合

具体实现步骤如下:
创建一个标准的Spring Boot工程,命名为:turbine。编辑pom.xml,具体依赖内容如下:
 
$(document).ready(function() {$('pre code').each(function(i, block) { hljs.highlightBlock( block); }); });<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
创建应用主类TurbineApplication,并使用@EnableTurbine注解开启Turbine。
@Configuration
@EnableAutoConfiguration
@EnableTurbine
@EnableDiscoveryClient
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}
在application.properties加入eureka和turbine的相关配置,具体如下:
spring.application.name=turbine
server.port=8989
management.port=8990
eureka.client.serviceUrl.defaultZone=http://localhost:1001/eureka/
turbine.app-config=eureka-consumer-ribbon-hystrix
turbine.cluster-name-expression="default"
turbine.combine-host-port=true参数说明

turbine.app-config参数指定了需要收集监控信息的服务名;
turbine.cluster-name-expression 参数指定了集群名称为default,当我们服务数量非常多的时候,可以启动多个Turbine服务来构建不同的聚合集群,而该参数可以用来区分这些不同的聚合集群,同时该参数值可以在Hystrix仪表盘中用来定位不同的聚合集群,只需要在Hystrix Stream的URL中通过cluster参数来指定;
turbine.combine-host-port参数设置为true,可以让同一主机上的服务通过主机名与端口号的组合来进行区分,默认情况下会以host来区分不同的服务,这会使得在本地调试的时候,本机上的不同服务聚合成一个服务来统计。

在完成了上面的内容构建之后,我们来体验一下Turbine对集群的监控能力。分别启动eureka-server、eureka-client、eureka-consumer-ribbon-hystrix、turbine以及hystrix-dashboard。访问Hystrix Dashboard,并开启对http://localhost:8989/turbine.stream`的监控,这时候,我们将看到针对服务`eureka-consumer-ribbon-hystrix`的聚合监控数据。

而此时的架构如下图所示:





 
通过消息代理收集聚合
 
Spring Cloud在封装Turbine的时候,还实现了基于消息代理的收集实现。所以,我们可以将所有需要收集的监控信息都输出到消息代理中,然后Turbine服务再从消息代理中异步的获取这些监控信息,最后将这些监控信息聚合并输出到Hystrix Dashboard中。通过引入消息代理,我们的Turbine和Hystrix Dashoard实现的监控架构可以改成如下图所示的结构:





 
从图中我们可以看到,这里多了一个重要元素:RabbitMQ。对于RabbitMQ的安装与基本时候我们可以查看之前的《Spring Boot中使用RabbitMQ》一文,这里不做过多的说明。下面,我们可以来构建一个新的应用来实现基于消息代理的Turbine聚合服务,具体步骤如下:
创建一个标准的Spring Boot工程,命名为:turbine-amqp。编辑pom.xml,具体依赖内容如下:
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>可以看到这里主要引入了spring-cloud-starter-turbine-amqp依赖,它实际上就是包装了spring-cloud-starter-turbine-stream和pring-cloud-starter-stream-rabbit。
注意:这里我们需要使用Java 8来运行
 
在应用主类中使用@EnableTurbineStream注解来启用Turbine Stream的配置。
@Configuration
@EnableAutoConfiguration
@EnableTurbineStream
@EnableDiscoveryClient
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}
配置application.properties文件:
spring.application.name=turbine-amqp
server.port=8989
management.port=8990
eureka.client.serviceUrl.defaultZone=http://localhost:1001/eureka/对于Turbine的配置已经完成了,下面我们需要对服务消费者eureka-consumer-ribbon-hystrix做一些修改,使其监控信息能够输出到RabbitMQ上。这个修改也非常简单,只需要在pom.xml中增加对spring-cloud-netflix-hystrix-amqp依赖,具体如下:
<dependencies>
...
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-amqp</artifactId>
</dependency>
</dependencies>在完成了上面的配置之后,我们可以继续之前的所有项目(除turbine以外),并通过Hystrix Dashboard开启对http://localhost:8989/turbine.stream的监控,我们可以获得如之前实现的同样效果,只是这里我们的监控信息收集时是通过了消息代理异步实现的。

更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。





 
代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

码云:点击查看
GitHub:点击查看

本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:Hystrix监控数据聚合【Dalston版】
版权归作者所有,转载请注明出处

 
 
  查看全部
上一篇我们介绍了使用Hystrix Dashboard来展示Hystrix用于熔断的各项度量指标。通过Hystrix Dashboard,我们可以方便的查看服务实例的综合情况,比如:服务调用次数、服务调用延迟等。但是仅通过Hystrix Dashboard我们只能实现对服务当个实例的数据展现,在生产环境我们的服务是肯定需要做高可用的,那么对于多实例的情况,我们就需要将这些度量指标数据进行聚合。下面,在本篇中,我们就来介绍一下另外一个工具:Turbine。
 
准备工作

在开始使用Turbine之前,我们先回顾一下上一篇中实现的架构,如下图所示:

spring-cloud-starter-dalston-5-2-1.png

 
其中,我们构建的内容包括:
 
  • eureka-server:服务注册中心
  • eureka-client:服务提供者
  • eureka-consumer-ribbon-hystrix:使用ribbon和hystrix实现的服务消费者
  • hystrix-dashboard:用于展示eureka-consumer-ribbon-hystrix服务的Hystrix数据

 
动手试一试

下面,我们将在上述架构基础上,引入Turbine来对服务的Hystrix数据进行聚合展示。这里我们将分别介绍两种聚合方式。

通过HTTP收集聚合

具体实现步骤如下:
  • 创建一个标准的Spring Boot工程,命名为:turbine。
  • 编辑pom.xml,具体依赖内容如下:

 
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>

  • 创建应用主类TurbineApplication,并使用@EnableTurbine注解开启Turbine。

@Configuration
@EnableAutoConfiguration
@EnableTurbine
@EnableDiscoveryClient
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}

  • 在application.properties加入eureka和turbine的相关配置,具体如下:

spring.application.name=turbine
server.port=8989
management.port=8990
eureka.client.serviceUrl.defaultZone=http://localhost:1001/eureka/
turbine.app-config=eureka-consumer-ribbon-hystrix
turbine.cluster-name-expression="default"
turbine.combine-host-port=true
参数说明

turbine.app-config参数指定了需要收集监控信息的服务名;
turbine.cluster-name-expression 参数指定了集群名称为default,当我们服务数量非常多的时候,可以启动多个Turbine服务来构建不同的聚合集群,而该参数可以用来区分这些不同的聚合集群,同时该参数值可以在Hystrix仪表盘中用来定位不同的聚合集群,只需要在Hystrix Stream的URL中通过cluster参数来指定;
turbine.combine-host-port参数设置为true,可以让同一主机上的服务通过主机名与端口号的组合来进行区分,默认情况下会以host来区分不同的服务,这会使得在本地调试的时候,本机上的不同服务聚合成一个服务来统计。

在完成了上面的内容构建之后,我们来体验一下Turbine对集群的监控能力。分别启动eureka-server、eureka-client、eureka-consumer-ribbon-hystrix、turbine以及hystrix-dashboard。访问Hystrix Dashboard,并开启对http://localhost:8989/turbine.stream`的监控,这时候,我们将看到针对服务`eureka-consumer-ribbon-hystrix`的聚合监控数据。

而此时的架构如下图所示:

spring-cloud-starter-dalston-5-2-2.png

 
通过消息代理收集聚合
 
Spring Cloud在封装Turbine的时候,还实现了基于消息代理的收集实现。所以,我们可以将所有需要收集的监控信息都输出到消息代理中,然后Turbine服务再从消息代理中异步的获取这些监控信息,最后将这些监控信息聚合并输出到Hystrix Dashboard中。通过引入消息代理,我们的Turbine和Hystrix Dashoard实现的监控架构可以改成如下图所示的结构:

spring-cloud-starter-dalston-5-2-3.png

 
从图中我们可以看到,这里多了一个重要元素:RabbitMQ。对于RabbitMQ的安装与基本时候我们可以查看之前的《Spring Boot中使用RabbitMQ》一文,这里不做过多的说明。下面,我们可以来构建一个新的应用来实现基于消息代理的Turbine聚合服务,具体步骤如下:
  • 创建一个标准的Spring Boot工程,命名为:turbine-amqp。
  • 编辑pom.xml,具体依赖内容如下:

<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
可以看到这里主要引入了spring-cloud-starter-turbine-amqp依赖,它实际上就是包装了spring-cloud-starter-turbine-stream和pring-cloud-starter-stream-rabbit。
注意:这里我们需要使用Java 8来运行
 
  • 在应用主类中使用@EnableTurbineStream注解来启用Turbine Stream的配置。

@Configuration
@EnableAutoConfiguration
@EnableTurbineStream
@EnableDiscoveryClient
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}

  • 配置application.properties文件:

spring.application.name=turbine-amqp
server.port=8989
management.port=8990
eureka.client.serviceUrl.defaultZone=http://localhost:1001/eureka/
对于Turbine的配置已经完成了,下面我们需要对服务消费者eureka-consumer-ribbon-hystrix做一些修改,使其监控信息能够输出到RabbitMQ上。这个修改也非常简单,只需要在pom.xml中增加对spring-cloud-netflix-hystrix-amqp依赖,具体如下:
<dependencies>
...
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-amqp</artifactId>
</dependency>
</dependencies>
在完成了上面的配置之后,我们可以继续之前的所有项目(除turbine以外),并通过Hystrix Dashboard开启对http://localhost:8989/turbine.stream的监控,我们可以获得如之前实现的同样效果,只是这里我们的监控信息收集时是通过了消息代理异步实现的。

更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。

翟永超-京东-推广链接.png

 
代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

码云:点击查看
GitHub:点击查看


本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:Hystrix监控数据聚合【Dalston版】
版权归作者所有,转载请注明出处


 
 
 

Spring Cloud构建微服务架构:Hystrix监控面板【Dalston版】

Spring Cloud程序猿DD 发表了文章 • 0 个评论 • 288 次浏览 • 2017-07-23 11:54 • 来自相关话题

在上一篇[《服务容错保护(hystrix断路器)》](http://blog.didispace.com/spri ... on-4-3)的介绍中,我们提到断路器是根据一段时间窗内的请求情况来判断并操作断路器的打开和关闭状态的。而这些请求情况的指标信息都是HystrixCommand和HystrixObservableCommand实例在执行过程中记录的重要度量信息,它们除了Hystrix断路器实现中使用之外,对于系统运维也有非常大的帮助。这些指标信息会以“滚动时间窗”与“桶”结合的方式进行汇总,并在内存中驻留一段时间,以供内部或外部进行查询使用,Hystrix Dashboard就是这些指标内容的消费者之一。

下面我们基于之前的示例来结合Hystrix Dashboard实现Hystrix指标数据的可视化面板,这里我们将用到下之前实现的几个应用,包括:

- eureka-server:服务注册中心
- eureka-client:服务提供者
- eureka-consumer-ribbon-hystrix:使用ribbon和hystrix实现的服务消费者

由于eureka-consumer-ribbon-hystrix项目中的`/consumer`接口实现使用了`@HystrixCommand`修饰,所以这个接口的调用情况会被Hystrix记录下来,以用来给断路器和Hystrix Dashboard使用。断路器我们在上一篇中已经介绍过了,下面我们来具体说说Hystrix Dashboard的构建。

动手试一试

在Spring Cloud中构建一个Hystrix Dashboard非常简单,只需要下面四步:

- 创建一个标准的Spring Boot工程,命名为:hystrix-dashboard。
- 编辑pom.xml,具体依赖内容如下:<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</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>
</dependencies>
- 为应用主类加上`@EnableHystrixDashboard`,启用Hystrix Dashboard功能。@EnableHystrixDashboard
@SpringCloudApplication
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}
- 根据实际情况修改`application.properties`配置文件,比如:选择一个未被占用的端口等,此步非必须。spring.application.name=hystrix-dashboard
server.port=1301
到这里我们已经完成了基本配置,接下来我们可以启动该应用,并访问:`http://localhost:1301/hystrix`,我们可以看到如下页面:






这是Hystrix Dashboard的监控首页,该页面中并没有具体的监控信息。从页面的文字内容中我们可以知道,Hystrix Dashboard共支持三种不同的监控方式,依次为:

- 默认的集群监控:通过URL`http://turbine-hostname:port/turbine.stream`开启,实现对默认集群的监控。
- 指定的集群监控:通过URL`http://turbine-hostname:port/t ... er%3D[clusterName]`开启,实现对clusterName集群的监控。
- 单体应用的监控:通过URL`http://hystrix-app:port/hystrix.stream`开启,实现对具体某个服务实例的监控。

前两者都对集群的监控,需要整合Turbine才能实现,这部分内容我们将在下一篇中做详细介绍。在本节中,我们主要实现对单个服务实例的监控,所以这里我们先来实现单个服务实例的监控。

既然Hystrix Dashboard监控单实例节点需要通过访问实例的`/hystrix.stream`接口来实现,自然我们需要为服务实例添加这个端点,而添加该功能的步骤也同样简单,只需要下面两步:

- 在服务实例`pom.xml`中的`dependencies`节点中新增`spring-boot-starter-actuator`监控模块以开启监控相关的端点,并确保已经引入断路器的依赖`spring-cloud-starter-hystrix`:<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`或`@EnableHystrix`注解,开启了断路器功能。

到这里已经完成了所有的配置,我们可以在Hystrix Dashboard的首页输入`http://localhost:2101/hystrix.stream`,已启动对“eureka-consumer-ribbon-hystrix”的监控,点击“Monitor Stream”按钮,此时我们可以看到如下页面:






在对该页面介绍前,我们先看看在首页中我们还没有介绍的两外两个参数:

- `Delay`:该参数用来控制服务器上轮询监控信息的延迟时间,默认为2000毫秒,我们可以通过配置该属性来降低客户端的网络和CPU消耗。
- `Title`:该参数对应了上图头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的URL,我们可以通过配置该信息来展示更合适的标题。

回到监控页面,我们来详细说说其中各元素的具体含义:

- 我们可以在监控信息的左上部分找到两个重要的图形信息:一个实心圆和一条曲线。
  - 实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,如下图所示,它的健康度从绿色、黄色、橙色、红色递减。该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,我们就可以在大量的实例中快速的发现故障实例和高压力实例。






  - 曲线:用来记录2分钟内流量的相对变化,我们可以通过它来观察到流量的上升和下降趋势。
- 其他一些数量指标如下图所示:






更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。

代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

- 码云:点击查看
- GitHub:点击查看
 

本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:Hystrix监控面板【Dalston版】
版权归作者所有,转载请注明出处 查看全部
在上一篇[《服务容错保护(hystrix断路器)》](http://blog.didispace.com/spri ... on-4-3)的介绍中,我们提到断路器是根据一段时间窗内的请求情况来判断并操作断路器的打开和关闭状态的。而这些请求情况的指标信息都是HystrixCommand和HystrixObservableCommand实例在执行过程中记录的重要度量信息,它们除了Hystrix断路器实现中使用之外,对于系统运维也有非常大的帮助。这些指标信息会以“滚动时间窗”与“桶”结合的方式进行汇总,并在内存中驻留一段时间,以供内部或外部进行查询使用,Hystrix Dashboard就是这些指标内容的消费者之一。

下面我们基于之前的示例来结合Hystrix Dashboard实现Hystrix指标数据的可视化面板,这里我们将用到下之前实现的几个应用,包括:

- eureka-server:服务注册中心
- eureka-client:服务提供者
- eureka-consumer-ribbon-hystrix:使用ribbon和hystrix实现的服务消费者

由于eureka-consumer-ribbon-hystrix项目中的`/consumer`接口实现使用了`@HystrixCommand`修饰,所以这个接口的调用情况会被Hystrix记录下来,以用来给断路器和Hystrix Dashboard使用。断路器我们在上一篇中已经介绍过了,下面我们来具体说说Hystrix Dashboard的构建。

动手试一试

在Spring Cloud中构建一个Hystrix Dashboard非常简单,只需要下面四步:

- 创建一个标准的Spring Boot工程,命名为:hystrix-dashboard。
- 编辑pom.xml,具体依赖内容如下:
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</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>
</dependencies>

- 为应用主类加上`@EnableHystrixDashboard`,启用Hystrix Dashboard功能。
@EnableHystrixDashboard
@SpringCloudApplication
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}

- 根据实际情况修改`application.properties`配置文件,比如:选择一个未被占用的端口等,此步非必须。
spring.application.name=hystrix-dashboard
server.port=1301

到这里我们已经完成了基本配置,接下来我们可以启动该应用,并访问:`http://localhost:1301/hystrix`,我们可以看到如下页面:

spring-cloud-starter-dalston-5-1-1.png


这是Hystrix Dashboard的监控首页,该页面中并没有具体的监控信息。从页面的文字内容中我们可以知道,Hystrix Dashboard共支持三种不同的监控方式,依次为:

- 默认的集群监控:通过URL`http://turbine-hostname:port/turbine.stream`开启,实现对默认集群的监控。
- 指定的集群监控:通过URL`http://turbine-hostname:port/t ... er%3D[clusterName]`开启,实现对clusterName集群的监控。
- 单体应用的监控:通过URL`http://hystrix-app:port/hystrix.stream`开启,实现对具体某个服务实例的监控。

前两者都对集群的监控,需要整合Turbine才能实现,这部分内容我们将在下一篇中做详细介绍。在本节中,我们主要实现对单个服务实例的监控,所以这里我们先来实现单个服务实例的监控。

既然Hystrix Dashboard监控单实例节点需要通过访问实例的`/hystrix.stream`接口来实现,自然我们需要为服务实例添加这个端点,而添加该功能的步骤也同样简单,只需要下面两步:

- 在服务实例`pom.xml`中的`dependencies`节点中新增`spring-boot-starter-actuator`监控模块以开启监控相关的端点,并确保已经引入断路器的依赖`spring-cloud-starter-hystrix`:
<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`或`@EnableHystrix`注解,开启了断路器功能。

到这里已经完成了所有的配置,我们可以在Hystrix Dashboard的首页输入`http://localhost:2101/hystrix.stream`,已启动对“eureka-consumer-ribbon-hystrix”的监控,点击“Monitor Stream”按钮,此时我们可以看到如下页面:

spring-cloud-starter-dalston-5-1-2.png


在对该页面介绍前,我们先看看在首页中我们还没有介绍的两外两个参数:

- `Delay`:该参数用来控制服务器上轮询监控信息的延迟时间,默认为2000毫秒,我们可以通过配置该属性来降低客户端的网络和CPU消耗。
- `Title`:该参数对应了上图头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的URL,我们可以通过配置该信息来展示更合适的标题。

回到监控页面,我们来详细说说其中各元素的具体含义:

- 我们可以在监控信息的左上部分找到两个重要的图形信息:一个实心圆和一条曲线。
  - 实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,如下图所示,它的健康度从绿色、黄色、橙色、红色递减。该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,我们就可以在大量的实例中快速的发现故障实例和高压力实例。

spring-cloud-starter-dalston-5-1-3.png


  - 曲线:用来记录2分钟内流量的相对变化,我们可以通过它来观察到流量的上升和下降趋势。
- 其他一些数量指标如下图所示:

spring-cloud-starter-dalston-5-1-4.png


更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。

代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

- 码云:点击查看
- GitHub:点击查看
 


本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:Hystrix监控面板【Dalston版】
版权归作者所有,转载请注明出处


Spring Cloud构建微服务架构:服务容错保护(Hystrix依赖隔离)【Dalston版】

Spring Cloud程序猿DD 发表了文章 • 2 个评论 • 488 次浏览 • 2017-07-12 22:29 • 来自相关话题

前言

在上一篇《Spring Cloud构建微服务架构:服务容错保护(Hystrix服务降级)》中,我们已经体验了如何使用@HystrixCommand来为一个依赖资源定义服务降级逻辑。实现方式非常简单,同时对于降级逻辑还能实现一些更加复杂的级联降级等策略。之前对于使用Hystrix来实现服务容错保护时,除了服务降级之外,我们还提到过线程隔离、断路器等功能。那么在本篇中我们就来具体说说线程隔离。

依赖隔离

“舱壁模式”对于熟悉Docker的读者一定不陌生,Docker通过“舱壁模式”实现进程的隔离,使得容器与容器之间不会互相影响。而Hystrix则使用该模式实现线程池的隔离,它会为每一个Hystrix命令创建一个独立的线程池,这样就算某个在Hystrix命令包装下的依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他的服务。

通过对依赖服务的线程池隔离实现,可以带来如下优势:
应用自身得到完全的保护,不会受不可控的依赖服务影响。即便给依赖服务分配的线程池被填满,也不会影响应用自身的额其余部分。可以有效的降低接入新服务的风险。如果新服务接入后运行不稳定或存在问题,完全不会影响到应用其他的请求。当依赖的服务从失效恢复正常后,它的线程池会被清理并且能够马上恢复健康的服务,相比之下容器级别的清理恢复速度要慢得多。当依赖的服务出现配置错误的时候,线程池会快速的反应出此问题(通过失败次数、延迟、超时、拒绝等指标的增加情况)。同时,我们可以在不影响应用功能的情况下通过实时的动态属性刷新(后续会通过Spring Cloud Config与Spring Cloud Bus的联合使用来介绍)来处理它。当依赖的服务因实现机制调整等原因造成其性能出现很大变化的时候,此时线程池的监控指标信息会反映出这样的变化。同时,我们也可以通过实时动态刷新自身应用对依赖服务的阈值进行调整以适应依赖方的改变。除了上面通过线程池隔离服务发挥的优点之外,每个专有线程池都提供了内置的并发实现,可以利用它为同步的依赖服务构建异步的访问。

总之,通过对依赖服务实现线程池隔离,让我们的应用更加健壮,不会因为个别依赖服务出现问题而引起非相关服务的异常。同时,也使得我们的应用变得更加灵活,可以在不停止服务的情况下,配合动态配置刷新实现性能配置上的调整。

虽然线程池隔离的方案带了如此多的好处,但是很多使用者可能会担心为每一个依赖服务都分配一个线程池是否会过多地增加系统的负载和开销。对于这一点,使用者不用过于担心,因为这些顾虑也是大部分工程师们会考虑到的,Netflix在设计Hystrix的时候,认为线程池上的开销相对于隔离所带来的好处是无法比拟的。同时,Netflix也针对线程池的开销做了相关的测试,以证明和打消Hystrix实现对性能影响的顾虑。

下图是Netflix Hystrix官方提供的一个Hystrix命令的性能监控,该命令以每秒60个请求的速度(QPS)向一个单服务实例进行访问,该服务实例每秒运行的线程数峰值为350个。






从图中的统计我们可以看到,使用线程池隔离与不使用线程池隔离的耗时差异如下表所示:






在99%的情况下,使用线程池隔离的延迟有9ms,对于大多数需求来说这样的消耗是微乎其微的,更何况为系统在稳定性和灵活性上所带来的巨大提升。虽然对于大部分的请求我们可以忽略线程池的额外开销,而对于小部分延迟本身就非常小的请求(可能只需要1ms),那么9ms的延迟开销还是非常昂贵的。实际上Hystrix也为此设计了另外的一个解决方案:信号量。

Hystrix中除了使用线程池之外,还可以使用信号量来控制单个依赖服务的并发度,信号量的开销要远比线程池的开销小得多,但是它不能设置超时和实现异步访问。所以,只有在依赖服务是足够可靠的情况下才使用信号量。在HystrixCommand和HystrixObservableCommand中2处支持信号量的使用:
 
命令执行:如果隔离策略参数execution.isolation.strategy设置为SEMAPHORE,Hystrix会使用信号量替代线程池来控制依赖服务的并发控制。降级逻辑:当Hystrix尝试降级逻辑时候,它会在调用线程中使用信号量。

信号量的默认值为10,我们也可以通过动态刷新配置的方式来控制并发线程的数量。对于信号量大小的估算方法与线程池并发度的估算类似。仅访问内存数据的请求一般耗时在1ms以内,性能可以达到5000rps,这样级别的请求我们可以将信号量设置为1或者2,我们可以按此标准并根据实际请求耗时来设置信号量。

如何使用

说了那么多依赖隔离的好处,那么我们如何使用Hystrix来实现依赖隔离呢?其实,我们在上一篇定义服务降级的时候,已经自动的实现了依赖隔离。

在上一篇的示例中,我们使用了@HystrixCommand来将某个函数包装成了Hystrix命令,这里除了定义服务降级之外,Hystrix框架就会自动的为这个函数实现调用的隔离。所以,依赖隔离、服务降级在使用时候都是一体化实现的,这样利用Hystrix来实现服务容错保护在编程模型上就非常方便的,并且考虑更为全面。除了依赖隔离、服务降级之外,还有一个重要元素:断路器。我们将在下一篇做详细的介绍,这三个重要利器构成了Hystrix实现服务容错保护的强力组合拳。

更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。

代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

码云:点击查看
GitHub:点击查看
 

本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:服务容错保护(Hystrix依赖隔离)【Dalston版】
版权归作者所有,转载请注明出处 查看全部
前言

在上一篇《Spring Cloud构建微服务架构:服务容错保护(Hystrix服务降级)》中,我们已经体验了如何使用@HystrixCommand来为一个依赖资源定义服务降级逻辑。实现方式非常简单,同时对于降级逻辑还能实现一些更加复杂的级联降级等策略。之前对于使用Hystrix来实现服务容错保护时,除了服务降级之外,我们还提到过线程隔离、断路器等功能。那么在本篇中我们就来具体说说线程隔离。

依赖隔离

“舱壁模式”对于熟悉Docker的读者一定不陌生,Docker通过“舱壁模式”实现进程的隔离,使得容器与容器之间不会互相影响。而Hystrix则使用该模式实现线程池的隔离,它会为每一个Hystrix命令创建一个独立的线程池,这样就算某个在Hystrix命令包装下的依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他的服务。

通过对依赖服务的线程池隔离实现,可以带来如下优势:
  • 应用自身得到完全的保护,不会受不可控的依赖服务影响。即便给依赖服务分配的线程池被填满,也不会影响应用自身的额其余部分。
  • 可以有效的降低接入新服务的风险。如果新服务接入后运行不稳定或存在问题,完全不会影响到应用其他的请求。
  • 当依赖的服务从失效恢复正常后,它的线程池会被清理并且能够马上恢复健康的服务,相比之下容器级别的清理恢复速度要慢得多。
  • 当依赖的服务出现配置错误的时候,线程池会快速的反应出此问题(通过失败次数、延迟、超时、拒绝等指标的增加情况)。同时,我们可以在不影响应用功能的情况下通过实时的动态属性刷新(后续会通过Spring Cloud Config与Spring Cloud Bus的联合使用来介绍)来处理它。
  • 当依赖的服务因实现机制调整等原因造成其性能出现很大变化的时候,此时线程池的监控指标信息会反映出这样的变化。同时,我们也可以通过实时动态刷新自身应用对依赖服务的阈值进行调整以适应依赖方的改变。
  • 除了上面通过线程池隔离服务发挥的优点之外,每个专有线程池都提供了内置的并发实现,可以利用它为同步的依赖服务构建异步的访问。


总之,通过对依赖服务实现线程池隔离,让我们的应用更加健壮,不会因为个别依赖服务出现问题而引起非相关服务的异常。同时,也使得我们的应用变得更加灵活,可以在不停止服务的情况下,配合动态配置刷新实现性能配置上的调整。

虽然线程池隔离的方案带了如此多的好处,但是很多使用者可能会担心为每一个依赖服务都分配一个线程池是否会过多地增加系统的负载和开销。对于这一点,使用者不用过于担心,因为这些顾虑也是大部分工程师们会考虑到的,Netflix在设计Hystrix的时候,认为线程池上的开销相对于隔离所带来的好处是无法比拟的。同时,Netflix也针对线程池的开销做了相关的测试,以证明和打消Hystrix实现对性能影响的顾虑。

下图是Netflix Hystrix官方提供的一个Hystrix命令的性能监控,该命令以每秒60个请求的速度(QPS)向一个单服务实例进行访问,该服务实例每秒运行的线程数峰值为350个。

spring-cloud-starter-dalston-4-2.png


从图中的统计我们可以看到,使用线程池隔离与不使用线程池隔离的耗时差异如下表所示:

微信截图_20170712222712.png


在99%的情况下,使用线程池隔离的延迟有9ms,对于大多数需求来说这样的消耗是微乎其微的,更何况为系统在稳定性和灵活性上所带来的巨大提升。虽然对于大部分的请求我们可以忽略线程池的额外开销,而对于小部分延迟本身就非常小的请求(可能只需要1ms),那么9ms的延迟开销还是非常昂贵的。实际上Hystrix也为此设计了另外的一个解决方案:信号量。

Hystrix中除了使用线程池之外,还可以使用信号量来控制单个依赖服务的并发度,信号量的开销要远比线程池的开销小得多,但是它不能设置超时和实现异步访问。所以,只有在依赖服务是足够可靠的情况下才使用信号量。在HystrixCommand和HystrixObservableCommand中2处支持信号量的使用:
 
  • 命令执行:如果隔离策略参数execution.isolation.strategy设置为SEMAPHORE,Hystrix会使用信号量替代线程池来控制依赖服务的并发控制。
  • 降级逻辑:当Hystrix尝试降级逻辑时候,它会在调用线程中使用信号量。


信号量的默认值为10,我们也可以通过动态刷新配置的方式来控制并发线程的数量。对于信号量大小的估算方法与线程池并发度的估算类似。仅访问内存数据的请求一般耗时在1ms以内,性能可以达到5000rps,这样级别的请求我们可以将信号量设置为1或者2,我们可以按此标准并根据实际请求耗时来设置信号量。

如何使用

说了那么多依赖隔离的好处,那么我们如何使用Hystrix来实现依赖隔离呢?其实,我们在上一篇定义服务降级的时候,已经自动的实现了依赖隔离。

在上一篇的示例中,我们使用了@HystrixCommand来将某个函数包装成了Hystrix命令,这里除了定义服务降级之外,Hystrix框架就会自动的为这个函数实现调用的隔离。所以,依赖隔离、服务降级在使用时候都是一体化实现的,这样利用Hystrix来实现服务容错保护在编程模型上就非常方便的,并且考虑更为全面。除了依赖隔离、服务降级之外,还有一个重要元素:断路器。我们将在下一篇做详细的介绍,这三个重要利器构成了Hystrix实现服务容错保护的强力组合拳。

更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。

代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

码云:点击查看
GitHub:点击查看
 


本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:服务容错保护(Hystrix依赖隔离)【Dalston版】
版权归作者所有,转载请注明出处


Spring Cloud构建微服务架构:服务容错保护(Hystrix服务降级)【Dalston版】

Spring Cloud程序猿DD 发表了文章 • 0 个评论 • 457 次浏览 • 2017-07-05 07:45 • 来自相关话题

前言

在微服务架构中,我们将系统拆分成了一个个的服务单元,各单元应用间通过服务注册与订阅的方式互相依赖。由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会出现因等待出现故障的依赖方响应而形成任务积压,线程资源无法释放,最终导致自身服务的瘫痪,进一步甚至出现故障的蔓延最终导致整个系统的瘫痪。如果这样的架构存在如此严重的隐患,那么相较传统架构就更加的不稳定。为了解决这样的问题,因此产生了断路器等一系列的服务保护机制。

针对上述问题,在Spring Cloud Hystrix中实现了线程隔离、断路器等一系列的服务保护功能。它也是基于Netflix的开源框架 Hystrix实现的,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备了服务降级、服务熔断、线程隔离、请求缓存、请求合并以及服务监控等强大功能。

接下来,我们就从一个简单示例开始对Spring Cloud Hystrix的学习与使用。

动手试一试

在开始使用Spring Cloud Hystrix实现断路器之前,我们先拿之前实现的一些内容作为基础,其中包括:

eureka-server工程:服务注册中心,端口:1001
eureka-client工程:服务提供者,两个实例启动端口分别为2001

下面我们可以复制一下之前实现的一个服务消费者:eureka-consumer-ribbon,命名为eureka-consumer-ribbon-hystrix。下面我们开始对其进行改在:

第一步:pom.xml的dependencies节点中引入spring-cloud-starter-hystrix依赖:<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>第二步:在应用主类中使用@EnableCircuitBreaker或@EnableHystrix注解开启Hystrix的使用:@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}注意:这里我们还可以使用Spring Cloud应用中的@SpringCloudApplication注解来修饰应用主类,该注解的具体定义如下所示。我们可以看到该注解中包含了上我们所引用的三个注解,这也意味着一个Spring Cloud标准应用应包含服务发现以及断路器。@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}第三步:改造服务消费方式,新增ConsumerService类,然后将在Controller中的逻辑迁移过去。最后,在为具体执行逻辑的函数上增加@HystrixCommand注解来指定服务降级方法,比如:@RestController
public class DcController {
@Autowired
ConsumerService consumerService;
@GetMapping("/consumer")
public String dc() {
return consumerService.consumer();
}
class ConsumerService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "fallback")
public String consumer() {
return restTemplate.getForObject("http://eureka-client/dc", String.class);
}
public String fallback() {
return "fallback";
}
}
}下面我们来验证一下上面Hystrix带来的一些基础功能。我们先把涉及的服务都启动起来,然后访问localhost:2101/consumer,此时可以获取正常的返回,比如:Services: [eureka-consumer-ribbon-hystrix, eureka-client]。

为了触发服务降级逻辑,我们可以将服务提供者eureka-client的逻辑加一些延迟,比如:@GetMapping("/dc")
public String dc() throws InterruptedException {
Thread.sleep(5000L);
String services = "Services: " + discoveryClient.getServices();
System.out.println(services);
return services;
}重启eureka-client之后,再尝试访问localhost:2101/consumer,此时我们将获得的返回结果为:fallback。我们从eureka-client的控制台中,可以看到服务提供方输出了原本要返回的结果,但是由于返回前延迟了5秒,而服务消费方触发了服务请求超时异常,服务消费者就通过HystrixCommand注解中指定的降级逻辑进行执行,因此该请求的结果返回了fallback。这样的机制,对自身服务起到了基础的保护,同时还为异常情况提供了自动的服务降级切换机制。

更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。
 
代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

码云:点击查看
GitHub:点击查看

具体工程说明如下:

eureka的服务注册中心:eureka-server
eureka的服务提供方:eureka-client
eureka的服务消费者:eureka-consumer-ribbon-hystrix
 

本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:服务容错保护(Hystrix服务降级)【Dalston版】
版权归作者所有,转载请注明出处 查看全部
前言

在微服务架构中,我们将系统拆分成了一个个的服务单元,各单元应用间通过服务注册与订阅的方式互相依赖。由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会出现因等待出现故障的依赖方响应而形成任务积压,线程资源无法释放,最终导致自身服务的瘫痪,进一步甚至出现故障的蔓延最终导致整个系统的瘫痪。如果这样的架构存在如此严重的隐患,那么相较传统架构就更加的不稳定。为了解决这样的问题,因此产生了断路器等一系列的服务保护机制。

针对上述问题,在Spring Cloud Hystrix中实现了线程隔离、断路器等一系列的服务保护功能。它也是基于Netflix的开源框架 Hystrix实现的,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备了服务降级、服务熔断、线程隔离、请求缓存、请求合并以及服务监控等强大功能。

接下来,我们就从一个简单示例开始对Spring Cloud Hystrix的学习与使用。

动手试一试

在开始使用Spring Cloud Hystrix实现断路器之前,我们先拿之前实现的一些内容作为基础,其中包括:

eureka-server工程:服务注册中心,端口:1001
eureka-client工程:服务提供者,两个实例启动端口分别为2001

下面我们可以复制一下之前实现的一个服务消费者:eureka-consumer-ribbon,命名为eureka-consumer-ribbon-hystrix。下面我们开始对其进行改在:

第一步:pom.xml的dependencies节点中引入spring-cloud-starter-hystrix依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
第二步:在应用主类中使用@EnableCircuitBreaker或@EnableHystrix注解开启Hystrix的使用:
@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
注意:这里我们还可以使用Spring Cloud应用中的@SpringCloudApplication注解来修饰应用主类,该注解的具体定义如下所示。我们可以看到该注解中包含了上我们所引用的三个注解,这也意味着一个Spring Cloud标准应用应包含服务发现以及断路器。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}
第三步:改造服务消费方式,新增ConsumerService类,然后将在Controller中的逻辑迁移过去。最后,在为具体执行逻辑的函数上增加@HystrixCommand注解来指定服务降级方法,比如:
@RestController
public class DcController {
@Autowired
ConsumerService consumerService;
@GetMapping("/consumer")
public String dc() {
return consumerService.consumer();
}
class ConsumerService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "fallback")
public String consumer() {
return restTemplate.getForObject("http://eureka-client/dc", String.class);
}
public String fallback() {
return "fallback";
}
}
}
下面我们来验证一下上面Hystrix带来的一些基础功能。我们先把涉及的服务都启动起来,然后访问localhost:2101/consumer,此时可以获取正常的返回,比如:Services: [eureka-consumer-ribbon-hystrix, eureka-client]。

为了触发服务降级逻辑,我们可以将服务提供者eureka-client的逻辑加一些延迟,比如:
@GetMapping("/dc")
public String dc() throws InterruptedException {
Thread.sleep(5000L);
String services = "Services: " + discoveryClient.getServices();
System.out.println(services);
return services;
}
重启eureka-client之后,再尝试访问localhost:2101/consumer,此时我们将获得的返回结果为:fallback。我们从eureka-client的控制台中,可以看到服务提供方输出了原本要返回的结果,但是由于返回前延迟了5秒,而服务消费方触发了服务请求超时异常,服务消费者就通过HystrixCommand注解中指定的降级逻辑进行执行,因此该请求的结果返回了fallback。这样的机制,对自身服务起到了基础的保护,同时还为异常情况提供了自动的服务降级切换机制。

更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。
 
代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

码云:点击查看
GitHub:点击查看

具体工程说明如下:

eureka的服务注册中心:eureka-server
eureka的服务提供方:eureka-client
eureka的服务消费者:eureka-consumer-ribbon-hystrix
 


本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:服务容错保护(Hystrix服务降级)【Dalston版】
版权归作者所有,转载请注明出处


探讨通过Feign配合Hystrix进行调用时异常的处理

Spring Cloudvincent 发表了文章 • 0 个评论 • 217 次浏览 • 2017-06-29 22:35 • 来自相关话题

# 前言* 此文所述处理方式为本人在实践过程中研究分析得出的一种解决方案。
* 本文不仅希望能为 SC 学习者提供一种如题问题的一种解决方案,并且希望通过本文引出各位 SC 的朋友对如题问题的共同探讨和最佳实践方案的分享。

# 场景及痛点
* 单个项目是通过 Jersey 来实现 restful 风格的架构
* 发生异常时异常信息总是提示没有回调方法,不能显示基础服务抛出的异常信息
* 暂时没有考虑发生异常之后进行回调返回特定内容
* 业务系统通过 feign 调用基础服务,基础服务是会根据请求抛出各种请求异常的(采用标准http状态码),现在我的想法是如果调用基础服务时发生请求异常,业务系统返回的能够返回基础服务抛出的状态码
* 当然基础服务抛出的请求异常不能触发 hystrix 的熔断机制

# 问题解决方案分析
## 解决思路
* 通过网上一些资料的查询,看到很多文章会说 `HystrixBadRequestException` 不会触发 hystrix 的熔断 --> 但是并没有介绍该异常的实践方案
* 感觉要解决项目的痛点,切入点应该就在 `HystrixBadRequestException` 了。于是先看源码,一方面对 Hystrix 加深理解,尝试理解作者设计的初衷与想法,另一方面看看是否能找到其他方案达到较高的实践标准

## 对应源码解释对应方案
### 主要类对象简介
* `interface UserRemoteCall` 定义feign的接口其上会有 `@FeignClient`,FeignClient 定义了自己的 Configuration --> `FeignConfiguration`
*  `class FeignConfiguration` 这里是定义了指定 Feign 接口使用的自定义配置,如果不想该配置成为全局配置,不要让该类被自动扫描到
* `class UserErrorDecoder implements ErrorDecoder` 该类会处理响应状态码 (![200,300) || !404)

### 不使用Hystrix
#### 源码分析
* Feign 的默认配置在 `org.springframework.cloud.netflix.feign.FeignClientsConfiguration` 类中,如果不自定义
 Feign.Builder,会优先配置 `feign.hystrix.HystrixFeign.Builder extends Feign.Builder`,该类会让 Feign 的内部调用受到 Hystrix 的控制

``` 
//省略部分代码
    @Configuration
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true)
        public Feign.Builder feignHystrixBuilder() {
        return HystrixFeign.builder();
        }
    }
//省略部分代码
```
#### 解决方案
* 当然不使用 Hystrix 就不会有熔断等问题出现,处理好 `ErrorDecoder.decode()` 即可。
* 不开启 Hystrix 的方式:
1. 配置增加 `feign.hystrix.enabled=false` ,这会在全局生效不推荐。
2. `FeignConfiguration` 增加:(推荐)
```
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    } 
```

### 使用 Hystrix 解决内部调用抛出异常问题
#### 源码分析
* Hystrix 的设计方案是通过命令模式加 RxJava 实现的观察者模式来开发的,想完全熟悉 Hystrix 的运作流程需要熟练掌握 RxJava,本文只对源码进行简单介绍,后面有时间有机会再详细介绍
* Hystrix如何处理异常的:
代码位置:`com.netflix.hystrix.AbstractCommand#executeCommandAndObserve`
```
 //省略部分代码
 private Observable<R> executeCommandAndObserve(final AbstractCommand<R> _cmd) {
//省略部分代码
        final Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {
            @Override
            public Observable<R> call(Throwable t) {
                Exception e = getExceptionFromThrowable(t);
                executionResult = executionResult.setExecutionException(e);
                if (e instanceof RejectedExecutionException) {
                    return handleThreadPoolRejectionViaFallback(e);
                } else if (t instanceof HystrixTimeoutException) {
                    return handleTimeoutViaFallback();
                } else if (t instanceof HystrixBadRequestException) {
                    return handleBadRequestByEmittingError(e);
                } else {
                    /*
                     * Treat HystrixBadRequestException from ExecutionHook like a plain HystrixBadRequestException.
                     */
                    if (e instanceof HystrixBadRequestException) {
                        eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);
                        return Observable.error(e);
                    }

                    return handleFailureViaFallback(e);
                }
            }
        };
//省略部分代码
    }
```
该类中该方法为发生异常的回调方法,由此可以看出如果抛出异常如果是 `HystrixBadRequestException` 是直接处理异常之后进行抛出(这里不会触发熔断机制),而不是进入回调方法。

#### 解决方案
* 那么我们对于请求异常的解决方案就需要通过 `HystrixBadRequestException` 来解决了(不会触发熔断机制),根据返回响应创建对应异常并将异常封装进 `HystrixBadRequestException`,业务系统调用中取出 `HystrixBadRequestException` 中的自定义异常进行处理,

封装异常说明:
```
public class UserErrorDecoder implements ErrorDecoder{

    private Logger logger = LoggerFactory.getLogger(getClass());

    public Exception decode(String methodKey, Response response) {
        ObjectMapper om = new JiaJianJacksonObjectMapper();
        JiaJianResponse resEntity;
        Exception exception = null;
        try {
            resEntity = om.readValue(Util.toString(response.body().asReader()), JiaJianResponse.class);
//为了说明我使用的 WebApplicationException 基类,去掉了封装
            exception = new WebApplicationException(javax.ws.rs.core.Response.status(response.status()).entity(resEntity).type(MediaType.APPLICATION_JSON).build());
        } catch (IOException ex) {
            logger.error(ex.getMessage(), ex);
        }
        // 这里只封装4开头的请求异常
        if (400 <= response.status() || response.status() < 500){
            exception = new HystrixBadRequestException("request exception wrapper", exception);
        }else{
            logger.error(exception.getMessage(), exception);
        }
        return exception;
    }
}
```
为 Feign 配置 ErrorDecoder
```
@Configuration
public class FeignConfiguration {
    @Bean
    public ErrorDecoder errorDecoder(){
        return new UserErrorDecoder();
    }
}
```

业务系统处理异常说明:
```
    @Override
    public UserSigninResEntity signIn(UserSigninReqEntity param) throws Exception {
    try {
//省略部分代码
        UserSigninResEntity entity = userRemoteCall.signin(secretConfiguration.getKeys().get("user-service"), param);
//省略部分代码
    } catch (Exception ex) {
        logger.error(ex.getMessage(), ex);
//这里进行异常处理
        if(ex.getCause() instanceof WebApplicationException){
            throw (WebApplicationException) ex.getCause();
        }
            throw ex;
        }
    }
```
* `WebApplicationException` 是 `javax.ws.rs` 包中异常,通过 Jersey 抛出该异常能够将返回的 HttpCode 封装进该异常中(上述代码中展示了如何封装 HttpCode),抛出该异常,调用端就能得到返回的 HttpCode。

# 总结
* 本文主要出发点在于如何解决在 Feign 中使用 Hystrix 时被调用端抛出请求异常的问题。
* 本项目使用 Jersey,封装 `WebApplicationException` 即可满足需求,其他架构也是大同小异了。
* 该解决方案我不确定是否为最佳实践方案,特别希望和欢迎有不同想法或意见的朋友来与我交流,包括但不限于解决方案、项目痛点是否合理等等。
 
原文链接:http://www.jianshu.com/p/f240ca7bb7c0 查看全部
# 前言* 此文所述处理方式为本人在实践过程中研究分析得出的一种解决方案。
* 本文不仅希望能为 SC 学习者提供一种如题问题的一种解决方案,并且希望通过本文引出各位 SC 的朋友对如题问题的共同探讨和最佳实践方案的分享。

# 场景及痛点
* 单个项目是通过 Jersey 来实现 restful 风格的架构
* 发生异常时异常信息总是提示没有回调方法,不能显示基础服务抛出的异常信息
* 暂时没有考虑发生异常之后进行回调返回特定内容
* 业务系统通过 feign 调用基础服务,基础服务是会根据请求抛出各种请求异常的(采用标准http状态码),现在我的想法是如果调用基础服务时发生请求异常,业务系统返回的能够返回基础服务抛出的状态码
* 当然基础服务抛出的请求异常不能触发 hystrix 的熔断机制

# 问题解决方案分析
## 解决思路
* 通过网上一些资料的查询,看到很多文章会说 `HystrixBadRequestException` 不会触发 hystrix 的熔断 --> 但是并没有介绍该异常的实践方案
* 感觉要解决项目的痛点,切入点应该就在 `HystrixBadRequestException` 了。于是先看源码,一方面对 Hystrix 加深理解,尝试理解作者设计的初衷与想法,另一方面看看是否能找到其他方案达到较高的实践标准

## 对应源码解释对应方案
### 主要类对象简介
* `interface UserRemoteCall` 定义feign的接口其上会有 `@FeignClient`,FeignClient 定义了自己的 Configuration --> `FeignConfiguration`
*  `class FeignConfiguration` 这里是定义了指定 Feign 接口使用的自定义配置,如果不想该配置成为全局配置,不要让该类被自动扫描到
* `class UserErrorDecoder implements ErrorDecoder` 该类会处理响应状态码 (![200,300) || !404)

### 不使用Hystrix
#### 源码分析
* Feign 的默认配置在 `org.springframework.cloud.netflix.feign.FeignClientsConfiguration` 类中,如果不自定义
 Feign.Builder,会优先配置 `feign.hystrix.HystrixFeign.Builder extends Feign.Builder`,该类会让 Feign 的内部调用受到 Hystrix 的控制

``` 
//省略部分代码
    @Configuration
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true)
        public Feign.Builder feignHystrixBuilder() {
        return HystrixFeign.builder();
        }
    }
//省略部分代码
```
#### 解决方案
* 当然不使用 Hystrix 就不会有熔断等问题出现,处理好 `ErrorDecoder.decode()` 即可。
* 不开启 Hystrix 的方式:
1. 配置增加 `feign.hystrix.enabled=false` ,这会在全局生效不推荐。
2. `FeignConfiguration` 增加:(推荐)
```
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    } 
```

### 使用 Hystrix 解决内部调用抛出异常问题
#### 源码分析
* Hystrix 的设计方案是通过命令模式加 RxJava 实现的观察者模式来开发的,想完全熟悉 Hystrix 的运作流程需要熟练掌握 RxJava,本文只对源码进行简单介绍,后面有时间有机会再详细介绍
* Hystrix如何处理异常的:
代码位置:`com.netflix.hystrix.AbstractCommand#executeCommandAndObserve`
```
 //省略部分代码
 private Observable<R> executeCommandAndObserve(final AbstractCommand<R> _cmd) {
//省略部分代码
        final Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {
            @Override
            public Observable<R> call(Throwable t) {
                Exception e = getExceptionFromThrowable(t);
                executionResult = executionResult.setExecutionException(e);
                if (e instanceof RejectedExecutionException) {
                    return handleThreadPoolRejectionViaFallback(e);
                } else if (t instanceof HystrixTimeoutException) {
                    return handleTimeoutViaFallback();
                } else if (t instanceof HystrixBadRequestException) {
                    return handleBadRequestByEmittingError(e);
                } else {
                    /*
                     * Treat HystrixBadRequestException from ExecutionHook like a plain HystrixBadRequestException.
                     */
                    if (e instanceof HystrixBadRequestException) {
                        eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);
                        return Observable.error(e);
                    }

                    return handleFailureViaFallback(e);
                }
            }
        };
//省略部分代码
    }
```
该类中该方法为发生异常的回调方法,由此可以看出如果抛出异常如果是 `HystrixBadRequestException` 是直接处理异常之后进行抛出(这里不会触发熔断机制),而不是进入回调方法。

#### 解决方案
* 那么我们对于请求异常的解决方案就需要通过 `HystrixBadRequestException` 来解决了(不会触发熔断机制),根据返回响应创建对应异常并将异常封装进 `HystrixBadRequestException`,业务系统调用中取出 `HystrixBadRequestException` 中的自定义异常进行处理,

封装异常说明:
```
public class UserErrorDecoder implements ErrorDecoder{

    private Logger logger = LoggerFactory.getLogger(getClass());

    public Exception decode(String methodKey, Response response) {
        ObjectMapper om = new JiaJianJacksonObjectMapper();
        JiaJianResponse resEntity;
        Exception exception = null;
        try {
            resEntity = om.readValue(Util.toString(response.body().asReader()), JiaJianResponse.class);
//为了说明我使用的 WebApplicationException 基类,去掉了封装
            exception = new WebApplicationException(javax.ws.rs.core.Response.status(response.status()).entity(resEntity).type(MediaType.APPLICATION_JSON).build());
        } catch (IOException ex) {
            logger.error(ex.getMessage(), ex);
        }
        // 这里只封装4开头的请求异常
        if (400 <= response.status() || response.status() < 500){
            exception = new HystrixBadRequestException("request exception wrapper", exception);
        }else{
            logger.error(exception.getMessage(), exception);
        }
        return exception;
    }
}
```
为 Feign 配置 ErrorDecoder
```
@Configuration
public class FeignConfiguration {
    @Bean
    public ErrorDecoder errorDecoder(){
        return new UserErrorDecoder();
    }
}
```

业务系统处理异常说明:
```
    @Override
    public UserSigninResEntity signIn(UserSigninReqEntity param) throws Exception {
    try {
//省略部分代码
        UserSigninResEntity entity = userRemoteCall.signin(secretConfiguration.getKeys().get("user-service"), param);
//省略部分代码
    } catch (Exception ex) {
        logger.error(ex.getMessage(), ex);
//这里进行异常处理
        if(ex.getCause() instanceof WebApplicationException){
            throw (WebApplicationException) ex.getCause();
        }
            throw ex;
        }
    }
```
* `WebApplicationException` 是 `javax.ws.rs` 包中异常,通过 Jersey 抛出该异常能够将返回的 HttpCode 封装进该异常中(上述代码中展示了如何封装 HttpCode),抛出该异常,调用端就能得到返回的 HttpCode。

# 总结
* 本文主要出发点在于如何解决在 Feign 中使用 Hystrix 时被调用端抛出请求异常的问题。
* 本项目使用 Jersey,封装 `WebApplicationException` 即可满足需求,其他架构也是大同小异了。
* 该解决方案我不确定是否为最佳实践方案,特别希望和欢迎有不同想法或意见的朋友来与我交流,包括但不限于解决方案、项目痛点是否合理等等。
 
原文链接:http://www.jianshu.com/p/f240ca7bb7c0

hystrix超时问题

Spring Clouditmuch.com 回复了问题 • 3 人关注 • 2 个回复 • 427 次浏览 • 2017-06-27 23:00 • 来自相关话题

暴露出/hystrix.stream端点安全问题

Spring Cloudwarmsheep 回复了问题 • 3 人关注 • 3 个回复 • 530 次浏览 • 2017-06-23 10:48 • 来自相关话题

Hystrix请求合并遇到的问题

Spring Cloudlyb269 回复了问题 • 2 人关注 • 2 个回复 • 443 次浏览 • 2017-06-13 22:22 • 来自相关话题

hystrix超时问题

回复

Spring Clouditmuch.com 回复了问题 • 3 人关注 • 2 个回复 • 427 次浏览 • 2017-06-27 23:00 • 来自相关话题

暴露出/hystrix.stream端点安全问题

回复

Spring Cloudwarmsheep 回复了问题 • 3 人关注 • 3 个回复 • 530 次浏览 • 2017-06-23 10:48 • 来自相关话题

Hystrix请求合并遇到的问题

回复

Spring Cloudlyb269 回复了问题 • 2 人关注 • 2 个回复 • 443 次浏览 • 2017-06-13 22:22 • 来自相关话题

Spring Cloud构建微服务架构:Hystrix监控数据聚合【Dalston版】

Spring Cloud程序猿DD 发表了文章 • 0 个评论 • 320 次浏览 • 2017-07-24 22:57 • 来自相关话题

上一篇我们介绍了使用Hystrix Dashboard来展示Hystrix用于熔断的各项度量指标。通过Hystrix Dashboard,我们可以方便的查看服务实例的综合情况,比如:服务调用次数、服务调用延迟等。但是仅通过Hystrix Dashboard我们只能实现对服务当个实例的数据展现,在生产环境我们的服务是肯定需要做高可用的,那么对于多实例的情况,我们就需要将这些度量指标数据进行聚合。下面,在本篇中,我们就来介绍一下另外一个工具:Turbine。
 
准备工作

在开始使用Turbine之前,我们先回顾一下上一篇中实现的架构,如下图所示:





 
其中,我们构建的内容包括:
 
eureka-server:服务注册中心eureka-client:服务提供者eureka-consumer-ribbon-hystrix:使用ribbon和hystrix实现的服务消费者hystrix-dashboard:用于展示eureka-consumer-ribbon-hystrix服务的Hystrix数据
 
动手试一试

下面,我们将在上述架构基础上,引入Turbine来对服务的Hystrix数据进行聚合展示。这里我们将分别介绍两种聚合方式。

通过HTTP收集聚合

具体实现步骤如下:
创建一个标准的Spring Boot工程,命名为:turbine。编辑pom.xml,具体依赖内容如下:
 
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
创建应用主类TurbineApplication,并使用@EnableTurbine注解开启Turbine。
@Configuration
@EnableAutoConfiguration
@EnableTurbine
@EnableDiscoveryClient
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}
在application.properties加入eureka和turbine的相关配置,具体如下:
spring.application.name=turbine
server.port=8989
management.port=8990
eureka.client.serviceUrl.defaultZone=http://localhost:1001/eureka/
turbine.app-config=eureka-consumer-ribbon-hystrix
turbine.cluster-name-expression="default"
turbine.combine-host-port=true参数说明

turbine.app-config参数指定了需要收集监控信息的服务名;
turbine.cluster-name-expression 参数指定了集群名称为default,当我们服务数量非常多的时候,可以启动多个Turbine服务来构建不同的聚合集群,而该参数可以用来区分这些不同的聚合集群,同时该参数值可以在Hystrix仪表盘中用来定位不同的聚合集群,只需要在Hystrix Stream的URL中通过cluster参数来指定;
turbine.combine-host-port参数设置为true,可以让同一主机上的服务通过主机名与端口号的组合来进行区分,默认情况下会以host来区分不同的服务,这会使得在本地调试的时候,本机上的不同服务聚合成一个服务来统计。

在完成了上面的内容构建之后,我们来体验一下Turbine对集群的监控能力。分别启动eureka-server、eureka-client、eureka-consumer-ribbon-hystrix、turbine以及hystrix-dashboard。访问Hystrix Dashboard,并开启对http://localhost:8989/turbine.stream`的监控,这时候,我们将看到针对服务`eureka-consumer-ribbon-hystrix`的聚合监控数据。

而此时的架构如下图所示:





 
通过消息代理收集聚合
 
Spring Cloud在封装Turbine的时候,还实现了基于消息代理的收集实现。所以,我们可以将所有需要收集的监控信息都输出到消息代理中,然后Turbine服务再从消息代理中异步的获取这些监控信息,最后将这些监控信息聚合并输出到Hystrix Dashboard中。通过引入消息代理,我们的Turbine和Hystrix Dashoard实现的监控架构可以改成如下图所示的结构:





 
从图中我们可以看到,这里多了一个重要元素:RabbitMQ。对于RabbitMQ的安装与基本时候我们可以查看之前的《Spring Boot中使用RabbitMQ》一文,这里不做过多的说明。下面,我们可以来构建一个新的应用来实现基于消息代理的Turbine聚合服务,具体步骤如下:
创建一个标准的Spring Boot工程,命名为:turbine-amqp。编辑pom.xml,具体依赖内容如下:
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>可以看到这里主要引入了spring-cloud-starter-turbine-amqp依赖,它实际上就是包装了spring-cloud-starter-turbine-stream和pring-cloud-starter-stream-rabbit。
注意:这里我们需要使用Java 8来运行
 
在应用主类中使用@EnableTurbineStream注解来启用Turbine Stream的配置。
@Configuration
@EnableAutoConfiguration
@EnableTurbineStream
@EnableDiscoveryClient
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}
配置application.properties文件:
spring.application.name=turbine-amqp
server.port=8989
management.port=8990
eureka.client.serviceUrl.defaultZone=http://localhost:1001/eureka/对于Turbine的配置已经完成了,下面我们需要对服务消费者eureka-consumer-ribbon-hystrix做一些修改,使其监控信息能够输出到RabbitMQ上。这个修改也非常简单,只需要在pom.xml中增加对spring-cloud-netflix-hystrix-amqp依赖,具体如下:
<dependencies>
...
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-amqp</artifactId>
</dependency>
</dependencies>在完成了上面的配置之后,我们可以继续之前的所有项目(除turbine以外),并通过Hystrix Dashboard开启对http://localhost:8989/turbine.stream的监控,我们可以获得如之前实现的同样效果,只是这里我们的监控信息收集时是通过了消息代理异步实现的。

更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。





 
代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

码云:点击查看
GitHub:点击查看

本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:Hystrix监控数据聚合【Dalston版】
版权归作者所有,转载请注明出处

 
 
  查看全部
上一篇我们介绍了使用Hystrix Dashboard来展示Hystrix用于熔断的各项度量指标。通过Hystrix Dashboard,我们可以方便的查看服务实例的综合情况,比如:服务调用次数、服务调用延迟等。但是仅通过Hystrix Dashboard我们只能实现对服务当个实例的数据展现,在生产环境我们的服务是肯定需要做高可用的,那么对于多实例的情况,我们就需要将这些度量指标数据进行聚合。下面,在本篇中,我们就来介绍一下另外一个工具:Turbine。
 
准备工作

在开始使用Turbine之前,我们先回顾一下上一篇中实现的架构,如下图所示:

spring-cloud-starter-dalston-5-2-1.png

 
其中,我们构建的内容包括:
 
  • eureka-server:服务注册中心
  • eureka-client:服务提供者
  • eureka-consumer-ribbon-hystrix:使用ribbon和hystrix实现的服务消费者
  • hystrix-dashboard:用于展示eureka-consumer-ribbon-hystrix服务的Hystrix数据

 
动手试一试

下面,我们将在上述架构基础上,引入Turbine来对服务的Hystrix数据进行聚合展示。这里我们将分别介绍两种聚合方式。

通过HTTP收集聚合

具体实现步骤如下:
  • 创建一个标准的Spring Boot工程,命名为:turbine。
  • 编辑pom.xml,具体依赖内容如下:

 
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>

  • 创建应用主类TurbineApplication,并使用@EnableTurbine注解开启Turbine。

@Configuration
@EnableAutoConfiguration
@EnableTurbine
@EnableDiscoveryClient
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}

  • 在application.properties加入eureka和turbine的相关配置,具体如下:

spring.application.name=turbine
server.port=8989
management.port=8990
eureka.client.serviceUrl.defaultZone=http://localhost:1001/eureka/
turbine.app-config=eureka-consumer-ribbon-hystrix
turbine.cluster-name-expression="default"
turbine.combine-host-port=true
参数说明

turbine.app-config参数指定了需要收集监控信息的服务名;
turbine.cluster-name-expression 参数指定了集群名称为default,当我们服务数量非常多的时候,可以启动多个Turbine服务来构建不同的聚合集群,而该参数可以用来区分这些不同的聚合集群,同时该参数值可以在Hystrix仪表盘中用来定位不同的聚合集群,只需要在Hystrix Stream的URL中通过cluster参数来指定;
turbine.combine-host-port参数设置为true,可以让同一主机上的服务通过主机名与端口号的组合来进行区分,默认情况下会以host来区分不同的服务,这会使得在本地调试的时候,本机上的不同服务聚合成一个服务来统计。

在完成了上面的内容构建之后,我们来体验一下Turbine对集群的监控能力。分别启动eureka-server、eureka-client、eureka-consumer-ribbon-hystrix、turbine以及hystrix-dashboard。访问Hystrix Dashboard,并开启对http://localhost:8989/turbine.stream`的监控,这时候,我们将看到针对服务`eureka-consumer-ribbon-hystrix`的聚合监控数据。

而此时的架构如下图所示:

spring-cloud-starter-dalston-5-2-2.png

 
通过消息代理收集聚合
 
Spring Cloud在封装Turbine的时候,还实现了基于消息代理的收集实现。所以,我们可以将所有需要收集的监控信息都输出到消息代理中,然后Turbine服务再从消息代理中异步的获取这些监控信息,最后将这些监控信息聚合并输出到Hystrix Dashboard中。通过引入消息代理,我们的Turbine和Hystrix Dashoard实现的监控架构可以改成如下图所示的结构:

spring-cloud-starter-dalston-5-2-3.png

 
从图中我们可以看到,这里多了一个重要元素:RabbitMQ。对于RabbitMQ的安装与基本时候我们可以查看之前的《Spring Boot中使用RabbitMQ》一文,这里不做过多的说明。下面,我们可以来构建一个新的应用来实现基于消息代理的Turbine聚合服务,具体步骤如下:
  • 创建一个标准的Spring Boot工程,命名为:turbine-amqp。
  • 编辑pom.xml,具体依赖内容如下:

<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
可以看到这里主要引入了spring-cloud-starter-turbine-amqp依赖,它实际上就是包装了spring-cloud-starter-turbine-stream和pring-cloud-starter-stream-rabbit。
注意:这里我们需要使用Java 8来运行
 
  • 在应用主类中使用@EnableTurbineStream注解来启用Turbine Stream的配置。

@Configuration
@EnableAutoConfiguration
@EnableTurbineStream
@EnableDiscoveryClient
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}

  • 配置application.properties文件:

spring.application.name=turbine-amqp
server.port=8989
management.port=8990
eureka.client.serviceUrl.defaultZone=http://localhost:1001/eureka/
对于Turbine的配置已经完成了,下面我们需要对服务消费者eureka-consumer-ribbon-hystrix做一些修改,使其监控信息能够输出到RabbitMQ上。这个修改也非常简单,只需要在pom.xml中增加对spring-cloud-netflix-hystrix-amqp依赖,具体如下:
<dependencies>
...
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-amqp</artifactId>
</dependency>
</dependencies>
在完成了上面的配置之后,我们可以继续之前的所有项目(除turbine以外),并通过Hystrix Dashboard开启对http://localhost:8989/turbine.stream的监控,我们可以获得如之前实现的同样效果,只是这里我们的监控信息收集时是通过了消息代理异步实现的。

更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。

翟永超-京东-推广链接.png

 
代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

码云:点击查看
GitHub:点击查看


本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:Hystrix监控数据聚合【Dalston版】
版权归作者所有,转载请注明出处


 
 
 

Spring Cloud构建微服务架构:Hystrix监控面板【Dalston版】

Spring Cloud程序猿DD 发表了文章 • 0 个评论 • 288 次浏览 • 2017-07-23 11:54 • 来自相关话题

在上一篇[《服务容错保护(hystrix断路器)》](http://blog.didispace.com/spri ... on-4-3)的介绍中,我们提到断路器是根据一段时间窗内的请求情况来判断并操作断路器的打开和关闭状态的。而这些请求情况的指标信息都是HystrixCommand和HystrixObservableCommand实例在执行过程中记录的重要度量信息,它们除了Hystrix断路器实现中使用之外,对于系统运维也有非常大的帮助。这些指标信息会以“滚动时间窗”与“桶”结合的方式进行汇总,并在内存中驻留一段时间,以供内部或外部进行查询使用,Hystrix Dashboard就是这些指标内容的消费者之一。

下面我们基于之前的示例来结合Hystrix Dashboard实现Hystrix指标数据的可视化面板,这里我们将用到下之前实现的几个应用,包括:

- eureka-server:服务注册中心
- eureka-client:服务提供者
- eureka-consumer-ribbon-hystrix:使用ribbon和hystrix实现的服务消费者

由于eureka-consumer-ribbon-hystrix项目中的`/consumer`接口实现使用了`@HystrixCommand`修饰,所以这个接口的调用情况会被Hystrix记录下来,以用来给断路器和Hystrix Dashboard使用。断路器我们在上一篇中已经介绍过了,下面我们来具体说说Hystrix Dashboard的构建。

动手试一试

在Spring Cloud中构建一个Hystrix Dashboard非常简单,只需要下面四步:

- 创建一个标准的Spring Boot工程,命名为:hystrix-dashboard。
- 编辑pom.xml,具体依赖内容如下:<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</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>
</dependencies>
- 为应用主类加上`@EnableHystrixDashboard`,启用Hystrix Dashboard功能。@EnableHystrixDashboard
@SpringCloudApplication
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}
- 根据实际情况修改`application.properties`配置文件,比如:选择一个未被占用的端口等,此步非必须。spring.application.name=hystrix-dashboard
server.port=1301
到这里我们已经完成了基本配置,接下来我们可以启动该应用,并访问:`http://localhost:1301/hystrix`,我们可以看到如下页面:






这是Hystrix Dashboard的监控首页,该页面中并没有具体的监控信息。从页面的文字内容中我们可以知道,Hystrix Dashboard共支持三种不同的监控方式,依次为:

- 默认的集群监控:通过URL`http://turbine-hostname:port/turbine.stream`开启,实现对默认集群的监控。
- 指定的集群监控:通过URL`http://turbine-hostname:port/t ... er%3D[clusterName]`开启,实现对clusterName集群的监控。
- 单体应用的监控:通过URL`http://hystrix-app:port/hystrix.stream`开启,实现对具体某个服务实例的监控。

前两者都对集群的监控,需要整合Turbine才能实现,这部分内容我们将在下一篇中做详细介绍。在本节中,我们主要实现对单个服务实例的监控,所以这里我们先来实现单个服务实例的监控。

既然Hystrix Dashboard监控单实例节点需要通过访问实例的`/hystrix.stream`接口来实现,自然我们需要为服务实例添加这个端点,而添加该功能的步骤也同样简单,只需要下面两步:

- 在服务实例`pom.xml`中的`dependencies`节点中新增`spring-boot-starter-actuator`监控模块以开启监控相关的端点,并确保已经引入断路器的依赖`spring-cloud-starter-hystrix`:<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`或`@EnableHystrix`注解,开启了断路器功能。

到这里已经完成了所有的配置,我们可以在Hystrix Dashboard的首页输入`http://localhost:2101/hystrix.stream`,已启动对“eureka-consumer-ribbon-hystrix”的监控,点击“Monitor Stream”按钮,此时我们可以看到如下页面:






在对该页面介绍前,我们先看看在首页中我们还没有介绍的两外两个参数:

- `Delay`:该参数用来控制服务器上轮询监控信息的延迟时间,默认为2000毫秒,我们可以通过配置该属性来降低客户端的网络和CPU消耗。
- `Title`:该参数对应了上图头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的URL,我们可以通过配置该信息来展示更合适的标题。

回到监控页面,我们来详细说说其中各元素的具体含义:

- 我们可以在监控信息的左上部分找到两个重要的图形信息:一个实心圆和一条曲线。
  - 实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,如下图所示,它的健康度从绿色、黄色、橙色、红色递减。该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,我们就可以在大量的实例中快速的发现故障实例和高压力实例。






  - 曲线:用来记录2分钟内流量的相对变化,我们可以通过它来观察到流量的上升和下降趋势。
- 其他一些数量指标如下图所示:






更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。

代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

- 码云:点击查看
- GitHub:点击查看
 

本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:Hystrix监控面板【Dalston版】
版权归作者所有,转载请注明出处 查看全部
在上一篇[《服务容错保护(hystrix断路器)》](http://blog.didispace.com/spri ... on-4-3)的介绍中,我们提到断路器是根据一段时间窗内的请求情况来判断并操作断路器的打开和关闭状态的。而这些请求情况的指标信息都是HystrixCommand和HystrixObservableCommand实例在执行过程中记录的重要度量信息,它们除了Hystrix断路器实现中使用之外,对于系统运维也有非常大的帮助。这些指标信息会以“滚动时间窗”与“桶”结合的方式进行汇总,并在内存中驻留一段时间,以供内部或外部进行查询使用,Hystrix Dashboard就是这些指标内容的消费者之一。

下面我们基于之前的示例来结合Hystrix Dashboard实现Hystrix指标数据的可视化面板,这里我们将用到下之前实现的几个应用,包括:

- eureka-server:服务注册中心
- eureka-client:服务提供者
- eureka-consumer-ribbon-hystrix:使用ribbon和hystrix实现的服务消费者

由于eureka-consumer-ribbon-hystrix项目中的`/consumer`接口实现使用了`@HystrixCommand`修饰,所以这个接口的调用情况会被Hystrix记录下来,以用来给断路器和Hystrix Dashboard使用。断路器我们在上一篇中已经介绍过了,下面我们来具体说说Hystrix Dashboard的构建。

动手试一试

在Spring Cloud中构建一个Hystrix Dashboard非常简单,只需要下面四步:

- 创建一个标准的Spring Boot工程,命名为:hystrix-dashboard。
- 编辑pom.xml,具体依赖内容如下:
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</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>
</dependencies>

- 为应用主类加上`@EnableHystrixDashboard`,启用Hystrix Dashboard功能。
@EnableHystrixDashboard
@SpringCloudApplication
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}

- 根据实际情况修改`application.properties`配置文件,比如:选择一个未被占用的端口等,此步非必须。
spring.application.name=hystrix-dashboard
server.port=1301

到这里我们已经完成了基本配置,接下来我们可以启动该应用,并访问:`http://localhost:1301/hystrix`,我们可以看到如下页面:

spring-cloud-starter-dalston-5-1-1.png


这是Hystrix Dashboard的监控首页,该页面中并没有具体的监控信息。从页面的文字内容中我们可以知道,Hystrix Dashboard共支持三种不同的监控方式,依次为:

- 默认的集群监控:通过URL`http://turbine-hostname:port/turbine.stream`开启,实现对默认集群的监控。
- 指定的集群监控:通过URL`http://turbine-hostname:port/t ... er%3D[clusterName]`开启,实现对clusterName集群的监控。
- 单体应用的监控:通过URL`http://hystrix-app:port/hystrix.stream`开启,实现对具体某个服务实例的监控。

前两者都对集群的监控,需要整合Turbine才能实现,这部分内容我们将在下一篇中做详细介绍。在本节中,我们主要实现对单个服务实例的监控,所以这里我们先来实现单个服务实例的监控。

既然Hystrix Dashboard监控单实例节点需要通过访问实例的`/hystrix.stream`接口来实现,自然我们需要为服务实例添加这个端点,而添加该功能的步骤也同样简单,只需要下面两步:

- 在服务实例`pom.xml`中的`dependencies`节点中新增`spring-boot-starter-actuator`监控模块以开启监控相关的端点,并确保已经引入断路器的依赖`spring-cloud-starter-hystrix`:
<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`或`@EnableHystrix`注解,开启了断路器功能。

到这里已经完成了所有的配置,我们可以在Hystrix Dashboard的首页输入`http://localhost:2101/hystrix.stream`,已启动对“eureka-consumer-ribbon-hystrix”的监控,点击“Monitor Stream”按钮,此时我们可以看到如下页面:

spring-cloud-starter-dalston-5-1-2.png


在对该页面介绍前,我们先看看在首页中我们还没有介绍的两外两个参数:

- `Delay`:该参数用来控制服务器上轮询监控信息的延迟时间,默认为2000毫秒,我们可以通过配置该属性来降低客户端的网络和CPU消耗。
- `Title`:该参数对应了上图头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的URL,我们可以通过配置该信息来展示更合适的标题。

回到监控页面,我们来详细说说其中各元素的具体含义:

- 我们可以在监控信息的左上部分找到两个重要的图形信息:一个实心圆和一条曲线。
  - 实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,如下图所示,它的健康度从绿色、黄色、橙色、红色递减。该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,我们就可以在大量的实例中快速的发现故障实例和高压力实例。

spring-cloud-starter-dalston-5-1-3.png


  - 曲线:用来记录2分钟内流量的相对变化,我们可以通过它来观察到流量的上升和下降趋势。
- 其他一些数量指标如下图所示:

spring-cloud-starter-dalston-5-1-4.png


更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。

代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

- 码云:点击查看
- GitHub:点击查看
 


本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:Hystrix监控面板【Dalston版】
版权归作者所有,转载请注明出处


Spring Cloud构建微服务架构:服务容错保护(Hystrix依赖隔离)【Dalston版】

Spring Cloud程序猿DD 发表了文章 • 2 个评论 • 488 次浏览 • 2017-07-12 22:29 • 来自相关话题

前言

在上一篇《Spring Cloud构建微服务架构:服务容错保护(Hystrix服务降级)》中,我们已经体验了如何使用@HystrixCommand来为一个依赖资源定义服务降级逻辑。实现方式非常简单,同时对于降级逻辑还能实现一些更加复杂的级联降级等策略。之前对于使用Hystrix来实现服务容错保护时,除了服务降级之外,我们还提到过线程隔离、断路器等功能。那么在本篇中我们就来具体说说线程隔离。

依赖隔离

“舱壁模式”对于熟悉Docker的读者一定不陌生,Docker通过“舱壁模式”实现进程的隔离,使得容器与容器之间不会互相影响。而Hystrix则使用该模式实现线程池的隔离,它会为每一个Hystrix命令创建一个独立的线程池,这样就算某个在Hystrix命令包装下的依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他的服务。

通过对依赖服务的线程池隔离实现,可以带来如下优势:
应用自身得到完全的保护,不会受不可控的依赖服务影响。即便给依赖服务分配的线程池被填满,也不会影响应用自身的额其余部分。可以有效的降低接入新服务的风险。如果新服务接入后运行不稳定或存在问题,完全不会影响到应用其他的请求。当依赖的服务从失效恢复正常后,它的线程池会被清理并且能够马上恢复健康的服务,相比之下容器级别的清理恢复速度要慢得多。当依赖的服务出现配置错误的时候,线程池会快速的反应出此问题(通过失败次数、延迟、超时、拒绝等指标的增加情况)。同时,我们可以在不影响应用功能的情况下通过实时的动态属性刷新(后续会通过Spring Cloud Config与Spring Cloud Bus的联合使用来介绍)来处理它。当依赖的服务因实现机制调整等原因造成其性能出现很大变化的时候,此时线程池的监控指标信息会反映出这样的变化。同时,我们也可以通过实时动态刷新自身应用对依赖服务的阈值进行调整以适应依赖方的改变。除了上面通过线程池隔离服务发挥的优点之外,每个专有线程池都提供了内置的并发实现,可以利用它为同步的依赖服务构建异步的访问。

总之,通过对依赖服务实现线程池隔离,让我们的应用更加健壮,不会因为个别依赖服务出现问题而引起非相关服务的异常。同时,也使得我们的应用变得更加灵活,可以在不停止服务的情况下,配合动态配置刷新实现性能配置上的调整。

虽然线程池隔离的方案带了如此多的好处,但是很多使用者可能会担心为每一个依赖服务都分配一个线程池是否会过多地增加系统的负载和开销。对于这一点,使用者不用过于担心,因为这些顾虑也是大部分工程师们会考虑到的,Netflix在设计Hystrix的时候,认为线程池上的开销相对于隔离所带来的好处是无法比拟的。同时,Netflix也针对线程池的开销做了相关的测试,以证明和打消Hystrix实现对性能影响的顾虑。

下图是Netflix Hystrix官方提供的一个Hystrix命令的性能监控,该命令以每秒60个请求的速度(QPS)向一个单服务实例进行访问,该服务实例每秒运行的线程数峰值为350个。






从图中的统计我们可以看到,使用线程池隔离与不使用线程池隔离的耗时差异如下表所示:






在99%的情况下,使用线程池隔离的延迟有9ms,对于大多数需求来说这样的消耗是微乎其微的,更何况为系统在稳定性和灵活性上所带来的巨大提升。虽然对于大部分的请求我们可以忽略线程池的额外开销,而对于小部分延迟本身就非常小的请求(可能只需要1ms),那么9ms的延迟开销还是非常昂贵的。实际上Hystrix也为此设计了另外的一个解决方案:信号量。

Hystrix中除了使用线程池之外,还可以使用信号量来控制单个依赖服务的并发度,信号量的开销要远比线程池的开销小得多,但是它不能设置超时和实现异步访问。所以,只有在依赖服务是足够可靠的情况下才使用信号量。在HystrixCommand和HystrixObservableCommand中2处支持信号量的使用:
 
命令执行:如果隔离策略参数execution.isolation.strategy设置为SEMAPHORE,Hystrix会使用信号量替代线程池来控制依赖服务的并发控制。降级逻辑:当Hystrix尝试降级逻辑时候,它会在调用线程中使用信号量。

信号量的默认值为10,我们也可以通过动态刷新配置的方式来控制并发线程的数量。对于信号量大小的估算方法与线程池并发度的估算类似。仅访问内存数据的请求一般耗时在1ms以内,性能可以达到5000rps,这样级别的请求我们可以将信号量设置为1或者2,我们可以按此标准并根据实际请求耗时来设置信号量。

如何使用

说了那么多依赖隔离的好处,那么我们如何使用Hystrix来实现依赖隔离呢?其实,我们在上一篇定义服务降级的时候,已经自动的实现了依赖隔离。

在上一篇的示例中,我们使用了@HystrixCommand来将某个函数包装成了Hystrix命令,这里除了定义服务降级之外,Hystrix框架就会自动的为这个函数实现调用的隔离。所以,依赖隔离、服务降级在使用时候都是一体化实现的,这样利用Hystrix来实现服务容错保护在编程模型上就非常方便的,并且考虑更为全面。除了依赖隔离、服务降级之外,还有一个重要元素:断路器。我们将在下一篇做详细的介绍,这三个重要利器构成了Hystrix实现服务容错保护的强力组合拳。

更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。

代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

码云:点击查看
GitHub:点击查看
 

本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:服务容错保护(Hystrix依赖隔离)【Dalston版】
版权归作者所有,转载请注明出处 查看全部
前言

在上一篇《Spring Cloud构建微服务架构:服务容错保护(Hystrix服务降级)》中,我们已经体验了如何使用@HystrixCommand来为一个依赖资源定义服务降级逻辑。实现方式非常简单,同时对于降级逻辑还能实现一些更加复杂的级联降级等策略。之前对于使用Hystrix来实现服务容错保护时,除了服务降级之外,我们还提到过线程隔离、断路器等功能。那么在本篇中我们就来具体说说线程隔离。

依赖隔离

“舱壁模式”对于熟悉Docker的读者一定不陌生,Docker通过“舱壁模式”实现进程的隔离,使得容器与容器之间不会互相影响。而Hystrix则使用该模式实现线程池的隔离,它会为每一个Hystrix命令创建一个独立的线程池,这样就算某个在Hystrix命令包装下的依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他的服务。

通过对依赖服务的线程池隔离实现,可以带来如下优势:
  • 应用自身得到完全的保护,不会受不可控的依赖服务影响。即便给依赖服务分配的线程池被填满,也不会影响应用自身的额其余部分。
  • 可以有效的降低接入新服务的风险。如果新服务接入后运行不稳定或存在问题,完全不会影响到应用其他的请求。
  • 当依赖的服务从失效恢复正常后,它的线程池会被清理并且能够马上恢复健康的服务,相比之下容器级别的清理恢复速度要慢得多。
  • 当依赖的服务出现配置错误的时候,线程池会快速的反应出此问题(通过失败次数、延迟、超时、拒绝等指标的增加情况)。同时,我们可以在不影响应用功能的情况下通过实时的动态属性刷新(后续会通过Spring Cloud Config与Spring Cloud Bus的联合使用来介绍)来处理它。
  • 当依赖的服务因实现机制调整等原因造成其性能出现很大变化的时候,此时线程池的监控指标信息会反映出这样的变化。同时,我们也可以通过实时动态刷新自身应用对依赖服务的阈值进行调整以适应依赖方的改变。
  • 除了上面通过线程池隔离服务发挥的优点之外,每个专有线程池都提供了内置的并发实现,可以利用它为同步的依赖服务构建异步的访问。


总之,通过对依赖服务实现线程池隔离,让我们的应用更加健壮,不会因为个别依赖服务出现问题而引起非相关服务的异常。同时,也使得我们的应用变得更加灵活,可以在不停止服务的情况下,配合动态配置刷新实现性能配置上的调整。

虽然线程池隔离的方案带了如此多的好处,但是很多使用者可能会担心为每一个依赖服务都分配一个线程池是否会过多地增加系统的负载和开销。对于这一点,使用者不用过于担心,因为这些顾虑也是大部分工程师们会考虑到的,Netflix在设计Hystrix的时候,认为线程池上的开销相对于隔离所带来的好处是无法比拟的。同时,Netflix也针对线程池的开销做了相关的测试,以证明和打消Hystrix实现对性能影响的顾虑。

下图是Netflix Hystrix官方提供的一个Hystrix命令的性能监控,该命令以每秒60个请求的速度(QPS)向一个单服务实例进行访问,该服务实例每秒运行的线程数峰值为350个。

spring-cloud-starter-dalston-4-2.png


从图中的统计我们可以看到,使用线程池隔离与不使用线程池隔离的耗时差异如下表所示:

微信截图_20170712222712.png


在99%的情况下,使用线程池隔离的延迟有9ms,对于大多数需求来说这样的消耗是微乎其微的,更何况为系统在稳定性和灵活性上所带来的巨大提升。虽然对于大部分的请求我们可以忽略线程池的额外开销,而对于小部分延迟本身就非常小的请求(可能只需要1ms),那么9ms的延迟开销还是非常昂贵的。实际上Hystrix也为此设计了另外的一个解决方案:信号量。

Hystrix中除了使用线程池之外,还可以使用信号量来控制单个依赖服务的并发度,信号量的开销要远比线程池的开销小得多,但是它不能设置超时和实现异步访问。所以,只有在依赖服务是足够可靠的情况下才使用信号量。在HystrixCommand和HystrixObservableCommand中2处支持信号量的使用:
 
  • 命令执行:如果隔离策略参数execution.isolation.strategy设置为SEMAPHORE,Hystrix会使用信号量替代线程池来控制依赖服务的并发控制。
  • 降级逻辑:当Hystrix尝试降级逻辑时候,它会在调用线程中使用信号量。


信号量的默认值为10,我们也可以通过动态刷新配置的方式来控制并发线程的数量。对于信号量大小的估算方法与线程池并发度的估算类似。仅访问内存数据的请求一般耗时在1ms以内,性能可以达到5000rps,这样级别的请求我们可以将信号量设置为1或者2,我们可以按此标准并根据实际请求耗时来设置信号量。

如何使用

说了那么多依赖隔离的好处,那么我们如何使用Hystrix来实现依赖隔离呢?其实,我们在上一篇定义服务降级的时候,已经自动的实现了依赖隔离。

在上一篇的示例中,我们使用了@HystrixCommand来将某个函数包装成了Hystrix命令,这里除了定义服务降级之外,Hystrix框架就会自动的为这个函数实现调用的隔离。所以,依赖隔离、服务降级在使用时候都是一体化实现的,这样利用Hystrix来实现服务容错保护在编程模型上就非常方便的,并且考虑更为全面。除了依赖隔离、服务降级之外,还有一个重要元素:断路器。我们将在下一篇做详细的介绍,这三个重要利器构成了Hystrix实现服务容错保护的强力组合拳。

更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。

代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

码云:点击查看
GitHub:点击查看
 


本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:服务容错保护(Hystrix依赖隔离)【Dalston版】
版权归作者所有,转载请注明出处


Spring Cloud构建微服务架构:服务容错保护(Hystrix服务降级)【Dalston版】

Spring Cloud程序猿DD 发表了文章 • 0 个评论 • 457 次浏览 • 2017-07-05 07:45 • 来自相关话题

前言

在微服务架构中,我们将系统拆分成了一个个的服务单元,各单元应用间通过服务注册与订阅的方式互相依赖。由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会出现因等待出现故障的依赖方响应而形成任务积压,线程资源无法释放,最终导致自身服务的瘫痪,进一步甚至出现故障的蔓延最终导致整个系统的瘫痪。如果这样的架构存在如此严重的隐患,那么相较传统架构就更加的不稳定。为了解决这样的问题,因此产生了断路器等一系列的服务保护机制。

针对上述问题,在Spring Cloud Hystrix中实现了线程隔离、断路器等一系列的服务保护功能。它也是基于Netflix的开源框架 Hystrix实现的,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备了服务降级、服务熔断、线程隔离、请求缓存、请求合并以及服务监控等强大功能。

接下来,我们就从一个简单示例开始对Spring Cloud Hystrix的学习与使用。

动手试一试

在开始使用Spring Cloud Hystrix实现断路器之前,我们先拿之前实现的一些内容作为基础,其中包括:

eureka-server工程:服务注册中心,端口:1001
eureka-client工程:服务提供者,两个实例启动端口分别为2001

下面我们可以复制一下之前实现的一个服务消费者:eureka-consumer-ribbon,命名为eureka-consumer-ribbon-hystrix。下面我们开始对其进行改在:

第一步:pom.xml的dependencies节点中引入spring-cloud-starter-hystrix依赖:<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>第二步:在应用主类中使用@EnableCircuitBreaker或@EnableHystrix注解开启Hystrix的使用:@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}注意:这里我们还可以使用Spring Cloud应用中的@SpringCloudApplication注解来修饰应用主类,该注解的具体定义如下所示。我们可以看到该注解中包含了上我们所引用的三个注解,这也意味着一个Spring Cloud标准应用应包含服务发现以及断路器。@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}第三步:改造服务消费方式,新增ConsumerService类,然后将在Controller中的逻辑迁移过去。最后,在为具体执行逻辑的函数上增加@HystrixCommand注解来指定服务降级方法,比如:@RestController
public class DcController {
@Autowired
ConsumerService consumerService;
@GetMapping("/consumer")
public String dc() {
return consumerService.consumer();
}
class ConsumerService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "fallback")
public String consumer() {
return restTemplate.getForObject("http://eureka-client/dc", String.class);
}
public String fallback() {
return "fallback";
}
}
}下面我们来验证一下上面Hystrix带来的一些基础功能。我们先把涉及的服务都启动起来,然后访问localhost:2101/consumer,此时可以获取正常的返回,比如:Services: [eureka-consumer-ribbon-hystrix, eureka-client]。

为了触发服务降级逻辑,我们可以将服务提供者eureka-client的逻辑加一些延迟,比如:@GetMapping("/dc")
public String dc() throws InterruptedException {
Thread.sleep(5000L);
String services = "Services: " + discoveryClient.getServices();
System.out.println(services);
return services;
}重启eureka-client之后,再尝试访问localhost:2101/consumer,此时我们将获得的返回结果为:fallback。我们从eureka-client的控制台中,可以看到服务提供方输出了原本要返回的结果,但是由于返回前延迟了5秒,而服务消费方触发了服务请求超时异常,服务消费者就通过HystrixCommand注解中指定的降级逻辑进行执行,因此该请求的结果返回了fallback。这样的机制,对自身服务起到了基础的保护,同时还为异常情况提供了自动的服务降级切换机制。

更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。
 
代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

码云:点击查看
GitHub:点击查看

具体工程说明如下:

eureka的服务注册中心:eureka-server
eureka的服务提供方:eureka-client
eureka的服务消费者:eureka-consumer-ribbon-hystrix
 

本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:服务容错保护(Hystrix服务降级)【Dalston版】
版权归作者所有,转载请注明出处 查看全部
前言

在微服务架构中,我们将系统拆分成了一个个的服务单元,各单元应用间通过服务注册与订阅的方式互相依赖。由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会出现因等待出现故障的依赖方响应而形成任务积压,线程资源无法释放,最终导致自身服务的瘫痪,进一步甚至出现故障的蔓延最终导致整个系统的瘫痪。如果这样的架构存在如此严重的隐患,那么相较传统架构就更加的不稳定。为了解决这样的问题,因此产生了断路器等一系列的服务保护机制。

针对上述问题,在Spring Cloud Hystrix中实现了线程隔离、断路器等一系列的服务保护功能。它也是基于Netflix的开源框架 Hystrix实现的,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备了服务降级、服务熔断、线程隔离、请求缓存、请求合并以及服务监控等强大功能。

接下来,我们就从一个简单示例开始对Spring Cloud Hystrix的学习与使用。

动手试一试

在开始使用Spring Cloud Hystrix实现断路器之前,我们先拿之前实现的一些内容作为基础,其中包括:

eureka-server工程:服务注册中心,端口:1001
eureka-client工程:服务提供者,两个实例启动端口分别为2001

下面我们可以复制一下之前实现的一个服务消费者:eureka-consumer-ribbon,命名为eureka-consumer-ribbon-hystrix。下面我们开始对其进行改在:

第一步:pom.xml的dependencies节点中引入spring-cloud-starter-hystrix依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
第二步:在应用主类中使用@EnableCircuitBreaker或@EnableHystrix注解开启Hystrix的使用:
@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
注意:这里我们还可以使用Spring Cloud应用中的@SpringCloudApplication注解来修饰应用主类,该注解的具体定义如下所示。我们可以看到该注解中包含了上我们所引用的三个注解,这也意味着一个Spring Cloud标准应用应包含服务发现以及断路器。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}
第三步:改造服务消费方式,新增ConsumerService类,然后将在Controller中的逻辑迁移过去。最后,在为具体执行逻辑的函数上增加@HystrixCommand注解来指定服务降级方法,比如:
@RestController
public class DcController {
@Autowired
ConsumerService consumerService;
@GetMapping("/consumer")
public String dc() {
return consumerService.consumer();
}
class ConsumerService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "fallback")
public String consumer() {
return restTemplate.getForObject("http://eureka-client/dc", String.class);
}
public String fallback() {
return "fallback";
}
}
}
下面我们来验证一下上面Hystrix带来的一些基础功能。我们先把涉及的服务都启动起来,然后访问localhost:2101/consumer,此时可以获取正常的返回,比如:Services: [eureka-consumer-ribbon-hystrix, eureka-client]。

为了触发服务降级逻辑,我们可以将服务提供者eureka-client的逻辑加一些延迟,比如:
@GetMapping("/dc")
public String dc() throws InterruptedException {
Thread.sleep(5000L);
String services = "Services: " + discoveryClient.getServices();
System.out.println(services);
return services;
}
重启eureka-client之后,再尝试访问localhost:2101/consumer,此时我们将获得的返回结果为:fallback。我们从eureka-client的控制台中,可以看到服务提供方输出了原本要返回的结果,但是由于返回前延迟了5秒,而服务消费方触发了服务请求超时异常,服务消费者就通过HystrixCommand注解中指定的降级逻辑进行执行,因此该请求的结果返回了fallback。这样的机制,对自身服务起到了基础的保护,同时还为异常情况提供了自动的服务降级切换机制。

更多Spring Cloud内容请持续关注我的博客更新或在《Spring Cloud微服务实战》中获取。
 
代码示例

样例工程将沿用之前在码云和GitHub上创建的SpringCloud-Learning项目,重新做了一下整理。通过不同目录来区分Brixton和Dalston的示例。

码云:点击查看
GitHub:点击查看

具体工程说明如下:

eureka的服务注册中心:eureka-server
eureka的服务提供方:eureka-client
eureka的服务消费者:eureka-consumer-ribbon-hystrix
 


本文作者:程序猿DD-翟永超
原文链接:Spring Cloud构建微服务架构:服务容错保护(Hystrix服务降级)【Dalston版】
版权归作者所有,转载请注明出处


探讨通过Feign配合Hystrix进行调用时异常的处理

Spring Cloudvincent 发表了文章 • 0 个评论 • 217 次浏览 • 2017-06-29 22:35 • 来自相关话题

# 前言* 此文所述处理方式为本人在实践过程中研究分析得出的一种解决方案。
* 本文不仅希望能为 SC 学习者提供一种如题问题的一种解决方案,并且希望通过本文引出各位 SC 的朋友对如题问题的共同探讨和最佳实践方案的分享。

# 场景及痛点
* 单个项目是通过 Jersey 来实现 restful 风格的架构
* 发生异常时异常信息总是提示没有回调方法,不能显示基础服务抛出的异常信息
* 暂时没有考虑发生异常之后进行回调返回特定内容
* 业务系统通过 feign 调用基础服务,基础服务是会根据请求抛出各种请求异常的(采用标准http状态码),现在我的想法是如果调用基础服务时发生请求异常,业务系统返回的能够返回基础服务抛出的状态码
* 当然基础服务抛出的请求异常不能触发 hystrix 的熔断机制

# 问题解决方案分析
## 解决思路
* 通过网上一些资料的查询,看到很多文章会说 `HystrixBadRequestException` 不会触发 hystrix 的熔断 --> 但是并没有介绍该异常的实践方案
* 感觉要解决项目的痛点,切入点应该就在 `HystrixBadRequestException` 了。于是先看源码,一方面对 Hystrix 加深理解,尝试理解作者设计的初衷与想法,另一方面看看是否能找到其他方案达到较高的实践标准

## 对应源码解释对应方案
### 主要类对象简介
* `interface UserRemoteCall` 定义feign的接口其上会有 `@FeignClient`,FeignClient 定义了自己的 Configuration --> `FeignConfiguration`
*  `class FeignConfiguration` 这里是定义了指定 Feign 接口使用的自定义配置,如果不想该配置成为全局配置,不要让该类被自动扫描到
* `class UserErrorDecoder implements ErrorDecoder` 该类会处理响应状态码 (![200,300) || !404)

### 不使用Hystrix
#### 源码分析
* Feign 的默认配置在 `org.springframework.cloud.netflix.feign.FeignClientsConfiguration` 类中,如果不自定义
 Feign.Builder,会优先配置 `feign.hystrix.HystrixFeign.Builder extends Feign.Builder`,该类会让 Feign 的内部调用受到 Hystrix 的控制

``` 
//省略部分代码
    @Configuration
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true)
        public Feign.Builder feignHystrixBuilder() {
        return HystrixFeign.builder();
        }
    }
//省略部分代码
```
#### 解决方案
* 当然不使用 Hystrix 就不会有熔断等问题出现,处理好 `ErrorDecoder.decode()` 即可。
* 不开启 Hystrix 的方式:
1. 配置增加 `feign.hystrix.enabled=false` ,这会在全局生效不推荐。
2. `FeignConfiguration` 增加:(推荐)
```
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    } 
```

### 使用 Hystrix 解决内部调用抛出异常问题
#### 源码分析
* Hystrix 的设计方案是通过命令模式加 RxJava 实现的观察者模式来开发的,想完全熟悉 Hystrix 的运作流程需要熟练掌握 RxJava,本文只对源码进行简单介绍,后面有时间有机会再详细介绍
* Hystrix如何处理异常的:
代码位置:`com.netflix.hystrix.AbstractCommand#executeCommandAndObserve`
```
 //省略部分代码
 private Observable<R> executeCommandAndObserve(final AbstractCommand<R> _cmd) {
//省略部分代码
        final Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {
            @Override
            public Observable<R> call(Throwable t) {
                Exception e = getExceptionFromThrowable(t);
                executionResult = executionResult.setExecutionException(e);
                if (e instanceof RejectedExecutionException) {
                    return handleThreadPoolRejectionViaFallback(e);
                } else if (t instanceof HystrixTimeoutException) {
                    return handleTimeoutViaFallback();
                } else if (t instanceof HystrixBadRequestException) {
                    return handleBadRequestByEmittingError(e);
                } else {
                    /*
                     * Treat HystrixBadRequestException from ExecutionHook like a plain HystrixBadRequestException.
                     */
                    if (e instanceof HystrixBadRequestException) {
                        eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);
                        return Observable.error(e);
                    }

                    return handleFailureViaFallback(e);
                }
            }
        };
//省略部分代码
    }
```
该类中该方法为发生异常的回调方法,由此可以看出如果抛出异常如果是 `HystrixBadRequestException` 是直接处理异常之后进行抛出(这里不会触发熔断机制),而不是进入回调方法。

#### 解决方案
* 那么我们对于请求异常的解决方案就需要通过 `HystrixBadRequestException` 来解决了(不会触发熔断机制),根据返回响应创建对应异常并将异常封装进 `HystrixBadRequestException`,业务系统调用中取出 `HystrixBadRequestException` 中的自定义异常进行处理,

封装异常说明:
```
public class UserErrorDecoder implements ErrorDecoder{

    private Logger logger = LoggerFactory.getLogger(getClass());

    public Exception decode(String methodKey, Response response) {
        ObjectMapper om = new JiaJianJacksonObjectMapper();
        JiaJianResponse resEntity;
        Exception exception = null;
        try {
            resEntity = om.readValue(Util.toString(response.body().asReader()), JiaJianResponse.class);
//为了说明我使用的 WebApplicationException 基类,去掉了封装
            exception = new WebApplicationException(javax.ws.rs.core.Response.status(response.status()).entity(resEntity).type(MediaType.APPLICATION_JSON).build());
        } catch (IOException ex) {
            logger.error(ex.getMessage(), ex);
        }
        // 这里只封装4开头的请求异常
        if (400 <= response.status() || response.status() < 500){
            exception = new HystrixBadRequestException("request exception wrapper", exception);
        }else{
            logger.error(exception.getMessage(), exception);
        }
        return exception;
    }
}
```
为 Feign 配置 ErrorDecoder
```
@Configuration
public class FeignConfiguration {
    @Bean
    public ErrorDecoder errorDecoder(){
        return new UserErrorDecoder();
    }
}
```

业务系统处理异常说明:
```
    @Override
    public UserSigninResEntity signIn(UserSigninReqEntity param) throws Exception {
    try {
//省略部分代码
        UserSigninResEntity entity = userRemoteCall.signin(secretConfiguration.getKeys().get("user-service"), param);
//省略部分代码
    } catch (Exception ex) {
        logger.error(ex.getMessage(), ex);
//这里进行异常处理
        if(ex.getCause() instanceof WebApplicationException){
            throw (WebApplicationException) ex.getCause();
        }
            throw ex;
        }
    }
```
* `WebApplicationException` 是 `javax.ws.rs` 包中异常,通过 Jersey 抛出该异常能够将返回的 HttpCode 封装进该异常中(上述代码中展示了如何封装 HttpCode),抛出该异常,调用端就能得到返回的 HttpCode。

# 总结
* 本文主要出发点在于如何解决在 Feign 中使用 Hystrix 时被调用端抛出请求异常的问题。
* 本项目使用 Jersey,封装 `WebApplicationException` 即可满足需求,其他架构也是大同小异了。
* 该解决方案我不确定是否为最佳实践方案,特别希望和欢迎有不同想法或意见的朋友来与我交流,包括但不限于解决方案、项目痛点是否合理等等。
 
原文链接:http://www.jianshu.com/p/f240ca7bb7c0 查看全部
# 前言* 此文所述处理方式为本人在实践过程中研究分析得出的一种解决方案。
* 本文不仅希望能为 SC 学习者提供一种如题问题的一种解决方案,并且希望通过本文引出各位 SC 的朋友对如题问题的共同探讨和最佳实践方案的分享。

# 场景及痛点
* 单个项目是通过 Jersey 来实现 restful 风格的架构
* 发生异常时异常信息总是提示没有回调方法,不能显示基础服务抛出的异常信息
* 暂时没有考虑发生异常之后进行回调返回特定内容
* 业务系统通过 feign 调用基础服务,基础服务是会根据请求抛出各种请求异常的(采用标准http状态码),现在我的想法是如果调用基础服务时发生请求异常,业务系统返回的能够返回基础服务抛出的状态码
* 当然基础服务抛出的请求异常不能触发 hystrix 的熔断机制

# 问题解决方案分析
## 解决思路
* 通过网上一些资料的查询,看到很多文章会说 `HystrixBadRequestException` 不会触发 hystrix 的熔断 --> 但是并没有介绍该异常的实践方案
* 感觉要解决项目的痛点,切入点应该就在 `HystrixBadRequestException` 了。于是先看源码,一方面对 Hystrix 加深理解,尝试理解作者设计的初衷与想法,另一方面看看是否能找到其他方案达到较高的实践标准

## 对应源码解释对应方案
### 主要类对象简介
* `interface UserRemoteCall` 定义feign的接口其上会有 `@FeignClient`,FeignClient 定义了自己的 Configuration --> `FeignConfiguration`
*  `class FeignConfiguration` 这里是定义了指定 Feign 接口使用的自定义配置,如果不想该配置成为全局配置,不要让该类被自动扫描到
* `class UserErrorDecoder implements ErrorDecoder` 该类会处理响应状态码 (![200,300) || !404)

### 不使用Hystrix
#### 源码分析
* Feign 的默认配置在 `org.springframework.cloud.netflix.feign.FeignClientsConfiguration` 类中,如果不自定义
 Feign.Builder,会优先配置 `feign.hystrix.HystrixFeign.Builder extends Feign.Builder`,该类会让 Feign 的内部调用受到 Hystrix 的控制

``` 
//省略部分代码
    @Configuration
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true)
        public Feign.Builder feignHystrixBuilder() {
        return HystrixFeign.builder();
        }
    }
//省略部分代码
```
#### 解决方案
* 当然不使用 Hystrix 就不会有熔断等问题出现,处理好 `ErrorDecoder.decode()` 即可。
* 不开启 Hystrix 的方式:
1. 配置增加 `feign.hystrix.enabled=false` ,这会在全局生效不推荐。
2. `FeignConfiguration` 增加:(推荐)
```
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    } 
```

### 使用 Hystrix 解决内部调用抛出异常问题
#### 源码分析
* Hystrix 的设计方案是通过命令模式加 RxJava 实现的观察者模式来开发的,想完全熟悉 Hystrix 的运作流程需要熟练掌握 RxJava,本文只对源码进行简单介绍,后面有时间有机会再详细介绍
* Hystrix如何处理异常的:
代码位置:`com.netflix.hystrix.AbstractCommand#executeCommandAndObserve`
```
 //省略部分代码
 private Observable<R> executeCommandAndObserve(final AbstractCommand<R> _cmd) {
//省略部分代码
        final Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {
            @Override
            public Observable<R> call(Throwable t) {
                Exception e = getExceptionFromThrowable(t);
                executionResult = executionResult.setExecutionException(e);
                if (e instanceof RejectedExecutionException) {
                    return handleThreadPoolRejectionViaFallback(e);
                } else if (t instanceof HystrixTimeoutException) {
                    return handleTimeoutViaFallback();
                } else if (t instanceof HystrixBadRequestException) {
                    return handleBadRequestByEmittingError(e);
                } else {
                    /*
                     * Treat HystrixBadRequestException from ExecutionHook like a plain HystrixBadRequestException.
                     */
                    if (e instanceof HystrixBadRequestException) {
                        eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);
                        return Observable.error(e);
                    }

                    return handleFailureViaFallback(e);
                }
            }
        };
//省略部分代码
    }
```
该类中该方法为发生异常的回调方法,由此可以看出如果抛出异常如果是 `HystrixBadRequestException` 是直接处理异常之后进行抛出(这里不会触发熔断机制),而不是进入回调方法。

#### 解决方案
* 那么我们对于请求异常的解决方案就需要通过 `HystrixBadRequestException` 来解决了(不会触发熔断机制),根据返回响应创建对应异常并将异常封装进 `HystrixBadRequestException`,业务系统调用中取出 `HystrixBadRequestException` 中的自定义异常进行处理,

封装异常说明:
```
public class UserErrorDecoder implements ErrorDecoder{

    private Logger logger = LoggerFactory.getLogger(getClass());

    public Exception decode(String methodKey, Response response) {
        ObjectMapper om = new JiaJianJacksonObjectMapper();
        JiaJianResponse resEntity;
        Exception exception = null;
        try {
            resEntity = om.readValue(Util.toString(response.body().asReader()), JiaJianResponse.class);
//为了说明我使用的 WebApplicationException 基类,去掉了封装
            exception = new WebApplicationException(javax.ws.rs.core.Response.status(response.status()).entity(resEntity).type(MediaType.APPLICATION_JSON).build());
        } catch (IOException ex) {
            logger.error(ex.getMessage(), ex);
        }
        // 这里只封装4开头的请求异常
        if (400 <= response.status() || response.status() < 500){
            exception = new HystrixBadRequestException("request exception wrapper", exception);
        }else{
            logger.error(exception.getMessage(), exception);
        }
        return exception;
    }
}
```
为 Feign 配置 ErrorDecoder
```
@Configuration
public class FeignConfiguration {
    @Bean
    public ErrorDecoder errorDecoder(){
        return new UserErrorDecoder();
    }
}
```

业务系统处理异常说明:
```
    @Override
    public UserSigninResEntity signIn(UserSigninReqEntity param) throws Exception {
    try {
//省略部分代码
        UserSigninResEntity entity = userRemoteCall.signin(secretConfiguration.getKeys().get("user-service"), param);
//省略部分代码
    } catch (Exception ex) {
        logger.error(ex.getMessage(), ex);
//这里进行异常处理
        if(ex.getCause() instanceof WebApplicationException){
            throw (WebApplicationException) ex.getCause();
        }
            throw ex;
        }
    }
```
* `WebApplicationException` 是 `javax.ws.rs` 包中异常,通过 Jersey 抛出该异常能够将返回的 HttpCode 封装进该异常中(上述代码中展示了如何封装 HttpCode),抛出该异常,调用端就能得到返回的 HttpCode。

# 总结
* 本文主要出发点在于如何解决在 Feign 中使用 Hystrix 时被调用端抛出请求异常的问题。
* 本项目使用 Jersey,封装 `WebApplicationException` 即可满足需求,其他架构也是大同小异了。
* 该解决方案我不确定是否为最佳实践方案,特别希望和欢迎有不同想法或意见的朋友来与我交流,包括但不限于解决方案、项目痛点是否合理等等。
 
原文链接:http://www.jianshu.com/p/f240ca7bb7c0