Hystrx权威指南--Hystrix是什么
来源:互联网 发布:linux curl 请求url 编辑:程序博客网 时间:2024/06/09 13:56
随着互联网发展,传统的大型机单块应用到后来的分布式计算及发展到现在的微服务,Docker的发展更是让微服务如虎添翼。微服务的优点不必细说,网上一搜一大把,这里就聊一下微服务带来的问题和如何去解决这些问题。通过这些问题分析下Hystrix是如何成为设计微服务利器的。
微服务带来的问题:
一、增加服务依赖复杂度:单块应用拆分成微服务之后,原来运行在同一个JVM进程内的功能,可能要分到多个JVM进程中,拆分后的微服务之间通过RPC调用来共同完成之前的功能,这就必然会增加服务依赖的复杂度;
二、不能FailFast : 原来系统是在同一个JVM进程内的,不存在网络调用,异常就会直接失败,不会导致处理hang住。微服务之后,逻辑功能则是由网络上的另一个服务提供的,如果提供者处理速度很慢,甚至最后会失败(超时),服务的请求端在返回失败之前就会一直hang在这里;另外在一段时间内90%的请求都返回失败,第91个请求就不需要再发送给服务端以增加服务端的压力了,直接fast fail,走后续流程即可。一段时间之后,再次尝试请求。
三、不能平均分配资源:微服务之后,服务的依赖会增多,每个依赖的服务端性能和吞吐量都不尽相同,这时就要隔离每个依赖所占有的资源。比如某个机器P1有三个服务SA、SB、SC,分别依赖于A、B、C三个依赖,一段时间之后,A依赖的服务端响应速度开始变慢,这时S1所在机器的CPU、线程、IO等资源都会耗尽到A依赖上,而没有空闲CPU、线程、IO等资源去处理B、C的依赖。以至于S1不仅不能对外正常提供SA服务,连SB、SC服务都会受到影响,最后甚至会导致机器P1宕机。
以上提到的这三点主要问题也是Hystrix提供的核心功能:监控、熔断器、隔离。
Hystrix简介
Hystrix天生就是为微服务提供的,是由Netflix公司开发,目前为Netflix公司每天处理着数百亿的线程隔离和更多的信号隔离请求,代码托管在Github(https://github.com/Netflix/Hystrix)上。Hystrix的监控可以随时查看一个微服务运行的健康情况,内部线程、信号占用率;熔断器则可以实现快速失败,同时还能保护后端服务;隔离机制更是为微服务的高效运行保驾护航,当某一个依赖出现异常时能够及时进行降级保护。
隔离
上面提到计算及的CPU、线程、IO等资源都是非常宝贵而且是有限的,当被使用达到上限时,那么距离该机器宕机也就不远了。另外一个容器对外提供的服务会有多个,可能每个服务都有一个依赖,当某一个依赖运行出现异常时,只能使该依赖对应的那个服务对外不可用,而其他的服务都应该是正常运行的。
而现实中恰恰是当某一个服务不可用时,紧接着第二个第三个服务都会出现问题。这就是没有对依赖进行隔离的原因。因为有一个依赖运行异常,该机器上的所有CPU、线程等资源都过来处理这个依赖资源的请求了,没有空闲的资源去处理其他服务的请求。这时就需要对每个依赖进行资源隔离。当某个依赖出现问题,只会有分配给该依赖的线程、IO等处于忙碌状态,其他线程、IO等资源应该正常处理其他的依赖。
Hystrix针对依赖资源的隔离提供了两种策略,分别是:线程池隔离和信号隔离。
为什么用Hystrix
在一个复杂的分布式架构系统中,依赖服务是避免不了的,特别是在一个微服务化的时代。如果一个系统不能对相关的依赖进行隔离,那该系统就会有被拖垮被宕机的风险。在一个高并发高访问量的系统中,当一个被依赖的服务出现网络连接缓慢、处理延迟等情况,在几分钟甚至几秒内,所有的线程都会block在被依赖的资源上,进而导致所有资源被耗尽,系统被拖垮。
所有依赖服务都正常时:
当其中一个依赖出现延迟时,服务器的大部分线程都会block在依赖I上,在一个高流量的系统中,几秒内所有的线程资源都被blocking在了依赖上,从而导致系统的其他服务也不可用,甚至整个系统被拖垮,不可用。
Hystrix对依赖进行隔离和控制,通常这些依赖都是网络调用。Hystrix通过线程池和信号进行隔离,给每一个依赖分配指定的资源,当该依赖的资源不足(线程池爆满、信号tryAcquire) 就会直接返回失败,不会占用其他依赖的资源。
同时,Hystrix还提供了CircuitBreaker机制,当失败率达到某一个阀值时,Hystrix会Fast Fail 并迅速恢复,或者优雅的降级。
环境搭建
使用Maven搭建的项目,在pom.xml文件中引入hystrix-core坐标:
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.4.18</version>
<artifactId>hystrix-core</artifactId>
<version>1.4.18</version>
</dependency>
环境搭建好之后就可以开发了,接下来会写如何开发Hystrix应用。
Hystrix开发
首先还从HelloWorld 开始,首先定义一个Command类,继承HystrixCommand类,在构造函数中设置相关参数。重写run() 和 getFallback() ,业务逻辑实现放在run方法里。所有非HystrixBadRequestException异常都会调用getFallback方法,降级方案一般在getFallback方法中实现。
class HelloWorldCommand extends HystrixCommand<String> {
protected HelloWorldCommand(String name) {
super(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(name)));
}
@Override
protected String run() throws Exception {
System.out.println("run success " + Thread.currentThread().getName());
return "run success " + Thread.currentThread().getName();
}
protected HelloWorldCommand(String name) {
super(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(name)));
}
@Override
protected String run() throws Exception {
System.out.println("run success " + Thread.currentThread().getName());
return "run success " + Thread.currentThread().getName();
}
}
很简单,在run方法里面返回了当前的线程名字。执行下 TestCase ,观察打印数据
@Test
public void helloWorldCommand() {
HelloWorldCommand helloWorldCommand = new HelloWorldCommand("klov");
String result = helloWorldCommand.execute();
System.out.println("【HelloWorldCommand】 result = "+result);
public void helloWorldCommand() {
HelloWorldCommand helloWorldCommand = new HelloWorldCommand("klov");
String result = helloWorldCommand.execute();
System.out.println("【HelloWorldCommand】 result = "+result);
}
打印数据如下:
run success hystrix-klov-1
【HelloWorldCommand】 result = run success hystrix-klov-1
由输出数据可知线程池的名字默认是 hystrix+commandGroupKey+inde。
到此,Hystrix的开发环境以及基本的开发已经完成,后面会继续介绍Hystrix两个隔离方式的开发及原理。
线程池隔离
使用线程会在以下三个场景带来性能消耗:
1、线程的创建和销毁;
2、线程上下文空间的切换,线程池调度需要操作系统介入,系统需要从用户态空间切换到内核态空间,调度完成后又需要切回到用户态空间。
Hystrix通过使用固定线程池大小的方式解决了第一个问题,在创建线程池时,Hystrix只允许你设置CoreSize,而不允许你设置MaxSize。在系统初始化线程池时,MaxSize等于CoreSize。这样就避免了线程的频繁创建和销毁带来的性能消耗。Hystrix的线程池创建源码是在HystrixConcurrencyStrategy类getThreadPool方法中实现的:
public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { return new ThreadPoolExecutor(corePoolSize.get(), maximumPoolSize.get(), keepAliveTime.get(), unit, workQueue, new ThreadFactory() { protected final AtomicInteger threadNumber = new AtomicInteger(0); @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r, "hystrix-" + threadPoolKey.name() + "-" + threadNumber.incrementAndGet()); thread.setDaemon(true); return thread; } }); }跟普通的创建线程池没什么区别,根据传进来的线程池参数创建一个线程池。但是玄机出现在调用的地方,传递的corePoolSize和maximumPoolSize是同一个值。调用方是HystrixThreadPool类的173行
public HystrixThreadPoolDefault(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesDefaults) { this.properties = HystrixPropertiesFactory.getThreadPoolProperties(threadPoolKey, propertiesDefaults); HystrixConcurrencyStrategy concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy(); this.queueSize = properties.maxQueueSize().get(); this.queue = concurrencyStrategy.getBlockingQueue(queueSize); this.metrics = HystrixThreadPoolMetrics.getInstance( threadPoolKey, //这里是调用线程池创建的地方,corePoolSize和maximumPoolSize传递的都是coreSize()这个值 concurrencyStrategy.getThreadPool(threadPoolKey,properties.coreSize(), properties.coreSize(), properties.keepAliveTimeMinutes(), TimeUnit.MINUTES, queue), properties); this.threadPool = metrics.getThreadPool(); /* strategy: HystrixMetricsPublisherThreadPool */ HystrixMetricsPublisherFactory.createOrRetrievePublisherForThreadPool(threadPoolKey, this.metrics, this.properties); }
信号量隔离
为解决线程池隔离带来的第二性能开销的场景,Hystrix使用了信号量隔离。信号量隔离通过原子操作类AtoInteger实现Permits的管理,AtoInteger类使用的是CAS操作,相对于锁,CAS是通过硬件实现的原子操作,减小了性能开销。下面是获取Permit的源码分析:
public boolean tryAcquire() { //count是AtoInteger类型的成员变量 int currentCount = count.incrementAndGet(); if (currentCount > numberOfPermits.get()) { count.decrementAndGet(); return false; } else { return true; } } tryAcquire获取Permit成功,调用方要在finally中释放Permit,调用release方法。 public void release() { //对Permit进行减一操作 count.decrementAndGet(); }
建议不同的业务之间通过线程池隔离,同一个业务不同的依赖资源则可以通过信号量隔离,以提高吞吐量和性能。
熔断器
熔断器的功能等同于家庭电路中的自动跳闸器,当电路中流经的电流负荷过高或者有漏电等非安全用电情况时,跳闸器就会自动跳闸,起到对家用电器保护的作用。软件系统中的熔断器和跳闸器是类似的,当请求回路中发生异常时,熔断器打开。只是Hystrix会在一段时间后试着关闭回路,让部分请求发送成功,以检测异常是否恢复。
熔断器机制是指,在后端服务可用率降低到阀值以下时,新来的请求不再发给后端服务,直接返回请求失败即可,以实现 Fail-Fast机制,快速给用户请求,执行后续的失败流程。这样既可以拦截不必要的请求,减少对后端本来就异常的服务的压力,还可以实现Fail-Fast机制。 Hystrix默认熔断器机制是开启的,可以在HystrixCommand的run方法中设置为开启,设置代码如下:
HystrixCommandProperties.Setter().withCircuitBreakerEnabled(true)
开启熔断器机制后,Hystrix默认的阀值是50%。如果在10秒内请求失败率达到50%及以上,Hystrix会自动断开回路,后面的请求不会再被发往后端的服务器中,会直接返回给客户端。5秒之后,Hystrix会试着关闭回路,放一部分请求过去,以检测异常是否恢复。
HystrixCircuitBreakerImpl类的allowSingleTest方法中实现该功能,源码如下:
public boolean allowSingleTest() { long timeCircuitOpenedOrWasLastTested = circuitOpenedOrLastTestedTime.get(); // 1) if the circuit is open // 2) and it's been longer than 'sleepWindow' since we opened the circuit if (circuitOpen.get() && System.currentTimeMillis() > timeCircuitOpenedOrWasLastTested +properties.circuitBreakerSleepWindowInMilliseconds().get()) { // We push the 'circuitOpenedTime' ahead by 'sleepWindow' since we have allowed one request to try. // If it succeeds the circuit will be closed, otherwise another singleTest will be allowed at the end of the 'sleepWindow'. if (circuitOpenedOrLastTestedTime.compareAndSet(timeCircuitOpenedOrWasLastTested, System.currentTimeMillis())) { // if this returns true that means we set the time so we'll return true to allow the singleTest // if it returned false it means another thread raced us and allowed the singleTest before we did return true; } } return false; }properties.circuitBreakerSleepWindowInMilliseconds().get()取的值就是设置的间隔时间,默认是5秒。在每次打开熔断器时都要保存当前时间,在下次决策是否需要关闭回路时,判断距离上次开启时间是否达到设置的值。在Hystrix中大量使用了CAS在保证成员变量的原子操作前提下,又提高了性能。
阅读全文
0 0
- Hystrx权威指南--Hystrix是什么
- Hystrx权威指南--Hystrix隔离策略
- Hystrx权威指南--Hystrix调用方法解析
- Hystrx权威指南--Hystrix的Timeout解析
- Hystrx权威指南--Hystrix执行流程
- Hystrx权威指南--Hystrix属性配置策略
- Hystrx权威指南--Hystrix实现原理
- Hystrx权威指南--Hystrix的注解方式
- Hystrx权威指南--Hystrix的类是如何组织的
- Hystrx权威指南--Hystrix的线程池解析
- Hystrx权威指南--Hystrix的熔断机制解析
- Hystrx权威指南--Hystrix请求缓存和COLLAPSER
- 【Hystrix权威指南二】Hystrix隔离策略
- 【Hystrix权威指南一】Hystrix开发之旅
- 【Hystrix权威指南三】Hystrix隔离策略源码分析一
- 【Hystrix权威指南四】Hystrix隔离策略源码分析二
- Hystrix是什么
- Hystrix入门指南
- Stack/Set/Map/Queue浅析
- Java多线程用法
- spring data redis 操作redis
- TCP/IP四层模型与OSI参考模型
- 剑指offer面试题59 对称的二叉树(java实现)
- Hystrx权威指南--Hystrix是什么
- Chapter04 编写基本的MapReduce程序(三) 非JAVA语言编写mapreduce程序2
- Linux curl命令模拟Http请求(get/post),绑定host访问
- 8.7 特征分解
- redis集群搭建
- Storm-HBase集成--配置和开发
- 使用docker部署codis
- ETL之kettle数据同步
- Android 开发实用小技巧