参考资料:
总结
dubbo服务调用主要分为两个过程,分别是消费者发送请求和接收响应结果,提供者接收请求。
消费者端:
- 发送请求:服务接口的代理对象执行目标方法,被
InvokerInvocationHandler#invoke
方法拦截,经过路由过滤、负载均衡后选择一个DubboInvoker对象,调用doInvoke方法。创建一个Request对象,并生成全局唯一的请求ID,接着实例化一个DefaultFuture对象,将请求ID作为key,把DefaultFuture保存到一个ConcurrentHashMap。最后,通过NettyClient把封装了目标方法信息的RpcInvocation序列化后发送出去- 接收响应:先通过响应ID,即请求ID,在缓存中找到对应的Future,执行doReceived方法。保存结果,接着唤醒对应的请求线程来处理响应结果
提供者端:
NettyServer接收到请求后,根据协议得到信息并反序列化成对象,派发到线程池等待处理。信息会被封转成ChannelEventRunnable对象,类型为RECEIVED。工作线程最终会调用
DubboProtocol#reply
方法,根据port、path、version、group构建serviceKey,从缓存中找到对应Exporter,经过层层调用,最后会找到真正实现类,执行目标方法返回结果。
调用流程图
客户端发起本地调用,实际上调用的是代理类,代理类通过远程客户端(默认是NettyClient)发起请求。先是构建协议头,指定通信协议、序列化器类型和body长度,接着将Java对象序列化成协议体,然后发送数据。
服务端(NettyServer)接收请求,分发给业务线程池处理,由业务线程找到对应的实现类,执行相应方法并返回结果。
这里省略了应用层的通信协议和序列化器的知识点,具体可查看参考资料。
客户端源码分析
上篇文章说到,消费者引用服务时,ReferenceConfig#init
会通过动态代理创建一个代理对象,所以当我们调用服务接口的方法时,都会被InvokerInvocationHandler#invoke
拦截
com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler#invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
// ... 省略部分代码
// 先创建RpcInvocation对象,然后调用MockClusterInvoker#invoke
// Invocation类的作用:基本上都是 将原本由InvocationHandler调用目标方法的执行时机交给其他类决定
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}
我们顺着代码,看下执行过程。
com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker#invoke
@Override
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")) {
//no mock
// 调用AbstractClusterInvoker
result = this.invoker.invoke(invocation);
}
// ..... 省略部分代码
return result;
}
com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker#invoke
@Override
public Result invoke(final Invocation invocation) throws RpcException {
// 检查是否被销毁
checkWhetherDestroyed();
LoadBalance loadbalance = null;
// binding attachments into invocation.
// 看下上下文有没有attachments,有的话绑定到Invocation
Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
if (contextAttachments != null && contextAttachments.size() != 0) {
((RpcInvocation) invocation).addAttachments(contextAttachments);
}
// 调用directory#list,里面做的是路由过滤
List<Invoker<T>> invokers = list(invocation);
if (invokers != null && !invokers.isEmpty()) {
// 通过spi获取loadbalance实现类
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(
invokers.get(0).getUrl().getMethodParameter(
RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
}
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
// 最终调用DubboInvoker#doInvoke方法,通过负载均衡策略选择一个Invoker
return doInvoke(invocation, invokers, loadbalance);
}
protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
List<Invoker<T>> invokers = directory.list(invocation);
return invokers;
}
DubboInvoker#doInvoke
我们先忽略路由、集群、负载均衡的相关知识点,重点看com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker#doInvoke
@Override
protected Result doInvoke(final Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
final String methodName = RpcUtils.getMethodName(invocation);
// 设置path和version到attachment
inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
inv.setAttachment(Constants.VERSION_KEY, version);
// 选择一个客户端
ExchangeClient currentClient;
if (clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
// 异步调用标记
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
// oneway发送方式标记
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
// 超时时间
int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
if (isOneway) {
// oneway方式发送,不管发送结果
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
RpcContext.getContext().setFuture(null);
return new RpcResult();
} else if (isAsync) {
// 异步发送
ResponseFuture future = currentClient.request(inv, timeout);
RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
return new RpcResult();
} else {
RpcContext.getContext().setFuture(null);
// 同步发送,直接调用future.get()阻塞等待结果
// 异步和同步的区别是,future.get()是用户调用还是组件调用
return (Result) currentClient.request(inv, timeout).get();
}
}
// ... 省略部分代码
}
调用的三种方式
从上面的代码可以看到调用一共分为三种方式,包括oneway、异步、同步。
- **oneway:**不需要关心发送结果时,可以选择这种方式,消耗最小,啥都不用管。
- **异步调用:**client发送请求后返回一个ResponseFuture,然后塞到上下文。在用户需要结果时,通过上下文获取future,调用
future#get
得到请求结果 - **同步调用:**这是我们最常用的,其实只不过组件帮我们手动调用
future#get
方法,使得给用户的感觉好像同步调用一样
发送请求
发送请求currentClient.request(inv, timeout)
的执行过程是,创建一个请求对象Request,设置版本号和请求内容,构造Future对象并缓存,调用NettyClient发送请求。
com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeChannel#request(java.lang.Object, int)
@Override
public ResponseFuture request(Object request, int timeout) throws RemotingException {
// ... 省略部分代码
// create request.
// 创建请求对象,同时生成唯一的请求ID
Request req = new Request();
req.setVersion(Version.getProtocolVersion());
req.setTwoWay(true);
req.setData(request);
// 新建并缓存Future
DefaultFuture future = new DefaultFuture(channel, req, timeout);
try {
// 最终调用NettyChannel#send
channel.send(req);
} catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}
构建Request
com.alibaba.dubbo.remoting.exchange.Request
public class Request {
private static final AtomicLong INVOKE_ID = new AtomicLong(0);
private final long mId;
public Request() {
mId = newId();
}
private static long newId() {
// getAndIncrement() When it grows to MAX_VALUE, it will grow to MIN_VALUE, and the negative can be used as ID
return INVOKE_ID.getAndIncrement();
}
public long getId() {
return mId;
}
// ...省略部分代码
}
构建Future
com.alibaba.dubbo.remoting.exchange.support.DefaultFuture#DefaultFuture
public DefaultFuture(Channel channel, Request request, int timeout) {
this.channel = channel;
this.request = request;
// 生成请求id,作为key,用于接收响应结果时从Map中获取到对应的Future和Channel
this.id = request.getId();
this.timeout = timeout > 0 ?
timeout : channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// put into waiting map.
FUTURES.put(id, this);
CHANNELS.put(id, channel);
}
// FUTURES和CHANNELS都是一个ConcurrentHashMap
private static final Map<Long, Channel> CHANNELS = new ConcurrentHashMap<Long, Channel>();
private static final Map<Long, DefaultFuture> FUTURES = new ConcurrentHashMap<Long, DefaultFuture>();
可以看到,当我们创建请求对象时,Request内部会调用AtomicLong#getAndIncrement
生成一个全局唯一的ID。当创建DefaultFuture对象时,将请求ID作为key,把Future保存到Map。当接收响应结果时,最终调用的是DefaultFuture#received
来接收结果
接收响应结果
com.alibaba.dubbo.remoting.exchange.support.DefaultFuture#received
public static void received(Channel channel, Response response) {
try {
// 响应返回的id,就是服务端请求时发送的id
// 根据请求id到缓存找到对应的Future
DefaultFuture future = FUTURES.remove(response.getId());
if (future != null) {
future.doReceived(response);
} else {
logger.warn("The timeout response finally returned at "
+ (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()))
+ ", response " + response
+ (channel == null ? "" : ", channel: " + channel.getLocalAddress()
+ " -> " + channel.getRemoteAddress()));
}
} finally {
CHANNELS.remove(response.getId());
}
}
private void doReceived(Response res) {
lock.lock();
try {
// 接收响应结果
response = res;
if (done != null) {
// 唤醒请求线程
done.signal();
}
} finally {
lock.unlock();
}
if (callback != null) {
invokeCallback(callback);
}
}
客户端接收到响应结果的处理过程:**根据响应结果ID,也就是请求ID,到缓存中找到对应的Future对象,调用doReceived方法,保存响应结果,并唤醒对应的请求线程。**过程如下图所示:
小结
主要过程:
1.服务接口的代理对象执行目标方法,被
InvokerInvocationHandler#invoke
方法拦截,进入代理过程。经过路由过滤、负载均衡,选择一个DubboInvoker,执行doInvoker方法。先是创建一个请求对象(Request),并生成全局唯一的ID。接着,创建DefaultFuture,并保存到ConcurrentHashMap中,key是请求ID。最后通过NettyClient把封装了目标方法信息的RpcInvocation序列化成消息发送出去2.接收到返回的响应结果,调用
DefaultFuture#received
方法,根据返回的请求ID,找到对应的DefaultFuture。接着调用doReceived方法,保存响应结果,并唤醒请求线程。最后,解析并返回执行结果
服务端源码分析
服务端接收到请求后,会解析得到消息。消息有五种派发策略:
默认是all,也就是所有消息都派发到业务线程池中,我们来看下AllChannelHandler的实现
com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler#received
@Override
public void received(Channel channel, Object message) throws RemotingException {
// 获取线程池
// Executors.newCachedThreadPool(new NamedThreadFactory("DubboSharedHandler", true));
ExecutorService cexecutor = getExecutorService();
try {
// ChannelEventRunnable#run方法,包含多种类型处理
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
}
// ...省略部分代码
}
ChannelEventRunnable
ChannelEventRunnable实现Runnable接口,我们看下它的run方法。
com.alibaba.dubbo.remoting.transport.dispatcher.ChannelEventRunnable#run
@Override
public void run() {
if (state == ChannelState.RECEIVED) {
try {
handler.received(channel, message);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel
+ ", message is " + message, e);
}
} else {
switch (state) {
case CONNECTED:
try {
handler.connected(channel);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel, e);
}
break;
case DISCONNECTED:
try {
handler.disconnected(channel);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel, e);
}
break;
case SENT:
try {
handler.sent(channel, message);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel
+ ", message is " + message, e);
}
case CAUGHT:
try {
handler.caught(channel, exception);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel
+ ", message is: " + message + ", exception is " + exception, e);
}
break;
default:
logger.warn("unknown state: " + state + ", message is " + message);
}
}
}
结合上面的代码,服务端接收到请求后解析成消息,接着获取线程池,把消息封装成ChannelEventHandler,类型是 ChannelState.RECEIVED。当空闲线程处理请求时,执行ChannelEventRunnable#received方法,最终调用HeaderExchangeHandler#handleRequest
HeaderExchangeHandler
com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler#handleRequest
Response handleRequest(ExchangeChannel channel, Request req) throws RemotingException {
// 封装一个响应对象,保存请求ID
Response res = new Response(req.getId(), req.getVersion());
// ...省略部分代码
// find handler by message class.
// 获取请求消息,比如方法名称、参数类型、参数值。也就是封装了目标方法信息的RpcInvocation
Object msg = req.getData();
try {
// handle data.
// 最终调用DubboProtocol#reply
Object result = handler.reply(channel, msg);
res.setStatus(Response.OK);
res.setResult(result);
} catch (Throwable e) {
res.setStatus(Response.SERVICE_ERROR);
res.setErrorMessage(StringUtils.toString(e));
}
return res;
}
DubboProtocol#reply
这是个关键方法,源码如下
com.alibaba.dubbo.remoting.exchange.support.ExchangeHandlerAdapter#reply
@Override
public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
Invocation inv = (Invocation) message;
// 找到Invoker
Invoker<?> invoker = getInvoker(channel, inv);
// ... 省略部分代码
RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
// 最终找到真正实现类,调用目标方法
return invoker.invoke(inv);
}
}
Invoker<?> getInvoker(Channel channel, Invocation inv) throws RemotingException {
// ... 省略部分代码
// 构建key,生成规则:group/serviceName:version:port
String serviceKey = serviceKey(port, path, inv.getAttachments().get(Constants.VERSION_KEY),
inv.getAttachments().get(Constants.GROUP_KEY));
// 根据key,从之前暴露服务的Map中找到对应的Exporter
DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey);
// 返回Exporter封装的Invoker
return exporter.getInvoker();
}
关键是serviceKey,在服务暴露时,生成的exporter会保存到exporterMap,它的key也是serviceKey。
小结
主要过程:NettyServer接收到请求后,解析成信息,并全部派发到线程池,封转成ChannelEventRunnable对象,等待线程执行。工作线程调用
DubboProtocol#reply
方法,构建serviceKey,从缓存中找到对应的Exporter,并执行invoke方法,最终找到真正实现类,执行目标方法并返回结果。