七、Hystrix请求合并(request collapser)

Hystrix支持N个请求自动合并为一个请求,这个功能在有网络交互的场景下尤其有用,比如每个请求都要网络访问远程资源,如果把请求合并为一个,将使多次网络交互变成一次,极大节省开销。

请求合并有很多种级别
1、Global Context,tomcat所有调用线程,对一个依赖服务的任何一个command调用都可以被合并在一起。
2、User Request Context,tomcat内某一个调用线程,将某一个tomcat线程对某个依赖服务的多个command调用合并在一起。
3、Object Modeling,基于对象的请求合并,如果有几百个对象,遍历后依次调用每个对象的某个方法,可能导致发起几百次网络请求,此时,可以将对多个对象模型的调用合并到一起。

请求合并的开销问题
虽然通过请求合并可以减少请求的数量以缓解依赖服务线程池的资源,但是在使用的时候也需要注意它所带来的额外开销:用于请求合并的延迟时间窗会使得依赖服务的请求延迟增高。比如:某个请求在不通过请求合并器访问的平均耗时为5ms,请求合并的延迟时间窗为10ms(默认值),那么当该请求的设置了请求合并器之后,最坏情况下(在延迟时间窗结束时才发起请求)该请求需要15ms才能完成。

由于请求合并器的延迟时间窗会带来额外开销,所以我们是否使用请求合并器需要根据依赖服务调用的实际情况来选择,主要考虑下面两个方面:
  • 请求命令本身的延迟。如果依赖服务的请求命令本身是一个高延迟的命令,那么可以使用请求合并器,因为延迟时间窗的时间消耗就显得莫不足道了。
  • 延迟时间窗内的并发量。如果一个时间窗内只有1-2个请求,那么这样的依赖服务不适合使用请求合并器,这种情况下不但不能提升系统性能,反而会成为系统瓶颈,因为每个请求都需要多消耗一个时间窗才响应。相反,如果一个时间窗内具有很高的并发量,并且服务提供方也实现了批量处理接口,那么使用请求合并器可以有效的减少网络连接数量并极大地提升系统吞吐量,此时延迟时间窗所增加的消耗就可以忽略不计了。

请求合并的好处
1、大幅度削减你的线程池的资源耗费。比如线程池有10个线程,一秒钟可以执行10个请求,合并在一起,1个线程执行10个请求,10个线程就可以执行100个请求。
2、增加你的吞吐量。减少你对后端服务访问时的网络资源的开销,10个请求,10个command,10次网络请求的开销,1次网络请求的开销了。

Hystrix支持2种请求合并方式:Request Scope 和 Global Scope,在collapser构造的时候指定scope模式,默认是 Request Scope。
Request Scope的collapser收集每个HystrixRequestContext的一个批次,而Global Scope的collapser收集一个批次跨多个HystrixRequestContext。因此,如果下游依赖关系在单个Command调用中无法处理多个HystrixRequestContext,则使用Request Scope是最好的选择。


HystrixCollapser定义说明
public abstract class HystrixCollapser<BatchReturnType, ResponseType, RequestArgumentType> implements
HystrixExecutable<ResponseType>, HystrixObservable<ResponseType> {
...
public abstract RequestArgumentType getRequestArgument();

protected abstract HystrixCommand<BatchReturnType> createCommand(Collection<CollapsedRequest<ResponseType, RequestArgumentType>> requests);

protected abstract void mapResponseToRequests(BatchReturnType batchResponse, Collection<CollapsedRequest<ResponseType, RequestArgumentType>> requests);
...
}
从HystrixCollapser抽象类中可以看到,需要指定三个不同的类型:
  • BatchReturnType:请求合并后的返回类型
  • ResponseType:单个请求的返回类型
  • RequestArgumentType:请求参数类型
也包含三个抽象方法:
  • RequestArgumentType getRequestArgument():该函数用来定义获取请求参数的方法。
  • HystrixCommand<BatchReturnType> createCommand(Collection<CollapsedRequest<ResponseType, RequestArgumentType>> requests):处理请求合并命令的具体实现方法
  • mapResponseToRequests(BatchReturnType batchResponse, Collection<CollapsedRequest<ResponseType,RequestArgumentType>> requests):请求合并命令结束返回后的处理,需要将返回的结果拆分并传递给合并前的各个原子请求命令的逻辑。

