vượt qua một luồng Akka đến một dịch vụ thượng nguồn để cư trú


9

Tôi cần gọi một dịch vụ ngược dòng (Dịch vụ Azure Blob) để đẩy dữ liệu lên OutputStream, sau đó tôi cần quay lại và đẩy nó trở lại máy khách, thông qua akka. Nếu không có akka (và chỉ có mã servlet), tôi sẽ lấy ServletOutputStream và chuyển nó sang phương thức của dịch vụ azure.

Lần gần nhất tôi có thể cố gắng vấp ngã, và rõ ràng điều này là sai, là một cái gì đó như thế này

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

Ý tưởng là tôi đang gọi một dịch vụ ngược dòng để có được một luồng đầu ra được điền bằng cách gọi blobClient.doad (os);

Có vẻ như hàm lambda được gọi và trả về, nhưng sau đó nó không thành công, vì không có dữ liệu hoặc thứ gì đó. Như thể tôi không được phép có chức năng lambda đó làm việc, nhưng có lẽ trả lại một số đối tượng thực hiện công việc? Không chắc.

Làm thế nào để làm điều này?


Hành vi của là downloadgì? Nó có truyền dữ liệu vào osvà chỉ trả về khi dữ liệu được ghi xong không?
Alec

Câu trả lời:


2

Vấn đề thực sự ở đây là API Azure không được thiết kế để tạo áp lực ngược. Không có cách nào để luồng đầu ra báo hiệu trở lại Azure mà nó không sẵn sàng cho nhiều dữ liệu hơn. Nói một cách khác: nếu Azure đẩy dữ liệu nhanh hơn mức bạn có thể tiêu thụ, thì sẽ phải có một số lỗi tràn bộ đệm xấu xí ở đâu đó.

Chấp nhận sự thật này, điều tốt nhất tiếp theo chúng ta có thể làm là:

  • Sử dụng Source.lazySourceđể chỉ bắt đầu tải xuống dữ liệu khi có nhu cầu hạ lưu (hay còn gọi là nguồn đang được chạy và dữ liệu đang được yêu cầu).
  • Đặt downloadcuộc gọi trong một số luồng khác để nó tiếp tục thực hiện mà không chặn nguồn được trả lại. Một khi cách để làm điều này là với một Future(Tôi không chắc chắn các thực tiễn tốt nhất của Java là gì, nhưng sẽ hoạt động tốt theo bất kỳ cách nào). Mặc dù ban đầu nó không thành vấn đề, nhưng bạn có thể cần phải chọn bối cảnh thực thi khác system.dispatcher- tất cả phụ thuộc vào việc downloadcó chặn hay không.

Tôi xin lỗi trước nếu mã Java này không đúng định dạng - Tôi sử dụng Akka với Scala, vì vậy tất cả chỉ là nhìn vào tham chiếu cú ​​pháp Java và API của Akka.

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

Tuyệt diệu. cảm ơn nhiều. Một chỉnh sửa nhỏ cho ví dụ của bạn là: Futures.future (() -> {blobClient.doad (cặp.first ()); return cặp.first ();}, system.getDispatcher ());
MeBigFatGuy

@MeBigFatGuy Đúng rồi, cảm ơn!
Alec

1

Trong OutputStreamtrường hợp này là "giá trị cụ thể hóa" của Sourcenó và nó sẽ chỉ được tạo khi luồng được chạy (hoặc "vật chất hóa" thành luồng đang chạy). Việc chạy nó nằm ngoài tầm kiểm soát của bạn vì bạn giao Sourcecho Akka HTTP và điều đó sau đó sẽ thực sự chạy nguồn của bạn.

.mapMaterializedValue(matval -> ...)thường được sử dụng để chuyển đổi giá trị cụ thể hóa nhưng vì nó được gọi là một phần của vật chất hóa, bạn có thể sử dụng nó để thực hiện các tác dụng phụ như gửi matval trong tin nhắn, giống như bạn đã nhận ra, không nhất thiết có gì sai với rằng ngay cả khi nó trông sôi nổi. Điều quan trọng là phải hiểu rằng luồng sẽ không hoàn thành việc vật chất hóa của nó và trở nên chạy cho đến khi lambda hoàn thành. Điều này có nghĩa là các vấn đề nếu download()đang chặn chứ không phải từ bỏ một số công việc trên một luồng khác và ngay lập tức quay trở lại.

Tuy nhiên, có một giải pháp khác : Source.preMaterialize(), nó cụ thể hóa nguồn và cung cấp cho bạn một Pairgiá trị cụ thể hóa và một giá trị mới Sourcecó thể được sử dụng để tiêu thụ nguồn đã bắt đầu:

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

Lưu ý rằng có một vài điều cần suy nghĩ trong mã của bạn, quan trọng nhất là nếu blobClient.download(os)cuộc gọi bị chặn cho đến khi nó được thực hiện và bạn gọi nó từ diễn viên, trong trường hợp đó bạn phải đảm bảo rằng diễn viên của bạn không bỏ đói người điều phối và dừng lại các tác nhân khác trong ứng dụng của bạn từ việc thực thi (xem tài liệu Akka: https://doc.akka.io/docs/akka/civerse/typed/dispatchers.html#blocking-needs-careful-man quản lý ).


1
Cảm ơn vì sự trả lời. Tôi không thấy làm thế nào điều này có thể làm việc? các byte sẽ đi đâu khi blobClient.doad (os) được gọi (nếu tôi tự gọi nó)? Hãy tưởng tượng có một terabyte dữ liệu đang chờ để được viết. Dường như với tôi rằng cuộc gọi blobClient.d Download phải được gọi từ cuộc gọi sender.tell để về cơ bản đây là một hoạt động giống như IOUtils.copy .. Sử dụng preM vật chất hóa tôi không thể thấy điều đó xảy ra như thế nào?
MeBigFatGuy

OutputStream có một bộ đệm bên trong, nó sẽ bắt đầu chấp nhận ghi cho đến khi bộ đệm đó đầy lên, nếu async xuôi dòng không bắt đầu tiêu thụ các phần tử thì nó sẽ chặn luồng viết (đó là lý do tại sao tôi đề cập rằng điều quan trọng là phải xử lý chặn).
johanandren

1
Nhưng nếu tôi sơ bộ hóa và nhận được OutputStream, thì đó là mã của tôi đang thực hiện blobClient.doad (os); chính xác? Điều đó có nghĩa là nó phải hoàn thành trước khi tôi có thể tiến hành, điều này là không thể.
MeBigFatGuy

Nếu tải xuống (os) không rẽ nhánh của một luồng, bạn sẽ phải đối phó với việc nó bị chặn và đảm bảo rằng nó không dừng một số hoạt động khác. Một cách là dùng một sợi chỉ để thực hiện công việc, một cách khác sẽ được phản hồi từ diễn viên trước và sau đó thực hiện công việc chặn ở đó, trong trường hợp đó bạn phải đảm bảo diễn viên không bỏ đói các diễn viên khác, xem liên kết ở cuối câu trả lời của tôi.
johanandren

tại thời điểm này tôi chỉ đang cố gắng để nó hoạt động. Nó thậm chí không thể xử lý tệp 10 byte.
MeBigFatGuy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.