本篇文章将讲Hystrix,该组件的核心功能就是请求熔断,服务降级。当然还有其他的功能:依赖隔离、请求缓存、请求合并。
1.请求熔断: 当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务. 断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN).
这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN). Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力.
2.服务降级:Fallback相当于是降级操作. 对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值. fallback方法的返回值一般是设置的默认值或者来自缓存.告知后面的请求服务不可用了,不要再来了。
3.依赖隔离(采用舱壁模式,Docker就是舱壁模式的一种):在Hystrix中, 主要通过线程池来实现资源隔离. 通常在使用的时候我们会根据调用的远程服务划分出多个线程池.比如说,一个服务调用两外两个服务,你如果调用两个服务都用一个线程池,那么如果一个服务卡在哪里,资源没被释放,后面的请求又来了,导致后面的请求都卡在哪里等待,导致你依赖的A服务把你卡在哪里,耗尽了资源,也导致了你另外一个B服务也不可用了。这时如果依赖隔离,某一个服务调用A B两个服务,如果这时我有100个线程可用,我给A服务分配50个,给B服务分配50个,这样就算A服务挂了,我的B服务依然可以用。
4.请求缓存:比如一个请求过来请求我userId=1的数据,你后面的请求也过来请求同样的数据,这时我不会继续走原来的那条请求链路了,而是把第一次请求缓存过了,把第一次的请求结果返回给后面的请求。
5.请求合并:我依赖于某一个服务,我要调用N次,比如说查数据库的时候,我发了N条请求发了N条SQL然后拿到一堆结果,这时候我们可以把多个请求合并成一个请求,发送一个查询多条数据的SQL的请求,这样我们只需查询一次数据库,提升了效率。
为什么需要有请求熔断呢?
例如:订单系统请求库存系统,结果一个请求发过去,库存系统服务挂掉或者是因为网络等问题很久没有反应,而这个时候更多的请求来到库存系统,就不断的创建线程,因为没有返回,线程得不到释放,导致服务崩溃。这时,Hystrix就可以解决这个问题。当订单系统请求库存系统时,库存系统两秒钟没有反应,就直接返回一个错误,这个错误具体返回什么就是服务降级里面的操作了。
如下图所示:
整个流程可以大致归纳为如下几个步骤:
下面代码实操一下。因为请求都是从Ribbon发起的,所以就在之前的Ribbon工程中进行操作。
一、引入Hystrix依赖:
下面的dashboard可以不用:仪表盘,对服务进行监控
org.springframework.cloud
spring-cloud-starter-hystrix
1.4.0.RELEASE
org.springframework.cloud
spring-cloud-starter-hystrix-dashboard
1.4.0.RELEASE
二、在启动类中加@EnableCircuitBreaker注解,代表允许断路器
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
}
三、创建HelloService类
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.ObservableExecutionMode;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey;
import com.netflix.hystrix.contrib.javanica.command.AsyncResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import rx.Observable;
import rx.Subscriber;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@Service
public class HelloService {
@Autowired
private RestTemplate restTemplate;
/**
*@Author ZNX
*@Date 2018/8/4 22:13
*@Description:@HystrixCommand:代表调用下面的方法发送请求
* fallbackMethod:请求降级:由我们控制,降级之后进行什么逻辑
* 内部参数相当于方法名
*/
@HystrixCommand(fallbackMethod = "helloFallBack")
public String helloService(){
return restTemplate.getForEntity("http://HELLO-SERVICE/hello",String.class).getBody();
}
//降级后执行该方法
public String helloFallBack(Throwable throwable){
return "error";
}
}
四:在Controller中注入Service并调用方法:
@RestController
public class ConsumerController {
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private HelloService helloService;
@RequestMapping("/consumer")
public String helloConsumer() throws ExecutionException, InterruptedException {
return helloService.helloService();
}
}
五、执行程序
正常状态下:可以访问(两个服务轮询访问)
这时关掉一个服务,
以上只是简单的演示,接下来进行深入的挖掘!
------------------------------------------------------------分割线------------------------------------------------------------------------------------
一、抛弃@HystrixCommand注解,在代码层面实现。
1.新建一个HelloServiceCommond类:
想实现HystrixCommand必须继承HystrixCommand类
public class HelloServiceCommand extends HystrixCommand{
private RestTemplate restTemplate;
protected HelloServiceCommand(String commandGroupKey,RestTemplate restTemplate) {
super(HystrixCommandGroupKey.Factory.asKey(commandGroupKey));
this.restTemplate=restTemplate;
}
/**
*@Author ZNX
*@Date 2018/8/4 22:28
*@Description:要执行的逻辑,要放在这个run()方法中执行
*/
@Override
protected String run() throws Exception {
System.out.println(Thread.currentThread().getName());
return restTemplate.getForEntity("http://HELLO-SERVICE/hello",String.class).getBody();
}
@Override
protected String getFallback() {
return "error";
}
@Override
protected String getCacheKey() {
return "hello";
}
}
2.修改Controller:
@RestController
public class ConsumerController {
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private HelloService helloService;
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/consumer")
public String helloConsumer() throws ExecutionException, InterruptedException {
HelloServiceCommand command = new HelloServiceCommand("hello",restTemplate);
String result = command.execute();
return result;
}
}
3.启动项目,访问成功
但是这种方式都是阻塞式的 ,效率低。
那么如何实现一个异步的IO呢?
我们这里只需要改变调用时候的执行方式就可以了。
将之前调用的Excute()方法改为queue()方法.方法改造如下:
@RequestMapping("/consumer")
public String helloConsumer() throws ExecutionException, InterruptedException {
HelloServiceCommand command = new HelloServiceCommand("hello",restTemplate);
Future future = command.queue();
return future.get();
}
重启项目,访问成功。
但是这里异步的优势并没有体现出来,接着对项目进行改造,加了几个时间和几个输出语句。
@RequestMapping("/consumer")
public String helloConsumer() throws ExecutionException, InterruptedException {
HelloServiceCommand command = new HelloServiceCommand("hello",restTemplate);
long now = System.currentTimeMillis();
Future future = command.queue();
System.out.println(1111111111);
long end = System.currentTimeMillis();
System.out.println(end-now);
String result = future.get();
long last = System.currentTimeMillis()-end;
System.out.println(last);
return result;
}
并且给其中一个服务睡眠800毫秒:这里不要睡太久,太久的话熔断器就熔断了——>服务降级
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello () throws InterruptedException {
System.out.println("service02");
Thread.sleep(800);
return "hello cloud02";
}
}
重启工程:访问:查看控制台结果:
上面是在代码层面实现异步。下面将在使用注解时进行异步实现。
修改HelloService:
@HystrixCommand(fallbackMethod = "helloFallBack")
public String helloService() throws ExecutionException, InterruptedException {
Future future = new AsyncResult() {
@Override
public String invoke() {
return restTemplate.getForEntity("http://HELLO-SERVICE/hello",String.class).getBody();
}
};
return future.get();
}
这样也是可以成功的。
初入SpringCloud,总结不到,还望指出。