Pigeon是美团点评的RPC框架,涉及很多模块,服务注册发现,负载均衡,路由,容灾,故障转移,消息传输等等,要想了解这些模块的实现机制,首先要找到一个入手点,作为一个RPC框架,最重要的当然就是服务之间的请求调用及数据传输了,那么我们就来看一下pigeon客户端的一次调用发生了些什么,又经过了那些链路调用?
首先要介绍一个关于服务调用的工厂类ServiceFactory(pigeon服务添加获取都在这里)。
客户端在依赖于spring启动时,服务代理对象serviceProxy会通过ReferenceBean注入来完成创建。
ReferenceBean的初始化过程如下:
其中会涉及到服务的获取:ServiceFactory.getService(invokerConfig)其中又调用了serviceProxy来通过代理获取服务对象,来看下这个接口
这是pigeon自定义的一个服务代理类,客户端就是通过其中的getProxy方法来获取服务代理对象的
这里又通过Serializer这个接口的proxyRequest方法来完成代理对象的创建
就是这里创建了服务的动态代理对象,并封装了相关的调用链(后面会介绍),最后将服务代理对象返回,供调用方调用,调用逻辑如下:
从上述源码可以看出,客户端是基于java动态代理来实现服务调用的
我们注意到在服务代理对象生成之前,有一个**InvokerBootStrap.startup();**方法被调用,这个方法是做什么的呢?
这个方法就是客户端的启动方法,接下来我们看一下客户端的启动流程
public static void startup() {
if (!isStartup) {
synchronized (InvokerBootStrap.class) {
if (!isStartup) {
//服务调用仓库初始化
ServiceInvocationRepository.getInstance().init();
//请求调用责任链初始化
InvokerProcessHandlerFactory.init();
//序列化工厂,消息的编解码都在这里面
SerializerFactory.init();
//负载均衡管理
LoadBalanceManager.init();
//路由策略管理
RoutePolicyManager.INSTANCE.init();
//监控
Monitor monitor = MonitorLoader.getMonitor();
if (monitor != null) {
monitor.init();
}
.....
isStartup = true;
logger.warn("pigeon client[version:" + VersionUtils.VERSION + "] has been started");
}
}
}
}
这里我们只看其中InvokerProcessHandlerFactory.init() 这个方法,看下它的初始化
public static void init() {
if (!isInitialized) {
synchronized (InvokerProcessHandlerFactory.class) {
if (!isInitialized) {
if (Constants.MONITOR_ENABLE) {
registerBizProcessFilter(new RemoteCallMonitorInvokeFilter());
}
//请求初始的一些定义
registerBizProcessFilter(new InvokerContextFilter());
//监控相关
registerBizProcessFilter(new TracerFilter());
registerBizProcessFilter(new InnerTracerFilter());
//通用调用处理
registerBizProcessFilter(new GenericInvokeFilter());
//服务降级
registerBizProcessFilter(new DegradationFilter());
//集群策略
registerBizProcessFilter(new ClusterInvokeFilter());
//故障演练
registerBizProcessFilter(new FaultInjectionFilter());
//统计信息
registerBizProcessFilter(new GatewayInvokeFilter());
//生成请求
registerBizProcessFilter(new ContextPrepareInvokeFilter());
//安全相关
registerBizProcessFilter(new SecurityFilter());
//远程请求调用
registerBizProcessFilter(new RemoteCallInvokeFilter());
//构建消息发送执行责任链
sendHandler = createSendProcessHandler(sendProcessFilters);
//构建消息接收执行责任链
recvHandler = createRecvProcessHandler(recvProcessFilters);
isInitialized = true;
}
}
}
}
这里面会有很多filter过滤器处理逻辑,涉及服务监控,降级,集群策略,故障演练,统计信息,生成请求,安全相关,
客户端调用RPC服务接口的请求会经过这些过滤器层层处理(服务是通过代理的方式调用的),最后交由RemoteCallInvokeFilter,发送到远端。
这里我们只看最后一个RemoteCallInvokeFilter这个涉及传输的处理器,它的核心逻辑在其invoke方法中
@Override
public InvocationResponse invoke(ServiceInvocationHandler handler, InvokerContext invocationContext) throws Throwable {
invocationContext.getTimeline().add(new InvocationContext.TimePoint(InvocationContext.TimePhase.Q));
//服务节点
Client client = invocationContext.getClient();
//请求内容
InvocationRequest request = invocationContext.getRequest();
//低啊用配置
InvokerConfig<?> invokerConfig = invocationContext.getInvokerConfig();
//调用模式
byte callMethodCode = invokerConfig.getCallMethod(invocationContext.getMethodName());
......
......
try {
switch (callMethod) {
//同步模式
case SYNC:
InvokerFuture futureSync = new InvokerFuture(invocationContext, request.getTimeout());
response = InvokerUtils.sendRequest(client, invocationContext.getRequest(), futureSync);
invocationContext.getTimeline().add(new InvocationContext.TimePoint(InvocationContext.TimePhase.Q));
if (response == null) {
response = futureSync.getResponse(request.getTimeout());
}
break;
//回调模式
case CALLBACK:
InvocationCallback callback = invokerConfig.getCallback();
InvocationCallback tlCallback = InvokerHelper.getCallback();
if (tlCallback != null) {
callback = tlCallback;
InvokerHelper.clearCallback();
}
InvokerUtils.sendRequest(client, invocationContext.getRequest(), new InvokerCallback(
invocationContext, callback));
response = NO_RETURN_RESPONSE;
invocationContext.getTimeline().add(new InvocationContext.TimePoint(InvocationContext.TimePhase.Q));
break;
//future模式
case FUTURE:
InvokerFuture future = new InvokerFuture(invocationContext, request.getTimeout());
InvokerUtils.sendRequest(client, invocationContext.getRequest(), future);
FutureFactory.setFuture(future);
response = InvokerUtils.createFutureResponse(future);
invocationContext.getTimeline().add(new InvocationContext.TimePoint(InvocationContext.TimePhase.Q));
break;
//oneway模式
case ONEWAY:
InvokerUtils.sendRequest(client, invocationContext.getRequest(), null);
response = NO_RETURN_RESPONSE;
invocationContext.getTimeline().add(new InvocationContext.TimePoint(InvocationContext.TimePhase.Q));
break;
default:
throw new BadRequestException("Call type[" + callMethod.getName() + "] is not supported!");
}
((DefaultInvokerContext) invocationContext).setResponse(response);
afterInvoke(invocationContext);
} catch (Throwable t) {
afterThrowing(invocationContext, t);
throw t;
}
return response;
}
这里我们可以看到Pigeon提供了四种调用模式供我们使用,SYNC,CALLBACK,FUTURE,ONEWAY
接下来就每个模式做个简单的介绍
SYNC模式
SYNC模式:顾名思义,是同步调用,客户端会等待服务端结果返回,等待过程是阻塞的,看一下它的实现机制
创建了一个InvokerFuture对象,参数是当前请求信息和超时时间,这里需要看一下这个对象的属性有哪些呢
可以看到InvokerFuture对象继承了JDK的future及Callback, CallFuture这两个回调接口,属性中有几个参数要注意看下,一个是InvocationResponse记录了响应的结果,另外是记录了当前调用结果的几种结果done,cancelled,complete。
再回到之前的逻辑,对象创建完成后,执行了InvokerUtils.sendRequest(client, invocationContext.getRequest(), futureSync)这个方法,看一下它的实现
执行流程:封装了RemoteInvocationBean对象,然后放到invocationRepository仓库里,然后调用了client.write方法发送数据,看它的实现,最终会走到doWrite方法,如下
public InvocationResponse doWrite(InvocationRequest request) throws NetworkException {
NettyChannel channel = null;
try {
//channel池化
channel = channelPool.selectChannel();
//netty发送数据
ChannelFuture future = channel.write0(request);
//后置处理
afterWrite(request, channel);
if (request.getMessageType() == Constants.MESSAGE_TYPE_SERVICE
|| request.getMessageType() == Constants.MESSAGE_TYPE_HEART) {
//future上增加MessageWriteListener监听,用来在发送中断或失败时,继续调用processResponse()做response处理
future.addListener(new MessageWriteListener(request, channel));
}
} catch (Exception e) {
throw new NetworkException("[doRequest] remote call failed:" + request, e);
}
return null;
}
可以看到Pigeon的TCP数据传输是基于netty(3.x)做的,数据发送(netty的发送是异步非阻塞的)之后,注意这里返回了null,并没有返回future,这里只是对future加了一个监听,在回到sendRequest方法时,response是空的返回,那么结果是怎么被调用方拿到的呢?
答案是:InvokerFuture
上面说到这个InvokerFuture里有InvocationResponse这个属性,它记录了响应的数据,那又是在哪里传进去的呢?
我们先回到最开始的位置继续来看它做了哪些处理
这里response一定是null,会调用futureSync.getResponse方法,毋庸置疑是用来获取response的,来接着看下它的实现
这里进来的时候会调用waitResponse方法,我们进去看一下实现
protected InvocationResponse waitResponse(long timeoutMillis) throws InterruptedException {
.....
lock.lock();
try {
long start = request.getCreateMillisTime();
long timeoutLeft = timeoutMillis;
//判断当前响应是否完成
while (!isDone()) {
//阻塞一定时长
condition.await(timeoutLeft, TimeUnit.MILLISECONDS);
long timeoutPassed = System.currentTimeMillis() - start;
//收到响应消息或超时则跳出循环
if (isDone() || timeoutPassed >= timeoutMillis) {
break;
} else {
timeoutLeft = timeoutMillis - timeoutPassed;
}
}
} finally {
lock.unlock();
}
....
return response;
}
可以看到这里进行了阻塞了调用方以等待响应结果的返回,这里我们先不管它的阻塞是在什么时候打开的,先来看一下接下来发生了什么
调用了**InvokerProcessHandlerFactory.defaultRecvHandler().handle(invocationContext)**方法,该方法就是调用我们最开始构建的责任链中的recvProcessFilters链,对response做处理并返回结果给调用方。
上面我们已经知道对于响应的response是如何处理的,那么这个response是什么时候给到的呢,我们并没有看到response的返回呀,
答案:这里Pigeon利用了netty的channelInboundHandler做的处理,我们需要看一下netty对于返回的数据做了哪些处理
这是netty客户端的ChannelPipeline的构建过程,来看看它分别添加了哪些handler到ChannelPipeline中,并分别做了些什么处理
netty对于接受消息的整个处理流程:
1.FrameDecoder 帧解码器–拆包提取消息
2.Crc32Handler 校验,差错控制
3.CompressHandler 解压缩
4.InvokerDecoder 字节流解码成消息
5.NettyClientHandler 消息接收处理
我们现在要关心的response在NettyClientHandler这个处理器中进行了处理,来看下
可以看到这里调用了 client.processResponse,我们进去看调用,最终是doProcessResponse这个方法做了真正的处理
这里构建了一个任务并将给responseProcessThreadPool(处理响应的线程池)去执行,我们看下receiveResponse的实现
public void receiveResponse(InvocationResponse response) {
//到调用仓库里拿到sendRequest时构建的调用bean
RemoteInvocationBean invocationBean = invocations.get(response.getSequence());
if (invocationBean != null) {
if (logger.isDebugEnabled()) {
logger.debug("received response:" + response);
}
InvocationRequest request = invocationBean.request;
try {
//拿到我们开始时构建的InvokerFuture(是callback的实现)
Callback callback = invocationBean.callback;
if (callback != null) {
Client client = callback.getClient();
if (client != null) {
//流量统计
ServiceStatisticsHolder.flowOut(request, client.getAddress());
}
//这里调用了InvokerFuture的callback方法实现
callback.callback(response);
callback.run();
}
} finally {
invocations.remove(response.getSequence());
}
}
}
上述代码可以看到整个过程,我们看到其调用了请求的InvokerFuture回调方法,这里我们应该已经想到response的传递一定是在这个地方做的,我们点进去看下,验证下我们猜想的是否正确
public void callback(InvocationResponse response) {
this.response = response;
}
public void run() {
lock.lock();
try {
this.done = true;
condition.signal();
} finally {
lock.unlock();
}
}
果然,在这里进行了response的传递,并对上面InvokerFuture阻塞的调用方进行了唤醒,调用方这时就可以拿到response,开始对应的recvProcessFilters链的处理了。
我们回到开始,这里response经过调用线程处理后,就可以完成结果的返回,调用完毕。
CALLBACK模式
接下来我们接着callback模式
这里和同步不同的是,它构建的是一个InvocationCallback对象,看一下它的接口方法
通过invokerConfig.getCallback()方法获取,这是一个由调用方实现的接口,并作为参数传给sendRequest方法,和上面的sendRequest是同一个方法,唯一不同的是它实现了自己的回调方法,并做为参数构建了一个InvokerCallback对象,之后的调用链路和同步模式下是一样的,不同在哪里的呢?
上面代码可以看到它是没有阻塞获取response的,并且结果是NO_RETURN_RESPONSE,直接返回了,那么调用方的回调实现在哪里去调用的呢?
这里要从InvokerCallback这个对象入手,我们看下最终的response回调(同Sync模式)
注意这里和同步模式不同的是:这里的callback接口实现的对象是InvokerCallback,而不是InvokerFuture,那么InvokerCallback的callback方法和run方法又有哪些不同呢?
@Override
public void callback(InvocationResponse response) {
this.response = response;
}
public void run() {
......
......
try {
((DefaultInvokerContext) invocationContext).setResponse(response);
//执行recvProcessFilters链处理
Object result = ServiceInvocationProxy.extractResult(
InvokerProcessHandlerFactory.defaultRecvHandler().handle(invocationContext),
invocationContext.getReturnType());
if (result instanceof Throwable) {
//调用方实现的回调
callback.onFailure((Throwable) result);
} else {
//调用方实现的回调
callback.onSuccess(result);
}
} catch (Throwable t) {
callback.onFailure(t);
}
}
可以看到callback()方法实现是一致的,run方法有很大的差别,可以看到,其在拿到response后直接调用了recvProcessFilters链处理,并在处理成功后,执行了由调用方提供的回调callback实现。这时,response就处理完毕了,调用完毕。
总结一下上述两种调用模式的逻辑:
从ServiceInvocationRepository中拿到响应的requestBean,找到其callback对象并执行回调方法,将response传入future,同步模式下会唤醒等待结果的业务线程,回调模式下会去调用recvProcessFilters责任链,完成后执行业务方提供的回调方法,上述逻辑都在ResponseThreadPool中执行
FUTURE模式
接着我们看一下FUTURE模式的调用逻辑
可以看到创建了一个InvokerFuture对象,和同步模式一样,调用sendRequest发送数据(调用链一致),不同的是,这里将future通过FutureFactory将future交给了threadFuture(ThreadLocal),由当前调用线程保存这个future
接着调用了**InvokerUtils.createFutureResponse(future);**去得到FutureResponse
这里直接将FutureResponse返回,由调用方去做具体的处理,调用完毕。
ONEWAY模式
oneway顾名思义就是单向的传输,不关心返回,这里将callback置为null,并发送消息给远端,调用完毕。
这种模式适合大量的日志传输且允许数据丢失的情况下使用,消息发送不关心结果,也没有重试容错的机制去保证。