1.1.1 概念
大意是一个系统依赖30个微服务,假设每个微服务可用时间是99.99%,换算成整个系统就是99.99的30次方约99.7%可用时间,0.3%不可用。1亿的请求有30万失败。30天有2个多小时不能正常提供服务。
现实通常比这更糟糕。举个例子:假设我们在电商网站买东西,从下单到完成要依赖三个微服务:订单、支付、物流。遇上双11或618,客户的订单蜂拥而至,这时候物流微服务可能因为程序优化不好或者硬件资源不够而不能及时提供服务,如果因为物流的问题而导致客户无法下单,这到手的钱挣不到了就很可惜了。
这时候就可以对物流微服务进行熔断,暂时不调用,全力以赴把订单、支付给完成了。熬过去这段峰值,回过头来再处理物流的问题,反正钱已到手。不会因为一颗老鼠屎坏一锅汤。
Spring Cloud的断路器是Hystrix,两种使用方法,一种是直接使用Hystrix,一种是通过FeignClient。
1.1.2 微服务设计引发新的问题
微服务的设计,服务分散在多个服务器上,服务之间互相调用,要调用的服务由于跨网络跨服务器调用,响应速度明显比传统项目单机调用慢很多,甚至由于网络涌动的不稳定的现象发生导致调用超时;还有类似级联失败、雪崩效应(依赖的基础服务宕机,关联的服务导致失败甚至宕机,就像滚雪球一样层层失败。)
如何解决这类新的问题呢?传统的机制就是超时机制。
1.1.3 超时机制
良好的设计,在通过网络请求其他服务时,都必须设置超时时间。正常情况下,一个远程调用几十毫秒内返回。当要调用的服务不可用时或者网络问题,响应时间要等超时,如HttpClient几十秒才超时返回。通常,一次远程调用对应一个线程/进程,如果大量的线程/进程得不到释放,并且越积越多,服务资源就会被耗尽,从而导致资深服务不可用。所以必须为每个请求设置超时时间。
例如:我们熟悉的tomcat就有超时设计
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" acceptCount="500" maxThreads="400" />
但我们发现传统的超时设计时间都比较久,面对今天互联网时代,用户对性能的极致要求时间显得太久了?20秒,用户要求网页秒级响应,等20秒,用户只能跟你说byebye了,扭头去找别的提供商了。
特别像微服务这样基于多个服务,服务之间都是远程调用,如果一个服务长时间等待,用户体验会极差的,那怎么办呢?断路器模式应运而生。
1.1.4 熔断机制
家里电表都有个断路器(俗称电闸),当使用的电器很多,用电巨大(例如功率过大、短路等),当电流过载时,电路就会升温,甚至烧断电路,引起火灾。有了这个断路器,我们及时拉闸,就不会造成严重后果了。
断路器可以实现快速失败,如果它在一段时间内检测到许多失败,如超时,就会强迫其以后的多个调用快速失败,不再请求所依赖的服务,从而防止应用程序不断地尝试执行可能会失败的操作,这样应用程序可以继续执行而不用等待修正错误,或者浪费CPU时间去等待长时间的超时。断路器也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试调用操作。
断路器模式像是那些容易导致错误的操作的一种代理。这种代理能够记录最近调用发生错误的次数,然后决定使用允许操作继续,或者立即返回错误。
1.1.5 状态
断路器有三种状态:
1. 关闭:当访问没有问题时,断路器处于关闭未使用。
2. 打开:当访问开始出现异常,错误次数增多,达到阀值时就会打开断路器,这样服务直接访问断路器,断路器会再尝试访问,如果失败直接返回。
3. 半开:那服务一直走断路器,系统就没法用了,万一被调用的服务以及稳定了呢。断路器的优势就来了,过一定时间窗口后(若干秒)它就会自动分流一部分服务再去尝试访问之前失败的服务。如果继续失败,那就不再转发,如果一个成功立即关闭断路器。
1.1.6 结构图
当服务B不可用时,开发人员需要写一个Fallback快速失败响应。可以设置为一个固定的值或者一个空值。
Hystrix默认的超时时间是1秒,如果超过这个时间尚未响应,将会进入fallback代码。而首次请求往往会比较慢(因为Spring的懒加载机制,要实例化一些类),这个响应时间可能就大于1秒了。
1.1.7 小结
l 不是立即宣布死亡,而是设置阀值,超越阀值才宣布死亡
l 传统方式死亡了就不管了,可断路器超级厉害,“死了也不放过”。自带心跳机制,自动测试路径是否可用,如发现又“活了”,会自动恢复调用关系。这一切开发者都无需编写代码
这样的设计太牛了,考虑非常细致全面,可以说做到了极致。不可用时立即响应,可用时自动恢复。不是网络抖动下,就永远宣布其死亡。这样的设计思想非常适合网络这种不稳定的应用场景。
1.2 消费者实现Hystrix
1.2.1 创建Maven项目
1.2.2 pom.xml
增加hystrix依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.wood</groupId>
<artifactId>spring-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wood</groupId>
<artifactId>consumer-hystrix</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>consumer-hystrix</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Feign依赖,声明式开发 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>LATEST</version>
</dependency>
<!-- Hystrix,Feign是基于Hystrix的,Hystrix下一章节会讲到 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>LATEST</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>LATEST</version>
</dependency>
</dependencies>
</project>
1.2.3 导入hystrix支持
Spring cloud没有直接使用NetFlix开源的Hystrix,而使用的是javanica,javanica在Hystrix基础上利用反射和注解技术增强和简化了Hystrix的开发。
1.2.4 application.properties
server.port=8094
spring.application.name=consumer-hystrix
eureka.client.serviceUrl.defaultZone=http://localhost:6001/eureka
# 定义根目录下日志级别
logging.level.root=INFO
# 放开 hystrix 、health、info 等功能
management.endpoints.web.exposure.include="*"
1.2.5 EurekaServiceFeign.java
package com.wood.consumerhystrix.remote;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* 这个接口相当于把原来的服务提供者项目当成一个Service类
**/
@FeignClient(value="provider-user", fallback = UserFeignFallback.class)
public interface EurekaServiceFeign {
/**
* Feign中没有原生的@GetMapping/@PostMapping/@DeleteMapping/@PutMapping
* 要指定需要用method进行
* */
@RequestMapping(value="/hello/{name}",method=RequestMethod.GET)
String hello(@PathVariable("name") String name);
}
1.2.6 HelloController.java
package com.wood.consumerhystrix.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.wood.consumerhystrix.remote.EurekaServiceFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
private EurekaServiceFeign eurekaServiceFeign;
/**
* hystrix配合feign使用
* 当feign连接出现故障,就不会再去请求了,直接进入fallback
* tip:默认开启了两个provider-user服务,每次访问时交替出现,feign自带负载均衡的功能
* 如果满足不了需求可以加上Ribbon来实现专业的负载均衡
* @RibbonClient(name="provider-user", configuration=RibbonRuleConfig.class)
* */
@GetMapping("/hello/{name}")
@ResponseBody
@HystrixCommand(fallbackMethod = "helloFallback")
public String hello(@PathVariable String name){
return eurekaServiceFeign.hello(name);
}
// 对应上面的方法,参数必须一致当访问失败时,hystrix直接回调用此方法
public String helloFallback(String name) {
return name + "\nhystrix配合feign使用,当feign连接出现故障,就不会再去请求了,直接进入fallback";
}
}
1.2.7 HystrixRunApp.java
package com.wood.consumerhystrix;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients //开启feign
@EnableCircuitBreaker //开启hystrix
@EnableHystrixDashboard
@EnableHystrix // spring cloud 2.x hystrix没有/actuator/hystrix.stream路径解决
public class ConsumerHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerHystrixApplication.class, args);
}
// spring cloud 2.x hystrix没有/actuator/hystrix.stream路径解决
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
1.2.8 测试
执行顺序:
先启动服务端 6001 eureka-server
在启动提供者1 8081 provider-user
最后启动消费者 8094 consumer-hystrix
访问Eureka控制台: http://localhost:6001/
访问请求: http://localhost:8094/hello/hi
注意:可能访问太快时第一次就出现断路器实现,这时访问太快,服务还未生效,多刷新几次,业务正常时应该出现上面的提示。
1.2.9 测试断路器
关闭provider-user,模拟服务失败宕机场景。
访问请求: http://localhost:8094/hello/hi
可以看到已经走了fallback方法。
重启provider-user服务,模拟服务修复,可以看到立即返回正确结果,说明服务正常时不会走断路器方法。
设置断点,和我们预想的相同,会发现只有异常时才走断路器的回调方法。
1.3 拓展:HystrixDashBoard
1.3.1 需要依赖jar包支持
只针对当前实例,actuator是SpringBoot提供的一些监控的扩展支持
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
1.3.2 pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>LATEST</version>
</dependency>
1.3.3 HystrixRunApp.java
@EnableHystrixDashboard
@EnableHystrix // spring cloud 2.x hystrix没有/actuator/hystrix.stream路径解决
// spring cloud 2.x hystrix没有/actuator/hystrix.stream路径解决
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
1.3.4 查看监控日志
访问链接:http://localhost:8094/hystrix
居然是404,为什么呢,spring-cloud 1.x 是OK的,但2.x不能访问
# 放开 hystrix 、health、info 等功能
management.endpoints.web.exposure.include="*"
@EnableCircuitBreaker //开启hystrix
@EnableHystrixDashboard
@EnableHystrix // spring cloud 2.x hystrix没有/actuator/hystrix.stream路径解决
// spring cloud 2.x hystrix没有/actuator/hystrix.stream路径解决
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
访问8094,消费者链接,展示ping:一直打印,转个不停。
访问一次业务 http://localhost:8094/hello/hi
停止提供者,刷新消费者,开始打印日志信息
可以基于控制台访问(http://localhost:8094/hystrix)
输入链接:http://localhost:8094/actuator/hystrix.stream,点击monitor按钮
具体指标的含义为:
在监控的界面有两个重要的图形信息:一个实心圆和一条曲线。
l 实心圆:
1、通过颜色的变化代表了实例的健康程度,健康程度从绿色、黄色、橙色、红色递减。
2、通过大小表示请求流量发生变化,流量越大该实心圆就越大。所以可以在大量的实例中快速发现故障实例和高压实例。
l 曲线:用来记录2分钟内流浪的相对变化,可以通过它来观察流量的上升和下降趋势。
提供demo下载