通常微服务架构中的依赖通过远程调用实现,**而远程调用中最常见的问题就是通信消耗与连接数占用。**在高并发的情况下,因为通信次数的增加,总的通信时间消耗会变得不那个理想。同时,因为对依赖服务线程池资源有限,将出现排队等待与响应延迟的情况。为了优化这两个问题,Hystrix提供了HystrixCollapser来实现请求的合并,以减少通信消耗和线程数的占用。
1. 理论
1. 请求合并的优点
把重复的请求批量的用一个HystrixCommand命令去执行,以减少通信消耗和线程数的占用,默认合并10ms之内的请求,可以减少通信开销。
2. 请求合并的缺点
虽然请求合并可以减少请求数量以缓解依赖服务线程池的资源,但是在使用的过程中也需要注意他所带来的额外的性能开销:用于请求合并的延迟时间窗会使得依赖服务的请求延迟增高。
**例如:**某个请求在不通过请求合并器访问的平均耗时为5ms,请求合并的延迟时间窗为10ms(默认值),那么当该请求的设置了请求合并器之后,最坏情况下(在延迟时间窗结束时才发起请求)该请求需要15ms才能完成。
3. 请求合并适用场景
服务提供者提供了**“返回单个对象”和“返回多个对象”**的查询接口,并且单个对象查询并发很高,服务提供者负载很高的时候,我们就可以使用请求合并来降低服务提供者的负载。
由于请求合并器的延迟时间窗会带来额外开销,所以我们是否使用请求合并器需要根据依赖服务调用的实际情况来选择,主要考虑下面两个方面:
- **【请求命令本身就是高延迟命令】**请求命令本身的延迟。如果依赖服务的请求命令本身是一个高延迟的命令,那么可以使用请求合并器,因为延迟时间窗的时间消耗就显得莫不足道了。
- **【延迟时间窗的并发量】**延迟时间窗内的并发量。如果一个时间窗内只有1-2个请求,那么这样的依赖服务不适合使用请求合并器,这种情况下不但不能提升系统性能,反而会成为系统瓶颈,因为每个请求都需要多消耗一个时间窗才响应。**相反,如果一个时间窗内具有很高的并发量,并且服务提供方也实现了批量处理接口,**那么使用请求合并器可以有效的减少网络连接数量并极大地提升系统吞吐量,此时延迟时间窗所增加的消耗就可以忽略不计了。
2. 实践
@HystrixCollapser(batchMethod = "findAll",
collapserProperties = {
@HystrixProperty(name = "timerDelayInMilliseconds", value = "3000") //请求之间的延迟
})
public Future<Account> findAcc(String id) {
//你会发现根本不会进入这个方法体
log.info("findAcc方法,ids= " + id);
return null;
}
@HystrixCommand
public List<Account> findAll(List<String> ids) {
log.info("合并请求线程操作:{}", Thread.currentThread().getName());
String url = "http://localhost:8001/getInfos";
List<Account> accounts = null;
try {
accounts = HttpClientUtils.doPost(url, ids, new TypeReference<List<Account>>() {
});
} catch (IOException e) {
e.printStackTrace();
}
return accounts;
}
调用请求
@RequestMapping("findAcc")
@ResponseBody
public String findCollapser() throws ExecutionException, InterruptedException {
//开启context
HystrixRequestContext context = HystrixRequestContext.initializeContext();
Future<Account> acc1 = accountServiceImpl.findAcc("001");
Future<Account> acc2 = accountServiceImpl.findAcc("002");
Account account1 = acc1.get();
Account account2 = acc2.get();
List<Account> accounts = new ArrayList<>();
accounts.add(account1);
accounts.add(account2);
context.shutdown();
return JSON.toJSONString(accounts);
}
1. 这里单个请求的service返回的是Future对象,若是返回原对象,则是同步请求,不会进行请求合并的。
2. @HystrixCollapser中有scope属性,scope的取值为REQUEST, GLOBAL。
REQUEST范围只对一个request请求内的多次服务请求进行合并,GLOBAL是多单个应用中的所有线程的请求中的多次服务请求进行合并。