前言
经过微服务精通之Ribbon原理解析的学习,我们了解到了服务消费者获取服务提供者实例的过程,在这之后,服务消费者会调用服务提供者的接口。但是在调用接口的过程中,我们经常会遇见服务之间的延迟和通信失败的问题,极端情况下,可能会出现因为某个服务性能的下降或者故障宕机,导致服务超时,层层传递,引发雪崩,最终导致整个系统崩溃,而这种情况,我们可以通过熔断器解决这个问题,提高系统的稳定性。
一、熔断器是什么?
顾名思义,熔断器的作用就是将异常的通道关闭,从而保护整个系统。
例如,有ABCD四个服务,BCD提供服务给A消费,当D因为负载过高或者网络异常导致响应超时时,会导致A对接很多的等待链接,从而导致A也变得异常,以至于其它依赖A的服务也跟着异常,以此类推,从而导致系统大面积的服务瘫痪,如下图。
这个时候,我们可以在所有服务中加上熔断器,正常情况下,熔断器保持关闭状态,当异常时,则开启熔断器。比如D异常的时候,我们可以把A跟D之间的熔断器打开,就可以避免服务的大面积瘫痪了。
二、Hystrix是什么?
Hystrix是由Netflix开发的,集成到微服务体系里面的一个组件,是微服务体系里面的熔断器。
三、Hystrix原理解析
1.总体流程
流程说明:
(1)创建Hystrix请求命令;
(2)执行Hystrix请求命令;
(3)请求结果是否被缓存,若有缓存,则直接返回;
(4)熔断器开关是否打开,若打开,则直接执行步骤8;
(5)信号量或者线程池资源请求,若信号量或者线程池资源已占满,则请求失败,执行步骤8;
(6)执行请求,若执行失败或者请求超时(默认超时时间为1秒),则直接执行步骤8;
(7)汇总请求信息,判断是否要打开熔断器开关;
(8)执行fallback方法,并返回fallback处理结果;
(9)返回成功响应。
2.请求命令和执行
Hystrix请求采用命令模式,其中,HystrixCommand表示单个操作请求命令,HystrixObservableCommand表示多个操作请求命令。
HystrixCommand执行:
execute:同步执行,返回一个单结果对象;
queue:异步执行,返回一个Future对象,其中包含了一个单结果对象。
HystrixObservableCommand执行:
observe:返回observable对象,它代表了多个操作结果,是一个Hot observable;
toObservable:同样返回observable对象,同样代表多个操作结果,但是它是一个Cold observable。
注:Hot observable和Cold observable的区别,可通过《RxJava 驯服数据流之 hot & cold Observable》这篇文章了解一下。
3.请求合并
Hystrix支持请求合并,即将一定时间段内对相同依赖服务的请求合并成一个请求。
3.1前提条件
依赖服务必须有一个多参数接口。比如说,有一个根据用户编号查询用户信息的接口,如下图:
如果要进行请求合并,则依赖服务USER-SERVICE必须提供一个批量查询用户信息的接口,如下图:
3.2 实现方法
在单参数调用方法上面添加注解@HystrixCollapser,多参数调用方法上面添加注解@HystrixCommand,如下图:
@HystrixCollapser参数说明:
batchMethod:多参数调用方法名。
collapserProperties:请求合并参数,timerDelayInMilliseconds表示请求合并时间长度,单位是毫秒;上图配置表示Hystrix会将最近100毫秒的请求合并。
3.3 实现原理
(1)单入参接口配置请求合并以后,Hystrix会对该请求进行拦截,然后将一定时间段内的请求入参合并起来;
(2)用合并后的请求参数去调用批量处理方法(3.2配置中的batchMethod);
(3)批量处理方法按照正常的Hystrix请求流程进行处理,得到批量处理结果;
(4)Hystrix将批量处理结果拆分,并返回给各个单入参接口;
(5)单入参接口返回结果。
3.4 优缺点
优点:
(1)降低依赖服务请求量,降低其服务压力;
(2)减少调用依赖服务时的网络消耗;
(3)减少调用线程池的线程占用。
缺点:
增加了接口的响应时间。
3.5 适用场景
(1)适用于响应时间原本就较长的接口,那合并请求导致的延迟对其几乎无影响;
(2)适用于并发性能较高,且网络消耗占比较高的接口,那合并请求后,可以减少网络消耗。
4.请求缓存
从Hystrix处理流程中,我们可以知道,Hystrix可以使用缓存来提高接口性能,相关注解如下图:
注:Hystrix缓存的是同一次请求中相同cachekey的查询结果,当新请求进来时,之前的缓存已失效。
5.熔断器状态
5.1 状态说明
(1)打开状态:该状态下,该接口请求会被拦截,直接进行失败处理;
(2)关闭状态:该状态下,该接口请求不会被拦截,可以继续后续处理;
(3)半打开状态:当熔断器打开一段时间后(默认是5秒),Hystrix会允许进行一次接口请求,当请求成功后,关闭熔断器,若请求失败,则再等一段时间后,再进行打开尝试。
5.2 熔断器打开条件
(1)请求总量达到阈值(默认是10秒内20个请求);
(2)错误率达到阈值(默认是10秒内错误率50%)。
6.信号量与线程隔离
信号量和线程池处理请求的流程如下图:
线程池流程:当用户请求进来时,熔断器会向线程池请求资源,若请求到资源,则将请求放入线程池中执行,执行完成请求后,再将资源放入线程池;若请求不到资源,则进行降级处理,调用fallback方法。
信号量流程:当用户请求进来时,熔断器会请求信号量,若请求到信号量,则直接执行请求,执行完成请求后,再释放信号量;若请求不到信号量,则进行降级处理,调用fallback方法。
两者对比:
四、Hystrix实战
1.Ribbon服务提供者
注:若已看过《微服务精通之Eureka原理解析》,则此章节可跳过。
(1)创建名为service-hi的maven工程
(2)引入Eureka-client依赖
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.hxq</groupId>
<artifactId>spring-cloud-hxq</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>service-hi</artifactId>
<name>service-hi</name>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(1)Web依赖:spring-boot-starter-web;
(2)Eureka-client依赖:spring-cloud-starter-netflix-eureka-client。
(3)创建启动类
package com.hxq;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 服务提供者
*
* @author Administrator
*
*/
@SpringBootApplication
@EnableEurekaClient
@RestController
public class ServiceHiApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceHiApplication.class, args);
}
/**
* 服务端口
*/
@Value("${server.port}")
private String port;
@RequestMapping("/hi")
public String home(@RequestParam(value = "name", defaultValue = "hxq") String name) {
return "hi " + name + " ,i am from port:" + port;
}
}
启动类要加上@EnableEurekaClient注解。
(4)创建application.yml配置文件
server:
port: 9100
spring:
application:
name: service-hi
eureka:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
配置说明:
server.port:Eureka客户端服务端口
spring.application.name:应用名称,将会显示在Eureka界面的应用名称列。
eureka.serviceUrl.defaultZone:Eureka服务器的地址,类型为HashMap,缺省的Key为 defaultZone;缺省的Value为 http://localhost:8761/eureka。如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。
2.修改Ribbon消费者
注在微服务精通之Ribbon原理解析中的service-ribbon服务的基础上进行修改。
(1)增加Hystrix依赖
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.hxq</groupId>
<artifactId>spring-cloud-hxq</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>service-ribbon</artifactId>
<name>service-ribbon</name>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
</project>
新增Hystrix依赖:spring-cloud-starter-netflix-hystrix。
(2)启动类增加Hystrix注解
package com.hxq;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* 服务消费者
*
* @author Administrator
*
*/
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableHystrix
public class ServiceRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceRibbonApplication.class, args);
}
/**
* 负载均衡器
*
* @return
*/
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
启动类新增@EnableHystrix注解,以启动Hystrix服务。
(3)修改测试接口
package com.hxq.service.impl;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.hxq.service.HelloService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult;
@Service("helloService")
public class HelloServiceImpl implements HelloService {
@Resource
private RestTemplate restTemplate;
@Override
@CacheResult(cacheKeyMethod = "getCacheKey")
@HystrixCommand(fallbackMethod = "hiError")
public String hiService(String name) {
return restTemplate.getForObject("http://SERVICE-HI/hi?name={1}", String.class, name);
}
private String getCacheKey(String name) {
return "cache::" + name;
}
public String hiError(String name) {
return "hi," + name + ",sorry,error!";
}
}
(1)@CacheResult注解:参考上述“请求缓存”章节,标注此接口使用接口缓存,缓存键值通过getCacheKey方法生成;
(2)@HystrixCommand注解:参考上述“请求命令和执行”章节,标注此接口是一个单操作的Hystrix接口,接口调用失败后的回调函数为hiError方法。
(4)增加过滤器,初始化Hystrix上下文
注:若不使用请求缓存,可不添加此过滤器。
package com.hxq.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import org.springframework.stereotype.Component;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
/**
* 设置Hystrix上下文
*
* @author Administrator
*
*/
@Component
@WebFilter(urlPatterns = "/*", asyncSupported = true)
public class HystrixRequestContextFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 上下文初始化
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
chain.doFilter(request, response);
} finally {
context.close();
}
}
}
3.服务验证
(1)启动service-hi服务
(2)启动service-ribbon服务
(3)调用测试接口
在浏览器中输入http://localhost:9200/hi
请求成功
如下图,可以看到接口返回service-hi服务的数据。
请求失败
如下图,可以看到接口返回回调方法的数据。