1.前言
在之前,我们已经优化过一个批量查询的接口了,request cache来做优化,可能有相同的商品就可以直接取用缓存了。
但现在还存在一个优化场景:多个商品,需要发送多次网络请求,调用多次接口,才能拿到结果。
2.request collapser介绍
-
可以使用HystrixCollapser将多个HystrixCommand合并到一起,多个command放在一个command里面去执行,发送一次网络请求,就拉取到多条数据
-
用请求合并技术,将多个请求合并起来,可以减少高并发访问下需要使用的线程数量以及网络连接数量,这都是hystrix自动进行的
-
其实对于高并发的访问来说,是可以提升性能的
-
请求合并技术的开销有多大
(1) 使用请求合并技术的开销就是导致延迟大幅度增加,因为需要一定的时间将多个请求合并起来
(2) 发送过来10个请求,每个请求本来大概是2ms可以返回,要把10个请求合并在一个command内,统一一起执行,先后等待一下,5ms
(3) 所以说,要考量一下,使用请求合并技术是否合适,如果一个请求本来耗费的时间就比较长,那么进行请求合并,增加一些延迟影响并不大
(4) 请求合并技术,不是针对那种访问延时特别低的请求的,比如说你的访问延时本身就比较高,20ms,10个请求合并在一起,25ms,这种情况下就还好 -
好处在哪里,增加你的吞吐量
(1) 大幅度削减你的线程池的资源耗费,线程池,10个线程,一秒钟可以执行10个请求,合并在一起,1个线程执行10个请求,10个线程就可以执行100个请求
(2) 减少你对后端服务访问时的网络资源的开销,10个请求,10个command,10次网络请求的开销,1次网络请求的开销了
2.request collapser参数
-
maxRequestsInBatch
// 设置所有实例的默认值 hystrix.collapser.default.maxRequestsInBatch=... // 设置实例HystrixCommandKey的此属性值 hystrix.collapser.HystrixCollapserKey.maxRequestsInBatch=...
设置同时批量执行的请求的最大数量,默认值:Integer.MAX_VALUE
-
timerDelayInMilliseconds
// 设置所有实例的默认值 hystrix.collapser.default.timerDelayInMilliseconds=... // 设置实例HystrixCommandKey的此属性值 hystrix.collapser.HystrixCollapserKey.timerDelayInMilliseconds=...
批量执行创建多久之后,再触发真正的请求,默认值:10
-
requestCache.enabled
// 设置所有实例的默认值 hystrix.collapser.default.requestCache.enabled=... // 设置实例HystrixCommandKey的此属性值
此属性指示是否为HystrixCollapser.execute()和HystrixCollapser.queue()调用启用请求高速缓存。默认值:true
3.例子
3.1 例子1
@HystrixProperty(name = "timerDelayInMilliseconds", value = "50")
@HystrixProperty(name = "maxRequestsInBatch", value = "200")
(1) 这两个属性表示合并请求的时间窗口为50ms,窗口时间内最多合并200个请求。默认值是10ms,合并请求数不限。这个根据自己实际情况来设定。
(2) timerDelayInMilliseconds建议尽量设置的小一点,如果并发量不大的话,其实也没有必要使用HystrixCollapser来处理
(3) 但是一旦用了HystrixCollapser之后,请求的最小响应延迟一定是大于这个时间窗口了,因为接受到的请求都要经过这个窗口时间的聚合,然后再进行处理,在分别返回给具体的调用方的。如果单个请求的响应时间比较大的话,并不会有多少影响,如果单次响应时间很小的话比如几毫秒,用了合并请求特性之后,至少都变成50几毫秒了,反而造成了响应时间变长的负作用。
3.2 例子2
/**
* 一次性批量查询多条商品数据的请求
*/
@RequestMapping("/getProductInfos")
@ResponseBody
public String getProductInfos(String productIds) {
List<Future<ProductInfo>> futures = new ArrayList<Future<ProductInfo>>();
for(String productId : productIds.split(",")) {
GetProductInfosCollapser getProductInfosCollapser =
new GetProductInfosCollapser(Long.valueOf(productId));
futures.add(getProductInfosCollapser.queue());
}
try {
for(Future<ProductInfo> future : futures) {
System.out.println("CacheController的结果:" + future.get());
}
} catch (Exception e) {
e.printStackTrace();
}
return "success";
}
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<com.netflix.hystrix.HystrixCollapser.CollapsedRequest<ProductInfo, Long>> requests) {
return new BatchCommand(requests);
}
@Override
protected void mapResponseToRequests(
List<ProductInfo> batchResponse,
Collection<com.netflix.hystrix.HystrixCollapser.CollapsedRequest<ProductInfo, Long>> requests) {
int count = 0;
for(CollapsedRequest<ProductInfo, Long> request : requests) {
request.setResponse(batchResponse.get(count++));
}
}
@Override
protected String getCacheKey() {
return "product_info_" + productId;
}
private static final class BatchCommand extends HystrixCommand<List<ProductInfo>> {
public final Collection<CollapsedRequest<ProductInfo, Long>> requests;
public BatchCommand(Collection<CollapsedRequest<ProductInfo, Long>> requests) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService"))
.andCommandKey(HystrixCommandKey.Factory.asKey("GetProductInfosCollapserBatchCommand")));
this.requests = requests;
}
@Override
protected List<ProductInfo> run() throws Exception {
// 将一个批次内的商品id给拼接在了一起
StringBuilder paramsBuilder = new StringBuilder("");
for(CollapsedRequest<ProductInfo, Long> request : requests) {
paramsBuilder.append(request.getArgument()).append(",");
}
String params = paramsBuilder.toString();
params = params.substring(0, params.length() - 1);
// 在这里,我们可以做到什么呢,将多个商品id合并在一个batch内,直接发送一次网络请求,获取到所有的结果
String url = "http://localhost:8082/getProductInfos?productIds=" + params;
String response = HttpClientUtils.sendGetRequest(url);
List<ProductInfo> productInfos = JSONArray.parseArray(response, ProductInfo.class);
for(ProductInfo productInfo : productInfos) {
System.out.println("BatchCommand内部,productInfo=" + productInfo);
}
return productInfos;
}
}
}