1、hystrix是什么
Hystrix是一款开源的容错插件,具有依赖隔离,系统容错降级等功能,这也是其最重要的两种用途,还有请求合并等功能
2、为什么要进行隔离
在实际工作中,尤其是分布式、微服务越来越普遍的今天,一个服务经常需要调用其他的服务,即RPC调用,而调用最多的方式还是通过http请求进行调用,这里面就有一个问题了,如果调用过程中,因为网络等原因,造成某个服务调用超时,如果没有熔断机制,此处的调用链路将会一直阻塞在这里,在高并发的环境下,如果许多个请求都卡在这里的话,服务器不得不为此分配更多的线程来处理源源不断涌入的请求
更恐怖的是,如果这是一个多级调用,即此处的服务的调用结果还被其他服务调用了,这就形成了所谓的雪崩效应,后果将不堪设想
因此,需要某种机制,在一定的异常接口调用出现的时候,能够自动发现这种异常,并快速进行服务降级,这就是hystrix要发挥的作用,常见的降级处理方式包括,超时处理、线程池隔离和信号量隔离,下面就对这几种方式做简单的说明
1、导入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<hystrix.version>1.5.12</hystrix.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- hysrtix -->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>${hystrix.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-metrics-event-stream</artifactId>
<version>${hystrix.version}</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>${hystrix.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.netflix.hystrix/hystrix-core -->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.12</version>
</dependency>
<dependency>
<groupId>org.databene</groupId>
<artifactId>contiperf</artifactId>
<version>2.3.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 指定maven版本 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2、配置bean组件
由于我这里使用的是springboot的方式,如果想要整合springboot使用,需要添加一个bean 的配置,网上也有通过配置文件的方式,那也是可以的
@Configuration
public class HystrixConfig {
@Bean
public HystrixCommandAspect hystrixCommandAspect(){
return new HystrixCommandAspect();
}
}
3、创建一个接口,模拟远程调用其他服务接口
@GetMapping("/createOrder")
public String createOrder(){
return orderService.createOrder();
}
接口代码很简单,就是远程获取一个订单号,下面看实现类,首先看第一种方式,即接口超时的处理,hystrix提供了超时降级熔断机制,即通过添加注解的方式,当这个注解配置在某个方法上面的时候,就会对这个方法生效,当然也可以直接写在接口上,超时配置代码如下,
方式1,超时降级
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(
commandKey = "createOrder",
commandProperties = {
@HystrixProperty(name="execution.timeout.enabled", value="true"),
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="3000"),
},
fallbackMethod = "createOrderFallbackMethod4Timeout"
)
public String createOrder() {
String orderNo = restTemplate.getForObject("http://localhost:8087/getUserOrder",String.class);
return "下单成功,订单号是:" + orderNo;
}
这里我们设置超时时间为3秒,在注解上有一个叫做fallbackMethod 的,这个里面对应的方法就是当达到超时时间时走的方法,如下,
//超时降级的方法
public String createOrderFallbackMethod4Timeout() throws Exception {
System.err.println("-------超时降级策略执行------------");
return "hysrtix timeout !";
}
下面我们来测试一下,在另外一个工程中,我这里提供了一个非常简单的接口,用以测试调用,
@RestController
public class UserInfoController {
@GetMapping("/getUserOrder")
public String getUserOrder(){
return UUID.randomUUID().toString().substring(0,6);
}
}
启动两个demo工程,为了模拟效果,我们将被调用的接口设置休眠5秒
@GetMapping("/getUserOrder")
public String getUserOrder() throws Exception{
Thread.sleep(5000);
return UUID.randomUUID().toString().substring(0,6);
}
启动完毕,浏览器访问:http://localhost:8083/order/createOrder,从返回结果可以看到,超时降级策略执行了,
超时降级也可在类级别定义降级执行方法
1、在类上方声明注解@DefaultProperties(defaultFallback = "defaultFallback")//默认服务降级,这里采用默认的服务降级,defaultFallback属性表示要编写的方法(服务降级的提示)
2、在方法上声明注解@HystrixCommand即可
方法层定义超时时间
@HystrixCommand(commandProperties = {
@HystrixProperty(name="execution.timeout.enabled", value="true"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")
})
默认降级执行方法
public ResponseEntity<String> defaultFallback(){
return ResponseEntity.ok().body("服务访问超时,请稍后访问~~");
}
方式2,线程池隔离
所谓线程池隔离,就是每个过来的请求,系统会单独将这个请求的执行放在一个线程或线程池里,这个根据自己的需求进行配置,以后,相同的请求不管有多少打进来,都会在指定数量的线程池内进行,因此不会出现诸如线程资源耗尽的情况,这种隔离方式在某些场景下是非常有用的,下面来看具体的代码,
线程池隔离这里同样采用注解的方式,只是注解里面的相关配置发生了变化,这里我们仍以请求创建订单号为例,
// 限流策略:线程池方式
@HystrixCommand(
commandKey = "createOrder",
commandProperties = {
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD")
},
threadPoolKey = "createOrderThreadPool",
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "3"),
@HystrixProperty(name = "maxQueueSize", value = "5"),
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "7")
},
fallbackMethod = "createOrderFallbackMethod4Thread"
)
public String createOrder() {
String orderNo = restTemplate.getForObject("http://localhost:8087/getUserOrder", String.class);
return "下单成功,订单号是:" + orderNo;
}
提供一个熔断超时的方法,
//线程池方式降级
public String createOrderFallbackMethod4Thread() throws Exception {
System.err.println("-------超时线程池降级策略执行------------");
return "hysrtix timeout !";
}
这个线程池隔离的方式模拟起来稍显麻烦,我这里直接在接口里面通过线程池的方法发起100个请求进行模拟测试,代码如下,
@GetMapping("/createOrder")
public String createOrder(){
ExecutorService executorService = Executors.newCachedThreadPool();
CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
Semaphore semaphore = new Semaphore(concurrentThreadNum);
for (int i = 0; i< requestTotal; i++) {
executorService.execute(()->{
try {
String result = orderService.createOrder();
System.out.println(result);
semaphore.release();
} catch (Exception e) {
System.out.println(e);
e.printStackTrace();
}
countDownLatch.countDown();
});
}
// return orderService.createOrder();
return "success";
}
然后启动工程,浏览器访问:http://localhost:8083/order/createOrder,通过控制台打印结果,可以看到,由于并发请求远超过我们在注解上配置的参数,因此走了线程池隔离的熔断方法,但由于这里并不会影响主线程,因此最终接口还是打印了主线程的结果,
方式3,信号量隔离
信号量的资源隔离只是起到一个开关的作用,比如,服务 A 的信号量大小为 10,那么就是说它同时只允许有 10 个 tomcat 线程来访问服务 A,其它的请求都会被拒绝,从而达到资源隔离和限流保护的作用,下面直接上代码,基本配置和上述两种相似,就是注解不同,
@HystrixCommand(
commandKey="createOrder",
commandProperties= {
@HystrixProperty(name="execution.isolation.strategy", value="SEMAPHORE"),
@HystrixProperty(name="execution.isolation.semaphore.maxConcurrentRequests", value="6")
},
fallbackMethod = "createOrderFallbackMethod4semaphore"
)
public String createOrder() {
String orderNo = restTemplate.getForObject("http://localhost:8087/getUserOrder", String.class);
return "下单成功,订单号是:" + orderNo;
}
超时熔断方法
// 信号量方式降级
public String createOrderFallbackMethod4semaphore() throws Exception {
System.err.println("-------信号量降级策略执行------------");
return "hysrtix timeout !";
}
这里通过字面意义理解,我们仍然可以用上述的接口进行模拟,启动工程,浏览器访问:http://localhost:8083/order/createOrder,通过控制台可以看到,由于我们在注解的配置里面设置的是6个,即允许同时通过的是6个请求,而我们请求的时候使用的是100个,达到了阈值,触发了熔断机制,
这里提供一个比较好用的模拟并发请求的组件,项目添加如下依赖即可,然后在自己的单元测试类中即可使用,下面贴上简单的使用代码,即只需要在你的测试方法上添加 @PerfTest(invocations = 10,threads = 10)这样的注解即可,里面的参数可以动态配置,
<dependency>
<groupId>org.databene</groupId>
<artifactId>contiperf</artifactId>
<version>2.3.4</version>
<scope>test</scope>
</dependency>
@SpringBootTest
@ContextConfiguration(classes = HystrixTest.class)
public class HystrixTest {
@Autowired
private OrderService orderService;
public ContiPerfRule contiPerfRule = new ContiPerfRule();
@Test
@PerfTest(invocations = 10,threads = 10)
public void test1() {
String result = orderService.createOrder();
System.out.println(result);
}
}