RxJava Single重试未触发服务端请求问题排查求助
问题排查与修复方案
核心问题
你这段代码的关键bug在于:gRPC Stub的超时时间(deadline)是绑定在实例上的一次性配置。你提前创建了带deadline的Stub,第一次请求失败触发重试时,复用的是同一个已经过期的Stub实例,导致重试请求直接在客户端超时失败,根本没发往服务端,所以服务端收不到新请求。
具体原因拆解
看你写的代码:
return Single.fromFuture( asyncStub.withDeadlineAfter(ClientConfiguration.INSTANCE.getMaxTimeout(), TimeUnit.SECONDS).checkBalance(payload)) .doOnSubscribe(s -> System.out.println("subscribing")) .doOnSuccess((resp) -> { log.info("Check Balance got response success {}", resp.getUserID()); log.debug("Check Balance got response success {}", resp.toString()); }) .doOnError(throwable -> { this.logError(throwable, "Check balance userid:" + payload); }) .retry((retryCount, error) -> retryCount < 3).toFuture();
asyncStub.withDeadlineAfter(...)会生成一个新的Stub,但这个Stub的deadline是从调用那一刻开始算的固定时长,过期就失效。- 当第一次请求失败触发重试时,
Single.fromFuture()里的代码不会重新执行——也就是说不会重新创建带新deadline的Stub,而是拿已经过期的旧Stub去发请求,结果就是请求直接在客户端挂掉,没到服务端。
修复代码
要让每次重试都用全新的、有有效deadline的Stub,得把Stub的创建逻辑放到Single.defer()里,确保每次重试都会重新生成带新超时的Stub:
return Single.defer(() -> Single.fromFuture( asyncStub.withDeadlineAfter(ClientConfiguration.INSTANCE.getMaxTimeout(), TimeUnit.SECONDS) .checkBalance(payload) ) ) .doOnSubscribe(s -> System.out.println("subscribing")) .doOnSuccess((resp) -> { log.info("Check Balance got response success {}", resp.getUserID()); log.debug("Check Balance got response success {}", resp.toString()); }) .doOnError(throwable -> { this.logError(throwable, "Check balance userid:" + payload); }) .retry((retryCount, error) -> retryCount < 3) .toFuture();
Single.defer()的作用是每次订阅(包括重试触发的订阅)都会重新执行内部的代码块,这样每次重试都会生成新的带有效deadline的Stub,请求就能正常发到服务端了。
额外建议
- 别啥错误都重试:当前代码对所有错误都重试3次,但像参数非法这种错误重试也没用,建议只对gRPC网络错误、服务端不可用这类可恢复的错误重试。
- 加重试日志:在
doOnError里打印当前重试次数和错误类型,方便确认重试是否真的触发,以及失败原因。
内容的提问来源于stack exchange,提问作者alok gupta
相关产品推荐
相关产品推荐

