为第三方HTTP协议依赖增加Hystrix保护你的系统


为第三方HTTP协议依赖增加Hystrix保护你的系统

为第三方HTTP协议依赖增加Hystrix保护你的系统

前言

后端开发的很多同学应该都有过调用第三方HTTP协议服务的经验。

比如调用百度查询经纬度编码信息、调用豆瓣查询时下热门电影、调用7牛云存储接口上传文件等;以及公司内部其他小组开放的服务接口。

常见开发模式是我们按照服务提供方定义的接口文档进行接口调用开发。

在java中常见的HTTP客户端库有:


为第三方HTTP协议依赖增加Hystrix保护你的系统

java中常见的HTTP客户端库

你的第三方依赖挂了怎么办?

系统并发很高的情况下,我们依赖的第三方服务挂了,调用HTTP协议接口超时线程阻塞一直得不到释放,系统的线程资源很快被耗尽,导致整个系统不可用。

试想一下如果业务系统中我们依赖的第三方服务只是一个增强型的功能没有的化也不影响主体业务的运行或者只是影响一部分服务,如果导致系统整体不可用这是绝对不允许的。

有什么办法可以解决这个问题呢?

我们可以使用代理模式增加服务调用的监控统计,在发现问题的时候直接进行方法返回从而避免产生雪崩效应。

伪代码如下

public interface ApiService {      /**      * 获取token      *      * @param username      * @param password      * @return      */     String getToken(String username, String password);      }  public static <S> S getSafeApiService(Class<S> serviceClass) {      S instance = ApiServiceFactory.createService(serviceClass);          return (S) Proxy.newProxyInstance(serviceClass.getClassLoader(), new Class<?>[]{serviceClass},                 (proxy, method, args) -> {                                          // 代理接口发现服务不可用时直接返回不执行下面的invoke方法                     if (failStatus) {                         log.error("error info")                         return null;                     } else {                         // 执行具体的调用                         Object result = method.invoke(instance, args);                         return result;                     }                                      }); } 

总结就是我们需要包裹请求,对请求做隔离。那么业内有没有此类功能成熟的框架呢?

答案是肯定的,Netflix这家公司开源的微服务组件中Hystrix就有对于服务隔离、降级和熔断的处理。

如何使用Hystrix

下面以调用百度地图地理位置反编码接口来演示Hystrix的使用

项目依赖

使用Spring Initializr初始化SpringBoot工程,在pom文件中新增如下依赖Retrofit和Hystrix依赖

    <properties>         <java.version>1.8</java.version>         <hytrix.version>1.5.18</hytrix.version>         <retrofit.version>2.3.0</retrofit.version>         <slf4j.version>1.7.7</slf4j.version>         <logback.version>1.1.2</logback.version>         <lombok.version>1.16.14</lombok.version>         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>     </properties>      <dependencies>         <!--Retrofit 客户端框架-->         <dependency>             <groupId>com.squareup.retrofit2</groupId>             <artifactId>retrofit</artifactId>             <version>${retrofit.version}</version>         </dependency>         <dependency>             <groupId>com.squareup.retrofit2</groupId>             <artifactId>converter-gson</artifactId>             <version>${retrofit.version}</version>         </dependency>          <!--Hystrix 断路器框架-->         <dependency>             <groupId>com.netflix.hystrix</groupId>             <artifactId>hystrix-core</artifactId>             <version>${hytrix.version}</version>         </dependency>          其他依赖...     </dependencies> 

创建HTTP接口的调用

HTTP客户端这里选择Retrofit,相关文档可查看
https://square.github.io/retrofit/

  1. Retrofit将百度的HTTP API转换为Java接口
public interface BaiduMapApi {      @GET("reverse_geocoding/v3/")     Call<AddressBean> decode(@Query("ak") String ak,                                        @Query("output") String output,                                        @Query("coordtype") String coordtype,                                        @Query("location") String location);  }  
  1. 使用Retrofit类生成的实现BaiduMapApi接口
@SpringBootApplication public class HystrixDemoApplication {      public static void main(String[] args) {         SpringApplication.run(HystrixDemoApplication.class, args);     }       @Bean     public BaiduMapApi baiduMapApi() {         Retrofit retrofit = new Retrofit.Builder()                 .baseUrl("http://api.map.baidu.com/")                 .addConverterFactory(GsonConverterFactory.create())                 .build();          return retrofit.create(BaiduMapApi.class);     }  }  
  1. 创建完BaiduMapApi的实现后就可以直接使用这个接口了
@Slf4j @SpringBootTest class BaiduMapApiTest {      @Autowired     private BaiduMapApi baiduMapApi;      @Test     void decode() throws IOException {          AddressBean addressBean = baiduMapApi.decode(                 "v1Xba4zeGLr6CScN39OFgvhiADPaXezd",                 "json",                 "wgs84ll",                 "31.225696563611,121.49884033194").execute().body();         if (addressBean != null) {             log.info(addressBean.toString());         }     } } 

执行单元测试后显示

为第三方HTTP协议依赖增加Hystrix保护你的系统

表明接口实现成功

为HTTP调用增加Hystrix保护

Hystrix官方示例:
https://github.com/Netflix/Hystrix/wiki/How-To-Use

Hello World!Code to be isolated is wrapped inside the run() method of a HystrixCommand similar to the following:

public class CommandHelloWorld extends HystrixCommand<String> {      private final String name;      public CommandHelloWorld(String name) {         super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));         this.name = name;     }      @Override     protected String run() {         return "Hello " + name + "!";     } } 

This command could be used like this:

String s = new CommandHelloWorld("Bob").execute(); Future<String> s = new CommandHelloWorld("Bob").queue(); Observable<String> s = new CommandHelloWorld("Bob").observe(); 
  1. 新增HystrixCommand实现
  2. 在run方法中执行具体的方法
  3. 通过调用HystrixCommand对象的execute或者queue或者observe方法触发命令执行

参考官方示例按照前面的分析对调用做隔离保护需要使用代理模式包裹请求,我们包装一下对百度接口的调用

