铺垫
grpc有三个核心的抽象层:
Stub
绝大数开发者会直接使用的一部分,proto文件编译所生成的Stub就是在此层的基础之上生成的。它提供了一种调用方和服务之间类型安全的绑定关系,对比http服务:http服务就不提供类型安全的绑定关系,调用方和服务方需要自行处理类型转化的相关工作。
Channel
channel层是对数据传输的抽象,便于用户更方便的进行 拦截器/装饰者 等类似的处理。它旨在使应用程序框架易于使用此层来解决诸如日志记录,监视,身份验证等交叉问题。流控制也暴露在此层,以允许更复杂的应用程序直接与其交互。
Transport
这一层在绝大多数情况下是我们不需要关心的,它的作用就是传输数据,底层基于netty,或者ok http的实现
grpc请求创建
分析一个典型的grpc请求创建的代码
请求从调用 grpcClient 的getFutureStub开始
CrmConsumeServiceGrpc.CrmConsumeServiceFutureStub rechargeService =
(CrmConsumeServiceGrpc.CrmConsumeServiceFutureStub) grpcClient.getFutureStub(CrmConsumeServiceGrpc.class);
getFutureStub 方法实际就是调用了 getStub
public Object getFutureStub(Class grpcClass) throws Exception {
return getStub(grpcClass, "newFutureStub");
}
实际上就是通过反射的方式调用了了 CrmConsumeServiceGrpc 的newFutureStub 方法
private Object getStub(Class grpcClass, String stubMethodName) throws Exception {
Method stubMethod = grpcClass.getMethod(stubMethodName, Channel.class);
return stubMethod.invoke(null, channel);
}
继续跟到具体方法
public static CrmConsumeServiceFutureStub newFutureStub(
io.grpc.Channel channel) {
return new CrmConsumeServiceFutureStub(channel);
}
类的私有构造方法
public static final class CrmConsumeServiceFutureStub extends io.grpc.stub.AbstractStub<CrmConsumeServiceFutureStub> {
private CrmConsumeServiceFutureStub(io.grpc.Channel channel) {
super(channel);
}
最终跟到了AbstractStub 类,这个类是stub层的核心类之一,实际上proto文件生成的Stub类,都是该类的子类。
protected AbstractStub(Channel channel) {
this(channel, CallOptions.DEFAULT);
}
简单的说,获取stub的过程,就是把GrpcClient 的channel属性传递给Stub的过程。那由此可以推断出,channel是在GrpcClient创建的时候,一起创建的。 下面看一下GrpcClient的源码。
private void init(String target, int maxMessageSize, Tracing tracing, LoadBalancer.Factory loadBalancerFactory, ClientInterceptor... interceptors) {
LoggingClientInterceptor loggingClientInterceptor = new LoggingClientInterceptor();
this.allClientInterceptors.add(loggingClientInterceptor);
if (tracing != null) {
GrpcTracing grpcTracing = GrpcTracing.create(tracing);
allClientInterceptors.add(grpcTracing.newClientInterceptor());
}
if (interceptors != null) {
allClientInterceptors.addAll(Arrays.asList(interceptors));
}
NettyChannelBuilder builder = NettyChannelBuilder
.forTarget(target)
.keepAliveTime(20, TimeUnit.SECONDS)
.keepAliveTimeout(2, TimeUnit.SECONDS)
.keepAliveWithoutCalls(true)
.idleTimeout(24, TimeUnit.HOURS)
.usePlaintext(true)
.intercept(allClientInterceptors)
.nameResolverFactory(NameResolverProvider.asFactory())
.loadBalancerFactory(RoundRobinLoadBalancerFactory.getInstance());
if (loadBalancerFactory != null) {
builder.loadBalancerFactory(loadBalancerFactory);
}
if (maxMessageSize > 0) {
builder.maxInboundMessageSize(maxMessageSize);
}
channel = builder.build();
}
调用 NettyChannelBuilder的build方法,创建了channel。下面的注释也验证了这点,NettyChannelBuilder 创建了一个以Netty 作为传输层的 channel。其中除了必要的target,也就是服务地址以外,传递给channel的参数还包含了三个比较重要的属性 intercept,namemResolverFactory,loadBalancerFactory。 。
/**
* A builder to help simplify construction of channels using the Netty transport.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1784")
@CanIgnoreReturnValue
public final class NettyChannelBuilder
先看一下channel能够做什么
Channel的注释:
A virtual connection to a conceptual endpoint, to perform RPCs. A channel is free to have zero or many actual connections to the endpoint based on configuration, load, etc. A channel is also free to determine which actual endpoints to use and may change it every RPC, permitting client-side load balancing. Applications are generally expected to use stubs instead of calling this class directly.
Applications can add common cross-cutting behaviors to stubs by decorating Channel implementations using ClientInterceptor. It is expected that most application code will not use this class directly but rather work with stubs that have been bound to a Channel that was decorated during application initialization.
Channel是一个虚拟的链接,它可以维护任意数量的真实链接,也可以自主选择具体使用的链接。也就是说,在channel上,我们可以实现客户端的 load balancing。另外,注释中建议应用使用stubs,而不是直接调用channel。其实也就说,我们可以避开Stub的创建,直接调用Channel。另外通过实现ClientInterceptor 可以实现对Channel的横切(cross-cutting),这个也是在Channel层做到的。
方法的调用
在看方法调用的源码前,先思考一个问题。grpc 实现远程调用,需要哪些参数。
首先远程调用首先是一个方法调用,所以需要定位一个方法,以及对应的参数。
另外因为是远端请求,所以还需要一个远端服务地址(Channel),远端的请求,必然涉及到序列化和反序列化的过程,另外grpc提供了类型安全的方法调用,所以序列化和反序列化的时候,也需要获取到 request 和response 的具体类型。
public com.google.common.util.concurrent.ListenableFuture<com.hualala.app.shop.pos.grpc.UpdateAgentInfoInterfaceData.UpdateAgentResponse> updateAgentInfo(
com.hualala.app.shop.pos.grpc.UpdateAgentInfoInterfaceData.UpdateAgentRequest request) {
return futureUnaryCall(
getChannel().newCall(METHOD_UPDATE_AGENT_INFO, getCallOptions()), request);
}
}
grpc 方法的调用就是调用proto自动生成的Stub上的方法,所有的的方法都是调用了 形如futureUnaryCall的方法,只是 每个方法传入的 METHOD_UPDATE_AGENT_INFO参数不一样。
public static final io.grpc.MethodDescriptor<com.hualala.app.shop.pos.grpc.UpdateAgentInfoInterfaceData.UpdateAgentRequest,
com.hualala.app.shop.pos.grpc.UpdateAgentInfoInterfaceData.UpdateAgentResponse> METHOD_UPDATE_AGENT_INFO =
io.grpc.MethodDescriptor.create(
io.grpc.MethodDescriptor.MethodType.UNARY,
generateFullMethodName(
"UpdateAgentInfoInterface", "updateAgentInfo"),
io.grpc.protobuf.ProtoUtils.marshaller(com.hualala.app.shop.pos.grpc.UpdateAgentInfoInterfaceData.UpdateAgentRequest.getDefaultInstance()),
io.grpc.protobuf.ProtoUtils.marshaller(com.hualala.app.shop.pos.grpc.UpdateAgentInfoInterfaceData.UpdateAgentResponse.getDefaultInstance()));
METHOD_UPDATE_AGENT_INFO 保存了 FullMethodName,还有两个marshaller,marshaller保存着request和response的类信息,用于序列化和反序列化。
到此为止,grpc发起的一个远端请求需要的信息怎么处理的我们都已经了解了。Channel通过GrpcClient创建,调用的方法信息和序列化的信息在Stub中保存,request 是调用方创建的。
之后其实就是这些信息的加工和组合。
首先第一步获取Channel,然后创建一个Call。Channel毫无疑问就是刚刚创建的ManagedChannelImpl。
public com.google.common.util.concurrent.ListenableFuture<com.hualala.app.shop.pos.grpc.UpdateAgentInfoInterfaceData.UpdateAgentResponse> updateAgentInfo(
com.hualala.app.shop.pos.grpc.UpdateAgentInfoInterfaceData.UpdateAgentRequest request) {
return futureUnaryCall(
getChannel().newCall(METHOD_UPDATE_AGENT_INFO, getCallOptions()), request);
}
}
newCall创建了一个 ClientCall,在看newCall的源码前,我们先看一下ClientCall文档
An instance of a call to a remote method. A call will send zero or more request messages to the server and receive zero or more response messages back.
Instances are created by a Channel and used by stubs to invoke their remote behavior.
clientCall是一个调用远程方法的实例,由Channel创建,被stubs调用。所以clientCasll是真正的发送请求的对象。另外在clientCall在创建的时候,也会创建对应的拦截器。
思考一个问题,拦截器的功能实现,需要考虑哪些东西。拦截器的效果就是在请求真正执行前,获取到相关的信息,可以进行鉴权,日志等等。那么就是说,拦截器主要功能又两点,一个就是获取到请求信息。另外一个就是对请求的转发(包括转发到另外的拦截器和真实请求)
了解到这之后,我们看一下newCall方法。
private static class InterceptorChannel extends Channel {
private final Channel channel;
private final ClientInterceptor interceptor;
private InterceptorChannel(Channel channel, ClientInterceptor interceptor) {
this.channel = channel;
this.interceptor = Preconditions.checkNotNull(interceptor, "interceptor");
}
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> newCall(
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions) {
return interceptor.interceptCall(method, callOptions, channel);
}
newCall方法设计的很巧妙,首先它本身继承自Channel,另外还有还有一个Channel的属性。在调用newCall方法的时候,Channel属性被当作参数传入了interceptCall中。如果InterceptorChannel中的channel,就同样是一个InterceptorChannel,那么如果在interceptCall的实现中,继续调用channel的newCall,那就顺序的创建了一批call
this.interceptorChannel = ClientInterceptors.intercept(new RealChannel(), interceptors);
下面看一下interceptorChannel到底是怎么初始化的,首先interceptorChannel是在ManagedChannelImpl出事的时候,在构造方法中初始化好的。传入了两个参数,一个是RealChannel,一个是build的时候传入的interceptorts(List<ClientInterceptor>)参数
该方法的注释,大概意思就是,这段逻辑是为了拦截器用的
Create a new Channel that will call interceptors before starting a call on the given channel. The last interceptor will have its ClientInterceptor.interceptCall called first.
public static Channel intercept(Channel channel, List<? extends ClientInterceptor> interceptors) {
Preconditions.checkNotNull(channel, "channel");
for (ClientInterceptor interceptor : interceptors) {
channel = new InterceptorChannel(channel, interceptor);
}
return channel;
}
下面继续看 newCall 方法本身,实际上就是inteceptor的inteceptorCall方法。
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> methodDescriptor, CallOptions callOptions, Channel channel) {
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(channel.newCall(methodDescriptor, callOptions)) {
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
super.start(new ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(responseListener) {
@Override
可以看到在这个方法内部确实调用了channel的newCall方法,新创建的一个匿名类SimpleForwardingClientCall,创建这个类的过程中调用了 channel的newCall。注意一个细节,在重写start方法后,还调用了super的start的方法
public abstract class ForwardingClientCall<ReqT, RespT> extends ClientCall<ReqT, RespT> {
/**
* Returns the delegated {@code ClientCall}.
*/
protected abstract ClientCall<ReqT, RespT> delegate();
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
delegate().start(responseListener, headers);
}
ForwardingClientCall 就是典型的代理模式,在调用A方法的时候,实际上调用的都是代理类的方法。delegate 就是nextChannel创建的call, 注意到刚刚在拦截器中重写start的时候,最后调用了父类的start,实际上就是通过ForwardingClientCall 转发到了代理类的start方法上
接下来就缕一下整个client的创建过程。
假设:
项目里有两个拦截器,logging auth
然后创建了两个InterceptorChannel
loggingChannel:channel realChannel inteceptor logging
authChannel:channel loggingChannel inteceprot auth
返回authChannel
调用authChannel 的newCall方法。
先调用 auth 的interceptCall 方法创建call (authCall)
在authCall创建的时候
调用authChannel的interceptCall 方法
然后继续调用loggingChanne的newCall方法(loggingCall)
在loggingCall的创建过程中
调用的realCall的new Call方法
创建一个ClientCallImpl
最后方法执行结束,返回authCall
authCall的执行(以start方法为例)
先调用authCall的start方法
然后调用super.start(),super.start()方法中的delegated 是logginCall
执行 loggingCall的start方法
loggingCall也同样调用 super的start,最终开始执行realCall的start方法
创建
调用
拦截器实际就是创建了一个clientCall,也就是说,clientCall的所有方法都可以被拦截,到底是在方法执行前拦截,还是方法执行后拦截,可以通过调用super方法的时机来确定。ClientCall在创建的时候还可以创建对应的Listener。下面会介绍Listener的作用
刚刚说了这么多,其实方法还在在说Stub里方法。下面继续看futureUnaryCall的调用过程。futureUnaryCall方法是ClientCalls的一个方法。
public static <ReqT, RespT> ListenableFuture<RespT> futureUnaryCall(
ClientCall<ReqT, RespT> call,
ReqT param) {
GrpcFuture<RespT> responseFuture = new GrpcFuture<RespT>(call);
asyncUnaryRequestCall(call, param, new UnaryStreamToFuture<RespT>(responseFuture), false);
return responseFuture;
}
futureUnaryCall方法中,创建了UnaryStreamToFuture。UnaryStreamToFuture是一个Listener,这就涉及到了设计模式里的观察者模式,观察者模式主要有两个要点,一个是事件和观察者的绑定,一个是事件触发的时候,会调用观察者。
private static final class UnaryStreamToFuture<RespT> extends ClientCall.Listener<RespT> {
private final GrpcFuture<RespT> responseFuture;
private RespT value;
开始实际发送请求
startCall,处理了call 和callListener的绑定。
call.sendMessage(param) 方法开始发送请求
call.halfClose() 是只关闭了这个call 发送 request message,但是不影响response的接收
实际上到此为止,请求的发送就已经结束了。
但是除了发送请求以外,还要接收请求。拦截器有一个onMessage方法,就是收到消息的时候,会调用的接口。我们通过这个切入点来看一下client端是怎么接收消息的。
private static <ReqT, RespT> void asyncUnaryRequestCall(
ClientCall<ReqT, RespT> call,
ReqT param,
ClientCall.Listener<RespT> responseListener,
boolean streamingResponse) {
startCall(call, responseListener, streamingResponse);
try {
call.sendMessage(param);
call.halfClose();
} catch (RuntimeException e) {
throw cancelThrow(call, e);
} catch (Error e) {
throw cancelThrow(call, e);
}
}
startCall方法中,调用了call的start方法。所以拦截器中也可以自定义的添加具体的listener。
private static <ReqT, RespT> void startCall(ClientCall<ReqT, RespT> call,
ClientCall.Listener<RespT> responseListener, boolean streamingResponse) {
call.start(responseListener, new Metadata());
if (streamingResponse) {
call.request(1);
} else {
// Initially ask for two responses from flow-control so that if a misbehaving server sends
// more than one responses, we can catch it and fail it in the listener.
call.request(2);
}
}
所以拦截器在重写start方法的时候,也可以传入对应的listener,看一下listener的创建
super.start(new ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(responseListener) {
Override
public void onMessage(RespT message) {
//如果当前在线,则onMessage是从服务端返回,这就相当于一次服务可用性的检测
healthCheck.setProxyStatus(true);
super.onMessage(message);
}
}, headers)
这个是项目里面accessTokenInterceptor里创建的Listener,可以看到也是创建里一个SimpleForwarding***,在onMessage的时候,也是调用了super的onMessage方法,所以listenter和clientCall类似,也是通过代理模式实现的转发。
看一下创建的真正实际工作的Call
return new ClientCallImpl<ReqT, RespT>(
method,
executor,
callOptions,
transportProvider,
terminated ? null : transportFactory.getScheduledExecutorService())
.setFullStreamDecompression(fullStreamDecompression)
.setDecompressorRegistry(decompressorRegistry)
.setCompressorRegistry(compressorRegistry);
传入的参数有
method :指定执行的方法
transportProvider: 提供链路信息
真正执行的start方法
if (!deadlineExceeded) {
updateTimeoutHeaders(effectiveDeadline, callOptions.getDeadline(),
context.getDeadline(), headers);
ClientTransport transport = clientTransportProvider.get(
new PickSubchannelArgsImpl(method, headers, callOptions));
Context origContext = context.attach();
try {
stream = transport.newStream(method, headers, callOptions);
} finally {
context.detach(origContext);
}
} else {
stream = new FailingClientStream(DEADLINE_EXCEEDED);
}
if (callOptions.getAuthority() != null) {
stream.setAuthority(callOptions.getAuthority());
}
if (callOptions.getMaxInboundMessageSize() != null) {
stream.setMaxInboundMessageSize(callOptions.getMaxInboundMessageSize());
}
if (callOptions.getMaxOutboundMessageSize() != null) {
stream.setMaxOutboundMessageSize(callOptions.getMaxOutboundMessageSize());
}
stream.setCompressor(compressor);
stream.setFullStreamDecompression(fullStreamDecompression);
stream.setDecompressorRegistry(decompressorRegistry);
stream.start(new ClientStreamListenerImpl(observer));
创建了一个stream ,并且执行了stream 的start方法。
在创建的时候,传入了method信息,header信息。并且执行了stream 的start方法。
看一下stream的作用
A single stream of communication between two end-points within a transport.
在transport基础上,用作两端通信的流
看一下这个stream的具体作用
public final void start(ClientStreamListener listener) {
transportState().setListener(listener);
if (!useGet) {
abstractClientStreamSink().writeHeaders(headers, null);
headers = null;
}
}
在Sink 里写入header
@Override
public void writeHeaders(Metadata headers, byte[] requestPayload) {
// Convert the headers into Netty HTTP/2 headers.
AsciiString defaultPath = (AsciiString) methodDescriptorAccessor.geRawMethodName(method);
if (defaultPath == null) {
defaultPath = new AsciiString("/" + method.getFullMethodName());
methodDescriptorAccessor.setRawMethodName(method, defaultPath);
}
boolean get = (requestPayload != null);
AsciiString httpMethod;
if (get) {
// Forge the query string
// TODO(ericgribkoff) Add the key back to the query string
defaultPath =
new AsciiString(defaultPath + "?" + BaseEncoding.base64().encode(requestPayload));
httpMethod = Utils.HTTP_GET_METHOD;
} else {
httpMethod = Utils.HTTP_METHOD;
}
Http2Headers http2Headers = Utils.convertClientHeaders(headers, scheme, defaultPath,
authority, httpMethod, userAgent);
ChannelFutureListener failureListener = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
// Stream creation failed. Close the stream if not already closed.
// When the channel is shutdown, the lifecycle manager has a better view of the failure,
// especially before negotiation completes (because the negotiator commonly doesn't
// receive the execeptionCaught because NettyClientHandler does not propagate it).
Status s = transportState().handler.getLifecycleManager().getShutdownStatus();
if (s == null) {
s = transportState().statusFromFailedFuture(future);
}
transportState().transportReportStatus(s, true, new Metadata());
}
}
};
// Write the command requesting the creation of the stream.
writeQueue.enqueue(new CreateStreamCommand(http2Headers, transportState(), get),
!method.getType().clientSendsOneMessage() || get).addListener(failureListener);
}
主要做了两件事
1:在header前添加上method,构建一个http2.0 的header
2: 把这个header 写入写队列。
后面再详细的调用就是netty框架的写入执行过程。