Асинхронна відповідь HTTP

Привіт, спільното!
Робимо сервіс який повинен приймати HTTP запити і відповідати кліенту.
Цей метод повинен відправляти в кафку топік send дані від кліента і забути про це на деякий час (але не блокуватися). Наприклад

@GetMapping("/comp")
public Mono<ShareObj> handleRequest() {
CompletableFuture<ShareObj> future = new CompletableFuture<>();
ShareObj objectC = new ShareObj();
objectC.setReq(UUID.randomUUID().toString());
kafkaTemplate.send("send", objectC);
futureMap.put(objectC.getReq(), future);
return Mono.fromFuture(future);
}

Десь дані с топіку send прочиталися і відповідь відправилась в топік resp. В тому методі де в нас @GetMapping("/comp") буде знаходитися KafkaListener який слухає топік resp звідки потрібно забрати дані для кліента.


 @KafkaListener(topics = "resp", groupId = "consGroup")
public void listen(@Payload ShareObj req) {
System.out.println(req);
var future = futureMap.get(req.getReq());
if (future != null) {
future.complete(req);
futureMap.remove(req.getReq());}}

Я не зміг придумати краще ніж з CompletableFuture щоб повертати відповідь пізніше. Але втут дуже погана пропускна спроможність.
Як можна по іншому вирішити цю проблему або взагалі якийсь інший підхід?

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Шкода, що піздно (і то й випадково) побачив топік.
Цілком реальна (і неприємна) задача, не розумію токсичність коментаторів.

Автор, яке рішення обрали?
Вдалось домовитись робити синхроний виклик до «процесора» замість кафки?
Чи через жорсткі обмеження — якийсь локалький стораж для респонсів завели?
Чи як?
Поділіться, будь ласка, цікаво.

