Hystrix 在项目中实践

阅读数:133 2019 年 9 月 26 日 16:11

Hystrix在项目中实践

1 背景

我们的业务通常会大量依赖于第三方提供的服务,这些服务都是基于 Http 请求,然后依赖方的服务能力又是参次不齐的,因此,第三方资源的稳定性会直接影响着我们系统的稳定性。简单的调用模式如图 1 表示:

Hystrix在项目中实践

图 1 调用关系时序图

当某一个依赖的外部服务发生了异常,如果外部服务可以快速失败这到还好,最怕的就是服务响应变慢。可以试想一下,一个 http 请求过去,对方的服务一直在慢慢的进行处理,一直到我们所设置的 http 处理时间达到我们所设置的超时上限。在这个过程当中,很有可能会因为某一个外部资源响应速度变慢,而会长时间占用我们的 http 连接池,导致其他可以正常提供服务的资源也无法正常运行,严重的情况下甚至会产生雪崩效应。

如图 2 所示:当依赖的服务 F 发生了严重超时的情况时,所有与服务 F 相关的请求都会堵塞在这里。当后续第 N 个请求来的时候,即使与服务 F 没有依赖关系,但由于前面的请求把资源都占用了,所有其仍然拿不到资源,无法调用外部服务。

Hystrix在项目中实践

图 2 资源耗尽 无法响应

2 为什么引入 Hystrix

Hystrix 是用于分布式场景下服务熔断、降级的开源 Java 库。它的主要作用有线程隔离,熔断,降级和监控报警。这里不会过多的介绍 Hystrix 本身的概念及定义,具体内容可以看其 github 网站或是自行搜索,会有大量的仔细介绍。根据自身业务的具体情况,把外部依赖进行隔离,从而保证某些外部服务出现问题时,其它的服务仍然是可用并且是不受影响的。如图 3 所示:服务 F 发生异常,导致请求 3 无法处理请求,但是请求 4、5、6 仍然可以继续对外提供服务。

Hystrix在项目中实践

图 3 线程池隔离

3 模拟不引入 Hystrix 时

当模拟不引入 Hystrix 时,线程耗尽的情况:

  • 模拟应用调用第三方服务(第三方是我们模拟的一个 10 秒睡眠的服务)
复制代码
1 /**
2 * controller 层
3 * @param sleep 睡眠时间,模拟第三方业务的处理时间。
4 */
5 @RequestMapping(value = "hystrixTest")
6 public ApiResult<String> hystrixTest(@RequestParam("sleep") int sleep){
7 ApiResult<String> apiResult =iHystrixService.execute();
8 return apiResult;
9 }
复制代码
1 /**
2 *service 层
3 *service 层中调用的 /hystrix/sleep 地址,就是我们模拟的第三方服务。
4 *@param sleep 睡眠时间,模拟第三方业务的处理时间。
5 *hystrix/sleep 地址只是根据传过来的 sleep 参数进行 Thread.sleep() 操作。
6 */
7 public ApiResult<String> execute(int sleep) {
8 try{
9 System.out.println(" 开始业务处理 调用 Http"
10 + FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss")
11 .format(new Date()));
12
13 // 模拟调用外部服务 逻辑仅是进行 Tread.sleep 操作模拟处理时间
14 ResponseEntity<String> responseEntity = restTemplate
15 .getForEntity("http://127.0.0.1:8080/hystrix/sleep?sleep="
16 +sleep,String.class);
17
18 System.out.println(" 调用 Http 完成 satusCode:"
19 +responseEntity.getStatusCode()+" "
20 + FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss")
21 .format(new Date()));
22
23 return ApiResult.SUCCESS();
24 }
25 catch (Exception e){
26 return ApiResult.FAILED();
27 }
28}
  • 配置线程池数量(这里配置成 5 个)
