前言:rpc模块是远程调用模块,抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理。这个模块的学习将使我们对服务的发布和调用更加清晰。
重点学习这几个类的作用和它们的实现类:ProxyFactory、Invoker、Protocol、Exporter
1,Invoker
这是一个可执行的对象,能够根据方法,参数得到执行结果,代码如下:
public interface Invoker<T> extends Node {
Class getInterface();
Result invoke(Invocation invocation) throws RpcException;
}
里面的Invocation包含了要执行的方法和参数
public interface Invocation {
String getMethodName();
Class>[] getParameterTypes();
Object[] getArguments();
Map<String, String> getAttachments();
String getAttachment(String key);
String getAttachment(String key, String defaultValue);
Invoker> getInvoker();
}
看一下Invocation的实现类:RpcInvocation。简略代码如下:
public class RpcInvocation implements Invocation, Serializable {
private static final long serialVersionUID = -4355285085441097045L;
private String methodName;
private Class>[] parameterTypes;
private Object[] arguments;
private Map attachments;
private transient Invoker> invoker;
}
其实也只是提供了Invocation所需的参数而已。我们把目光又放回Invoker。
Invoker的执行过程分为三种类型
(1)本地执行类的Invoker
(2)远程通信执行类的Invoker
(3)多个(2)的Invoker聚合成的集群版的Invoker(需要设计到负载均衡)
2,ProxyFactory
对于server端,主要负责将服务统一进行包装成一个Invoker,这些Invoker通过反射来执行具体的对象的方法。
@SPI("javassist")
public interface ProxyFactory {
//针对client端,创建出代理对象
@Adaptive({Constants.PROXY_KEY})
T getProxy(Invoker invoker) throws RpcException;
//针对server端,将服务对象包装成一个Invoker对象
@Adaptive({Constants.PROXY_KEY})
Invoker getInvoker(T proxy, Class type, URL url) throws RpcException;
}
实现类主要有JdkProxyFactory、JavassistProxyFactory。默认是JavassistProxyFactory
3,Protocol
我们发布服务的第一个过程是将服务封装成一个本地执行的invoker,执行服务这个就是执行这个invoker(调用这个invoker的invoke方法),通过反射执行。但是我们看到invoke方法的参数-invocation,这个参数是如何得来的?
服务端的Protocol需要根据指定的协议对外公布服务,当客户端根据协议调用这个服务时候,将用户传递的invocation参数交给invoker来执行,所以Protocol加入了远程通信协议这一块,根据用户的请求获取参数Invocation。
@Extension("dubbo")
public interface Protocol {
int getDefaultPort();
//针对server端来说,将本地执行类的Invoker通过协议暴漏给外部。这样外部就可以通过协议发送执行参数Invocation,然后交给本地Invoker来执行
@Adaptive
Exporter export(Invoker invoker) throws RpcException;
//这个是针对客户端的,客户端从注册中心获取服务器端发布的服务信息
//通过服务信息得知服务器端使用的协议,然后客户端仍然使用该协议构造一个Invoker。这个Invoker是远程通信类的Invoker。
//执行时,需要将执行信息通过指定协议发送给服务器端,服务器端接收到参数Invocation,然后交给服务器端的本地Invoker来执行
@Adaptive
Invoker refer(Class type, URL url) throws RpcException;
void destroy();
}
服务发布的第二步是暴露invoker。
Exporter> exporter = protocol.export(invoker);
暴露过程即根据invoker的URL的配置信息来最终选择Protocol实现,默认是dubbo,扩展实现即DubboProtocol,然后再对DubboProtocol进行依赖注入,进行wrap包装。先来看看Protocol的实现情况:
可以看到在返回DubboProtocol之前,经过了ProtocolFilterWrapper(核心方法是buildInvokerChain,构建Chain)、ProtocolListenerWrapper、RegistryProtocol的包装。
包装时候的是装饰模式,类似AOP功能
package com.alibaba.xxx;
import com.alibaba.dubbo.rpc.Protocol;
public class XxxProtocolWrapper implemenets Protocol {
Protocol impl;
public XxxProtocol(Protocol protocol) { impl = protocol; }
// 接口方法做一个操作后,再调用extension的方法
public Exporter<T> export(final Invoker<T> invoker) {
//... 一些操作
impl .export(invoker);
// ... 一些操作
}
// ...
}
当服务发布时候会先经过RegistryProtocol,这个类的主要功能如下:
1,利用内部的Protocol即DubboProtocol,将服务进行导出,
2,根据注册中心的registryUrl获取注册服务Registry,然后将serviceUrl注册到注册中心上,供客户端订阅
接下来就需要看到DubboProtocol类了,注重看它的服务导出功能。
public Exporter export(Invoker invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
String key = serviceKey(url);
DubboExporter exporter = new DubboExporter(invoker, key, exporterMap);
exporterMap.put(key, exporter);
//export an stub service for dispaching event
Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY,Constants.DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice){
String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0 ){
if (logger.isWarnEnabled()){
logger.warn(new IllegalStateException("consumer [" +url.getParameter(Constants.INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
} else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
}
openServer(url);
return exporter;
}
创建一个DubboExporter,封装invoker。然后根据url的port、path(接口的名称)、版本号、分组号作为key,将DubboExporter存至Map
private void openServer(URL url) {
// find server.
String key = url.getAddress();
//client 也可以暴露一个只有server可以调用的服务。
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY,true);
if (isServer) {
ExchangeServer server = serverMap.get(key);
if (server == null) {
serverMap.put(key, createServer(url));
} else {
//server支持reset,配合override功能使用
server.reset(url);
}
}
}
首先根据Invoker的url获取ExchangeServer通信对象(负责与客户端的通信模块),以url中的host和port作为key存至Map
private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
Invocation inv = (Invocation) message;
Invoker> invoker = getInvoker(channel, inv);
RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
return invoker.invoke(inv);
}
throw new RemotingException(channel, "Unsupported request: " + message == null ? null : (message.getClass().getName() + ": " + message) + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
}
};
可以看到在获取到Invocation参数后,调用getInvoker(channel, inv)来获取本地Invoker。获取过程就是根据channel获取port,根据Invocation inv信息获取要调用的服务接口、版本号、分组号等,以此组装成key,从上述Map
public interface Exporter {
Invoker getInvoker();
void unexport();
}
包含了一个Invoker对象。一旦想撤销该服务,就会调用Invoker的destroy()方法,同时清理上述exporterMap中的数据。对于RegistryProtocol来说就需要向注册中心撤销该服务
二,DubboCodec
这个类是上一篇遗留的,当初涉及到编解码问题,接下来就来解读dubbo传输的底层协议组成以及它的编码解码过程。
1,传输协议
协议格式
3,bodydata
是消息传递的真正内容,body的占用的字节大小由协议头后四位保存。
4,序列化Request和Response
@Override
protected void encodeRequestData(Channel channel, ObjectOutput out, Object data) throws IOException {
RpcInvocation inv = (RpcInvocation) data;
out.writeUTF(inv.getAttachment(Constants.DUBBO_VERSION_KEY, DUBBO_VERSION));
out.writeUTF(inv.getAttachment(Constants.PATH_KEY));
out.writeUTF(inv.getAttachment(Constants.VERSION_KEY));
out.writeUTF(inv.getMethodName());
out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes()));
Object[] args = inv.getArguments();
if (args != null)
for (int i = 0; i < args.length; i++){
out.writeObject(encodeInvocationArgument(channel, inv, i));
}
out.writeObject(inv.getAttachments());
}