Доброго дня, нажаль варіантів крім одного поки не домовились.
Дякую за розуміння — токсичності коментарів.
Через жорсткі обмеженя ми тримаемо в мапі CompletableFuture.
Не -масштабується, поки живемо з цим(

Інстанс один? Як вирішили

А кто дает гарантии что чтение с топика будет на том же инстансе что и обрабатываемый «/comp»?

Я до того, що якщо інстанс один, то варіант з CompletableFuture — так сяк підходить.
Але, зазвичай, по дефолту інстансів >1, горизонтальне маштабування, failover оце все.
Я, з першогу погляду, придумовую тільки велосипед з key-value storage, класти туди uuid request-a, ну і poll-ити його доки ліснер (можливо з іншого інстанса) покладе туди response. Така синхронна обгортка асинхронного «процесора».

А простіше всього домовитись про синхронний call і прокидувати його від клієнта до «процесора».

Цікаво почути інші рішення.

домовились без масштабування(

Треба ще SQL в React вставити як в сусідньому топіку і вуаля — ми винайшли купу коду, яка не може навіть нормально порендерити юзернейм.

Зразковий щіткод.
Потенційний меморі лік, непрацездатність при scale>1, імітація синхронностф через асинхронну кафку і черги reequest+response, ммм бімба.

В топіку вже дали відповідь — або пуш через вебхук або пулл через одразу повернутий ууід асинхронного пошуку.

Є ще варіант з вебсокетами але то складніше.

так, розумію що виглядає так собі. Дякую за пораду

Воно не просто «так собі». Воно погано. Тому треба зрозуміть чому воно погано і не робити так.

це поганий підхід, хто сказав що кафка поверне результат швидко, а якщо запитів багато?
ви забьете кількість можливих коннектов і сервіс встане раком коли в вас буде навантаження

в вас два підхода норм:

пуш: в апі запросити коллбек хттп куди ви сходите коли в вас буде результат із кафки
пулл: повертати uuid запиту по якому клієнт може ходити питати статус

якщо клієнт туповат і не вміє в такій підхід можна зробити сдк в якому ці механізми будут сховані а назовні буде просто виглядати як блокірующий колл

пуш: в апі запросити коллбек хттп куди ви сходите коли в вас буде результат із кафки

не всі клієнти можуть вміти в асинхронщину. замість того, щоб інкапсулювати це в 1 компоненті, пропонується це перенести на всіх клієнтів?

це поганий підхід

цей підхід називається long-polling, працював в часи, коли не було websocket’ів і імплементується саме таким підходом. Мінус — наявність висячих з’єднань, у разі повільних відповідей в кафку. Але це легко масштабується і вирішується регулюванням таймаутів.

хто сказав що кафка поверне результат швидко

на це сподіватися марно в будь якому випадку. навіть pull механізм може працювати повільно, якщо система (база, мережа, диск) не вивозять. для цього потрібні метрики, горизонтальне масштабування, механізм backpressure, налаштовані timeout’и.

якщо запитів багато

backpressure feedback. треба мати обмеження, скільки запитів в один час може бути (семафор якийсь, лімітована черга), і всі надлишкові повинні відкидатися.

ви забьете кількість можливих коннектов

вирішується обмеженням кількості з’єднань, таймаутами, фронтенд проксі, який буде масштабувати або фільтрувати зайве. Це в будь якому разі варто робити

У ваших випадках:

пуш: в апі запросити коллбек хттп куди ви сходите коли в вас буде результат із кафки

вимагає ускладнення сервіса, додаванням бази даних, або якогось shared storage між інстансами, щоб зберігати цю інформацію. Плюс не завжди умови безпеки і інфраструктури дозволяють внутрішнім сервісам виходити назовні. Відповідно, треба ще якийсь фронтенд-проксі, який буде в світ віддавати відповіді, щоб приховувати внутрішню кухню

пулл: повертати uuid запиту по якому клієнт може ходити питати статус

і тут або всі з’єднання вже мають keep-alive, і маємо ту ж саму проблему у вигляді висячих з’єднань, або ж оверхед у вигляді нових tcp/tls сесій, які дають зайве навантаження. Х сервісів можуть прийти одночасно за результатом, і відповідно знову нам треба фільтруючий проксі, який буде регулювати кількість одночасних запитів, щоб система не просіла.

А кто дает гарантии что чтение с топика будет на том же инстансе что и обрабатываемый «/comp»?

В тому методі де в нас @GetMapping("/comp") буде знаходитися KafkaListener який слухає топік resp звідки потрібно забрати дані для кліента.

Если у вас клиент ждет ответа то зачем городить асинхронную работу? Делайте как проще.

Як можна по іншому вирішити цю проблему або взагалі якийсь інший підхід?

Ви праві, ніхто не гарантує цього. Але ніхто про це чути не хоче, потрібно зробити і все =)
Тут багато кліентів і не хочу заблокувати всі пули для запитів. А наша платформа яка обробляє запит може віддавати відповідь десь за 500 мс буває.

так если у вас Reactor(Mono/Flux) то должна быть вся система реактивная и значит сервис легко десятки тысяч запросов может переваривать.

на жаль, зараз це так не працює)

Ось такий ще є підхід

learn.microsoft.com/...​terns/async-request-reply

Повернути статус-лінку с унікальним id и приблизний час коли перепитати статус результату (але можна і без цієї інформації).

Приблизний час не обов’язково якось вираховувати, можна і захардкодити якесь мінімально адекватне значення спираючись на логіку сервісу. Це все одно рекомендація і стукати раніше ніхто не заборонить , якщо рейт лімітер не стоїть.

Дякую за відповідь, але кліент працює з нами тільки через http і очікує запит-відповідь =(

Дякую за відповідь, але кліент працює з нами тільки через http і очікує запит-відповідь =(

Що не так з цим рішенням? Якщо є потреба strong consistent response, а не бекенді розподілена система то це доволі типова ситуація буде очікування асинхронної відповіді від message broker або оновлення eventual consistent представлення відповіді як більш узагальненому сценарію.

У вас є якісь рішення для цього, щось можете підсказати?

Я не підскажу рішення для java + kafka і не знаю які у вас очікування по throughput та і власне де саме виникає bottlneck. Але 5 хв гугління дають доволі багато варіантів рішення типових. Але варто розібратися де у вас проблема та вирішити ії, зазвичай такий стиль коммунікації вимагає доволі великого паралелізму обробки або батчінгу і low latency queue. Треба дивитися на вашу специфіку і тюнити відповідно.
callistaenterprise.se/...​request-reply-over-kafka
dzone.com/...​ng-spring-request-reply-1

Підписатись на коментарі