复制代码
1 <!-- 连接池管理器 HttpClient 是线程安全的 且可同时执行多个线程的请求
2 管理持久的 http 连接 节省创建连接时间 -->
3 <!-- 维护的连接数在每个路由基础和总数上都有限制 -->
4 <bean id="pollingConnectionManager" class="org.apache.http.impl.conn
5 .PoolingHttpClientConnectionManager">
6 <!-- 最大连接数 -->
7 <property name="maxTotal" value="5"/>
8 <!-- 每个路由基础的连接 -->
9 <property name="defaultMaxPerRoute" value="5"/>
10 </bean>
  • 模拟客户端服务调用(这里用的是 6 个)

我们模拟 6 个并发调用 controller,也就会发起 6 次服务调用,由于我们用 sleep 简单模拟的第三方服务,因此在我们发起第 6 次 controller 请求时,前面的 5 个请求均没有处理完毕返回结果,所以第 6 次的请求会因为拿不到 http 的连接无法调用 hystrix/sleep 这个模拟服务的地址。
从图 3 中我们也可以看出来,有 5 个请求因为拿到了 http 连接资源,可以正常访问外部服务,而有一个则因为当时 http 连接已被耗尽,则不能继续访问第三方服务。日志如图 4 所示:

Hystrix在项目中实践

图 4 有 1 个请求拿不到 http 连接资源

我们可以试想这样一种场景,刚刚 6 个请求,其对应的是 A,B 两个外部服务。假如 A 服务由于某种情况发生响应变慢,而 B 仍然能良好的提供服务,在这种情况下,就会发生由于 A 服务发生问题,而恰巧依赖 A 服务的外部接口又有大量请求,此时就会使得业务系统的资源全部被 A 服务占用,这样业务系统中与 A 服务无关的业务,也无法拿到资源去访问服务良好的 B 服务,最坏的情况就是业务系统中的一台机器被耗尽无法对外提供服务而 down 倒,由于负载均衡或是服务发现的机制,接下来所有的请求又会打到另外的机器上,一直到所有机器都被打爆,发生雪崩效应。
所以我们需要一种机制能够隔离坏的资源,让正常的资源提供正常的服务。这里我们就用到了上面说到的 Hystrix。
本文会介绍 hystrix 的两种使用方式,命令行编码和注解方式。

4Hystrix 的引用

  • 引入 maven 依赖
复制代码
1 <hystrix.core.version>1.5.12</hystrix.core.version>
2 <hystrix.javanica.version>1.5.12</hystrix.javanica.version>
3 <hystrix.metrics.version>1.5.12</hystrix.metrics.version>
4 <dependency>
5 <groupId>com.netflix.hystrix</groupId>
6 <artifactId>hystrix-core</artifactId>
7 <version>${hystrix.core.version}</version>
8 </dependency>
9 <dependency>
10 <groupId>com.netflix.hystrix</groupId>
11 <artifactId>hystrix-metrics-event-stream</artifactId>
12 <version>${hystrix.metrics.version}</version>
13 </dependency>
14 <dependency>
15 <groupId>com.netflix.hystrix</groupId>
16 <artifactId>hystrix-javanica</artifactId>
17 <version>${hystrix.javanica.version}</version>
18 </dependency>

5 注解方式使用 Hystrix

5.1 线程池隔离

业务层增加 @HystrixCommand 注解:

复制代码
1 @HystrixCommand(groupKey = "HystrixServiceImpl", // groupKey 该命令属于哪个组
2 commandKey = "execute", // 该命令的名称
3 threadPoolKey = "HystrixServiceImpl", // 该命令所属线程池的名称, 默认同 groupKey
4 fallbackMethod = "executeFallback", // 服务降级方法
5 commandProperties = {
6 @HystrixProperty(name = "execution.isolation.strategy",
7 value = "THREAD"), // 线程池方式
8 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",
9 value = "5000") // 超时时间为 5 秒
10 })
11 public ApiResult<String> execute() {
12 try{
13 System.out.println(" 开始业务处理 调用 Http"
14 + FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss")
15 .format(new Date()));
16
17 ResponseEntity<String> responseEntity = restTemplate
18 .getForEntity("http://127.0.0.1:8080/hystrix/sleep",String.class);
19
20 System.out.println(" 调用 Http 完成 satusCode:"
21 + responseEntity.getStatusCode()+" "
22 + FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss")
23 .format(new Date()));
24
25 return ApiResult.SUCCESS();
26 }
27 catch (Exception e){
28 return ApiResult.FAILED();
29 }
30 }
  • 增加回退方法
