gRPC客户端随机触发CANCELLED异常问题求助
碰到这种随机的CANCELLED错误确实头疼,我之前处理过几个类似的grpc-java案例,咱们从代码和版本两个核心方向来排查:
一、最可能的根源:复用非线程安全的StreamObserver
你代码里的responseObserver是类成员变量,全局共用的,但gRPC的StreamObserver不是线程安全的——它是和单个请求绑定的回调对象,多个请求复用同一个实例会导致不同请求的上下文(Context)互相干扰,甚至引发意外的取消操作。
修复方案:每次请求创建独立的StreamObserver
修改callFoo()方法,把StreamObserver的创建移到方法内部,确保每个请求都有自己的回调实例:
public void callFoo() { // 为每个请求创建独立的StreamObserver StreamObserver<Empty> requestObserver = new StreamObserver<>() { @Override public void onNext(Empty value) { } @Override public void onError(Throwable t) { logger.error("Error: ", t); } @Override public void onCompleted() { } }; fooStub.withDeadlineAfter(500L, TimeUnit.MILLISECONDS) .myMethod(whatever, requestObserver); }
二、检查调用线程的Context状态
gRPC请求会继承调用线程的Context,如果调用callFoo()的线程本身处于被取消的Context(比如线程所在的任务被中断、父Context超时取消),那么gRPC请求会被连带取消,就会出现这个错误。
排查方法:打印当前Context状态
在callFoo()开头添加日志,确认调用时的Context是否正常:
public void callFoo() { Context currentCtx = Context.current(); logger.info("Call foo with context cancelled: {}, deadline: {}", currentCtx.isCancelled(), currentCtx.getDeadline()); // 后续请求代码 }
如果发现调用时Context已经被取消,就要向上排查调用callFoo()的上游逻辑,看是否有任务超时、线程中断等情况。
三、升级grpc-java版本(关键修复)
你使用的grpc-java 1.22.0是2019年的旧版本,存在不少已知的Context传播和Stub线程安全bug,比如复用Stub时的Context泄漏、线程池与Context绑定异常等,这些bug在后续版本中已经被修复。
建议版本:升级到1.30+的稳定版
比如升级到1.50.5(LTS稳定版),Maven依赖修改如下:
<dependency> <groupId>io.grpc</groupId> <artifactId>grpc-netty-shaded</artifactId> <version>1.50.5</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-protobuf</artifactId> <version>1.50.5</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-stub</artifactId> <version>1.50.5</version> </dependency>
四、额外排查点:ManagedChannel状态
偶尔也会出现Channel被意外关闭但未被检测到的情况,导致请求发送失败并触发取消错误。可以在请求前检查Channel状态:
public void callFoo() { if (channel.isShutdown() || channel.isTerminated()) { logger.error("gRPC channel is already shutdown/terminated, cannot send request"); return; } // 执行请求 }
关于服务器有时收到请求的解释
如果请求已经通过网络发送到服务器,之后客户端Context才被取消,服务器会继续处理请求;但如果Context在请求发送前就被取消,请求根本不会发出,服务器自然收不到——这完全符合你描述的现象。
内容的提问来源于stack exchange,提问作者hylowaker

