分布式系统中常常会出现某个基础服务不可用形成整个系统不可用的状况,这种现象被称为服务雪崩效应。为了应对服务雪崩,一种常见的作法是手动服务降级。而 Hystrix 的出现,给咱们提供了另外一种选择。java
在微服务架构中, 咱们将系统拆分红了不少服务单元, 各单元的应用间经过服务注册 与订阅的方式互相依赖。 因为每一个单元都在不一样的进程中运行,依赖经过远程调用的方式 执行, 这样就有可能由于网络缘由或是依赖服务自身间题出现调用故障或延迟, 而这些问 题会直接致使调用方的对外服务也出现延迟, 若此时调用方的请求不断增长, 最后就会因 等待出现故障的依赖方响应造成任务积压, 最终致使自身服务的瘫痪。 举个例子, 在一个电商网站中, 咱们可能会将系统拆分红用户、 订单、 库存、 积分、 评论等一系列服务单元。 用户建立一个订单的时候, 客户端将调用订单服务的建立订单接 口,此时建立订单接口又会向库存服务来请求出货(判断是否有足够库存来出货)。 此时若 库存服务因自身处理逻辑等缘由形成响应缓慢, 会直接致使建立订单服务的线程被挂起, 以等待库存申请服务的响应, 在漫长的等待以后用户会由于请求库存失败而获得建立订单 失败的结果。 若是在高并发状况之下,因这些挂起的线程在等待库存服务的响应而未能释 放, 使得后续到来的建立订单请求被阻塞, 最终致使订单服务也不可用。即雪崩效应。web
服务雪崩效应是一种因 服务提供者 的不可用致使 服务调用者 的不可用,并将不可用 逐渐放大 的过程。spring
服务雪崩产生的过程分为如下三个阶段来分析造成的缘由:express
服务雪崩的每一个阶段均可能由不一样的缘由形成,好比形成 服务不可用 的缘由有:apache
而造成 重试加大流量 的缘由有:json
服务调用者不可用 产生的主要缘由有:后端
针对形成服务雪崩的不一样缘由,可使用不一样的应对策略:浏览器
流量控制 的具体措施包括:缓存
改进缓存模式 的措施包括:服务器
服务调用者降级服务 的措施包括:
咱们根据具体业务,将依赖服务分为: 强依赖和若依赖。强依赖服务不可用会致使当前业务停止,而弱依赖服务的不可用不会致使当前业务的停止。
不可用服务的调用快速失败通常经过 超时机制, 熔断器 和熔断后的 降级方法 来实现。
对于查询操做,咱们能够实现一个 fallback 方法,当请求后端服务出现异常的时候,可使用 fallback 方法返回的值。fallback 方法的返回值通常是设置的默认值或者来自缓存。
在 Hystrix 中,主要经过线程池来实现资源隔离。一般在使用的时候咱们会根据调用的远程服务划分出多个线程池。例如调用产品服务的 Command 放入 A 线程池,调用帐户服务的 Command 放入 B 线程池。这样作的主要优势是运行环境被隔离开了。这样就算调用服务的代码存在 bug 或者因为其余缘由致使本身所在线程池被耗尽时,不会对系统的其余服务形成影响。 经过实现对依赖服务的线程池隔离, 能够带来以下优点:
Hystrix 中除了使用线程池以外,还可使用信号量来控制单个依赖服务的并发度,信号量的开销要远比线程池的开销小得多,可是它不能设置超时和实现异步访问。因此,只有在依赖服务是足够可靠的状况下才使用信号量。在 HystrixCommand 和 HystrixObservableCommand 中 2 处支持信号量的使用:
信号量的默认值为 10,咱们也能够经过动态刷新配置的方式来控制并发线程的数量。对于信号量大小的估算方法与线程池并发度的估算相似。仅访问内存数据的请求通常耗时在 1ms 之内,性能能够达到 5000rps,这样级别的请求咱们能够将信号量设置为 1 或者 2,咱们能够按此标准并根据实际请求耗时来设置信号量。
在分布式架构中,断路器模式的做用也是相似的,当某个服务单元发生故障(相似用电器发生短路)以后,经过断路器的故障监控(相似熔断保险丝),直接切断原来的主逻辑调用。可是,在 Hystrix 中的断路器除了切断主逻辑的功能以外,还有更复杂的逻辑,下面咱们来看看它更为深层次的处理逻辑。 断路器开关相互转换的逻辑以下图:
当 Hystrix Command 请求后端服务失败数量超过必定阈值,断路器会切换到开路状态 (Open)。这时全部请求会直接失败而不会发送到后端服务。
这个阈值涉及到三个重要参数:快照时间窗、请求总数下限、错误百分比下限。这个参数的做用分别是: 快照时间窗:断路器肯定是否打开须要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的 10 秒。 请求总数下限:在快照时间窗内,必须知足请求总数下限才有资格进行熔断。默认为 20,意味着在 10 秒内,若是该 Hystrix Command 的调用此时不足 20 次,即时全部的请求都超时或其余缘由失败,断路器都不会打开。 错误百分比下限:当请求总数在快照时间窗内超过了下限,好比发生了 30 次调用,若是在这 30 次调用中,有 16 次发生了超时异常,也就是超过 50% 的错误百分比,在默认设定 50% 下限状况下,这时候就会将断路器打开。
断路器保持在开路状态一段时间后 (默认 5 秒),自动切换到半开路状态 (HALF-OPEN)。这时会判断下一次请求的返回状况,若是请求成功,断路器切回闭路状态 (CLOSED),不然从新切换到开路状态 (OPEN)。
由于熔断只是做用在服务调用这一端,所以咱们根据上一篇的示例代码只须要改动 server-businessb-woqu 项目相关代码就能够。
feign: hystrix: enabled: true
/** * @author orrin */ @Component(value = "additionHystrix") public class AdditionHystrix implements Addition { private static final Logger LOGGER = LoggerFactory.getLogger(AdditionHystrix.class); @Override public Integer add(int x, int y) { LOGGER.error(" Addition is disabled "); return 0; } }
@FeignClient(serviceId = "business-a-woqu", fallback = AdditionHystrix.class) public interface Addition { @GetMapping("/add") public Integer add(@RequestParam("x") int x, @RequestParam("y") int y); }
GET http://192.168.2.102:9002/area?length=1&width=2&heigh=3 HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Mon, 19 Nov 2018 07:39:24 GMT 0 Response code: 200; Time: 80ms; Content length: 1 bytes
Hystrix还有如下使用方式:
咱们提到断路器是根据一段时间窗内的请求状况来判断并操做断路器的打开和关闭状态的。而这些请求状况的指标信息都是 HystrixCommand 和 HystrixObservableCommand 实例在执行过程当中记录的重要度量信息,它们除了 Hystrix 断路器实现中使用以外,对于系统运维也有很是大的帮助。这些指标信息会以 “滚动时间窗” 与 “桶” 结合的方式进行汇总,并在内存中驻留一段时间,以供内部或外部进行查询使用,Hystrix Dashboard 就是这些指标内容的消费者之一。
下面咱们基于以前的示例来结合 Hystrix Dashboard 实现 Hystrix 指标数据的可视化面板,这里咱们将用到下以前实现的几个应用,包括:
建立一个标准的 Spring Boot 工程,命名为:hystrix-dashboard-woqu
<?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"> <parent> <artifactId>common-server-woqu</artifactId> <groupId>com.orrin</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>hystrix-dashboard-woqu</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.jolokia</groupId> <artifactId>jolokia-core</artifactId> </dependency> </dependencies> </project>
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard; /** * @author orrin */ @EnableHystrixDashboard @SpringBootApplication public class HystrixDashboardApplication { public static void main(String[] args) { SpringApplication.run(HystrixDashboardApplication.class, args); } }
spring: application: name: hystrix-dashboard-woqu server: port: 7002
启动应用,而后再浏览器中输入 http://localhost:7002/hystrix 能够看到以下界面:
经过 Hystrix Dashboard 主页面的文字介绍,咱们能够知道,Hystrix Dashboard 共支持三种不一样的监控方式:
前二者都对集群的监控,须要整合 Turbine 才能实现。这一部分咱们先实现对单体应用的监控,这里的单体应用就用咱们以前使用 Feign 和 Hystrix 实现的服务消费者——server-businessb-woqu。
既然 Hystrix Dashboard 监控单实例节点须要经过访问实例的/actuator/hystrix.stream接口来实现,天然咱们须要为服务实例添加这个 endpoint。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients(basePackages = "com.woqu") @EnableHystrix @ComponentScan(value = "com.woqu") public class BusinessBApp { public static void main(String[] args) { SpringApplication.run(BusinessBApp.class, args); } @LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
management: endpoints: web: exposure: include: hystrix.stream
以上图来讲明其中各元素的具体含义:
建立一个标准的 Spring Boot 工程,命名为:turbine-woqu。
<?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"> <parent> <artifactId>common-server-woqu</artifactId> <groupId>com.orrin</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>turbine-woqu</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-turbine</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.jolokia</groupId> <artifactId>jolokia-core</artifactId> </dependency> </dependencies> </project>
package com.woqu.common.turbine; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.turbine.EnableTurbine; /** * @author orrin */ @EnableTurbine @EnableDiscoveryClient @SpringBootApplication public class TurbineApplication { public static void main(String[] args) { SpringApplication.run(TurbineApplication.class, args); } }
spring: application: name: common-server-turbine cloud: consul: host: woqu.consul port: 8500 discovery: instance-id: ${spring.application.name} instance-group: ${spring.application.name} register: true service-name: ${spring.application.name} applications: common-server-gateway,business-b-woqu,business-a-woqu turbine: aggregator: cluster-config: ${applications} app-config: ${applications} #cluster-name-expression: new String("default") combine-host-port: true management: endpoints: web: exposure: include: "*" exclude: dev server: port: 7003
参数说明
注意:new String("default")这个必定要用 String 来包一下,不然启动的时候会抛出异常。
在浏览器中访问http://localhost:7002/hystrix,经过/clusters获取能够监控的集群
GET http://127.0.0.1:7003/clusters HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 20 Nov 2018 05:47:18 GMT [ { "name": "business-b-woqu", "link": "http://127.0.0.1:7003/turbine.stream?cluster=business-b-woqu" }, { "name": "common-server-gateway", "link": "http://127.0.0.1:7003/turbine.stream?cluster=common-server-gateway" }, { "name": "business-a-woqu", "link": "http://127.0.0.1:7003/turbine.stream?cluster=business-a-woqu" } ] Response code: 200; Time: 12ms; Content length: 304 bytes
在Hystrix Dashboard输入http://127.0.0.1:7003/turbine.stream?cluster=business-b-woqu,请求GET http://192.168.2.102:9002/area?length=1&width=2&heigh=3,便可查看到监控效果
Spring Cloud 在封装 Turbine 的时候,还实现了基于消息代理的收集实现。因此,咱们能够将全部须要收集的监控信息都输出到消息代理中,而后 Turbine 服务再从消息代理中异步的获取这些监控信息,最后将这些监控信息聚合并输出到 Hystrix Dashboard 中。
这里咱们使用RabbitMQ来实现基于消息代理的 Turbine 聚合服务。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-turbine-stream</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
@SpringBootApplication @EnableTurbineStream @EnableDiscoveryClient public class TurbineStreamRabbitmqApplication { public static void main(String[] args) { SpringApplication.run(TurbineStreamRabbitmqApplication.class, args); } @Bean public ConfigurableCompositeMessageConverter integrationArgumentResolverMessageConverter(CompositeMessageConverterFactory factory) { return new ConfigurableCompositeMessageConverter(factory.getMessageConverterForAllRegistered().getConverters()); } }
spring: application: name: common-server-turbine-stream cloud: consul: host: woqu.consul port: 8500 discovery: instance-id: ${spring.application.name} instance-group: ${spring.application.name} register: true service-name: ${spring.application.name} stream: rabbit: bindings: test: consumer: prefix: z bindings: input: group: default rabbitmq: addresses: rabbitmq.server port: 5672 username: test password: password virtualHost: /test applications: common-server-gateway,business-b-woqu,business-a-woqu turbine: aggregator: cluster-config: ${applications} app-config: ${applications} #cluster-name-expression: new String("default") combine-host-port: true stream: port: 18888 # 这是turbine的端口即暴露监控数据的端口,跟server.port不一样 management: endpoints: web: exposure: include: "*" exclude: dev server: port: 7004
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-hystrix-stream</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
再在启动类上加上@EnableHystrix注解
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients(basePackages = "com.woqu") @EnableHystrix @ComponentScan(value = "com.woqu") public class BusinessBApp { public static void main(String[] args) { SpringApplication.run(BusinessBApp.class, args); } @LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
添加配置文件
spring: rabbitmq: addresses: rabbitmq.server port: 5672 username: test password: password virtualHost: /test cloud: stream: rabbit: bindings: test: consumer: prefix: z bindings: input: group: default