参考资料:
说明:源码版本是2.6.5
总结
主线:
从业务代码角度看:封装URL -> 本地暴露 -> 远程暴露
从对象构建及转移角度看:具体实现类 -> Invoker -> Exporter
大概过程:
1.当spring容器刷新完成,会发布ContextRefreshEvent事件,ServiceBean执行doExport方法,开始服务暴露。
2.先获取注册中心的URL,把各种协议都注册到注册中心
3.根据各种参数,封装服务接口的URL对象
4.如果scope没有配置Remote,则进行本地暴露。把协议改成injvm,然后调用代理工厂生成Invoker,封装真正实现类的调用逻辑,最后通过具体协议,导出Exporter
5.如果scope没有配置Local,则进行远程暴露。也是通过代理工厂封装具体实现类,生成Invoker,接着导出Exporter。不过,这里的export分两种情况。
5.1 第一种情况:Registry类型的export;这是指将服务注册到注册中心,以zk为例,会创建一个zk连接,然后在指定目录下创建一个节点,节点信息包括了服务提供者的ip、端口、服务接口等信息
5.2 第二种情况:非Registry类型的export;比如,dubbo协议。这个过程是创建Netty服务端,打开并监听端口。
URL说明
在开始分析dubbo服务暴露过程前,先简单说下URL,因为dubbo就是把URL作为约定参数类型,即通过URL对象进行数据的传递。
URL是指统一资源定位符,标准格式如下:
protocol://username:password@host:port/path?key=value&key=value
- protocol:是指dubbo中使用到的各种协议,比如 dubbo、http、registry
- path:接口的全限定名
- 参数:键值对
配置解析
我们使用xml文件配置dubbo时,是通过DubboNamespaceHandler向spring容器注册各种标签的解析器,用来读取标签内容,保存配置信息 。
<dubbo:service interface="cn.com.service.RegionService" ref="regionService" />
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
@Override
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
}
从上面的代码可以看到,标签dubbo:service的解析器是ServiceBean
服务暴露过程
暴露时机
在spring容器刷新完成后,会发布ContextRefreshedEvent事件,ServiceBean执行onApplicationEvent方法,该方法调用父类ServiceConfig的export方法,开始暴露服务。
doExportUrls
doExport方法很长,先经过各种配置的检查,填充属性。接着调用doExportUrls方法。该方法先调用loadRegistries获取所有注册中心的url,接着遍历各种协议,把每种协议都往注册中心注册。比如,同时支持 dubbo 协议和 hessian 协议,那么需要将这个服务用两种协议分别向注册中心暴露
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
loadRegistries
获取注册中心的url,如下所示
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-provider&application.version=1.0&dubbo=2.5.3&environment=product&organization=china&owner=cheng.xi&pid=2939®istry=zookeeper×tamp=1488898049284
doExportUrlsFor1Protocol
该方法大致做三件事情:
- 先拼接服务URL对象
- 本地暴露服务
- 远程暴露服务
拼接服务url
由于代码比较长就不贴出来了,就是指定服务的协议、服务接口及方法,最终拼接出服务URL对象,如下所示
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
dubbo://10.0.0.83:20886/cn.com.bluemoon.asset.dubbo.IDubboCommonService?anyhost=true&application=bm-officeAuto-asset&complexity=0&dubbo=2.8.4.BM-SNAPSHOT&generic=false&interface=cn.com.bluemoon.asset.dubbo.IDubboCommonService&methods=assetPushSMS&pid=516&retries=0&sendmsg=false&side=provider&timeout=60000×tamp=1608039139999
本地暴露
服务URL拼接完成后,接着就是暴露服务,主要分为本地暴露和远程暴露。两者过程基本都是,先使用代理工厂生成Invoker,封装实现类的调用逻辑。接着根据不同协议,导出对应的Exporter,提供给消费方使用。
// 这段注释直接copy参考资料的。。。
private void exportLocal(URL url) {
// 如果已经是injvm协议,不需要再执行
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
//这时候转成本地暴露的url:injvm://127.0.0.1/dubbo.common.hello.service.HelloService?anyhost=true&
//application=dubbo-provider&application.version=1.0&dubbo=2.5.3&environment=product&
//interface=dubbo.common.hello.service.HelloService&methods=sayHello&
//organization=china&owner=cheng.xi&pid=720&side=provider×tamp=1489716708276
URL local = URL.valueOf(url.toFullString())
.setProtocol(Constants.LOCAL_PROTOCOL)
.setHost(NetUtils.LOCALHOST)
.setPort(0);
//首先获得Invoker
//然后导出成Exporter,并缓存
//这里的proxyFactory实际是JavassistProxyFactory
//有关详细的获得Invoke以及exporter会在下面的流程解析,在本地暴露这个流程就不再说明。
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() +" to local registry");
}
}
本地暴露时,协议被改成injvm,ip为127.0.0.1,端口是0;
远程暴露
创建Invoker
在上面的代码中,我们都能看到先是创建Invoker,接着是根据协议导出Exporter。
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
目前,有两种方式获取Invoker,分别是JavassistProxyFactory#getInvoker和JdkProxyFactory#getInvoker,默认使用JavassistProxyFactory。
public class JavassistProxyFactory extends AbstractProxyFactory {
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper类不能正确处理带$的类名
// 如果类是$开头,使用接口类型获取
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
// wrapper.invokeMethod 会调用实现类的实现方法
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
}
public class JdkProxyFactory extends AbstractProxyFactory {
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
interfaces, new InvokerInvocationHandler(invoker));
}
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
Method method = proxy.getClass().getMethod(methodName, parameterTypes);
return method.invoke(proxy, arguments);
}
};
}
}
可以看到,其实两个实现方式大同小异,区别是JavassistProxyFactory会对具体实现类做了一个包装,最终都是调用具体实现类的目标方法。因此,Invoker作用是调用具体实现类的实现方法。
导出Exporter
Invoker导出为Exporter分为两种情况:第一种是Registry类型的Invoker,第二种是非Registry类型的Invoker。
方法描述:
/**
* 暴露远程服务:<br>
* 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
* 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br>
* 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br>
*
* @param <T> 服务的类型
* @param invoker 服务的执行体
* @return exporter 暴露服务的引用,用于取消暴露
* @throws RpcException 当暴露服务出错时抛出,比如端口已占用
*/
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
方法入口:
Exporter<?> exporter = protocol.export(invoker);
Registry类型的export
com.alibaba.dubbo.registry.integration.RegistryProtocol#export
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// export invoker
// doLocalExport:这里是执行其他协议的暴露,比如dubbo
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
//registry provider
// 根据Invoker的url获取Registry实例对象
final Registry registry = getRegistry(originInvoker);
// 获取要注册到注册中心的url,即服务接口url
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
// 调用远端注册中心的register方法进行服务注册
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实例
//返回暴露后的Exporter给上层ServiceConfig进行缓存,便于后期撤销暴露。
return new Exporter<T>() {
public Invoker<T> 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);
}
}
};
}
这里面涉及到的方法很多,具体解析可看参考资料。
-
getRegistry(originInvoker)
-
根据dubbo spi机制获取到具体的Registry实例,如果是zookeeper协议,则返回ZookeeperRegistry,该对象保存着连接到zookeeper的zkClient
-
/** * 根据invoker的地址获取registry实例 * @param originInvoker * @return */ private Registry getRegistry(final Invoker<?> originInvoker){ URL registryUrl = originInvoker.getUrl(); if (Constants.REGISTRY_PROTOCOL.equals(registryUrl.getProtocol())) { String protocol = registryUrl.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_DIRECTORY); registryUrl = registryUrl.setProtocol(protocol).removeParameter(Constants.REGISTRY_KEY); } return registryFactory.getRegistry(registryUrl); }
-
-
getRegistedProviderUrl(originInvoker)
-
获取要注册到注册中心的url,即提供方的服务接口URL
-
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker); //得到的URL是: //dubbo://192.168.1.100:20880/dubbo.common.hello.service.HelloService? //anyhost=true&application=dubbo-provider&application.version=1.0&dubbo=2.5.3&environment=product& //interface=dubbo.common.hello.service.HelloService&methods=sayHello& //organization=china&owner=cheng.xi&pid=9457&side=provider×tamp=1489807681627
-
-
registry.register(registedProviderUrl)
-
调用远端注册中心的register方法进行服务注册
-
该方法会调用doRegistry,如果是zk注册中心,会默认创建一个临时节点
-
com.alibaba.dubbo.registry.support.FailbackRegistry#register -
-
com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry#doRegister -
protected void doRegister(URL url) { try { //这里zkClient就是我们上面调用构造的时候生成的ZkClientZookeeperClient //开始注册,也就是在Zookeeper中创建节点 //这里toUrlPath获取到的path为: ///dubbo/dubbo.common.hello.service.HelloService/providers/dubbo%3A%2F%2F192.168.1.100%3A20880%2F //dubbo.common.hello.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-provider%26 //application.version%3D1.0%26dubbo%3D2.5.3%26environment%3Dproduct%26interface%3D //dubbo.common.hello.service.HelloService%26methods%3DsayHello%26 //organization%3Dchina%26owner%3Dcheng.xi%26pid%3D8920%26side%3Dprovider%26timestamp%3D1489828029449 //默认创建的节点是临时节点 zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true)); } catch (Throwable e) { } } -
注册成功后,zk上的节点信息
-
/dubbo dubbo.common.hello.service.HelloService providers /dubbo/dubbo.common.hello.service.HelloService/providers/ dubbo%3A%2F%2F192.168.1.100%3A20880%2Fdubbo.common.hello.service.HelloService%3F anyhost%3Dtrue%26application%3Ddubbo-provider%26 application.version%3D1.0%26dubbo%3D2.5.3%26environment%3Dproduct%26 interface%3Ddubbo.common.hello.service.HelloService%26methods%3DsayHello%26 organization%3Dchina%26owner%3Dcheng.xi%26pid%3D13239%26side%3D provider%26timestamp%3D1489829293525 -
-
非Registry类型的export
主要步骤是打开本地端口,并监听。
-
doLocalExport(invoker)
-
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker){ //原始的invoker中的url: //registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService? //application=dubbo-provider&application.version=1.0&dubbo=2.5.3 //&environment=product&export=dubbo%3A%2F%2F10.42.0.1%3A20880%2F //dubbo.common.hello.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-provider%26 //application.version%3D1.0%26dubbo%3D2.5.3%26environment%3Dproduct%26 //interface%3Ddubbo.common.hello.service.HelloService%26methods%3DsayHello%26 //organization%3Dchina%26owner%3Dcheng.xi%26pid%3D7876%26side%3Dprovider%26timestamp%3D1489057305001& //organization=china&owner=cheng.xi&pid=7876®istry=zookeeper×tamp=1489057304900 //从原始的invoker中得到的key: (读取原始url中的export参数值) //dubbo://10.42.0.1:20880/dubbo.common.hello.service.HelloService?anyhost=true&application=dubbo-provider& //application.version=1.0&dubbo=2.5.3&environment=product&interface=dubbo.common.hello.service.HelloService& //methods=sayHello&organization=china&owner=cheng.xi&pid=7876&side=provider×tamp=1489057305001 String key = getCacheKey(originInvoker); ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key); if (exporter == null) { synchronized (bounds) { exporter = (ExporterChangeableWrapper<T>) bounds.get(key); if (exporter == null) { //得到一个Invoker代理,里面包含原来的Invoker final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker)); //此处protocol还是最上面生成的代码,调用代码中的export方法,会根据协议名选择调用具体的实现类 //这里我们需要调用DubboProtocol的export方法 //这里的使用具体协议进行导出的invoker是个代理invoker //导出完之后,返回一个新的ExporterChangeableWrapper实例 exporter = new ExporterChangeableWrapper<T>((Exporter<T>)protocol.export(invokerDelegete), originInvoker); bounds.put(key, exporter); } } } return (ExporterChangeableWrapper<T>) exporter; }
-
-
DubboProtocol的export方法
-
生成的exporter会被放到缓存中,key是由服务名、端口、ip组成。客户端发送请求,根据key找到Exporter,然后找到invoker进行调用。
-
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { //dubbo://10.42.0.1:20880/dubbo.common.hello.service.HelloService? //anyhost=true&application=dubbo-provider& //application.version=1.0&dubbo=2.5.3&environment=product& //interface=dubbo.common.hello.service.HelloService& //methods=sayHello&organization=china&owner=cheng.xi& //pid=7876&side=provider×tamp=1489057305001 URL url = invoker.getUrl(); // export service. //key由serviceName,port,version,group组成 //当nio客户端发起远程调用时,nio服务端通过此key来决定调用哪个Exporter,也就是执行的Invoker。 //dubbo.common.hello.service.HelloService:20880 String key = serviceKey(url); //将Invoker转换成Exporter //直接new一个新实例 //没做啥处理,就是做一些赋值操作 //这里的exporter就包含了invoker DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap); //缓存要暴露的服务,key是上面生成的 exporterMap.put(key, exporter); //export an stub service for dispaching event //是否支持本地存根 //远程服务后,客户端通常只剩下接口,而实现全在服务器端, //但提供方有些时候想在客户端也执行部分逻辑,比如:做ThreadLocal缓存, //提前验证参数,调用失败后伪造容错数据等等,此时就需要在API中带上Stub, //客户端生成Proxy实,会把Proxy通过构造函数传给Stub, //然后把Stub暴露组给用户,Stub可以决定要不要去调Proxy。 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 ){ } else { stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods); } } //根据URL绑定IP与端口,建立NIO框架的Server openServer(url); return exporter; }
-
-
openServer(url)
-
创建NIO服务器,并监听指定端口。(这里涉及到代码也很多,具体看参考资料)
-
private void openServer(URL url) { // find server. //key是IP:PORT //192.168.110.197:20880 String key = url.getAddress(); //client 也可以暴露一个只有server可以调用的服务。 boolean isServer = url.getParameter(Constants.IS_SERVER_KEY,true); if (isServer) { ExchangeServer server = serverMap.get(key); //同一JVM中,同协议的服务,共享同一个Server, //第一个暴露服务的时候创建server, //以后相同协议的服务都使用同一个server if (server == null) { serverMap.put(key, createServer(url)); } else { //同协议的服务后来暴露服务的则使用第一次创建的同一Server //server支持reset,配合override功能使用 //accept、idleTimeout、threads、heartbeat参数的变化会引起Server的属性发生变化 //这时需要重新设置Server server.reset(url); } } }
-
至此,dubbo的服务暴露过程基本过了一遍,里面内容很多很复杂,只能慢慢消化。