    @Autowired     private BaiduMapApi baiduMapApi;      @GetMapping("/decode")     public AddressBean test(Double lon, Double lat) {         // 使用HystrixCommand包裹请求         return HystrixCommandUtil.execute(                 "BaiduMapApi",                 "decode",                 baiduMapApi.decode("v1Xba4zeGLr6CScN39OFgvhiADPaXezd",                         "json",                         "wgs84ll",                         lat + "," + lon)                 , throwable -> {                     log.error("触发出错返回,告警!", throwable);                     return null;                 });       } 
@Slf4j public class HystrixCommandUtil { 	 	/** 	 * 客户端参数异常时将抛出HystrixBadRequestException 	 * 	 * @param groupKey 	 * @param commandKey 	 * @param call 	 * @param fallback 	 * @param <T> 	 * @return 	 * @throws HystrixBadRequestException 	 */ 	public static <T> T execute(String groupKey, String commandKey, Call<T> call, HystrixFallback<T> fallback) throws HystrixBadRequestException { 		if (groupKey == null) { 			throw new IllegalArgumentException("groupKey 不能为空"); 		} 		if (commandKey == null) { 			throw new IllegalArgumentException("CommandKey 不能为空"); 		} 		if (call == null) { 			throw new IllegalArgumentException("call 不能为空"); 		} 		if (fallback == null) { 			throw new IllegalArgumentException("fallback 不能为空"); 		}  		return new HystrixCommand<T>(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey)) 			.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))) {  			@Override 			protected T run() throws Exception { 				Response<T> response = call.execute(); 				if (response != null) { 					if (response.code() >= 200 && response.code() < 300) { 						return response.body(); 					} else if (response.code() >= 400 && response.code() < 500) { 						if (response.errorBody() != null) { 							throw new HystrixBadRequestException(response.errorBody().string()); 						} else { 							throw new HystrixBadRequestException("客户端参数非法"); 						} 					} else { 						if (response.errorBody() != null) { 							throw new RuntimeException(response.errorBody().string()); 						} else { 							throw new RuntimeException("服务端未知异常"); 						} 					} 				} else { 					throw new RuntimeException("未知异常"); 				} 			}  			@Override 			protected T getFallback() { 				return fallback.fallback(getExecutionException()); 			}  		}.execute(); 	} } 

上述示例代码GitHub地址

Hystrix原理

hystrix是如何隔离调用的?

hystrix缺省使用了线程池进行隔离,HystrixCommand中的run方法是在异步线程池中执行的。

使用了hystrix它的断路触发规则是什么样子的呢?

默认的触发熔断的条件是:

  1. 在最近的一个时间窗口期(hystrix.command.default.metrics.rollingStats.timeInMilliseconds = 10000 // 默认10秒)内
  2. 总请求次数>=(hystrix.command.default.circuitBreaker.requestVolumeThreshold = 20 //默认20)
  3. 并且发生异常次数的比例>=(hystrix.command.default.circuitBreaker.errorThresholdPercentage = 50 // 默认50%)

满足1~3条件后断路器打开,触发熔断后续的执行会被拦截直接走getFallback方法返回。5秒以后(
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds = 5000 // 缺省5秒)下一个的请求会被通过(处于半打开状态),如果该请求执行失败,回路器会在睡眠窗口期间返回OPEN,如果请求成功,回路器会被置为关闭状态。

有些异常是客户端问题却错误地统计进了熔断监控统计中该怎么办?

查看官方文档可知:

Execution Exception types


为第三方HTTP协议依赖增加Hystrix保护你的系统

Execution Exception types



HystrixBadRequestException 不会记录进熔断统计中
我们可以此异常包装我们的客户端异常

为第三方HTTP协议依赖增加Hystrix保护你的系统

官方wiki的两张图很好的展示了Hystrix原理


为第三方HTTP协议依赖增加Hystrix保护你的系统

Hystrix Flow Chart


为第三方HTTP协议依赖增加Hystrix保护你的系统

断路器触发规则


尾巴

通过Hystrix的引入再次深入了服务的容错处理实现。

构建强健的系统远比demo付出的多太多。深入再深入一点,加油^_^

Hystrix
分享到:

您可能还会对下面的文章感兴趣: