- 隔离原理
- 用户的请求将不再直接访问服务,而是通过线程池中的空闲进程来访问服务,如果线程池已满,则会进行降级处理,用户的请求不会被阻塞,至少可以看到一个执行结果,而不是无休止的等待或者看到系统崩溃。
- 其本质是将服务视为资源,当请求该资源的数量超过了线程池中的数量限制时则不可以在对该资源进行访问,从而保护该资源不会过载而造成阻塞。
- 熔断模式
- 如果某个目标服务调用慢或者有大量超时,此时熔断该服务的调用,对于后续调用请求,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
- 开发示例
- 新建maven项目
- 配置依赖
- 这里注意,Hystrix版本1.5.10使用的log为log4j2。需要添加的依赖为(示例中只用到了log4j-slf4j-impl,log4j-core 和 log4j-api):
-
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.10</version>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
<version>1.2.9</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
- 添加配置文件:log4j.xml,可根据需要进行更改。查看详细内容 https://github.com/apache/logging-log4j2
-
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<File name="A1" fileName="A1.log" append="false">
<PatternLayout pattern="%t %-5p %c{2} - %m%n"/>
</File>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="org.apache.log4j.xml" level="debug">
<AppenderRef ref="A1"/>
</Logger>
<Root level="debug">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
- 新建简单的类。Hystrix使用的是命令模式,我们就拿这个举例
-
public class CommandHelloWorld extends HystrixCommand<String>{
public static final Logger LOGGER = LoggerFactory.getLogger(CommandHelloWorld.class);
public final String name;
protected CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("Xiaofei"));
this.name = name;
}
@Override
protected String run(){
return "Hello "+ name + "!";
}
public static void main(String[] args) {
String str = new CommandHelloWorld("Xiaofei").execute();
LOGGER.info(str);
}
}
- 使用总结:Hystrix在Spring boot中的使用,可以用来控制对某个服务的请求,对于请求数过多(线程池已满)或者请求超时(服务不可用)或者其他服务无法及时返回结果的情况,用Hystrix可以加以控制,避免其他依赖此服务的服务阻塞而导致整个系统的崩溃等问题。
- 运行原理
- 构造一个HystrixCommand或HystrixObservableCommand对象,对象的参数可以传入请求所带的参数在。
- 处理命令:
execute()- 块,然后返回从依赖关系接收的单个响应(或者在发生错误时抛出异常)
queue()- 返回一个Future可以从依赖关系中获得单个响应的结果
observe()- 订阅代表依赖Observable关系的响应,并返回一个Observable复制该源的响应Observable
toObservable()- 返回一个Observable,当您订阅它时,将执行Hystrix命令并发出其响应
- 回应是否缓存,查看该命令是否启用了请求缓存,如果启用并且缓存中对该请求的响应可用,则此缓存响应将立即返回
- 回路是否打开,即检查回路是否被熔断,如果熔断,则该命令将不会被执行,会将流程回退(即重新开始检查是否缓存,是否回路打开,线程池是否已满,或者信号指示是否可用)。
- 线程池/队列/信号量是否完整,如果不满足要求,命令就会回退。
- 执行命令,如果发生异常或者执行失败,会继续回退。
- 命令执行成功后进行超时判断,如果超时就再回退重新执行,否则返回结果。
- 备注:在线程池/队列/信号量是否完整,执行命令是否失败和命令是否超时的时候,会返回一个度量标准用于计算回路的健康状态,如果超出指标,那么回路便会发生熔断,发生熔断后也会进入回退操作。
- 回退操作都做了什么:在命令执行失败之后,Hystrix会尝试恢复到回退,回退中一般需要编写后备逻辑(正式开发中一定要编写后备逻辑),如果没有后备逻辑,那么会根据调用Hystrix的命令的同而返回不同的结果:
execute() - 抛出异常
queue()- 成功返回一个Future,但Future如果get()调用它的方法,这将抛出异常
observe()- 返回一个Observable,当您订阅它时,将通过调用用户的onError方法立即终止
toObservable()- 返回一个Observable,当您订阅它将通过调用订户的onError方法终止
- 断路器
- 断路器设置在Hystrix命令的入口处,即命令执行中根据命令执行返回的结果计算的回路健康状态,如果是不健康的即如果断路器处于打开状态,即回路是短路的,此时命令就会进入断路器的处理方法中,注意断路器打开的时候不是熔断状态,此时如果允许请求,那么请求会在这里进行处理,一般是断路器打开时请求进入休眠状态(在请求数未超过断路器中设置的峰值的情况下),如果到达峰值后又有新的请求进来,那么最早处于断路器请求状态中的请求会被踢出,即这些请求会返回失败。
- HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()的值用来控制断路器的短路时间,经过这段时间以后,下一个请求会要求通过,如果请求失败,断路器会继续返回打开状态,如果请求成功了,断路器将会被关闭,请求会被命令执行逻辑接管。
- 线程和线程池
- Hystrix为每一个依赖创建一个线程池,即对某一个依赖进行请求的时候,如果线程池线程已经满了,那么请求就会回退。这样做的好处是可以在程序恢复正常以后迅速做出反应,而不是等服务器恢复正常。每个依赖建立单独的线程池,每个命令单独建立线程处理,线程池之间是隔离的,即如果某一个服务无法正常使用导致这个线程池中的所有线程都处于非正常状态下,但是对其他的线程池并不会产生影响。
- 线程和线程池的这种设定无疑会增加系统的开销。所以对线程池中线程数量的分配应合理使用。
- Semaphores(信号量/计数器)
- 不使用线程池的情况下使用信号量,即使用计数器限制对给定依赖的并发请求数。
- 请求折叠
- 多个相同的请求也可以进行折叠为一个线程池中的一个线程,发送一次网络请求,将返回结果分发给各个请求。
- 请求折叠会自动进行,一般不需要开发人员手动协调批量请求。