passando un flusso Akka a un servizio a monte per popolare


9

Ho bisogno di chiamare un servizio upstream (Servizio BLOB di Azure) per inviare i dati a un OutputStream, che poi devo girare e rispedire al client, tramite Akka. Senza Akka (e solo codice servlet), otterrei semplicemente ServletOutputStream e lo passerei al metodo del servizio azzurro.

Il più vicino che posso provare a inciampare, e chiaramente questo è sbagliato, è qualcosa di simile

        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());

L'idea è che sto chiamando un servizio upstream per ottenere un outputstream popolato chiamando blobClient.download (os);

Sembra che la funzione lambda venga chiamata e ritorni, ma poi fallisce, perché non ci sono dati o qualcosa del genere. Come se non dovessi avere quella funzione lambda per fare il lavoro, ma forse restituire qualche oggetto che fa il lavoro? Non sono sicuro.

Come si fa a fare questo?


Qual è il comportamento di download? Trasmette i dati in streaming ose li restituisce solo dopo che i dati sono stati scritti?
Alec,

Risposte:


2

Il vero problema qui è che l'API di Azure non è progettata per la contropressione. Non è possibile per il flusso di output segnalare ad Azure che non è pronto per ulteriori dati. Per dirla in altro modo: se Azure spinge i dati più velocemente di quanto tu sia in grado di consumarli, ci sarà qualche brutto errore di overflow del buffer da qualche parte.

Accettando questo fatto, la prossima cosa migliore che possiamo fare è:

  • Utilizzare Source.lazySourceper avviare il download dei dati solo quando vi è una richiesta a valle (ovvero la sorgente viene eseguita e vengono richiesti i dati).
  • Inserisci la downloadchiamata in qualche altro thread in modo che continui l'esecuzione senza bloccare la restituzione del codice sorgente. Una volta il modo per farlo è con un Future(non sono sicuro di quali siano le migliori pratiche Java, ma dovrebbe funzionare bene in entrambi i modi). Sebbene inizialmente non abbia importanza, potrebbe essere necessario scegliere un contesto di esecuzione diverso da system.dispatcher- tutto dipende dal fatto che downloadsia bloccato o meno.

Mi scuso in anticipo se questo codice Java non è corretto - Uso Akka con Scala, quindi tutto ciò deriva dall'osservazione dell'API Akka Java e del riferimento alla sintassi 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());

Fantastico. grazie mille Una piccola modifica al tuo esempio è: Futures.future (() -> {blobClient.download (pair.first ()); return pair.first ();}, system.getDispatcher ());
MeBigFatGuy

@MeBigFatGuy Bene, grazie!
Alec,

1

In OutputStreamquesto caso è il "valore materializzato" di Sourcee verrà creato solo una volta eseguito il flusso (o "materializzato" in un flusso in esecuzione). Eseguirlo è fuori dal tuo controllo poiché Sourcepassi a Akka HTTP e che successivamente eseguirà effettivamente il tuo sorgente.

.mapMaterializedValue(matval -> ...)viene solitamente utilizzato per trasformare il valore materializzato ma poiché è invocato come parte della materializzazione, puoi usarlo per fare effetti collaterali come l'invio del matval in un messaggio, proprio come hai capito, non c'è necessariamente nulla di sbagliato in che anche se sembra funky. È importante capire che il flusso non completerà la sua materializzazione e diventerà in esecuzione fino al completamento di lambda. Questo significa problemi se si download()sta bloccando piuttosto che rinunciare a qualche lavoro su un thread diverso e tornare immediatamente.

C'è comunque un'altra soluzione: Source.preMaterialize()materializza la fonte e ti dà un Pairvalore materializzato e una nuova Sourceche può essere usata per consumare la fonte già avviata:

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

Nota che ci sono alcune cose aggiuntive a cui pensare nel tuo codice, soprattutto se la blobClient.download(os)chiamata si blocca fino a quando non viene eseguita e la chiami dall'attore, in tal caso devi assicurarti che il tuo attore non muoia di fame il dispatcher e si fermi altri attori nella tua applicazione dall'esecuzione (vedi documenti Akka: https://doc.akka.io/docs/akka/current/typed/dispatchers.html#blocking-needs-careful-management ).


1
Grazie per la risposta. Non vedo come potrebbe funzionare? dove vanno i byte quando viene chiamato blobClient.download (os) (se lo sto chiamando da solo)? Immagina che ci sia un terabyte di dati in attesa di essere scritti. mi sembra che la chiamata blobClient.download debba essere invocata dalla chiamata sender.tell in modo che questa sia fondamentalmente un'operazione simile a IOUtils.copy .. Usando preMaterialize non riesco a vedere come succede?
MeBigFatGuy il

OutputStream ha un buffer interno, inizierà ad accettare le scritture fino a quando il buffer non si riempie, se il downstream asincrono non ha iniziato a consumare elementi, bloccherà il thread di scrittura (motivo per cui ho detto che è importante gestire il blocco).
johanandren,

1
Ma se preMaterialize e ottengo OutputStream, è il mio codice che sta eseguendo blobClient.download (os); corretta? Ciò significa che deve essere completato prima che io possa procedere, il che è impossibile.
MeBigFatGuy

Se il download (os) non esegue il fork di un thread, dovrai occuparti del blocco e assicurarti che non si interrompano altre operazioni. Un modo sarebbe di rovesciare un thread per fare il lavoro, un altro sarebbe prima rispondere dall'attore e poi fare il lavoro di blocco lì, in quel caso devi assicurarti che l'attore non muoia di fame gli altri attori, vedi il link alla fine di la mia risposta.
johanandren,

a questo punto sto solo cercando di farlo funzionare. Non è nemmeno in grado di elaborare un file di 10 byte.
MeBigFatGuy
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.