复制代码
1 public ApiResult<String> executeFallback() {
2 System.out.println(Thread.currentThread().getId()
3 + " "+Thread.currentThread().getName()+" "
4 + Thread.currentThread().getState().toString());
5
6 return ApiResult.FAILED("hystrix->executeFallback");
7 }
  • 模拟超时

模拟第三方执行时间为 10S,而我们在 Hystrix 中设置的为 5S,所以会触发服务降级,应用在超时后执行 executeFallback 降级方法。如图 5 所示:

Hystrix在项目中实践

图 5 服务超时降级

细心点的人可以看出来,虽然客户端返回以及服务端执行了服务降级方法,但是原有的处理逻辑仍然继续执行,对于降级的请求,原有本身处理过程不会被中断。当服务降级到达一定的阀值时会启动断路器,后续一段时间内的请求都将进入降级方法,不会再调用业务逻辑。

  • 使用 Hystrix 进行线程隔离

新增加一个外部模拟服务 B,用于模拟访问不同的外部依赖, 分别设置其线程池大小为 2 个大小。

复制代码
1 @HystrixCommand(groupKey = "HystrixServiceImpl",commandKey = "execute",
2 fallbackMethod = "executeFallback",
3 commandProperties = {
4 @HystrixProperty(name = "execution.isolation.strategy",value = "THREAD"),
5 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",
6 value = "5000")
7 },
8 threadPoolProperties = {
9 @HystrixProperty(name = "coreSize",value = "2"),// 线程池大小
10 @HystrixProperty(name = "maQueueSize",value = "-1")// -1 表示不等待直接拒绝
11 })
  • 模拟 4 个客户端访问系统资源,其中 3 个访问 A 资源 1 个访问 B 资源。如图 6 所示:

Hystrix在项目中实践

图 6 4 个客户端请求 AB 两个资源

从日志可以看出,访问 A 资源的 3 个客户端,有 2 个成功,1 个返回 fallback 结果,这是因为我们设置的 Hystrix 的线程池大小为 2,所以第三个请求来的时候获取不到可用的线程池资源。而总的 restTemplate 线程池之前设置的为 5,此时仍有连接资源,所以 B 资源可以继续访问,而 A 由于业务处理的慢则没有连接返回释放资源,所以 A 的后继请求都是失败。
通过这种方式,可以起到线程隔离的效果,但是当所有的线程隔离池中当前所使用的总线程数大于了我们设置的 http 连接总数,总体业务当然还是失败的。Hystrix 只是起到了线程池资源隔离的作用,所以具体每个线程池设置多大的参数,还是要经过开发人员线上真实情况动态调整。

5.2 并发量

Hystrix 除了支持线程池外,还支持信号量方式控制请求并发数。

复制代码
1// 修改 HystrixCommand 为信息量方式
2@HystrixCommand(groupKey = "HystrixServiceImpl",
3 commandKey = "execute",
4 fallbackMethod = "executeFallback",
5 commandProperties = {
6 @HystrixProperty(name = "execution.isolation.strategy"
7 ,value = "SEMAPHORE"), // 信号量方式
8 @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests"
9 ,value = "3") // 最大并发数为 3
10})

客户端模拟 4 个并发请求,只有 3 个是正常返回业务结果,另一个超过了并发数量限量触发了 fallback 方法。如下图 7:

Hystrix在项目中实践

图 7 并发日志

5.3 小结

通过上面的例子,可以了解到使用注解的方式使用 Hystrix 简单,易用并且开发量小。但其也有一些缺点,在实际应用当中,我们需要随业务的具体情况而动态的调整 Hystrix 参数,尤其是超时时间和并发数数量这些重要参数。另外,如果是对老业务的改造,使用注解方式需要把原有的所有外部依赖的地方都修改一下,其工作量也是不小。

