亿级流量电商详情页系统实战-43.基于request collapser请求合并技术进一步优化批量查询

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;
		}
		
	}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值