在看远程暴露之前,我们先来浏览下项目跑动的日志:
[com.alibaba.dubbo.config.AbstractConfig] - [DUBBO] The service ready on spring started. service: com.api.IDubboTestService,
[com.alibaba.dubbo.config.AbstractConfig] - [DUBBO] Export dubbo service com.api.IDubboTestService to local registry
[com.alibaba.dubbo.config.AbstractConfig] - [DUBBO] Export dubbo service com.api.IDubboTestService to url dubbo://192.168.1.12:20880/com.api.IDubboTestService
[com.alibaba.dubbo.config.AbstractConfig] - [DUBBO] Register dubbo service com.api.IDubboTestService url dubbo://192.168.1.12:20880/com.api.IDubboTestService
[com.alibaba.dubbo.remoting.transport.AbstractServer] - [DUBBO] Start NettyServer bind /0.0.0.0:20880, export /192.168.1.12:20880
[org.apache.zookeeper.ClientCnxn] - Session establishment complete on server /192.168.1.1:2181, sessionid = 0x16311ba0b5b40a9, negotiated timeout = 5000
[com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry] - [DUBBO] Register: dubbo://192.168.1.12:20880/com.api.IDubboTestService
[com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry] - [DUBBO] Subscribe: provider://192.168.1.12:20880/com.api.IDubboTestService
[com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry] - [DUBBO] Notify urls for subscribe url provider://192.168.1.12:20880/com.api.IDubboTestService
2018-05-18 16:36:26,914 INFO [org.I0Itec.zkclient.ZkClient] - zookeeper state changed (Disconnected)
从日志里我们可以大概知道一些粗的流程:
1.将接口所需的配置,属性等等全部准备好,ready
2.暴露本地服务,to local标志
3.暴露远程服务
4.启动Netty
5.链接Zk
6.注册接口,ZookeeperRegistry Register
7.订阅接口,ZookeeperRegistry Subscribe
关于远程暴露,首先这一篇我不想深入多少,因为远程暴露很复杂,此阶段学习需要总体理解里面的流程,对于内部的处理细节不想过多追究,一旦深入追究会陷入于此,出不来,对于调用到的代码会去大致了解它的目标。
1.远程暴露入口处(ServiceConfig):
1.1 将ref封装成Invoke(本地暴露时讲过,此篇不在关注,具体调用不难)
1.2 Invoke封装成exporter
if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
if (registryURLs != null && registryURLs.size() > 0
&& url.getParameter("register", true)) {
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
Exporter> exporter = protocol.export(invoker);
exporters.add(exporter);
}
} else {
Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
Exporter> exporter = protocol.export(invoker);
exporters.add(exporter);
}
2. Invoke 转化 Exporter
Exporter> exporter = protocol.export(invoker);
此处通过SPI机制实现自动调用,具体调用哪一个不在说明,此处调用的是RegistryProtocol。它主要实现了远程暴露的全部流程
2-1 Invoke 到 Exporter的转化
2-2 Netty的启动
2-3 注册服务到zk。
2-4 订阅当前注册服务。
2-5 返回新的exporter实例
public Exporter export(final Invoker originInvoker) throws RpcException {
//export invoker
final ExporterChangeableWrapper exporter = doLocalExport(originInvoker);
//registry provider
final Registry registry = getRegistry(originInvoker);
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
registry.register(registedProviderUrl);
// 订阅override数据
// FIXME 提供者订阅时,会影响同一JVM即暴露服务,又引用同一服务的的场景,因为subscribed以服务名为缓存的key,导致订阅信息覆盖。
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//保证每次export都返回一个新的exporter实例
return new Exporter() {
public Invoker getInvoker() {
return exporter.getInvoker();
}
public void unexport() {
try {
exporter.unexport();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
registry.unregister(registedProviderUrl);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
overrideListeners.remove(overrideSubscribeUrl);
registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
};
}
这一篇我们主要去学习Invoke到Exporter的转化,也就是上面代码的第一行......毕竟暴露的过程是很复杂的。
2.1doLocalExport()
过多的细节不去深究,主要它获取了一个invokerDelegete。
private ExporterChangeableWrapper doLocalExport(final Invoker originInvoker){
String key = getCacheKey(originInvoker);
ExporterChangeableWrapper exporter = (ExporterChangeableWrapper) bounds.get(key);
if (exporter == null) {
synchronized (bounds) {
exporter = (ExporterChangeableWrapper) bounds.get(key);
if (exporter == null) {
final Invoker> invokerDelegete = new InvokerDelegete(originInvoker, getProviderUrl(originInvoker));
exporter = new ExporterChangeableWrapper((Exporter)protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}
return (ExporterChangeableWrapper) exporter;
}
2.2 invokerDelegete
invokerDelegete是Invoker的委托类,存储了发布过程最原始的originInvoker,另外还存储了providerUrl,这个url就是我们要在注册中心注册的url,通过debug可以看见。
final Invoker> invokerDelegete = new InvokerDelegete(originInvoker, getProviderUrl(originInvoker));
public static class InvokerDelegete extends InvokerWrapper{
private final Invoker invoker;
/**
* @param invoker
* @param url invoker.getUrl返回此值
*/
public InvokerDelegete(Invoker invoker, URL url){
super(invoker, url);
this.invoker = invoker;
}
public Invoker getInvoker(){
if (invoker instanceof InvokerDelegete){
return ((InvokerDelegete)invoker).getInvoker();
} else {
return invoker;
}
}
}
2.3 ExporterChangeableWrapper
这边又是SPI机制的调用,也同样不在叙述,最终调用的是DubboProtocol,需要注意的是这边是存在装饰器类的作用,也就是dubbo的IOC和AOP。包装的过程我们不去关注,只能说明分支太杂了,哎,累人啊。
exporter = new ExporterChangeableWrapper((Exporter) protocol.export(invokerDelegete), originInvoker)
我们直接关注经过层层保证之后的最终调用。
首先从url中获取需要暴露的key,本篇博客中的获取到的key是dev/com.api.IDubboTestService:2.0.0:20880。因为笔者项目中是存在version的概念的,所以多了一个dev,表示开发环境。然后去创建exporter。
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;
}
2.4 DubboExporter的构造。
public DubboExporter(Invoker invoker, String key, Map> exporterMap){
super(invoker);
this.key = key;
this.exporterMap = exporterMap;
}
2.5 ExchangeServer的开启
如果serverMap中没有获取到对应的(host:port)对应的Server,则创建。
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);
}
}
}
2.5.1 创建ExchangeServer:createServer(URL providerUrl)关于Netty是怎么启动的,怎么绑定的,不在本文反问内,只关注一些包装,
1. 默认开启了readonly事件
2.默认加上了心跳参数
channel.readonly.sent=true&heartbeat=60000&codec=dubbo
private ExchangeServer createServer(URL url) {
//默认开启server关闭时发送readonly事件
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
//默认开启heartbeat
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
if (str != null && str.length() > 0 && ! ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
throw new RpcException("Unsupported server type: " + str + ", url: " + url);
url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
ExchangeServer server;
try {
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
str = url.getParameter(Constants.CLIENT_KEY);
if (str != null && str.length() > 0) {
Set supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
if (!supportedTypes.contains(str)) {
throw new RpcException("Unsupported client type: " + str);
}
}
return server;
}
2.6 至此完成Invoker到exporter的转化,里面还夹杂着Netty的启动等等详情,此处没有关注,因为我没有Netty的经验,所以很多源码看的不是很清楚。