如何利用 Spring Web MVC 控制器中的非阻塞请求
How to take advantage of non-blocking requests in Spring Web MVC Controllers
我正在尝试展示在 Spring MVC 中使用 Reactive Streams 的优势。为此,我有一个小型 Jetty 服务器 运行 两个端点:
/normal
returns一个POJO
/flux
returns 包裹在 Mono
中的同一对象
然后我启动一个客户端并在其中一个端点同时发起几千个请求。我希望在第二个端点看到更少的错误,处理是异步发生的。但是,我有时会在启用异步的端点上观察到 more 错误;在这两种情况下,Connection refused: no further information
.
的 60 - 90% 误差之间的任何地方
要么是我哪里做错了,要么是我不太明白。 Connection refused
正是我希望避免的事情。
服务器
这是我来自服务器的代码。在 normal
的情况下,我确实用 .sleep()
:
阻塞了线程
@Controller
public class FluxController {
@GetMapping(value = "/normal", produces = MediaType.APPLICATION_JSON_VALUE)
public Map normal() throws Exception {
Thread.sleep(randomTime());
return Collections.singletonMap("type", "normal");
}
@GetMapping(value = "/flux", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<Map> flux() {
return Mono.delay(Duration.ofMillis(randomTime()))
.map(x -> Collections.singletonMap("type", "flux"));
}
private static long randomTime() {
return ThreadLocalRandom.current().nextLong(200, 1000);
}
}
服务器通过 Maven 在 Jetty 9.4.15 上 运行 并且 web.xml 定义为:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
客户
我的客户端使用 Spring WebClient:
public class ClientApplication {
private static final String ENDPOINT = "normal";
private static final int REPETITIONS = 10_000;
public static void main(String[] args) {
WebClient client = WebClient.create("http://localhost:8080");
AtomicInteger errors = new AtomicInteger(0);
List<Mono<Response>> responses = IntStream.range(0, REPETITIONS)
.mapToObj(i -> client.get()
.uri(ENDPOINT)
.retrieve()
.bodyToMono(Response.class)
.doOnError(e -> errors.incrementAndGet())
.onErrorResume(e -> Mono.empty())
)
.collect(Collectors.toList());
Mono.when(responses)
.block();
System.out.println(String.format("%-2f %% errors", errors.get() * 100.0 / REPETITIONS));
}
static class Response {
public String type;
}
}
与此处问题类似的前提:WebFlux async processing。主要区别是我测试的是错误率,或者同步连接数;我不希望速度提高。
事实证明,虽然 Servlet 3.1 规范支持 non-blocking IO,但 Spring MVC 不支持。要充分利用响应式 API,您必须使用 WebFlux。有关详细信息,请参阅此处:https://youtu.be/Dp_aJh-akkU?t=1738.
此图演示了 Spring MVC(左侧)与 Webflux(右侧)的工作原理。
我使用 Gatling 进行了更多测试并得到了相似的结果:两者花费的时间大致相同,异步稍微不可靠。但是,我确实注意到一个 semi-reproducable 区别:异步结果有时响应速度更快:
正常
第 50 个百分位数:33.6 秒
第 95 个百分位数:35.4 秒
被动
第 50 个百分位数:6.51 秒
第 95 个百分位数:49.5 秒
我仍然不清楚在 Spring MVC 中使用异步调用(例如 DeferredResult)或反应流 API 的优势。因此,如果有人能够用具体的用例来阐明这一点,我们将不胜感激。
我正在尝试展示在 Spring MVC 中使用 Reactive Streams 的优势。为此,我有一个小型 Jetty 服务器 运行 两个端点:
/normal
returns一个POJO/flux
returns 包裹在Mono
中的同一对象
然后我启动一个客户端并在其中一个端点同时发起几千个请求。我希望在第二个端点看到更少的错误,处理是异步发生的。但是,我有时会在启用异步的端点上观察到 more 错误;在这两种情况下,Connection refused: no further information
.
要么是我哪里做错了,要么是我不太明白。 Connection refused
正是我希望避免的事情。
服务器
这是我来自服务器的代码。在 normal
的情况下,我确实用 .sleep()
:
@Controller
public class FluxController {
@GetMapping(value = "/normal", produces = MediaType.APPLICATION_JSON_VALUE)
public Map normal() throws Exception {
Thread.sleep(randomTime());
return Collections.singletonMap("type", "normal");
}
@GetMapping(value = "/flux", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<Map> flux() {
return Mono.delay(Duration.ofMillis(randomTime()))
.map(x -> Collections.singletonMap("type", "flux"));
}
private static long randomTime() {
return ThreadLocalRandom.current().nextLong(200, 1000);
}
}
服务器通过 Maven 在 Jetty 9.4.15 上 运行 并且 web.xml 定义为:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
客户
我的客户端使用 Spring WebClient:
public class ClientApplication {
private static final String ENDPOINT = "normal";
private static final int REPETITIONS = 10_000;
public static void main(String[] args) {
WebClient client = WebClient.create("http://localhost:8080");
AtomicInteger errors = new AtomicInteger(0);
List<Mono<Response>> responses = IntStream.range(0, REPETITIONS)
.mapToObj(i -> client.get()
.uri(ENDPOINT)
.retrieve()
.bodyToMono(Response.class)
.doOnError(e -> errors.incrementAndGet())
.onErrorResume(e -> Mono.empty())
)
.collect(Collectors.toList());
Mono.when(responses)
.block();
System.out.println(String.format("%-2f %% errors", errors.get() * 100.0 / REPETITIONS));
}
static class Response {
public String type;
}
}
与此处问题类似的前提:WebFlux async processing。主要区别是我测试的是错误率,或者同步连接数;我不希望速度提高。
事实证明,虽然 Servlet 3.1 规范支持 non-blocking IO,但 Spring MVC 不支持。要充分利用响应式 API,您必须使用 WebFlux。有关详细信息,请参阅此处:https://youtu.be/Dp_aJh-akkU?t=1738.
此图演示了 Spring MVC(左侧)与 Webflux(右侧)的工作原理。
我使用 Gatling 进行了更多测试并得到了相似的结果:两者花费的时间大致相同,异步稍微不可靠。但是,我确实注意到一个 semi-reproducable 区别:异步结果有时响应速度更快:
正常
第 50 个百分位数:33.6 秒 第 95 个百分位数:35.4 秒
被动
第 50 个百分位数:6.51 秒 第 95 个百分位数:49.5 秒
我仍然不清楚在 Spring MVC 中使用异步调用(例如 DeferredResult)或反应流 API 的优势。因此,如果有人能够用具体的用例来阐明这一点,我们将不胜感激。