Hystrix主要功能介绍:
降级:通过设置的fallback来实现,如果达到降级条件,则会调用fallback方法来返回降级后的内容
熔断:hystrix可以通过设置熔断开关进行强制打开熔断或关闭熔断,也可设置相关参数,在运行时由hystrix进行统计时间窗口内的服务错误情况自主进行“跳闸”处理
隔离模式:
线程隔离:默认
信号量隔离:可通过如下方法进行设置 HystrixCommandProperties.Setter().withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE))
Hystrix的使用:
Hystrix主要通过HystrixCommand来把依赖调用封装进run方法,通过执行command的execute()/queue做同步或异步调用。 当触发降级逻辑时,会调用command中的 fallback方法,触发的条件一般为如下4种:
(1):run()方法抛出非HystrixBadRequestException异常。( HystrixBadRequestException异常需要单独处理)。
(2):run()方法调用超时
(3):熔断器开启拦截调用
(4):线程池/队列/信号量是否跑满
在使用hystrix之前,首先要引入POM
<!-- hystrix -->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.4.22</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-metrics-event-stream</artifactId>
<version>1.4.22</version>
</dependency>
有多种方式使用Hystrix,分别如下:
1)注解方式,在方法上添加@HystrixCommand进行配置,具体参考微博 : https://blog.csdn.net/xiaomin1991222/article/details/84817401
2)命令模式封装依赖逻辑。写一个自定义的command 继承 HystrixCommand 即可,具体示例可以参考微博:https://blog.csdn.net/zjcsuct/article/details/78198632 里面介绍的比较全面
3)如果原来项目中各逻辑服务都已完成,不想全量添加注解或创建大量自定义command类,则可以采用spring切面来完成对逻辑方法(service)的熔断处理,但也会有一个弊端 -- 受制于spring切面的局限,无法对同一个service内的方法调用执行拦截。
如下为第3种方式的代码:
1、新建一个自定义注解,主要是为了让各方法自定义commandKey及返回降级报文(如果系统中可接受统一的降级报文,也可以不用自定义注解。切面拦截时针对包路径进行拦截,然后commandKey以Method名称为准。一般这种情况较少,故这里不做代码示例了)
@Retention(RetentionPolicy.RUNTIME)//注解会在class中存在,运行时可通过反射获取
@Target(ElementType.METHOD)//目标是方法
@Documented//文档生成时,该注解将被包含在javadoc中
public @interface HystrixConfig {
//组key
String groupKey() default "";
//指令key
String commandKey() default "";
//降级时返回的报文
String fallBackContent() default "{}";
}
2、实现切面逻辑,对自定义注解进行拦截
/**
* @program: dipapi
* @description: Hystrix熔断器切面,支持熔断器的配置可以动态修改。
* @author: 19042501
* @create: 2019-08-16 16:22
*/
@Component
@Aspect
public class HystrixCommandAdvice {
private Logger logger = Logger.getLogger(HystrixCommandAdvice.class);
@Autowired
private SystemVariableService systemVariableService;
@Around(value = "@annotation(hystrixConfig)")
public Object runHystrixCommand(final ProceedingJoinPoint jp, HystrixConfig hystrixConfig){
final String fallBackContent = hystrixConfig.fallBackContent();
HystrixCommand.Setter setter = setter(hystrixConfig);
HystrixCommand<Object> command = new HystrixCommand<Object>(setter)
{
@Override
protected Object run() throws Exception {
try {
return jp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
throw (Exception) throwable;
}
}
@Override
protected Object getFallback() {
return fallBackContent;
}
};
return command.execute();
}
/**
* 熔断器配置
* @param hystrixConfig
* @return
*/
private HystrixCommand.Setter setter(HystrixConfig hystrixConfig)
{
//如果要重设配置,需将原配置从缓存中清除掉,否则动态配置不生效 -- 不建议这么做,动态配置通过archaius来实现
//HystrixPropertiesFactory.reset();
String groupKey = hystrixConfig.groupKey();
String commandKey = hystrixConfig.commandKey();
String requestVolume = systemVariableService.getSystemConfigValueByKey("requestVolume");
HystrixCommandProperties.Setter hystrixSetter = HystrixCommandProperties.Setter()
//至少有10个请求,熔断器才进行错误率的计算
.withCircuitBreakerRequestVolumeThreshold(requestVolume == null ? 10 : Integer.valueOf(requestVolume))
//熔断器中断请求5秒后会进入半打开状态,放部分流量过去重试
.withCircuitBreakerSleepWindowInMilliseconds(5000)
//错误率达到50开启熔断保护
.withCircuitBreakerErrorThresholdPercentage(50);
//调用者执行的超时时间
//.withExecutionIsolationThreadTimeoutInMilliseconds(500)
//断路器开关标记,1:强制打开断路器,拒绝所有请求 2:强制关闭断路器,放行所有请求
//.withCircuitBreakerForceOpen(true);
return HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
.andCommandPropertiesDefaults(hystrixSetter)
//线程池大小 .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(10).withMaxQueueSize(50));
}
}
3、需要做降级或熔断的service设置注解
@Service
public class TestHystrixService {
@HystrixConfig(groupKey = "TestHystrixService", commandKey = "testPrint1", fallBackContent = "{this is fallback1}")
public String testPrint()
{
System.out.println("==== 111 begin ===");
return "====== test print ====";
}
@HystrixConfig(groupKey = "TestHystrixService", commandKey = "testPrint2", fallBackContent = "{this is fallback2}")
public String testPrint2() {
System.out.println("==== 222 begin ===");
try {
Thread.sleep(600);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
return "====== test print 222222 ====";
}
public String testPrint3() {
System.out.println("==== 3333 begin ===");
return testPrint2();
}
}
如此就可实现项目中已存的service加入熔断功能。
看到这里,不免会有疑问,这种方式与直接在每个service上直接添加@HystrixCommand注解有何区别呢?其实还是有区别的,至少配置的参数统一了,这样就可以通过读取db或配置文件,在aop内进行new HystrixCommand时设置相关参数。如果后期有调整,无需再一个个去service方法进行修改啦!
仅仅如些还是不够的,因为熔断的参数在我们实际投产的分布式系统中是有运行时动态调整的需求的。那么可以通过archaius来达到这一目的。
在上代码之前,先了解一下hystrix各配置生效的优先级,从低到高如下:
1 内置全局默认值:写死在代码里的值
2 动态全局默认属性:通过属性文件配置全局的值
3 内置实例默认值:写死在代码里的实例的值
4 动态配置实例属性:通过属性文件配置特定实例的值
也可参考hystrix中HystrixCommandProperties的代码:
private static HystrixProperty<Integer> getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, Integer builderOverrideValue, Integer defaultValue) {
return Factory.asProperty(new IntegerProperty(new DynamicIntegerProperty(propertyPrefix + ".command." + key.name() + "." + instanceProperty, builderOverrideValue), new DynamicIntegerProperty(propertyPrefix + ".command.default." + instanceProperty, defaultValue)));
}
我们切面中所设置的值属于第3级别,故建议切面代码中尽量少设置实例的值,尽可能采用配置的方式(即archaius的方式)
Archaius的使用较为简单,引入的hystrix的pom配置中是包含archaius相关包的,默认读取项目classpath下的config.properties文件。不清楚的可以参考博客:https://blog.csdn.net/liuchuanhong1/article/details/73718483
但不幸的是,我们的项目还没有接入配置中心,配置文件的修改发布的生产环境,涉及N台机器,太过繁琐。这种情况可以通过配置一个动态配置类,去加载我们DB或redis中设置的参数,就可以解决了。具体示例代码如下:
@Configuration
public class InitDynamicConfig {
@Bean
public DynamicConfiguration dynamicConfiguration(DynamicConfigSource configource) {
DynamicConfiguration configuration = new DynamicConfiguration(configource,
new FixedDelayPollingScheduler(30 * 100, 60 * 100, false));
// 启动schedel,定时调用DynamicConfigSource.poll()更新配置
ConfigurationManager.install(configuration);
return configuration;
}
}
@Component
public class DynamicConfigSource implements PolledConfigurationSource {
private static final Logger logger = LoggerFactory.getLogger(DynamicConfigSource.class);
@Autowired
private SystemVariableService systemVariableService;
@Override
public PollResult poll(boolean initial, Object checkPoint) throws Exception {
Map<String, Object> complete = new HashMap<String, Object>();
/** * 配置中心配置了对应的key/value。其中commandKey是我指定的具体接口。 */
//断路器开关标记,1:强制打开断路器,拒绝所有请求 2:强制关闭断路器,放行所有请求
String circuitForceFlag = systemVariableService.getSystemConfigValueByKey("circuitForceFlag");
if (circuitForceFlag != null)
{
if ("1".equals(circuitForceFlag))
{
//hystrixSetter.withCircuitBreakerForceOpen(true);
//调用者执行的超时时间
//complete.put("hystrix.command.default.circuitBreaker.forceOpen", false);
complete.put("hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds", 500);
}
if ("2".equals(circuitForceFlag))
{
//hystrixSetter.withCircuitBreakerForceClosed(true)
//调用者执行的超时时间
//complete.put("hystrix.command.default.circuitBreaker.forceOpen", true);
complete.put("hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds", 1000);
}
}
System.out.println(String.format("poll:%s", complete));
return PollResult.createFull(complete);
}
}
个人学习见解,如表述有问题,欢迎大家不吝指证!