Снижение производительности Webflux + Netty NIO в 30 раз по сравнению с традиционным вводом-выводом - PullRequest
0 голосов
/ 16 октября 2018

У нас проблемы с передачей по сети при использовании Spring Boot 2.0, Webflux 5.0.7 и Netty 4.1.25.Мы хотим передать 100000 элементов, сериализованных в формате JSON (примерно 10 МБ сетевого трафика), одному клиенту.

Производительность передачи по сети резко отличается между NIO и традиционным IO.Результаты теста приведены ниже:

Start reading 100000 from server in 5 iterations
Avg HTTP 283 ms
Avg stream 8130 ms

На данный момент количество запросов в секунду не является проблемой , но скорость передачи по сети равна.Мы читали, что с точки зрения скорости сети NIO может быть примерно на 30% медленнее, но 1 / 30x является избыточным.

При выборке на стороне клиента и сервера мы заметили, что причина в основном в реализации на стороне сервера.Из скриншота ниже видно, что большую часть времени сервер тратит на методы select() и doWrite().

Sampling

Сам код конечной точки:

@RestController
@RequestMapping(produces = {APPLICATION_JSON_VALUE, APPLICATION_STREAM_JSON_VALUE})
@Validated
public class StreamingController {

    private static final Logger log = LoggerFactory.getLogger(StreamingController.class);


    @GetMapping("/instruments/{eodDate}")
    public Flux<TestItem> getInstruments(
            @PathVariable @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate eodDate,
            @RequestParam(required = false) Instant asOfTimestamp) {
        //Generate test data in memory
        List<TestItem> collect = IntStream.range(0, 100000).mapToObj(i -> new TestItem.Builder().build()).collect(Collectors.toList());
        return Flux
                .fromIterable(collect);
    }

Мы используем конфигурацию Spring Boot для Netty, и мы подозреваем, что по умолчанию Netty настроена неправильно.Мы ищем вашей помощи.Я добавлю любые другие детали по вашему запросу.

Upd: Цель состоит в том, чтобы прочитать весь ответ в пакетном режиме, чтобы избежать помещения всего ответа в память, поскольку ожидаемые объемы данных огромны (параГб).Допустимо использовать пакет данных на стороне клиента вместо одного элемента.

1 Ответ

0 голосов
/ 17 октября 2018

Вы на самом деле не тестируете NIO против IO.Приложения Spring WebFlux всегда используют неблокирующий ввод-вывод на уровне сервера (с Netty, Undertow или любым совместимым с Servlet 3.1+ сервером асинхронного ввода-вывода).

В этом случае вы сравниваете:

  1. обслуживает полезную нагрузку "application/json" за один раз с Spring WebFlux
  2. обслуживает поток "application/stream+json" ответ с Spring WebFlux

В первом случае Spring WebFlux имеет видсоздание тела ответа в реактивной манере, но решение о буферизации и сбросе остается на самом сервере.Запись в сеть требует затрат, буферизация, а запись больших кусков эффективна.

Во втором случае вы просите Spring WebFlux написать и сбросить для каждого элемента * 1016.*.Это полезно, когда клиенты прослушивают (возможно, бесконечный) поток событий, и между двумя разными событиями может пройти некоторое время.Этот подход потребляет больше ресурсов и объясняет разницу в производительности.

Таким образом, этот тест показывает не IO против NIO, а потоковый и не потоковый.

Если вы хотите детализированный контроль надзапись / очистка ответа, вы можете опуститься до уровня ServerHttpResponse и использовать writeAndFlushWith(Flux<Flux<DataBuffer>>), но это довольно низкий уровень, поскольку вы имеете дело с DataBuffer экземплярами напрямую.

Альтернативой может бытьсоздать промежуточные объекты JSON, которые содержат списки TestItem, например:

public Flux<TestItemBatch> batch() {
    Flux<TestItem> items= //...;
    Flux<List<TestItem>> itemsLists = items.buffer(100);
    return itemsLists.map(list -> new TestItemBatch(list));
}
...