6 命令式编辑使用 Hystrix

6.1 使用方式

上面介绍了在项目中使用 Hystrix 注解的方式对服务进行隔离和降级的注解方式。虽然简单、方便、易与使用,但是对于老业务来讲,把所有的方法都要加上 Hystrix 注解的话也不太友好,而且也不算灵活,无法做到动态修改。因此,想到使用 AOP 的方式,通过命令式编程对目标方式进行拦截,对需要使用服务降级的方法进行 Hystrix 操作,完全基于配置,这样就无需对原有的业务代码进行改动。

  • 服务降价配置说明
复制代码
1 # 对 offerService.ocrIdCardImg 方法进行降级配置 true:启用 hystrix 降级逻辑 false: 不受 hystrix 控制
2 hystrix.method.com.ke.life.thirdpart.service.offerService.ocrIdCardImg=true
3 # 对 offerService.ocrIdCardImg 方法设置服务超时时间
4 hystrix.timeout.com.ke.life.thirdpart.service.offerService.ocrIdCardImg=1000
5 # 当使信号量时,对 offerService.ocrIdCardImg 进行并发数设置
6 hystrix.requestSize.com.ke.life.thirdpart.service.offerService.ocrIdCardImg=50
7 # 并发策略 SEMAPHORE 或 THREAD 也可以不配置默认为线程池
8 hystrix.isolationStrategy.com.ke.life.thirdpart.service.offerService.ocrIdCardImg=THREAD
9 # offerService.ocrIdCardImg 触发 hystrix 服务降级时,返回的降级数据 void 方法可以不配置
10 hystrix.default.return.com.ke.life.thirdpart.service.offerService.ocrIdCardImg={"errno":"-1","msg":" 服务降级 "}

此外,我们还有一些是业务的开关,在代码中是通过 ISystemSettingService 接口逻辑业务开关,代码中会有说明。

  • Hystrix 拦截器代码结构说明
