4. Dubbo源码解析之服务调用过程
Dubbo服务调用过程,分为三部分,一部分为消费者发送请求部分,一部分为提供者处理消息过程,一部分消费者接受并解析结果部分。
4.1 消费者发送请求
服务引入完成后,会创建一个接口代理类实例,并注入到接口应用处,dubbo服务调用发送请求就是用这个代理来处理的,从上一节解析中得到,最终会经过 InvokerInvocationHandler
处理器去处理.
4.1.1 Invoker 准备工作(降级,列举,负载均衡策略,异步幂等)
InvokerInvocationHandler 中的 invoker 成员变量类型为 MockClusterInvoker,MockClusterInvoker 内部封装了服务降级逻辑。下面简单看一下:
public class MockClusterInvoker<T> implements Invoker<T> {
private final Invoker<T> invoker;
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
// 获取 mock 配置值
String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
if (value.length() == 0 || value.equalsIgnoreCase("false")) {
// 无 mock 逻辑,直接调用其他 Invoker 对象的 invoke 方法,
// 比如 FailoverClusterInvoker
result = this.invoker.invoke(invocation);
} else if (value.startsWith("force")) {
// force:xxx 直接执行 mock 逻辑,不发起远程调用
result = doMockInvoke(invocation, null);
} else {
// fail:xxx 表示消费方对调用服务失败后,再执行 mock 逻辑,不抛出异常
try {
// 调用其他 Invoker 对象的 invoke 方法
result = this.invoker.invoke(invocation);
} catch (RpcException e) {
if (e.isBiz()) {
throw e;
} else {
// 调用失败,执行 mock 逻辑
result = doMockInvoke(invocation, e);
}
}
}
return result;
}
// 省略其他方法
}
如果配置了Mock,且不是强制要求mock的话,服务会先调用其他的invoker进行服务调用,如果没有成功时,不会报错,只会mock掉,mock处理逻辑相当于服务降级处理。
这里会调用FailfastClusterInvoker.invoker()
进行处理,这里用到了模板模式,来看看它的处理逻辑:
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
// binding attachments into invocation.
Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
if (contextAttachments != null && contextAttachments.size() != 0) {
((RpcInvocation) invocation).addAttachments(contextAttachments);
}
// 列举invokers
List<Invoker<T>> invokers = list(invocation);
// 初始化负载均衡器
LoadBalance loadbalance = initLoadBalance(invokers, invocation);
// 如果异步时生成InvocationId
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}
-
列举invokers
invokers = routerChain.route(getConsumerUrl(), invocation);
会通过在服务引入的订阅逻辑时设置的路由链拿到 invokers -
初始化负载均衡
没有配置时,会获取到 RandomLoadBalance
-
异步时生成InvocationId
只为幂等操作
4.1.2 负载均衡选择提供者
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
checkInvokers(invokers, invocation);
// 根据负载均衡策略,筛选出所要执行的invoker
Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
try {
return invoker.invoke(invocation);
} catch (Throwable e) {
...
}
}
这个集群策略是,失败后不做任何补救措施。
这里最关键的一步是,根据负载均衡策略选出其中一个invoker,做后续处理,即从多个提供者中选出一个提供者进行服务调用。Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);
,关于负载均衡策略,其他章节介绍。
4.1.3 DubboInvoker
最终选择出来的invoker对象如下图:
调用链如下:
FailfastClusterInvoker -> InvokerWrapper -> ListenerInvokerWrapper
-> ProtocolFilterWrapper(这里是一个过滤器链,其他filter不写了) -> FutureFilter -> DubboInvoker
最终会调用DubboInvoker,具体注入过程前面已经介绍过。看看DubboInvoker做了什么?
首先看个比较重要的实体类 Invocation :
protected Result doInvoke(final Invocation invocation) throws Throwable {
// ${装配 invocation}
// 获取客户端
ExchangeClient currentClient;
if (clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
// ${获取异步配置}
// 异步无返回值
if (isOneway) {
...
}
// 异步有返回值
else if (isAsync) {
...
}
// 同步调用
else {
RpcContext.getContext().setFuture(null);
// DefaultFuture 调用 get(time) 方法阻塞
return (Result) currentClient.request(inv, timeout).get();
}
} catch (TimeoutException e) {
...
}
}
获取客户端,如下图:
4.1.4 客户端发送请求
构造request对象,将invocation进行封装,最终通过netty的channel发送消息。
ChannelFuture future = channel.writeAndFlush(message);
发送过程中,会经过编解码、序列化处理,解决粘包和半包问题。这里不做过多解析。
4.2 提供者处理请求
消费者发送完消息后,提供者会通过处理器接收消息,具体调用步骤如下:
NettyHandler#messageReceived
|- MultiMessageHandler#received
|- HeartbeatHandler#received
|- AllChannelHandler#received
|- ChannelEventRunnable#run
|- DecodeHandler#received
|- HeaderExchangeHandler#received
|- HeaderExchangeHandler#handleRequest
|- DubboProtocol.requestHandler#reply
|- DubboProtocol#getInvoker
|- ProtocolFilterWrapper.filter#invoker
|- AbstractProxyInvoker#invoker
|- AbstractProxyInvoker#doInvoker
|- Wrapper#invokeMethod
|- DemoFacade#sayHello
|- HeaderExchangeHandler#send
4.2.1 线程池相关
在创建WrappedChannelHandler 处理器时,会初始化线程操作,具体如下:
public WrappedChannelHandler(ChannelHandler handler, URL url) {
...
executor = (ExecutorService) ExtensionLoader.getExtensionLoader(ThreadPool.class)
.getAdaptiveExtension().getExecutor(url);
...
}
默认是fixed,看看线程池里具体运行些什么任务?
public void run() {
if (state == ChannelState.RECEIVED) {
try {
// 接收消息
handler.received(channel, message);
} catch (Exception e) {
...
}
} else {
switch (state) {
case CONNECTED:
...
break;
case DISCONNECTED:
...
break;
case SENT:
...
break;
case CAUGHT:
...
break;
default:
logger.warn("unknown state: " + state + ", message is " + message);
}
}
4.2.2 获取invoker
String serviceKey = serviceKey(port, path,
inv.getAttachments().get(Constants.VERSION_KEY),
inv.getAttachments().get(Constants.GROUP_KEY));
DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey);
exporter.getInvoker();
4.2.3 过滤器调用链
ProtocolFilterWrapper#buildInvokerChain
该方法会创建 invoker 调用链,如下:
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
// 真正的invoker
Invoker<T> last = invoker;
// 获取过滤器
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class)
.getActivateExtension(invoker.getUrl(), key, group);
if (!filters.isEmpty()) {
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
last = new Invoker<T>() {
...
@Override
public Result invoke(Invocation invocation) throws RpcException {
Result result = filter.invoke(next, invocation);
if (result instanceof AsyncRpcResult) {
AsyncRpcResult asyncResult = (AsyncRpcResult) result;
asyncResult.thenApplyWithContext(r -> filter.onResponse(r, invoker, invocation));
return asyncResult;
} else {
return filter.onResponse(result, invoker, invocation);
}
}
...
};
}
}
return last;
}
4.2.4 获取结果
Wrapper 是一个抽象类,其中 invokeMethod 是一个抽象方法。Dubbo 会在运行时通过 Javassist 框架为 Wrapper 生成实现类,并实现 invokeMethod 方法,该方法最终会根据调用信息调用具体的服务。以 DemoFacadeImpl 为例,Javassist 为其生成的代理类如下。
public class Wrapper0 extends Wrapper implements ClassGenerator.DC {
public static String[] pns;
public static Map pts;
public static String[] mns;
public static String[] dmns;
public static Class[] mts0;
// 省略其他方法
public Object invokeMethod(Object object, String string, Class[] arrclass, Object[] arrobject) throws InvocationTargetException {
DemoService demoService;
try {
// 类型转换
demoService = (DemoService)object;
}
catch (Throwable throwable) {
throw new IllegalArgumentException(throwable);
}
try {
// 根据方法名调用指定的方法
if ("sayHello".equals(string) && arrclass.length == 1) {
return demoService.sayHello((String)arrobject[0]);
}
}
catch (Throwable throwable) {
throw new InvocationTargetException(throwable);
}
throw new NoSuchMethodException(new StringBuffer().append("Not found method \"").append(string).append("\" in class com.alibaba.dubbo.demo.DemoService.").toString());
}
}
4.2.5 发送结果到消费者
具体发送过程和请求过程一样,不做解析。
4.3 消费者解析结果
4.3.1 结果接收和处理
编码、序列化处理过程,不做解析
4.3.2 传递结果到用户线程
服务消费方在收到响应数据后,首先要做的事情是对响应数据进行解码,得到 Response 对象。然后再将该对象传递给下一个入站处理器,这个入站处理器就是 NettyHandler。接下来 NettyHandler 会将这个对象继续向下传递,最后 AllChannelHandler 的 received 方法会收到这个对象,并将这个对象派发到线程池中。这个过程和服务提供方接收请求的过程是一样的,因此这里就不重复分析了。本节我们重点分析两个方面的内容,一是响应数据的解码过程,二是 Dubbo 如何将调用结果传递给用户线程的。下面先来分析响应数据的解码过程。
响应数据解码完成后,Dubbo 会将响应对象派发到线程池上。要注意的是,线程池中的线程并非用户的调用线程,所以要想办法将响应对象从线程池线程传递到用户线程上。分析过用户线程在发送完请求后的动作,即调用 DefaultFuture 的 get 方法等待响应对象的到来。当响应对象到来后,用户线程会被唤醒,并通过调用编号获取属于自己的响应对象。下面我们来看一下整个过程对应的代码。
public void received(Channel channel, Object message) throws RemotingException {
channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());
final ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
try {
if (message instanceof Request) {
...
} else if (message instanceof Response) {
// 处理响应
handleResponse(channel, (Response) message);
} else if (message instanceof String) {
...
} else {
handler.received(exchangeChannel, message);
}
} finally {
HeaderExchangeChannel.removeChannelIfDisconnected(channel);
}
}
static void handleResponse(Channel channel, Response response) throws RemotingException {
if (response != null && !response.isHeartbeat()) {
// 继续向下调用
DefaultFuture.received(channel, response);
}
}
看看DefaultFuture阻塞和唤醒怎么实现的?
private final Lock lock = new ReentrantLock();
private final Condition done = lock.newCondition();
private volatile ResponseCallback callback;
4.3.2.1 DefultFuture.get()
public Object get(int timeout) throws RemotingException {
if (timeout <= 0) {
timeout = Constants.DEFAULT_TIMEOUT;
}
if (!isDone()) {
long start = System.currentTimeMillis();
lock.lock();
try {
while (!isDone()) {
done.await(timeout, TimeUnit.MILLISECONDS);
if (isDone() || System.currentTimeMillis() - start > timeout) {
break;
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
if (!isDone()) {
throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));
}
}
return returnFromResponse();
}
利用锁的信号量阻塞的,使用await()
4.3.2.2 DefultFuture.received()
private void doReceived(Response res) {
lock.lock();
try {
response = res;
if (done != null) {
// 唤醒用户线程
done.signal();
}
} finally {
lock.unlock();
}
if (callback != null) {
invokeCallback(callback);
}
}
利用锁的信号量唤醒的,使用signal()
本篇文章在多个地方都强调过调用编号很重要,但一直没有解释原因,这里简单说明一下。一般情况下,服务消费方会并发调用多个服务,每个用户线程发送请求后,会调用不同 DefaultFuture 对象的 get 方法进行等待。 一段时间后,服务消费方的线程池会收到多个响应对象。这个时候要考虑一个问题,如何将每个响应对象传递给相应的 DefaultFuture 对象,且不出错。答案是通过调用编号。DefaultFuture 被创建时,会要求传入一个 Request 对象。此时 DefaultFuture 可从 Request 对象中获取调用编号,并将 <调用编号, DefaultFuture 对象> 映射关系存入到静态 Map 中,即 FUTURES。线程池中的线程在收到 Response 对象后,会根据 Response 对象中的调用编号到 FUTURES 集合中取出相应的 DefaultFuture 对象,然后再将 Response 对象设置到 DefaultFuture 对象中。最后再唤醒用户线程,这样用户线程即可从 DefaultFuture 对象中获取调用结果了