Как воспользоваться преимуществами неблокирующих запросов в Spring Web MVC Controllers - PullRequest
0 голосов
/ 10 мая 2019

Я пытаюсь продемонстрировать преимущества использования Reactive Streams в Spring MVC. Для этого у меня есть небольшой сервер Jetty, работающий с двумя конечными точками:

  • /normal возвращает POJO
  • /flux возвращает тот же объект, завернутый в Mono

Затем я запускаю клиент и запускаю несколько тысяч одновременных запросов в одной из этих конечных точек. Я ожидал бы увидеть меньше ошибок со второй конечной точкой, где обработка происходит асинхронно. Однако иногда я наблюдаю больше ошибок на конечной точке с поддержкой асинхронизации; в обоих случаях, где-то между 60 - 90% ошибок 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);
    }
}

Сервер работает на Jetty 9.4.15 через Maven и 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 . Основное различие заключается в том, что я проверяю частоту ошибок или количество синхронных соединений; Я не ожидаю увеличения скорости.

1 Ответ

0 голосов
/ 15 мая 2019

Оказывается, что хотя спецификации Servlet 3.1 поддерживают неблокирующий ввод-вывод, Spring MVC - нет. Чтобы воспользоваться всеми преимуществами реактивного API, вы должны использовать WebFlux. Смотрите здесь для получения дополнительной информации: https://youtu.be/Dp_aJh-akkU?t=1738.

Это изображение демонстрирует работу Spring MVC (слева) по сравнению с Webflux (справа).

Spring MVC vs Webflux Stack

Я выполнил еще несколько тестов, используя Гатлинг и получил схожие результаты: оба заняли примерно одинаковое количество времени, асинхронность была немного менее надежной. Однако я заметил одну полу-воспроизводимую разницу: результаты асинхронности иногда быстрее реагировали:

Нормальный

Response Time Distribution when returning standard type

50-й процентиль: 33,6 с 95-й процентиль: 35,4 с

Реактивная

Response Time Distribution when returning Flux

50-й процентиль: 6,51 с 95-й процентиль: 49,5 с

Мне все еще неясно, в чем преимущества использования асинхронных вызовов (например, DeferredResult) или API Reactive Streams в Spring MVC. Так что, если кто-то сможет разъяснить это с конкретными случаями использования, это будет с благодарностью.

...