复制代码
1 @Component
2 @Aspect
3 public class HystrixCommandAdvice {
4
5 private static final Logger logger = LoggerFactory.getLogger(HystrixCommandAdvice.class);
6
7 // 服务降级方法配置前缀
8 private static String HYSTRIX_METHOD_PREFIX="hystrix.method.";
9
10 // 服务降级方法超时时间配置前缀
11 private static String HYSTRIX_TIMEOUT_PREFIX="hystrix.timeout.";
12
13 // 服务降级方法并发数配置前缀
14 private static String HYSTIRX_REQUEST_SIZE_PREFIX="hystrix.requestSize.";
15
16 // 服务降级方法策略配置前缀 使用信号量还是线程池
17 private static String HYSTRIX_ISOLATION_STRATEGE_PREFIX = "hystrix.isolationStrategy.";
18
19 // 服务降级方法返回类型配置前缀
20 private static String HYSTRIX_DEFAULT_RETURN_PREFIX="hystrix.default.return.";
21
22 // iSystemSettingService 获取业务中的配置,可以来自数据库、redis 以及配置中心
23 @Autowired
24 private ISystemSettingService iSystemSettingService;
25
26 // 定义切点,拦截某些特定第三方服务
27 @Pointcut("execution(* com.ke.life.thirdpart.service.*.*(..))")
28 public void hystrixPointcut(){}
29
30 /**
31 * 切面
32 */
33 @Around("hystrixPointcut()")
34 public Object runCommand(final ProceedingJoinPoint pJoinPoint) throws Throwable {
35 // 此处省略,下面有具体代码说明
36 }
37
38 /**
39 * Hystrix 参数设置
40 */
41 private HystrixCommand.Setter setter(String commonGroupKey,String commandKey,
42 int timeout,int requestSize,
43 String strategy) {
44 // 此处省略,下面有具体代码说明
45 }
46
47 /**
48 * 生成降级后的返回内容
49 */
50 public static Object generateClass(String clazzPackage , String defaultValue)
51 throws Exception{
52 // 此处省略,下面有具体代码说明
53 }
  • runCommand 方法代码
复制代码
1 @Around("hystrixPointcut()")
2 public Object runCommand(final ProceedingJoinPoint pJoinPoint) throws Throwable {
3
4 // 获取服务签名 public xxxx.returnObject.class. com.xxxx.serice.method()
5 String signature = pJoinPoint.getSignature().toLongString();
6 String serviceMethod = signature.substring(signature.lastIndexOf(" ")+1,
7 signature.lastIndexOf("("));
8 boolean hystrixServiceBoolean = DynamicPropertyFactory.getInstance()
9 .getBooleanProperty(HYSTRIX_METHOD_PREFIX+serviceMethod, false).get();
10
11 // 降级开关打开状态并且配置此业务方法的降级方案
12 // iSystemSettingService 为我们自己定义的字典表,保存业务的开关和配置信息
13 if(true == hystrixServiceBoolean
14 && "true".equals(iSystemSettingService.getByKey("hystrix.switch"))){
15 // 使用 Hystrix 服务降级封装的方法,具体代码下面有示例
16 return wrapWithHystrixCommnad(pJoinPoint).execute();
17 }
18 // 没有配置此业务方法的降级方案,则直接执行
19 try {
20 return pJoinPoint.proceed();
21 } catch (Throwable throwable) {
22 throw (Exception) throwable;
23 }
24 }
  • 服务方法封装 Hystirx 功能,wrapWithHystrixCommnad 代码
复制代码
1 // 使用 hystrix 封装业务执行的方法
2 private HystrixCommand<Object> wrapWithHystrixCommnad(final ProceedingJoinPoint
3 pJoinPoint) {
4 // 获取降级服务的方法签名
5 // 如:public xxxx.return.class com.xxxx.serice.method()
6 String signature = pJoinPoint.getSignature().toLongString();
7 // 解析出方法签名中返回的类型 如:xxxx.return.class
8 String returnClass = signature.substring(signature.indexOf(" ")+1
9 ,signature.lastIndexOf(" "));
10 // 解析出就去签名中的方法 如:com.xxxx.serice.method
11 String serviceMethod = signature.substring(signature.lastIndexOf(" ")+1
12 ,signature.lastIndexOf("("));
13
14 // 获取配置文件中的配置数据,6.2 章节中有具体说明
15 DynamicPropertyFactory dynamicPropertyFactory = DynamicPropertyFactory
16 .getInstance();
17 // 超时时间 默认 5 秒
18 int timeout = dynamicPropertyFactory
19 .getIntProperty(HYSTRIX_TIMEOUT_PREFIX+serviceMethod, 5000).get();
20
21 // 返回值类型 默认空即 void
22 String defaultReturn = dynamicPropertyFactory
23 .getStringProperty(HYSTRIX_DEFAULT_RETURN_PREFIX+serviceMethod,"").get();
24
25 // 请求数大小 默认 50 个 只有在策略使用信号量时再会用到
26 int requestSize= dynamicPropertyFactory
27 .getIntProperty(HYSTIRX_REQUEST_SIZE_PREFIX+serviceMethod, 50).get();
28
29 // 隔离策略
30 String strategy= dynamicPropertyFactory
31 .getStringProperty(HYSTRIX_ISOLATION_STRATEGE_PREFIX+serviceMethod, "THREAD")
32 .get();
33 String declaringTypeName = pJoinPoint.getSignature()
34 .getDeclaringTypeName();
35 String commandKey = pJoinPoint.getSignature().getName();
36 String commonGroupKey = declaringTypeName
37 .substring(declaringTypeName.lastIndexOf(".")+1)+"."+commandKey;
38
39 return new HystrixCommand<Object>(setter(commonGroupKey,commandKey
40 ,timeout,requestSize,strategy)) {
41
42 @Override
43 protected Object run() throws Exception {
44 try {
45 Object object = pJoinPoint.proceed();
46 return object;
47 } catch (Throwable throwable) {
48 throw (Exception) throwable;
49 }
50 }
51 /**
52 * 以下四种情况将触发 getFallback 调用:
53 * 1)run() 方法抛出非 HystrixBadRequestException 异常
54 * 2)run() 方法调用超时
55 * 3)断路器开启拦截调用
56 * 4)线程池 / 队列 / 信号量是否跑满
57 * 实现 getFallback() 后,执行命令时遇到以上 4 种情况将被 fallback 接管,
58 * 不会抛出异常或其他
59 */
60 @Override
61 protected Object getFallback() {
62
63 logger.info(" 服务触发了降级 {} 超时时间 {} 方法返回类型 {} 是否启动了熔断 {}"
64 ,serviceMethod,timeout,returnClass,this.isCircuitBreakerOpen());
65 try{
66 return generateClass(returnClass,defaultReturn);
67 }
68 catch (Exception e){
69 logger.error(" 生成降级返回对象异常 ",e);
70 }
71 return null;
72 }
73 };
74 }
  • Hystrix 参数设置(AOP 方式同样也可以设置为线程池方式和信号量方式),setter 方法
复制代码
1 // hystrix 参数设置
2 private HystrixCommand.Setter setter(String commonGroupKey,String commandKey
3 ,int timeout,int requestSize,String strategy) {
4 // 使用信号量
5 if("SEMAPHORE".equalsIgnoreCase(strategy)){
6 return HystrixCommand.Setter
7 .withGroupKey(HystrixCommandGroupKey.Factory
8 .asKey(commonGroupKey==null?"commonGroupKey":commonGroupKey))
9 .andCommandKey(HystrixCommandKey.Factory
10 .asKey(commandKey==null?"commandKey":commandKey))
11 // 信号量
12 .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
13 .withExecutionIsolationStrategy(HystrixCommandProperties
14 .ExecutionIsolationStrategy.SEMAPHORE)
15 // 并发数量
16 .withExecutionIsolationSemaphoreMaxConcurrentRequests(requestSize)
17 .withExecutionTimeoutInMilliseconds(timeout));
18 }
19 else {
20 // 使用线程池
21 return HystrixCommand.Setter
22 .withGroupKey(HystrixCommandGroupKey.Factory
23 .asKey(commonGroupKey==null?"commonGroupKey":commonGroupKey))
24 .andCommandKey(HystrixCommandKey.Factory
25 .asKey(commandKey==null?"commandKey":commandKey))
26 // 线程池大小
27 .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
28 .withCoreSize(requestSize))
29 .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
30 // 线程池
31 .withExecutionIsolationStrategy(HystrixCommandProperties
32 .ExecutionIsolationStrategy.THREAD)
33 // 超时时间
34 .withExecutionTimeoutInMilliseconds(timeout)
35 .withExecutionIsolationThreadInterruptOnTimeout(true)
36 .withExecutionTimeoutEnabled(true));
37 }
38 }
  • 生成降级返回的数据,generateClass 方法
