簡體   English   中英

Stream在Micronaut controller大響應memory

[英]Stream large response in Micronaut controller without going out of memory

我們將 Micronaut 與 Mongo 一起使用,通過一些控制器公開數據。 由於響應實體的大小正在增長,我們的應用程序 go 有時會超出 memory。 因此,我們正在研究切換到異步 mongo 驅動程序並使用對 stream 數據的響應式響應將數據發送到客戶端。 不幸的是,我們無法更改 API 響應結構和內容類型(所有application/json

我們的 API 之一返回的實體結構如下:

[
  { "field": "value" },
  { "field": "value" },
  ...
  { "field": "value" }
]

我們使用這個 controller 開始工作,其中dataStore返回一個Publisher<Example>

    @Get("all")
    Flowable<Example> getAllExamples() {
        return Flowable.fromPublisher(dataStore.find()).map(SomeMapper::toPublic);
    }

這很好用,在將其流式傳輸到客戶端之前,不必將大量示例列表完全加載到 memory 中。

其他 API 返回(imo 更明智的)結構:

{
  "list": [
    { "field": "value" },
    { "field": "value" },
    ...
    { "field": "value" }
  ],
  "meta": {
    ...
  }
}

我們是否可以為這樣的實體應用類似的發布者/可流動模式,或者在發送之前將此類響應的數據加載到 memory 中?

我們嘗試了以下簽名:

    @Get("all/dev")
    Single<ExamplesWrapper> getAllDev() {
        Publisher<Example> dev = dataStore.find();
        return Flowable.fromPublisher(dev)
                .map(mapper::map)
                .collect((Callable<ArrayList<Example>>) ArrayList::new, ArrayList::add)
                .map(ExampleWrapper::new);
    }

包裝器將添加一些元數據的地方。 但這再次將其全部加載到 memory 中,然后再發送出去,導致應用程序崩潰。

將 Flowable 添加到響應包裝器中:


public class ExamplesWrapper {

    private final Flowable<Example> examples;

    @ConstructorProperties({"examples"})
    public ExamplesWrapper(Flowable<Example> examples) {
        this.examples = examples;
    }

    public Flowable<Example> getExamples() {
        return examples;
    }
}

也因一些不錯的 Jackson 映射異常而失敗。

元數據不依賴於實際的示例數據(它添加了一些 static 公司信息)。 我們能否以某種方式實現這樣的端點,而不必將所有數據加載到 memory 中?

文檔

6.20 寫入響應數據

響應式寫入響應數據

Micronaut 的 HTTP 服務器支持通過返回一個發布者來寫入響應數據塊,該發布者發出可以編碼為 HTTP 響應的對象。

下表總結了示例返回類型簽名以及服務器為處理它們而表現出的行為: 返回類型描述

  • Flowable<byte[]>:一個Flowable,將每個內容塊作為一個字節[]發出而不阻塞
  • Flux<ByteBuf>:將每個塊作為 Netty ByteBuf 發出的 Reactor Flux
  • Publisher<String>:將每個內容塊作為字符串發出的 Publisher
  • Flowable<Book> 發出 POJO 時,每個發出的 object 默認編碼為 JSON 不阻塞

返回響應式類型時,服務器使用分塊的 Transfer-Encoding 並不斷寫入數據,直到調用 Publisher onComplete 方法。

我理解這一點,因此如果您希望將 Micronaut 機制設置為 stream 您的東西需要具有像Flowable<item>Flux<item>Publisher<item>這樣的簽名,其中 item 是您響應的一部分,而不是完整的項目. 然后,Micronaut 將響應來自 Flowable 或等效的塊。

在這種情況下,我想到的一件事是您可以自己拆分成合適的塊。 這樣流大響應而不將它們緩沖到 memory 應該可以工作。

所以是這樣的:

@Get("all")
public Flowable<String> getAllExamples() {
    ObjectMapper objectMapper = new ObjectMapper();
    Publisher<Example> dev = dataStore.find();
    return Flowable.fromPublisher(dev)
            .map(mapper::map)
            .concatMap(item -> Flowable.just(objectMapper.writeValueAsString(item), ","))
            .startWith("{\"list\": [")
            .concatWith(Flowable.just("],\"meta\":\"whatever\"}"));
}

它很hacky,但似乎適用於這種情況。


一些不起作用的方法:

I did test writing directly to JsonGenerator in a custom Jackson mapper, flushing objects as they go as outlined in jackson streaming api , but micronaut RoutingInboundHandler does not seem to flush the response back to end user but buffer it, resulting in out of memory. 方法適用於 Spring Boot,因此它可能是 Micronaut 中缺少的功能。

當使用Micronaut可寫(阻塞)響應並嘗試在寫入數據時刷新數據時,我也發生了相同的緩沖。 我向 micronaut core 提出了一個關於該問題的問題

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM