Akka 스트림을 업스트림 서비스로 전달하여 채우기


9

업스트림 서비스 (Azure Blob Service)를 호출하여 데이터를 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데이터 쓰기가 완료되면 데이터를 스트리밍 하고 반환합니까?
Alec

답변:


2

여기서 실제 문제는 Azure API가 역 프레싱을 위해 설계되지 않았다는 것입니다. 출력 스트림이 더 많은 데이터를 준비 할 수 없다는 것을 Azure에 다시 신호를 보낼 수있는 방법이 없습니다. 다시 말해, Azure가 데이터를 소비 할 수있는 것보다 빠르게 데이터를 푸시하면 어딘가에 못생긴 버퍼 오버플로 오류가 발생해야합니다.

이 사실을 받아들이면 다음으로 할 수있는 최선의 방법은 다음과 같습니다.

  • Source.lazySource다운 스트림 수요 (일명 소스가 실행 중이고 데이터 요청 중)가있는 경우에만 데이터 다운로드를 시작하는 데 사용하십시오 .
  • download소스가 리턴되지 않도록 차단하지 않고 계속 실행되도록 다른 스레드에 호출을 넣으십시오 . 일단이 작업을 수행하는 방법은 Future(자바 모범 사례가 확실하지 않지만 어느 쪽이든 잘 작동해야 함)입니다. 처음에는 중요하지 않지만 실행 컨텍스트를 선택해야 할 수도 있습니다. system.dispatcher모두 download차단 여부에 따라 다릅니다 .

이 Java 코드가 잘못된 경우 사전에 사과드립니다-Akka를 Scala와 함께 사용하므로 Akka Java API 및 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 그래, 고마워!
Alec

1

OutputStream이 경우는의 "값을 구체화"입니다 Source그리고 그것은 단지 스트림이 실행되면 생성 (또는 실행 스트림에 "구체화")됩니다. SourceAkka HTTP 를 넘겨 주므로 나중에 소스를 실행하기 때문에 제어가 불가능 합니다.

.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

다운로드 (들)가 스레드를 포크하지 않으면 스레드를 차단하고 다른 작업을 중지하지 않는지 확인해야합니다. 한 가지 방법은 작업을 수행하기 위해 스레드를 포크하는 것이고, 다른 하나는 액터로부터 먼저 응답 한 다음 차단 작업을 수행하는 것입니다.이 경우 액터가 다른 액터를 굶지 않도록해야합니다. 내 대답.
johanandren

이 시점에서 나는 단지 그것이 작동하도록 노력하고 있습니다. 심지어 10 바이트 파일도 처리 할 수 ​​없습니다.
MeBigFatGuy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.