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
相关产品推荐
相关产品推荐

