передаючи потік Акка службі вище за течією для заселення


9

Мені потрібно зателефонувати до висхідної служби (Служба Azure Blob), щоб підштовхнути дані до OutputStream, і тоді мені потрібно обернутися і відштовхнути його до клієнта, через akka. Без akka (і просто сервлет-коду) я просто отримаю ServletOutputStream і передаю його методу служби azure.

Найближчі я можу спробувати наткнутися, і явно це неправильно, є щось подібне

        Source<ByteString, OutputStream> source = StreamConverters.asOutputStream().mapMaterializedValue(os -> {
            blobClient.download(os);
            return os;
        });

        ResponseEntity resposeEntity = HttpEntities.create(ContentTypes.APPLICATION_OCTET_STREAM, preAuthData.getFileSize(), source);

        sender().tell(new RequestResult(resposeEntity, StatusCodes.OK), self());

Ідея полягає в тому, що я закликаю службу вище, щоб отримати вихідний потік, зателефонувавши на blobClient.download (os);

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

Як це зробити?


Яка поведінка download? Чи передає вони дані osта повертаються лише після того, як дані записані?
Алек

Відповіді:


2

Справжня проблема тут полягає в тому, що API Azure не розроблений для зворотного тиску. Немає способу вихідний потік повернути Azure, що він не готовий до отримання більшої кількості даних. Інакше кажучи: якщо Azure виштовхує дані швидше, ніж ви зможете їх споживати, десь повинен виникнути невдалий збій переповнення буфера.

Зважаючи на цей факт, наступне найкраще, що ми можемо зробити:

  • Використовуйте, Source.lazySourceщоб почати завантажувати дані лише тоді, коли є попит нижче (поточно. Запускається джерело і запитуються дані).
  • Покладіть downloadвиклик в інший потік, щоб він продовжував виконувати без блокування повернення джерела. Один із способів зробити це з допомогою Future(я не впевнений, що найкраща практика Java, але в будь-якому випадку повинен працювати добре). Хоча це спочатку не має значення, можливо, вам доведеться вибрати інший контекст виконання, крім того, system.dispatcher- все залежить від того download, блокується він чи ні.

Прошу вибачення заздалегідь, якщо цей код Java неправильно сформований - я використовую Akka зі Scala, тому це все, лише не дивлячись на посилання на синтаксис API Akka Java та синтаксис Java.

ResponseEntity responseEntity = HttpEntities.create(
  ContentTypes.APPLICATION_OCTET_STREAM,
  preAuthData.getFileSize(),

  // Wait until there is downstream demand to intialize the source...
  Source.lazySource(() -> {
    // Pre-materialize the outputstream before the source starts running
    Pair<OutputStream, Source<ByteString, NotUsed>> pair =
      StreamConverters.asOutputStream().preMaterialize(system);

    // Start writing into the download stream in a separate thread
    Futures.future(() -> { blobClient.download(pair.first()); return pair.first(); }, system.getDispatcher());

    // Return the source - it should start running since `lazySource` indicated demand
    return pair.second();
  })
);

sender().tell(new RequestResult(responseEntity, StatusCodes.OK), self());

Фантастичний. велике спасибі Невелика редакція вашого прикладу: Futures.future (() -> {blobClient.download (pair.first ()); return pair.first ();}, system.getDispatcher ());
MeBigFatGuy

@MeBigFatGuy Правильно, дякую!
Алек

1

У OutputStreamцьому випадку є "матеріалізованим значенням" Sourceі воно буде створене лише після запуску потоку (або "матеріалізації" у поточний потік). Запускаючи його, ви не можете контролювати, оскільки ви Sourceпередаєте HTTP Akka, і згодом він фактично запустить ваш джерело.

.mapMaterializedValue(matval -> ...)зазвичай використовується для трансформації матеріалізованого значення, але оскільки він викликається як частина матеріалізації, ви можете використовувати це для того, щоб робити побічні ефекти, такі як відправлення matval у повідомленні, як ви зрозуміли, не обов'язково нічого поганого що навіть якщо це виглядає дивно. Важливо розуміти, що потік не завершить свою матеріалізацію і не запуститься до тих пір, поки лямбда не завершиться. Це означає проблеми, якщо download()вони блокують, а не прищеплюють якусь роботу з іншої теми та негайно повертаються.

Однак є ще одне рішення: Source.preMaterialize()воно матеріалізує джерело і дає вам Pairматеріальне значення та нове, Sourceяке можна використовувати для споживання вже запущеного джерела:

Pair<OutputStream, Source<ByteString, NotUsed>> pair = 
  StreamConverters.asOutputStream().preMaterialize(system);
OutputStream os = pair.first();
Source<ByteString, NotUsed> source = pair.second();

Зауважте, що у вашому коді є декілька додаткових речей, що найголовніше, якщо blobClient.download(os)виклик блокується, поки це не зроблено, і ви зателефонуєте йому від актора, у цьому випадку ви повинні переконатися, що ваш актор не голодує з диспетчером і не зупиняється інші учасники вашої програми із виконання (див. документи Akka: https://doc.akka.io/docs/akka/current/typed/dispatchers.html#blocking-needs-careful-management ).


1
Дякуємо за відповідь. Я не бачу, як це могло б працювати? куди йдуть байти, коли викликається blobClient.download (os) (якщо я сам його називаю)? Уявіть, що там є терабайт даних, який сидить і чекає їх запису. мені здається, що виклик blobClient.download повинен викликатись від дзвінка sender.tell, так що це в основному операція, схожа на IOUtils.copy. Використовуючи preMaterialize, я не бачу, як це відбувається?
MeBigFatGuy

У OutputStream є внутрішній буфер, він почне приймати записи до тих пір, поки цей буфер не заповниться, якщо асинхронний потік не почав споживати елементи, тоді він заблокує запис потоку (саме тому я зазначив, що важливо керувати блокуванням).
johanandren

1
Але якщо я preMaterialize і отримую OutputStream, то саме мій код робить blobClient.download (os); правильно? Це означає, що він повинен завершитись, перш ніж я можу продовжувати, що неможливо.
MeBigFatGuy

Якщо завантаження (os) не роздвоєне потоком, вам доведеться зіткнутися з його блокуванням і переконатися, що це не зупинить якусь іншу операцію. Один із способів - роздвоєти нитку, щоб зробити твір, інший - відповісти спочатку від актора, а потім виконати роботу блокування там; у такому випадку ви повинні переконатися, що актор не голодує інших акторів, дивіться посилання в кінці моя відповідь.
johanandren

на даний момент я просто намагаюся змусити його взагалі працювати. Він навіть не може обробити 10-байтний файл.
MeBigFatGuy
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.