例子
第一步,继承HystrixCollapser实现请求合并功能:
public class GetProductInfosCollapser extends HystrixCollapser<
List<ProductInfo>, ProductInfo, Long> {

private Long productId;

public GetProductInfosCollapser(Long productId) {
this.productId = productId;
}

//该函数用来定义获取请求参数的方法。
@Override
public Long getRequestArgument() {
return productId;
}

//处理请求合并命令的具体实现方法
@Override
protected HystrixCommand<List<ProductInfo>> createCommand(
Collection<CollapsedRequest<ProductInfo, Long>> requests) {
StringBuilder sb = new StringBuilder();
for (CollapsedRequest<ProductInfo, Long> request : requests) {
sb.append(request.getArgument()).append(",");
}
String param = sb.toString();
param = param.substring(0,param.length()-1);
System.out.println("createCommand方法执行,params="+param);
return new BatchCommand(requests);
}

//请求合并命令结束返回后的处理,需要将返回的结果拆
//分并传递给合并前的各个原子请求命令的逻辑
@Override
protected void mapResponseToRequests(List<ProductInfo> batchResponse,
Collection<CollapsedRequest<ProductInfo, Long>> requests) {
int count = 0;
for (CollapsedRequest<ProductInfo, Long> request : requests) {
request.setResponse(batchResponse.get(count++));
}
}
//定义请求合并之后的Command
private static final class BatchCommand extends HystrixCommand<List<ProductInfo>> {

private final Collection<CollapsedRequest<ProductInfo, Long>> requests;

private BatchCommand(Collection<CollapsedRequest<ProductInfo, Long>> requests) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("BatchCommand")));
this.requests = requests;
}

@Override
protected List<ProductInfo> run() throws Exception {
if(null==requests || requests.size()==0)
return new ArrayList<ProductInfo>();
StringBuilder sb = new StringBuilder();
for (CollapsedRequest<ProductInfo, Long> request : requests) {
sb.append(request.getArgument()).append(",");
}
String param = sb.toString();
param = param.substring(0,param.length()-1);
String url = "http://127.0.0.1:8082/getProductInfos?productIds="+param;
String response = HttpClientUtils.sendGetRequest(url);
List<ProductInfo> productInfos=JSONObject.parseArray(response,ProductInfo.class);
return productInfos;
}
}
}
第二步,实现前端调用请求合并的功能
@RequestMapping("/getProductInfos")
@ResponseBody
public String getProductInfos(String productIds) {
List<Future<ProductInfo>> futures = new ArrayList<Future<ProductInfo>>();
for(String productId:productIds.split(",")) {
GetProductInfosCollapser collapser = new GetProductInfosCollapser(Long.valueOf(productId));
futures.add(collapser.queue());
}
for(Future<ProductInfo> future:futures) {
try {
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
return "success";
}

第三步,实现后端服务的功能
@RequestMapping("/getProductInfos")
@ResponseBody
public String getProductInfos(String productIds) {
System.out.println("getProductInfos接口,接收到一次请求,productIds="+productIds);
JSONArray jsonArray = new JSONArray();

for (String productId : productIds.split(",")) {
jsonArray.add(JSONObject.parseObject("{\"id\": " + productId
+ ", \"name\": \"iphone7手机\", \"price\": 5599, \"pictureList\":\"a.jpg,b.jpg\", \"specification\": \"iphone7的规格\", \"service\": \"iphone7的售后服务\", \"color\": \"红色,白色,黑色\", \"size\": \"5.5\", \"shopId\": 1, \"modifiedTime\": \"2017-01-01 12:00:00\",\"cityId\":1,\"brandId\":1}"));
}
return jsonArray.toJSONString();
}

猜你喜欢

转载自blog.csdn.net/sun_qiangwei/article/details/80376761