对于扩展者而言,可以继承NameResolver实现自定义的地址解析服务,例如使用Zookeeper替换DnsNameResolver,把Zookeeper作为动态的服务地址配置中心,它的伪代码示例如下:
第一步:继承NameResolver,实现start(Listener listener)方法:
void start(Listener listener) { //获取ZooKeeper地址,并连接 //创建Watcher,并实现process(WatchedEvent event),监听地址变更 //根据接口名和方法名,调用getChildren方法,获取发布该服务的地址列表 //将地址列表加到List中 // 调用NameResolver.Listener.onAddresses(),通知地址解析完成第二步:创建ManagedChannelBuilder时,指定Target的地址为Zookeeper服务端地址,同时设置nameResolver为Zookeeper NameResolver,示例代码如下所示:
this(ManagedChannelBuilder.forTarget(zookeeperAddr) .loadBalancerFactory(RoundRobinLoadBalancerFactory.getInstance()) .nameResolverFactory(new ZookeeperNameResolverProvider()) .usePlaintext(false));3. LoadBalancer负责从nameResolver中解析获得的服务端URL中按照指定路由策略,选择一个目标服务端地址,并创建ClientTransport。同样,可以通过覆盖handleResolvedAddressGroups实现自定义负载均衡策略。
通过LoadBalancer + NameResolver,可以实现灵活的负载均衡策略扩展。例如基于Zookeeper、etcd的分布式配置服务中心方案。
1.3.7. RPC请求消息发送流程gRPC默认基于Netty HTTP/2 + PB进行RPC调用,请求消息发送流程如下所示:
(点击放大图像)
图1-11 gRPC请求消息发送流程图
流程关键技术点解读:
ClientCallImpl的sendMessage调用,主要完成了请求对象的序列化(基于PB)、HTTP/2 Frame的初始化。
ClientCallImpl的halfClose调用将客户端准备就绪的请求Frame封装成自定义的SendGrpcFrameCommand,写入到WriteQueue中。
WriteQueue执行flush()将SendGrpcFrameCommand写入到Netty的Channel中,调用Channel的write方法,被NettyClientHandler拦截到,由NettyClientHandler负责具体的发送操作。
NettyClientHandler调用Http2ConnectionEncoder的writeData方法,将Frame写入到HTTP/2 Stream中,完成请求消息的发送。
1.3.8. RPC响应接收和处理流程gRPC客户端响应消息的接收入口是NettyClientHandler,它的处理流程如下所示:
(点击放大图像)
图1-12 gRPC响应消息接收流程图
流程关键技术点解读:
NettyClientHandler的onHeadersRead(int streamId, Http2Headers headers, boolean endStream)方法会被调用两次,根据endStream判断是否是Stream结尾。
请求和响应的关联:根据streamId可以关联同一个HTTP/2 Stream,将NettyClientStream缓存到Stream中,客户端就可以在接收到响应消息头或消息体时还原出NettyClientStream,进行后续处理。
RPC客户端调用线程的阻塞和唤醒使用到了GrpcFuture的wait和notify机制,来实现客户端调用线程的同步阻塞和唤醒。
客户端和服务端的HTTP/2 Header和Data Frame解析共用同一个方法,即MessageDeframer的deliver()。
2. 客户端源码分析gRPC客户端调用原理并不复杂,但是代码却相对比较繁杂。下面围绕关键的类库,对主要功能点进行源码分析。
2.1. NettyClientTransport功能和源码分析NettyClientTransport的主要功能如下:
通过start(Listener transportListener) 创建HTTP/2 Client,并连接gRPC服务端
通过newStream(MethodDescriptor<?, ?> method, Metadata headers, CallOptions callOptions) 创建ClientStream
通过shutdown() 关闭底层的HTTP/2连接
以启动HTTP/2客户端为例进行讲解:
(点击放大图像)
根据启动时配置的HTTP/2协商策略,以NettyClientHandler为参数创建ProtocolNegotiator.Handler。
创建Bootstrap,并设置EventLoopGroup,需要指出的是,此处并没有使用EventLoopGroup,而是它的一种实现类EventLoop,原因在前文中已经说明,相关代码示例如下:
(点击放大图像)
创建WriteQueue并设置到NettyClientHandler中,用于接收内部的各种QueuedCommand,初始化完成之后,发起HTTP/2连接,代码如下:
(点击放大图像)