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

Webflux Mono<Void> POST接口耗时统计时doOnSuccess未触发问题

问题根因
  • response.bodyToMono(Void.class) 对于无响应体的返回(如204 No Content、空响应体的200请求)会直接触发onComplete信号,不会发出任何onNext事件
  • doOnSuccess 回调仅在Mono正常发出onNext事件时触发,无值直接完成的Mono不会执行该回调
  • elapsed() 操作符统计的是从订阅到第一个onNext事件的耗时,无onNext事件的场景下不会触发后续逻辑
  • 无参subscribe() 会静默吞掉所有调用异常,出现错误时无日志输出,会让人误以为接口未被调用
修复方案

方案1:手动计时(兼容性最好)

直接在订阅前记录开始时间,成功回调中计算耗时,不需要依赖elapsed操作符:

public void createTask(CreateTaskDTO task) {
    long startTime = System.currentTimeMillis();
    client
            .post()
            .uri(path)
            .body(BodyInserters.fromValue(task))
            .exchangeToMono(response -> {
                // 可选:非2xx状态码直接抛出异常走错误处理
                if (!response.statusCode().is2xxSuccessful()) {
                    return response.createException().flatMap(Mono::error);
                }
                // 释放响应体,避免内存泄漏
                return response.releaseBody();
            })
            .doOnSuccess(unused -> {
                Status.incrementTaskCreationCount(task.getLoginIds().size());
                Status.incrementWriteCount();
                long costTime = System.currentTimeMillis() - startTime;
                log.info(
                        "time taken by the ServiceImpl createTask=[{}] for {}",
                        costTime,
                        task);
            })
            .doOnError(e -> {
                // 错误日志&失败埋点逻辑
                log.error("createTask failed, task: {}", task, e);
            })
            .subscribe();
}

方案2:适配elapsed操作符

如果要保留elapsed的写法,只需让Mono主动发射一个值触发onNext即可:

public void createTask(CreateTaskDTO task) {
    client
            .post()
            .uri(path)
            .body(BodyInserters.fromValue(task))
            .exchangeToMono(response -> {
                if (!response.statusCode().is2xxSuccessful()) {
                    return response.createException().flatMap(Mono::error);
                }
                // 释放响应体后返回Void实例,触发onNext事件
                return response.releaseBody().thenReturn(Void.INSTANCE);
            })
            .elapsed()
            .doOnSuccess(
                    tuple -> {
                        Status.incrementTaskCreationCount(task.getLoginIds().size());
                        Status.incrementWriteCount();
                        log.info(
                                "time taken by the ServiceImpl createTask=[{}] for {}",
                                tuple.getT1(),
                                task);
                    })
            .doOnError(e -> log.error("createTask failed, task: {}", task, e))
            .subscribe();
}
注意事项
  • 调用返回Void的接口时,优先使用releaseBody()释放响应资源,比bodyToMono(Void.class)兼容性更好,可避免内存泄漏
  • 禁止使用无参subscribe(),必须添加错误回调,否则异常会被静默丢弃,难以排查问题
  • 如果需要统计总调用次数(无论成功失败),可以把通用逻辑放到doFinally回调中执行

内容的提问来源于stack exchange,提问作者Chandresh Mishra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 17:54:05