复制代码
1 /**
2 * @param clazzPackage 返回的类
3 * @param defaultValue 降级后返回的值
4 */
5 public static Object generateClass(String clazzPackage , String defaultValue) throws
6 Exception{
7
8 if(StringUtils.isBlank(clazzPackage) || StringUtils.isBlank(defaultValue)){
9 return null;
10 }
11
12 logger.info(" 返回的类 "+ clazzPackage);
13 // 返回的类型是 String 类型的
14 if(clazzPackage.contains("String")){
15 String returnStr = new String(defaultValue.getBytes("iso8859-1"),"UTF-8");
16 logger.info(" 降级返回的字符串 {}",returnStr);
17 return returnStr;
18 }
19
20 JSONObject hystrixReturnValueJson = JSONObject.parseObject(defaultValue);
21 Iterator iterator = hystrixReturnValueJson.keySet().iterator();
22 HashMap<String,String> fieldMap = new HashMap<>();
23 while(iterator.hasNext()){
24 String key = (String)iterator.next();
25 String value = new String(hystrixReturnValueJson.getString(key)
26 .getBytes("iso8859-1"),"UTF-8");
27 logger.info(" 字段属性 "+ key +" "+value);
28 fieldMap.put(key,value);
29 }
30
31 // 使用反射,生成降级后返回的对象
32 Class clazz = Class.forName(clazzPackage);
33 Object object = clazz.newInstance();
34 BeanUtils.populate(object,fieldMap);
35
36 logger.info(" 生成的降级返回类 {} {}",object.toString(),JSON.toJSONString(object));
37 return object;
38 }

