ส่งกระแสข้อมูล Akka ไปยังบริการอัปสตรีมเพื่อเติมข้อมูล


9

ฉันจำเป็นต้องเรียกใช้บริการอัปสตรีม (Azure Blob Service) เพื่อส่งข้อมูลไปยัง OutputStream ซึ่งฉันต้องเปิดและส่งกลับไปยังไคลเอนต์ถึง akka หากไม่มี akka (และเพียงรหัส servlet) ฉันจะได้รับ 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());

แนวคิดคือฉันกำลังเรียกใช้บริการ upstream เพื่อรับข้อมูลขาออกโดยการเรียก blobClient.download (os)

ดูเหมือนว่าฟังก์ชั่นแลมบ์ดาได้รับการโทรและส่งคืน แต่หลังจากนั้นมันก็ล้มเหลวเนื่องจากไม่มีข้อมูลหรืออะไรบางอย่าง ราวกับว่าฉันไม่ควรที่จะมีฟังก์ชั่นแลมบ์ดาทำงาน แต่อาจส่งคืนวัตถุบางอย่างที่ทำงานได้หรือไม่ ไม่แน่ใจ.

คนเราจะทำสิ่งนี้ได้อย่างไร


พฤติกรรมdownloadคืออะไร? มันสตรีมข้อมูลเข้าosและกลับมาเมื่อเขียนข้อมูลเสร็จแล้วหรือไม่
อเล็กซ์

คำตอบ:


2

ปัญหาจริงที่นี่คือ Azure API ไม่ได้ออกแบบมาสำหรับการกดกลับ ไม่มีวิธีใดสำหรับเอาต์พุตสตรีมที่ส่งสัญญาณกลับไปยัง Azure ว่ายังไม่พร้อมสำหรับข้อมูลเพิ่มเติม หากต้องการใช้อีกวิธีหนึ่ง: หาก Azure ผลักดันข้อมูลเร็วกว่าที่คุณสามารถใช้งานได้จะต้องมีความล้มเหลวของบัฟเฟอร์ล้นที่น่าเกลียดบางแห่ง

ยอมรับความจริงข้อนี้สิ่งที่ดีที่สุดที่เราสามารถทำได้คือ:

  • ใช้Source.lazySourceเพื่อเริ่มต้นการดาวน์โหลดข้อมูลเมื่อมีความต้องการดาวน์สตรีมเท่านั้น (aka. แหล่งกำลังทำงานและกำลังร้องขอข้อมูล)
  • วางdownloadสายในเธรดอื่นเพื่อให้สามารถดำเนินการต่อโดยไม่ปิดกั้นแหล่งที่มาจากการส่งคืน วิธีหนึ่งในการทำเช่นนี้คือFuture(ฉันไม่แน่ใจว่าแนวทางปฏิบัติที่ดีที่สุดของ Java คืออะไร แม้ว่ามันจะไม่สำคัญในตอนแรกคุณอาจต้องเลือกบริบทการดำเนินการอื่นนอกเหนือจาก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 ถูกต้องขอบคุณ!
อเล็กซ์

1

OutputStreamในกรณีนี้เป็น "รูปธรรม" ค่าของSourceและมันจะถูกสร้างขึ้นแล้วกระแสการเรียกใช้ (หรือ "รูปธรรม" เข้าไปในกระแสการทำงาน) การเรียกใช้นั้นไม่ได้อยู่ในการควบคุมของคุณเนื่องจากคุณมอบSourceให้กับ Akka HTTP และนั่นจะเป็นการเรียกใช้แหล่งที่มาของคุณในภายหลัง

.mapMaterializedValue(matval -> ...)มักจะถูกใช้เพื่อแปลงค่า materialized แต่เนื่องจากมันถูกเรียกใช้เป็นส่วนหนึ่งของ materialization คุณสามารถใช้เพื่อทำผลข้างเคียงเช่นการส่ง 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 มีบัฟเฟอร์ภายในมันจะเริ่มยอมรับการเขียนจนกว่าบัฟเฟอร์นั้นจะเต็มถ้าหาก async down สตรีมไม่ได้เริ่มองค์ประกอบเสียแล้วมันจะบล็อกเธรดการเขียน (ซึ่งเป็นเหตุผลที่ฉันบอกว่ามันเป็นสิ่งสำคัญในการจัดการการบล็อก)
johanandren

1
แต่ถ้าฉัน preMaterialize และรับ OutputStream แล้วมันเป็นรหัสของฉันที่กำลังทำ blobClient.download (os); แก้ไข? นั่นหมายความว่าจะต้องทำให้เสร็จก่อนที่ฉันจะสามารถดำเนินการต่อไปได้
MeBigFatGuy

หากการดาวน์โหลด (OS) ไม่แยกเธรดคุณจะต้องจัดการกับการบล็อกและตรวจสอบให้แน่ใจว่าจะไม่หยุดการดำเนินการอื่น วิธีหนึ่งที่จะแยกด้ายเพื่อใช้ในการทำงานอีกวิธีหนึ่งคือการตอบสนองจากนักแสดงก่อนแล้วจึงปิดกั้นการทำงานที่นั่นในกรณีนี้คุณต้องทำให้แน่ใจว่านักแสดงไม่อดอาหารนักแสดงคนอื่น ๆ คำตอบของฉัน.
johanandren

ณ จุดนี้ฉันแค่พยายามทำให้มันใช้งานได้ มันไม่สามารถแม้แต่จะประมวลผลไฟล์ 10 ไบต์
MeBigFatGuy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.