You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化基于Mono/Flux返回分页结果的Controller接口性能

可行优化方案

当前/snapshots接口响应慢、内存占用高的核心原因有两点:

  • Mono.zip 要求count查询、分页数据查询两个流全部完成后才会构造返回值,首字节响应必须等待两个查询全部执行完毕,和流式输出的全量接口差距极大
  • collectList() 会将当前分页的所有PersonVm对象全部攒在内存中才开始序列化输出,分页量越大内存占用越高,完全没有利用响应式框架的流式响应能力

存在完全满足约束的优化方案,核心是自定义支持响应式字段的分页返回类型,保持两个查询并行执行的同时,实现和全量接口一致的流式输出,对调用方完全透明。

具体实现

1. 自定义流式分页返回类型

通过注解控制JSON序列化字段顺序,将items数组放在最前面,分页元数据放在后面,字段类型直接使用响应流类型,不需要提前攒成实体集合:

@Introspected
@JsonPropertyOrder("items", "totalNumberOfItems", "pageable")
data class StreamingPage(
    val items: Flux<PersonVm>,
    val totalNumberOfItems: Mono<Long>,
    val pageable: Pageable
)

2. 重写接口逻辑

方法入口就触发两个查询并行执行,直接返回流式分页对象,不需要等待任何一个流完成:

@Get("/snapshots")
fun snapshots(
    @Format("yyyy-MM-dd") cutoffDate: LocalDate,
    pageable: Pageable
): StreamingPage {
    // 入口立刻发起两个查询,通过cache()保证后续序列化订阅时不会重复发起查询,保持并行执行
    val totalCountMono = snapshotDao.getSnapshotCount(cutoffDate).cache()
    val pageItemsFlux = snapshotDao.getSnapshots(
        cutoffDate,
        pageable.size,
        pageable.offset
    ).cache()

    return StreamingPage(
        items = pageItemsFlux,
        totalNumberOfItems = totalCountMono,
        pageable = pageable
    )
}

方案优势

  • 完全保留并行查询逻辑:两个查询在方法入口就同时发往BigQuery,和原有Mono.zip方案的执行逻辑一致,不会出现串行查询导致的1秒额外耗时
  • 首字节时间和全量接口持平:只要分页数据查询吐出第一条PersonVm记录,响应就会立刻开始输出,不需要等待count查询完成,也不需要等待所有分页数据查完
  • 内存占用和全量接口一致:去掉了collectList()攒集合的逻辑,每查到一条记录就立刻序列化写入响应,内存中最多仅保留当前处理的单条记录
  • 对调用方完全无感知:最终输出的JSON结构和原有CustomPage返回结构完全一致,分页元数据全部在响应体中仅出现一次,不需要修改任何对外接口文档

注意事项

  • 确保项目引入了响应式Jackson序列化模块(Spring WebFlux、Micronaut Reactor默认依赖都会自带),框架会自动识别Mono/Flux类型的字段,按流式逻辑序列化:Flux字段会序列化为普通JSON数组,元素逐个输出;Mono字段会等待返回值后序列化为对应类型的普通值
  • 必须在方法入口就初始化两个响应流并调用cache(),避免序列化阶段按字段顺序订阅时,先执行完items查询才触发count查询,退化为串行执行

内容的提问来源于stack exchange,提问作者Roar S.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 19:24:23