gRPC中stub的含义是什么?是否推荐单个channel对应创建一个stub?
gRPC Stub 含义与使用最佳实践解答
1. 什么是gRPC Stub
Stub是gRPC客户端侧的本地调用代理,由Protobuf定义的服务代码自动生成,封装了底层的网络通信、Protobuf序列化/反序列化、请求调度等逻辑。上层业务只需要调用Stub暴露的方法即可完成对服务端的请求,不需要手动处理网络交互细节。
目前gRPC默认生成三类Stub,适配不同的调用场景:
- 阻塞Stub:同步调用,请求发起后线程阻塞直到收到服务端响应
- 异步Stub:异步调用,通过回调函数处理响应结果
- FutureStub:异步调用,返回ListenableFuture对象,你代码中使用的就是这类Stub
2. 核心问题解答:是否推荐一个Channel对应一个Stub
首先明确两个对象的特性:
ManagedChannel是重量级对象:内部维护了连接池、请求队列、工作线程等资源,创建和销毁成本极高,原生支持线程安全,官方推荐同一个服务端目标全局复用同一个Channel。Stub是轻量级对象:本身几乎没有额外的资源开销,是无状态的(如果没有绑定自定义CallOption,比如超时配置、自定义请求头这类个性化参数),同样原生支持线程安全。
官方没有强制要求一个Channel必须绑定单个Stub,完全不需要为每次请求重复创建Stub:
如果没有个性化调用参数的需求,完全可以一个Channel对应一个Stub,全局复用即可;如果有不同请求需要不同的调用配置,可以基于同一个Channel创建多个特化的Stub,也不会产生明显的性能损耗。
3. 你现有代码的优化建议
你当前的代码在每次调用myService方法时,都循环遍历Channel列表新建Stub,属于完全不必要的冗余操作,针对Spring Boot场景可以做如下优化:
3.1 提前初始化Stub并交给Spring容器管理
直接在gRpcConfig中,创建完Channel列表后,同步初始化对应Stub列表作为Bean,避免每次业务调用重复创建,同时补充资源销毁逻辑避免连接泄漏:
@Configuration public class gRpcConfig{ @Autowired private Environment gRpcInfo; @Bean(name="myChannelList") public ArrayList<ManagedChannel> setChannel() throws InterruptedException{ String host1 = gRpcInfo.getProperty("Server1.host"); Integer port1 = gRpcInfo.getProperty("Server1.port", Integer.class); String host2 = gRpcInfo.getProperty("Server2.host"); Integer port2 = gRpcInfo.getProperty("Server2.port", Integer.class); ArrayList<ManagedChannel> channelList = new ArrayList<>(); ManagedChannel ch = NettyChannelBuilder.forTarget(host1 + ":" + port1).usePlaintext().build(); channelList.add(ch); ch = NettyChannelBuilder.forTarget(host2 + ":" + port2).usePlaintext().build(); channelList.add(ch); return channelList; } @Bean(name = "myFutureStubList") public ArrayList<myGrpc.myFutureStub> setStubList(@Qualifier("myChannelList") ArrayList<ManagedChannel> channelList) { ArrayList<myGrpc.myFutureStub> stubList = new ArrayList<>(); for (ManagedChannel ch : channelList) { stubList.add(myGrpc.newFutureStub(ch)); } return stubList; } // 配置销毁逻辑,释放Channel资源 @PreDestroy public void destroyChannel(@Qualifier("myChannelList") ArrayList<ManagedChannel> channelList) { for (ManagedChannel channel : channelList) { channel.shutdown(); } } }
3.2 业务代码直接注入复用Stub
@Service public class MyService { @Resource(name = "myFutureStubList") private ArrayList<myGrpc.myFutureStub> myStubList; public void myService() { MyProtoBuf myProtoBuf = MyProtoBuf.newBuilder().setSth("").build(); final ArrayList<myResult> resultList = new ArrayList<>(); // 直接复用已初始化的Stub发起请求即可 for (myGrpc.myFutureStub stub : myStubList){ ListenableFuture<myResult> responseFuture = stub.myMethod(myProtoBuf); Futures.addCallback(responseFuture , new FutureCallback<myResult>(){ @Override public void onSuccess(@Nullable myResult result){ myResult resultFuture = result.getResult(); resultList.add(resultFuture); } // 省略异常处理逻辑 }, MoreExecutors.directExecutor()); } } }
3.3 现有代码的小问题修正
Enviroment拼写错误,应为Environment- 拼接host和port时缺少
+号 @Resource(name=myChannelList)缺少引号,应为@Resource(name="myChannelList")build、getResult方法调用后缺少括号@Oveeride拼写错误,应为@Override- 回调方法未指定执行器,建议补充执行器参数避免线程安全问题
4. 通用最佳实践总结
- 同一个服务端目标的Channel全局复用,不要频繁创建销毁,应用关闭时主动释放Channel资源
- 无特殊调用参数需求时,一个Channel对应复用一个Stub即可,无需为每次请求新建Stub
- 有个性化调用配置(如不同超时、自定义元数据)时,可基于同一个Channel创建多个特化Stub,性能开销极低无需担心
- Stub本身线程安全,多线程场景下可以直接复用无需加锁
内容的提问来源于stack exchange,提问作者SAXYCOW
相关产品推荐
相关产品推荐