6.2. 动态修改配置

Hystrix 的超时时间,线程池大小以及并发数,都需要针对不对的场景和情况进行动态调整,所以动态配置是非常有必要的。例如:服务是从网关层统一转发到后端的业务应用上,那么网关层做动态限流就很有必要。随着后端业务机器及能力的变化可以在不重启网关服务的情况下随时动态修改网关的流量配置。

携程的 Apollo 和阿里的 Diamond 都是比较好的动态配置管理工具,界面也对用户友好。本文演示使用的是 netflix 的 Archaius。

Archaius 默认会自动读取项目类路径下的 config.properties 文件并且每分钟去重新加载下属性配置。

maven 引入 archaius 包

复制代码
1 <dependency>
2 <groupId>com.netflix.archaius</groupId>
3 <artifactId>archaius-core</artifactId>
4 <version>0.6.0</version>
5 </dependency>
  • 使用方式

在上面的示例代码中,其实就已经使用到了 archaius,其使用方式也是非常的简单

复制代码
1 DynamicPropertyFactory dynamicPropertyFactory = DynamicPropertyFactory.getInstance();
2 // 获取 config.properties 属性文件中 key 为 hystrix.timeout 的值。如果取不到,则使用默认值
3 int value = dynamicPropertyFactory.getIntProperty("hystrix.timeout", 5000).get();

6.3 小结

使用 AOP 的方式,我个人感觉是更灵活些,也利于对 Hystrix 做统一管理,更加模块化也更收敛。最重要的是,对于老项目接入 Hystrix 的项目来说,改动量相对要小一些。只要设计出适合业务自身的降级逻辑就可以了,在 AOP 中统一配置实现。

7 断路器

Hystrix 本身就提供熔断功能。当服务降级达到一个阀值时,断路器会自动开启,断路器开启后,所有的服务会被拦在外面。

断路器开关由关闭到打开的状态转换是通过当前服务健康状况和设定阈值决定的。

下图 8 显示了断路器开关的流程。

服务的健康状况 = 请求失败数 / 请求总数

Hystrix在项目中实践

图 8 断路器

每个断路器默认维护 10 个 bucket,每秒一个 bucket, 每个 bucket 记录成功、失败、超时、拒绝的状态,默认为错误超过 50% 且 10 秒内超过 20 个请求进行中断拦截。
这里特别要说明一下 10 秒内超过 20 个请求,它的意思是说在统计窗口 10 秒内有至少有 20 个请求并且失败率为 50%,即一个失败就发生熔断。但是, 如果在 10 秒内不足 20 个请求,即使失败率为 100%,也是不满足断路器开启条件的。

可以通过 hystrix.command.default.circuitBreaker.requestVolumeThreshold 配置修改窗口内的失败次数。

8 总结

伴随着分布式应用和服务化以及微服务的开发方式已成为了主流,所以服务间的调用越来越复杂,也越来越频繁。那么如何构建一个高可用和高并发的服务,就变成了我们最为棘手的问题。Hystrix 可以有效的隔离外部坏的服务来保护自己。在流量高峰超过系统处理能力的时候,可以进行快速失败,保证已接收处理的请求不受影响。此外,Hystrix 也被纳入了 spring-cloud 体系,足可以看出其在微服务中的重要性。

作者介绍:
王大可(企业代号名),贝壳找房后端研发工程师。

本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。

原文链接:

https://mp.weixin.qq.com/s/4Fg0COnWRB3rRWfxbJt7gA

评论

发布