服务注册
服务暴露入口
-
Spring应用启动的时候,在
ServiceAnnotationBeanPostProcessor#postProcessBeanDefinitionRegistry方法中会为每一个带有@service注解的类创建一个BeanDefinition,beanName为ServiceBean:实现类全类名这种格式; 除此之后,ServiceAnnotationBeanPostProcessor#postProcessBeanDefinitionRegistry方法中还会为所有带有@service注解的原始的类,如:org.apache.dubbo.demo.provider.DemoServiceImpl生成BeanDefinition信息(DubboClassPathBeanDefinitionScanner.scan); 即:每个带有@service注解的类在Spring容器中会对应两个Bean,ServiceBean是实现provider的关键; -
ServiceBean实现了InitializingBean接口,在ServiceBean#afterPropertiesSet注册provider; -
ServiceBean#afterPropertiesSet方法调用了ServiceConfig#export,ServiceConfig#export是实provider注册的关键; -
ServiceConfig#export=>启动配置中心;ServiceConfig#doExport=>ServiceConfig#doExportUrls=>ServiceConfig#doExportUrlsFor1Protocol=>创建provider对应的Exporter对象;publish服务url到元数据中心;
代理对象创建
默认通过javassist创建代理服务,ProxyFactory对应的JavassistProxyFactory;
- 在
ServiceConfig#doExportUrlsFor1Protocol方法中,首次涉及到动态代理的创建时这行代码,貌似是为例获取到该接口的方法,不太清楚这里为什么要创建一个动态代理对象:
// 返回 ["sayHello"]
String[] methods = Wrapper.getWrapper(org.apache.dubbo.demo.DemoService.class).getMethodNames();
//通过 javassist 动态生成的代码如下
package org.apache.dubbo.common.bytecode;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import org.apache.dubbo.common.bytecode.ClassGenerator.DC;
import org.apache.dubbo.demo.DemoService;
public class Wrapper0 extends Wrapper implements DC {
public static String[] pns;
public static Map pts;
public static String[] mns;
public static String[] dmns;
public static Class[] mts0;
public String[] getPropertyNames() {
return pns;
}
public boolean hasProperty(String var1) {
return pts.containsKey(var1);
}
public Class getPropertyType(String var1) {
return (Class)pts.get(var1);
}
public String[] getMethodNames() {
return mns;
}
public String[] getDeclaredMethodNames() {
return dmns;
}
public void setPropertyValue(Object var1, String var2, Object var3) {
try {
DemoService var4 = (DemoService)var1;
} catch (Throwable var6) {
throw new IllegalArgumentException(var6);
}
throw new NoSuchPropertyException("Not found property \"" + var2 + "\" field or setter method in class org.apache.dubbo.demo.DemoService.");
}
public Object getPropertyValue(Object var1, String var2) {
try {
DemoService var3 = (DemoService)var1;
} catch (Throwable var5) {
throw new IllegalArgumentException(var5);
}
throw new NoSuchPropertyException("Not found property \"" + var2 + "\" field or setter method in class org.apache.dubbo.demo.DemoService.");
}
public Object invokeMethod(Object var1, String var2, Class[] var3, Object[] var4) throws InvocationTargetException {
DemoService var5;
try {
var5 = (DemoService)var1;
} catch (Throwable var8) {
throw new IllegalArgumentException(var8);
}
try {
if ("sayHello".equals(var2) && var3.length == 1) {
return var5.sayHello((String)var4[0]);
}
} catch (Throwable var9) {
throw new InvocationTargetException(var9);
}
throw new NoSuchMethodException("Not found method \"" + var2 + "\" in class org.apache.dubbo.demo.DemoService.");
}
public Wrapper0() {
}
}
- 在接下来的
本地服务暴露或者远程服务暴露过程中,会根据实现类org.apache.dubbo.demo.DemoServiceImpl再创建一个代理对象,还是在ServiceConfig#doExportUrlsFor1Protocol方法中,主要就是通过SPI拿到JavassistProxyFactory,然后再执行JavassistProxyFactory#getInvoker方法,先创建动态代理对象,最终返回一个AbstractProxyInvoker。而根据下面的代码可以看出,调用Invoker.doInvoke方法的时候,最终还是调用了代理对象的invokeMethod方法
proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
// JavassistProxyFactory#getInvoker
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// 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 {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
有关于javassist创建代理对象的过程就不展开了,最后生成的代理对象如下:
package org.apache.dubbo.common.bytecode;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import org.apache.dubbo.common.bytecode.ClassGenerator.DC;
import org.apache.dubbo.demo.provider.DemoServiceImpl;
public class Wrapper1 extends Wrapper implements DC {
public static String[] pns;
public static Map pts;
public static String[] mns;
public static String[] dmns;
public static Class[] mts0;
public String[] getPropertyNames() {
return pns;
}
public boolean hasProperty(String var1) {
return pts.containsKey(var1);
}
public Class getPropertyType(String var1) {
return (Class)pts.get(var1);
}
public String[] getMethodNames() {
return mns;
}
public String[] getDeclaredMethodNames() {
return dmns;
}
public void setPropertyValue(Object var1, String var2, Object var3) {
try {
DemoServiceImpl var4 = (DemoServiceImpl)var1;
} catch (Throwable var6) {
throw new IllegalArgumentException(var6);
}
throw new NoSuchPropertyException("Not found property \"" + var2 + "\" field or setter method in class org.apache.dubbo.demo.provider.DemoServiceImpl.");
}
public Object getPropertyValue(Object var1, String var2) {
try {
DemoServiceImpl var3 = (DemoServiceImpl)var1;
} catch (Throwable var5) {
throw new IllegalArgumentException(var5);
}
throw new NoSuchPropertyException("Not found property \"" + var2 + "\" field or setter method in class org.apache.dubbo.demo.provider.DemoServiceImpl.");
}
public Object invokeMethod(Object var1, String var2, Class[] var3, Object[] var4) throws InvocationTargetException {
DemoServiceImpl var5;
try {
var5 = (DemoServiceImpl)var1;
} catch (Throwable var8) {
throw new IllegalArgumentException(var8);
}
try {
if ("sayHello".equals(var2) && var3.length == 1) {
return var5.sayHello((String)var4[0]);
}
} catch (Throwable var9) {
throw new InvocationTargetException(var9);
}
throw new NoSuchMethodException("Not found method \"" + var2 + "\" in class org.apache.dubbo.demo.provider.DemoServiceImpl.");
}
public Wrapper1() {
}
}
本地服务暴露
有关于暴露本地服务还是远程服务,是通过scope属性进行控制
scope=none:既不暴露本地服务,也不暴露远程服务;scope=remote:只暴露远程服务;scope=local:只暴露本地服务;scope未配置:即暴露本地服务,又暴露远程服务;
默认情况下,scope是没有配置的,所以会先暴露本地服务,然后暴露远程服务,同样是在ServiceConfig#doExportUrlsFor1Protocol方法中。暴露本地服务调用的是exportLocal方法:
// ServiceConfig#exportLocal
private void exportLocal(URL url) {
URL local = URLBuilder.from(url)
.setProtocol(LOCAL_PROTOCOL)
.setHost(LOCALHOST_VALUE)
.setPort(0)
.build();
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
}
通过上一小节的代理服务创建已经知道,proxyFactory#getInvoker方法将返回一个AbstractProxyInvoker对象,接下来将以这个AbstractProxyInvoker对象创建一个Exporter对象;
根据SPI机制可以知道,此时的protocol对应的是InjvmProtocol,所以暴露本地服务的时候,将执行InjvmProtocol#export方法。该方法比较简单,没有涉及到注册服务到注册中心以及打开服务端口等复杂操作,仅仅是返回一个InjvmExporter对象,在服务引用的时候会用到。
// InjvmProtocol#export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}
远程服务暴露
同样是在ServiceConfig#doExportUrlsFor1Protocol方法中,以下这行代码将完成远程服务暴露操作:
Exporter<?> exporter = protocol.export(wrapperInvoker);
根据SPI机制,此处protocol对应的是RegistryProtocol,所以这里将执行RegistryProtocol#export方法
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
URL registryUrl = getRegistryUrl(originInvoker);
// url to export locally
URL providerUrl = getProviderUrl(originInvoker);
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
//暴露服务 通过netty开一个端口
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// url to registry
final Registry registry = getRegistry(originInvoker);
final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
registryUrl, registeredProviderUrl);
//to judge if we need to delay publish
boolean register = registeredProviderUrl.getParameter("register", true);
if (register) {
// 注册服务
register(registryUrl, registeredProviderUrl);
providerInvokerWrapper.setReg(true);
}
// 订阅override节点 /dubbo/service/configurations
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<>(exporter);
}
所谓的服务注册,核心就是Exporter对象的生成过程,而在生成Exporter对象的过程中,还涉及到动态配置、路由规则的监听工作和服务端暴露工作。
动态配置监听
在dubbo2.7之前,这些配置都是放在/dubbo/service/routers和/dubbo/service/configurations节点下面,从dubbo2.7开始,可以将这些信息放在/dubbo/config/service/xxx下面。
那么在创建Exporter对象过程中,有关于节点监听这部分工作是怎么样的流程?
创建Exporter对象主要在RegistryProtocol#export方法中进行的;
-
通过
RegistryProtocol#register注册provider的节点,以:org.apache.dubbo.demo.provider.DemoService服务为例,这个方法就是在zookeeper生成/dubbo/org.apache.dubbo.demo.provider.DemoService节点; -
以provider的URL信息创建
OverrideListener; -
以
OverrideListener为入参创建ChildListener; -
以
ChildListener为入参创建CuratorWatcherImpl,CuratorWatcherImpl继承了CuratorWatcher; -
为
CuratorFramework添加监听器CuratorWatcherImpl,CuratorZookeeperClient持有CuratorFramework实例。如果节点发生变更,执行ChildListener逻辑,即执行ZookeeperRegistry#notify方法,如果有必要重新导出Exporter; -
执行
notify方法;如果此时/dubb/service/configurations下面有数据,拿到这些数据,封装成Configurator,根据这些Configurator,重新生成服务端的URL,然后重新生成服务端对应的Exporter; -
如果
/dubb/service/configurations节点的内容发生变更,它的触发流程是这样的:CuratorWatcherImpl#process=>ChildListener#childChanged=>ZookeeperRegistry#notify=>ZookeeperRegistry#doNotify=>OverrideListener#notify=>OverrideListener#doOverrideIfNecessary;
服务端暴露
这部分工作在RegistryProtocol#doLocalExport方法中
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
String key = getCacheKey(originInvoker);
return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
});
}
此处的protocol是通过SPI的依赖注入功能生成的,默认情况下对应DubboProtocol,所以这里将执行DubboProtocol#export方法。这部分涉及到的代码非常多,目前并不是太懂,就以时序图的方式介绍吧:
DubboProtocol#export => DubboProtocol#openServer => DubboProtocol#createServer => Exchangers#bind => Transporters#bind => new NettyServer => NettyServer#doOpen
Exchangers#bind方法用于返回一个ExchangeServer对象,SPI机制默认会使用HeaderExchanger,所以默认执行HeaderExchanger#bind;从以下代码中可以发现,默认返回一个HeaderExchangeServer
@Override
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
Transporters#bind方法用于返回一个org.apache.dubbo.remoting.Server对象,SPI机制默认会使用NettyTransporter,所以默认执行NettyTransporter#bind;从以下代码中可以发现,默认返回一个NettyServer
@Override
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
return new NettyServer(url, listener);
}
- 在创建
NettyServer的时候会调用NettyServer#doOpen方法开启一个netty服务端;
元数据中心
元数据中心从dubbo2.7开始支持
ServiceConfig#doExportUrlsFor1Protocol方法中,export服务之后,如果设置了元数据中心地址,就执行MetadataReportService#publishProvider方法,该方法将provider相关的元信息写入元数据中心;ReferenceConfig#createProxy方法中,在为consumer创建代理服务之前,如果设置了元数据中心地址,就执行MetadataReportService#publishConsumer方法,该方法将consumer相关的元信息写入元数据中心;
配置中心
配置中心从dubbo2.7开始支持
-
ServiceConfig#checkAndUpdateSubConfigs方法中,startConfigCenter方法会判断是否需要启动配置中心,如果设置了配置中心地址,就会启动配置中心; -
ReferenceConfig#checkAndUpdateSubConfigs方法中,startConfigCenter方法会判断是否需要启动配置中心,如果设置了配置中心地址,就会启动配置中心; -
以zookeeper配置中心为例,在
startConfigCenter方法中创建ZookeeperDynamicConfiguration的时候,会创建CuratorZookeeperClient,然后通过AbstractZookeeperClient#addDataListener为CuratorZookeeperClient添加DataListener; -
新的配置中心节点监听是通过
TreeCacheListener#childEvent进行监听的,对应DataListener,TreeCache通过ZookeeperDynamicConfiguration中定义的Executor执行监听逻辑; 老的/dubbo/service/configurations是通过CuratorWatcher#process进行监听的,对应ChildListener; 一个application中,只需要创建一个'ZookeeperDynamicConfiguration'即可,然后会为每个服务创建对应ConfigurationListener; -
新老配置中心触发监听逻辑的流程如下:
老:
CuratorWatcherImpl#process=>ChildListener#childChanged=>ZookeeperRegistry#notify=>ZookeeperRegistry#doNotify=>OverrideListener#notify=>OverrideListener#doOverrideIfNecessary;新:
CuratorWatcherImpl#childEvent=>DataListener#dataChanged=>ConfigurationListener#process=>AbstractConfiguratorListener#process;AbstractConfiguratorListener#genConfiguratorsFromRawRule;=>OverrideListener#doOverrideIfNecessary; -
OverrideListener#doOverrideIfNecessary方法会根据配置中心的配置信息重新为Provider生成Exporter对象;那么这和配置中心有什么关系?AbstractConfiguratorListener#genConfiguratorsFromRawRule方法会根据监听到的数据动动态刷新AbstractConfiguratorListener中的configurators,在OverrideListener#doOverrideIfNecessary方法中会获取AbstractConfiguratorListener中的configurators的值,然后Merge出一个新的URL,然后用这个新的URL和老的URL对比,如果不一致,就会为Provider重新生成一个Exporter对象;