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

Grpc调用出现负时长DEADLINE_EXCEEDED异常的原因及解决方案

问题根源与解决方案

为什么会出现负数的deadline?

你遇到的核心问题是将带有固定deadline的gRPC Stub声明为了单例Spring Bean。

gRPC的withDeadlineAfter(duration, unit)方法是在调用该方法的时间点,计算出一个固定的截止时间点,并返回一个新的Stub实例——这个Stub的deadline是静态的,不会随后续调用动态更新。

因为Spring的@Bean默认是单例的,所以你的myAppSenderServiceBlockingStub和myAppCodeLoaderServiceBlockingStub会在应用启动时初始化,此时deadline就被固定为「启动时间 + 300000ms(5分钟)」。当超过5分钟后,任何后续调用都会发现当前时间已经晚于这个固定截止时间,错误信息里的负数-175.597476157s from now就是在告诉你:截止时间已经过去175秒了。

首次调用成功是因为此时还在5分钟的有效期内,后续调用超出这个窗口就会触发DEADLINE_EXCEEDED异常。


如何解决?

我们需要让每次gRPC调用都使用动态计算的deadline,而不是依赖单例Stub上的静态deadline。有两种常用方案:

方案1:移除单例Stub上的deadline,调用时动态设置

修改你的配置类,去掉Stub Bean定义中的withDeadlineAfter,只创建基础Stub:

@Bean
public MyAppSenderServiceGrpc.MyAppSenderServiceBlockingStub myAppSenderServiceBlockingStub(
        TracingClientInterceptor tracingClientInterceptor, ManagedChannel managedChannel) {
    return MyAppSenderServiceGrpc
            .newBlockingStub(tracingClientInterceptor.intercept(managedChannel));
}

@Bean
public MyAppCodeLoaderServiceGrpc.MyAppCodeLoaderServiceBlockingStub myAppCodeLoaderServiceBlockingStub(
        TracingClientInterceptor tracingClientInterceptor, ManagedChannel managedChannel) {
    return MyAppCodeLoaderServiceGrpc
            .newBlockingStub(tracingClientInterceptor.intercept(managedChannel));
}

然后在调用Stub的业务代码中,每次调用前动态添加deadline:

// 注入基础Stub
@Autowired
private MyAppSenderServiceBlockingStub baseSenderStub;
@Value("${grpc.client.deadline:300000}")
private long grpcDeadline;

// 业务方法中调用gRPC
public void sendEvent(ContextMyAppEventGrpc event) {
    // 每次调用都创建一个带新deadline的Stub实例
    baseSenderStub.withDeadlineAfter(grpcDeadline, TimeUnit.MILLISECONDS)
                  .sendMessage(event, new StreamObserver<Empty>() {
                      // 实现你的回调逻辑
                      @Override
                      public void onNext(Empty empty) {
                          // 处理响应
                      }

                      @Override
                      public void onError(Throwable throwable) {
                          // 处理错误
                      }

                      @Override
                      public void onCompleted() {
                          // 调用完成
                      }
                  });
}

方案2:使用Supplier提供动态Stub(更优雅的配置层处理)

如果你希望在配置层封装deadline逻辑,可以用Supplier来每次返回一个带新deadline的Stub:

@Bean
public Supplier<MyAppSenderServiceBlockingStub> myAppSenderServiceBlockingStubSupplier(
        TracingClientInterceptor tracingClientInterceptor, ManagedChannel managedChannel,
        @Value("${grpc.client.deadline:300000}") long deadline) {
    // 每次调用get()都会生成一个带新deadline的Stub
    return () -> MyAppSenderServiceGrpc
            .newBlockingStub(tracingClientInterceptor.intercept(managedChannel))
            .withDeadlineAfter(deadline, TimeUnit.MILLISECONDS);
}

在业务代码中通过Supplier获取Stub:

@Autowired
private Supplier<MyAppSenderServiceBlockingStub> senderStubSupplier;

public void sendEvent(ContextMyAppEventGrpc event, StreamObserver<Empty> responseObserver) {
    // 获取带最新deadline的Stub并调用
    senderStubSupplier.get().sendMessage(event, responseObserver);
}

额外注意点

  • gRPC的Stub是不可变且线程安全的,所以每次调用withDeadlineAfter生成新实例的开销极小,不用担心性能问题。
  • 如果你需要不同的调用使用不同的deadline,方案1的灵活性更高,可以根据业务场景动态调整时长。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 21:07:39