如何使用Hystrix实现服务请求合并?

标签: Java

保留所有版权,请引用而不是转载本文(原文地址 https://yeecode.top/blog/133/ )。

概述

Hystrix支持对方法合并,即将多个单次调用合并为一个批量调用。其具体的条件是这样的:

这样当我们调用原方法时,Hystrix就将一定时间内的请求合并起来,然后去调用批处理方法。于是,针对原方法的多次调用就被转化为了针对批量方法的单次调用。如果原方法内执行的是网络请求、数据库查询等操作,则方法合并可以极大地提升性能。

在进行请求的合并操作时,如果规定的时间内只有原方法的一次调用,那Hystrix也会去调用批处理方法。这意味着原方法永远都不会被真正调用。因此,我们在编写代码时,原方法内的逻辑不需要编写,直接返回null即可。

下面,我们通过示例代码进行介绍。

环境配置

通过Hystrix就可以方便地实现这一操作。接下来,我们给出简单的示例。

首先,准备SpringBoot项目,然后在其中引入适用于SpringBoot的hystrix包:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.2.7.RELEASE</version>
</dependency>

注意,如果这时候启动提示如下的错误,则基本是SpringBoot版本与hystrix版本不匹配导致的。因为hystrix是基于SpringCloud的,而SpringCloud和SpringBoot存在版本对应关系。如果对应不一致,则可能导致出现下面的错误。

java.lang.NoSuchMethodError: 'void org.springframework.boot.builder.SpringApplicationBuilder.<init>(java.lang.Object[])'

这里我们给出一个可用的对应版本,即SpringBoot的2.3.5.RELEASE和spring-cloud-starter-netflix-hystrix的2.2.7.RELEASE。它们这一对是肯定没有问题的。

然后,我们需要在SpringBoot的主类上增加@EnableHystrix注解,以启用Hystrix。如下所示。

@SpringBootApplication
@EnableHystrix
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

环境配置就是这么简单,接下来我们就可以写业务代码了。

功能实现

下面的示例代码中即展示了原方法queryUserNameById和对应的批处理方法queryUsersByIds。

@HystrixCollapser(
        batchMethod = "queryUsersByIds",
        collapserProperties = {
                // 批处理中允许的最大请求数为5
                @HystrixProperty(name = HystrixPropertiesManager.MAX_REQUESTS_IN_BATCH, value = "5"),
                // 批处理的最大等待时间为500ms
                @HystrixProperty(name = HystrixPropertiesManager.TIMER_DELAY_IN_MILLISECONDS, value = "500"),
        }
)
Future<String> queryUserNameById(Integer i) {
    return null;
}


@HystrixCommand
public List<String> queryUsersByIds(List<Integer> idList) {
    System.out.print("批量查询用户名,编号列表为: ");
    idList.forEach(x -> System.out.print(x.toString() + ";"));
    System.out.println();

    List<String> stringList = new ArrayList<>();
    idList.forEach(x -> stringList.add("易哥" + x));
    return stringList;
}

我们需要在原方法上增加@HystrixCollapser注解,并在批处理方法上增加@HystrixCommand注解。这样,当我们调用原方法时,Hystrix会合并这些调用转而去调用批处理方法。调用批处理方法的时间点有一下几个:

以上介绍来自《高性能架构之道(第二版)》,本文的示例代码也来自该书。大家要展开学习的话可以参考这本书。

我们通过如下的代码对上述原方法展开40次调用。

StringBuilder stringBuilder = new StringBuilder();
HystrixRequestContext context = HystrixRequestContext.initializeContext();

List<Future<String>> futureList = new ArrayList<>();

// 连续的10次调用
for (int i = 1; i <= 10; i++) {
    futureList.add(collapserService.queryUserNameById(i));
}
Thread.sleep(2000);

// 间隔1000ms的10次调用
for (int i = 11; i <= 20; i++) {
    Thread.sleep(1000);
    futureList.add(collapserService.queryUserNameById(i));
}
Thread.sleep(2000);

// 间隔150ms的10次调用
for (int i = 21; i <= 30; i++) {
    Thread.sleep(150);
    futureList.add(collapserService.queryUserNameById(i));
}
Thread.sleep(2000);

// 获取以上30次调用的结果
for (Future<String> future : futureList) {
    stringBuilder.append(future.get()).append("\r\n");
}

// 连续进行10次调用,但每次调用后立刻同步获取结果
for (int i = 31; i <= 40; i++) {
    stringBuilder.append(collapserService.queryUserNameById(i).get()).append("\r\n");
}

context.close();
return stringBuilder.toString();

在上述代码中,一个关键的操作是使用HystrixRequestContext.initializeContext()在当前线程创建了一个上下文。这是因为Hystrix的隔离策略使得下游批处理方法会在各自的线程中执行,需要通过这个上下文进行线程间数据的传递。

执行上述代码,可以在控制台看到如下的输出:

批量查询用户名,编号列表为: 2;3;1;5;4;
批量查询用户名,编号列表为: 8;10;9;6;7;
批量查询用户名,编号列表为: 11;
批量查询用户名,编号列表为: 12;
批量查询用户名,编号列表为: 13;
批量查询用户名,编号列表为: 14;
批量查询用户名,编号列表为: 15;
批量查询用户名,编号列表为: 16;
批量查询用户名,编号列表为: 17;
批量查询用户名,编号列表为: 18;
批量查询用户名,编号列表为: 19;
批量查询用户名,编号列表为: 20;
批量查询用户名,编号列表为: 22;21;
批量查询用户名,编号列表为: 23;24;25;
批量查询用户名,编号列表为: 28;27;26;
批量查询用户名,编号列表为: 30;29;
批量查询用户名,编号列表为: 31;
批量查询用户名,编号列表为: 32;
批量查询用户名,编号列表为: 33;
批量查询用户名,编号列表为: 34;
批量查询用户名,编号列表为: 35;
批量查询用户名,编号列表为: 36;
批量查询用户名,编号列表为: 37;
批量查询用户名,编号列表为: 38;
批量查询用户名,编号列表为: 39;
批量查询用户名,编号列表为: 40;

可见Hystrix确实按照参数设置对原方法的调用进行了聚合。最终,返回了如下的结果。可见从最终结果上看,调用方拿到的返回值和逐次调用原方法的返回值是一样的。方法合并对于上游调用方而言完全透明。

易哥1
易哥2
易哥3
易哥4
易哥5
易哥6
易哥7
易哥8
易哥9
易哥10
易哥11
易哥12
易哥13
(一直输出到“易哥40”,故省略)……

通过该示例,我们就演示了请求合并功能。

参数详解

以上实现的是最简单的请求合并示例,也仅仅使用了最简单的参数,而其他参数都是用的默认值。但是这个作为示例是可以的,真正使用时,大家还是要对Hystrix的各个参数有详细的了解,至少也要知道大体的含义和默认值。否则,后续使用中,可能会遇到不少百思不得其解的地方。

总之,Hystrix要比这个强大的多。大家还是要好好学一下。

下面是我对@HystrixCollapser参数的介绍,主要是摘抄参考了《高性能架构之道(第二版)》这本书。上面的示例也是参考的书里的。下面的参数我这里列的不是很全,如果要细致学习的话,十分建议大家去看下这本书。

好了,以上就是Hystrix实现请求合并的简单示例。

可以访问个人知乎阅读更多文章:易哥(https://www.zhihu.com/people/yeecode),欢迎关注。

作者书籍推荐