概述
该了解dubbo哪些基本知识?
首先,根据官网介绍,Apache Dubbo™ 是一款高性能Java RPC框架,所谓RPC框架,就是指一台服务器可以像调用本地对象一样调用另一台服务器上对应的方法。这就是RPC,而dubbo只是其中的一种。像RMI,gRPC(Google),Motan都属于RPC框架。
官方背景介绍:http://dubbo.apache.org/zh-cn/docs/user/preface/background.html
Spring集成
一般通过Spring集成的框架,都要看看它的初始化步骤,dubbo也不例外,通过Spring套路可以很好地学习dubbo的原理;
首先,要搞清楚一些基本认知:
- dubbo的配置在spring中都有被管理,就是说dubbo的那些<dubbo:service/>,<dubbo:application/>,<dubbo:protocol/>,<dubbo:consumer/>等等,都是Spring管理的bean;
- dubbo的初始化也遵循Spring的套路,无非就是通过实现初始化接口,后置处理器接口,监听器接口等实现;
- dubbo的基本操作:服务导出,服务注册(创建注册中心),服务发现,服务推送等等。
dubbo bean的注册
首先来看第一点,既然是Bean,那么就一定有对应的BeanClass,dubbo的配置里面又没有给出具体的BeanClass,那Spring是怎么给这些BeanDefinition赋Class的呢?
如果跟踪Spring源码,会发现在这一步完成后BeanClass就已经赋值了。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
所以肯定是obtainFreshBeanFactory方法里面进行的赋值;
详细Spring步骤就不贴代码,最终是走到了XmlBeanDefinitionReader里面,贴个调用链:
org.springframework.beans.factory.xml.XmlBeanDefinitionReader
private NamespaceHandlerResolver namespaceHandlerResolver;
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 大家看这个类是纯Spring的类,但是dubbo:service最终的BeanClass一定是dubbo的Class,那怎么会赋值dubbo的Class呢?
// 猜想这里肯定是通过命名空间Handler来获取BeanClass的。
// 这里要仔仔细细的看,先看这个createReaderContext方法
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
public XmlReaderContext createReaderContext(Resource resource) {
// 这里是重点,因为这个getNamespaceHandlerResolver方法,很可能就跟dubbo产生关系了
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
// 跟代码debug的话,到这里就会得到dubboNamespaceHandler
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
// 这里根据ClassLoader来确定
return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}
org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
META-INF/spring.handlers
http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
META-INF/dubbo.xsd
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://code.alibabatech.com/schema/dubbo"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
targetNamespace="http://code.alibabatech.com/schema/dubbo">
<!-- dubbo:service 结点 -->
<xsd:element name="service" type="serviceType">
<xsd:annotation>
<xsd:documentation><![CDATA[ Export service config ]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:schema>
至于如何取dubbo下面的spring.handler文件,更详细的可以参考这篇文章:https://www.jianshu.com/p/91f6068adff2
继续看注册代码
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
// 套路命名doXXXXX
doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate; // 首先拿到一个委托解析类的对象用于文件解析
this.delegate = createDelegate(getReaderContext(), root, parent);
// 判断释放解析过了,如果已经解析过了,就返回不解析,acceptsProfiles这个方法控制
// 判断是否是root结点:beans,只有beans结点才解析
// 所以这就是dubbo的配置文件根节点是beans
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
// 预处理,就是可以扩展原有的结点,但需要自己实现,这里Spring给的是空实现,所以不关心
preProcessXml(root);
// 实际解析在这一步
parseBeanDefinitions(root, this.delegate);
// 对应preProcessXml
postProcessXml(root);
this.delegate = parent;
}
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 解析配置xml,逐一处理结点,从root:beans开始
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
// 这里面获得的handler就是DubboNamespaceHandler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 返回BeanDefinition
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
// 这一步返回真正的NamespaceHandler
public NamespaceHandler resolve(String namespaceUri) {
// 解析spring.handlers获取映射关系
Map<String, Object> handlerMappings = getHandlerMappings();
// 这里的namespaceUri = http://code.alibabatech.com/schema/dubbo
// 对应value = com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
String className = (String) handlerOrClassName;
try {
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "] not found", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "]: problem with handler class file or dependent class", err);
}
}
}
先来看看 com.alibaba.dubbo.config.spring.schema. DubboNamespaceHandler
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
// 这个handler的作用就是给它的不同的结点定义不同的BeanClass
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 DubboBeanDefinitionParser(AnnotationBean.class, true));
}
}
可以看到DubboNamespaceHandler的继承关系,父类中的Map:parsers用于结点BeanClass的注册,注册的方式是通过<K,V>的形式,注册的内容是一个BeanDefinitionParser,所以到这里我们又看到一种BeanClass的注册方式,正常Bean可以通过xml配置,可以通过直接扫描目录,也可以像现在这样,通过一个BeanDefinitionParser来实现;
DubboNamespaceHandler类没有重写parse方法,所以它用的是父类的方法,如下:
org.springframework.beans.factory.xml.NamespaceHandlerSupport
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 先找到BeanDefinitionParser,再调用 ***** parse ***** 方法得到BeanDefinition
return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
// dubbo通过初始化init方法已经把相应的<key:节点名称, Value:对应的parser>注册进parsers了
// 这里只需要根据Bean的名称得到对应的parser
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
com.alibaba.dubbo.config.spring.schema.DubboBeanDefinitionParser
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
// 看到了吧!!!!终于找到setBeanClass了!!!!!
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
String id = element.getAttribute("id");
// 后面省略n行代码
}
综上,总结了dubbo各个结点的注册逻辑。下面进入服务导出和注册逻辑
dubbo服务导出和注册
其实这部分代码,在官网上面有详细的介绍,我也不打算贴的很详细了,但是几个概念要先明确;
服务导出,服务注册,服务订阅等
首先,服务注册操作对于 Dubbo 来说不是必需的,通过服务直连的方式就可以绕过注册中心。但通常我们不会这么做,直连方式不利于服务治理,仅推荐在测试服务时使用。对于 Dubbo 来说,注册中心虽不是必需,但却是必要的。——官网原话
1、当没有注册中心的时候,只需要服务导出就可以了,它分为本地服务导出和远程服务导出,又叫本地暴露和远程暴露。
- 本地暴露就是暴露在当前JVM下,处理类是InjvmProtocal,简单理解就是把服务封装好放到某个类的集合或者Map中去;
- 远程暴露就简单说就是起一个Server Socket(dubbo协议实际是用Netty,http协议使用HttpServer,不是所有协议都支持远程暴露,像redis就不支持,doExport直接抛异常),等待远端客户端连接,根据协议选用不同Protocal,比如:dubbo协议的处理类是DubboProtocal;
但不管是本地暴露还是单纯的远程暴露,都没有服务治理功能,因此dubbo又引入的注册中心,默认使用ZK作为注册中心;在dubbo源码中,远程暴露分为有注册中心和无注册中心两种;
为什么会有本地暴露和远程暴露呢?
在dubbo中,一个服务可能既是Provider又是Consumer,因此就存在自己调用自己服务的情况,如果这种场景下再通过网络去访问,那自然是舍近求远,因此就有了本地暴露这个设计。
- 本地暴露是暴露在本机JVM中,调用本地服务不需要网络通信,本地暴露的url是以injvm开头的;
- 远程暴露是将ip,端口等信息暴露给远程客户端,使用Netty作为服务端,调用远程服务时需要网络通信;
2、当有注册中心的时候,除了服务导出还要服务注册,服务注册其实是在/root/interface/providers下创建一个临时节点,这个节点的路径就是服务的url。而取消注册就是将该节点删除。服务注册必然还要创建注册中心,注册中心可以有很多,下面以ZK的代码介绍;
如图,以dubbo协议为例,如果有注册中心,远程暴露先走封装类RegistryProtocol,先服务导出(服务导出就起Netty服务了),后服务注册;如果是没有注册中心的话只起Netty服务。
先看下ServiceBean的继承关系
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware {
private static final long serialVersionUID = 213195494150089726L;
private static transient ApplicationContext SPRING_CONTEXT;
private transient ApplicationContext applicationContext;
private transient String beanName;
}
这个BeanClass继承了几个关键的接口:
- InitializingBean:初始化接口,实现方法:afterPropertiesSet
- ApplicationListener:监听器接口,实现方法:onApplicationEvent
- DisposableBean:销毁接口,实现方法:destroy
public void afterPropertiesSet() throws Exception {
if (getProvider() == null) {
..............
//获取provider配置
}
if (getApplication() == null && (getProvider() == null || getProvider().getApplication() == null)) {
...............
//获取application配置
}
if (getModule() == null && (getProvider() == null || getProvider().getModule() == null)) {
...............
//获取module配置
}
if ((getRegistries() == null || getRegistries().size() == 0)
&& (getProvider() == null || getProvider().getRegistries() == null || getProvider().getRegistries().size() == 0)
&& (getApplication() == null || getApplication().getRegistries() == null || getApplication().getRegistries().size() == 0)) {
.................
//获取注册中心的配置
}
if (getMonitor() == null
&& (getProvider() == null || getProvider().getMonitor() == null)
&& (getApplication() == null || getApplication().getMonitor() == null)) {
................
//获取monitor配置
}
if ((getProtocols() == null || getProtocols().size() == 0)
&& (getProvider() == null || getProvider().getProtocols() == null || getProvider().getProtocols().size() == 0)) {
...............
//获取protocol配置
}
if (getPath() == null || getPath().length() == 0) { //获取<dubbo:service/>的path属性,path即服务的发布路径
if (beanName != null && beanName.length() > 0
&& getInterface() != null && getInterface().length() > 0
&& beanName.startsWith(getInterface())) {
setPath(beanName); //如果没有设置path属性,则默认会以beanName作为path
}
}
// delay:dubbo属性配置,延迟注册服务时间(毫秒) ,默认值0,设为-1时,表示延迟到Spring容器初始化完成时暴露服务
// 需要注意的是这里说的默认0是指需要明确加上delay属性的,如果不加delay属性,那就是null,null相当于-1,即:延迟
// 如果设置service是延迟的,那么这里就不加载了
// isDelay方法返回 true 时,表示无需延迟导出。返回 false 时,表示需要延迟导出
if (! isDelay()) {
export(); //进行服务导出
}
}
public void onApplicationEvent(ApplicationEvent event) {
if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) {
// 延迟的 && 未发布的 && 未被取消导出
// isDelay方法返回 true 时,表示无需延迟导出。返回 false 时,表示需要延迟导出。
if (isDelay() && ! isExported() && ! isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
// 服务发布
export();
}
}
}
public void destroy() throws Exception {
unexport();
}
重点看服务导出方法,export是父类的方法
com.alibaba.dubbo.config.ServiceConfig
public synchronized void export() {
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
if (export != null && ! export.booleanValue()) {
return;
}
if (delay != null && delay > 0) {
// 延迟加载,为啥这么处理,我觉得应该跟服务依赖有关吧
// delay > 0,延时导出服务
Thread thread = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(delay);
} catch (Throwable e) {
}
// 起一个守护线程进行服务发布
doExport();
}
});
thread.setDaemon(true);
thread.setName("DelayExportServiceThread");
thread.start();
} else {
// 非延迟加载的情况,立即导出服务
doExport();
}
}
protected synchronized void doExport() {
// 。。。。。。。。。。。。。。。。。。。。。。。。。。。
doExportUrls();
}
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
private void exportLocal(URL url) {
// 官网注释:如果 URL 的协议头等于 injvm,说明已经导出到本地了,无需再次导出
if (!"injvm".equalsIgnoreCase(url.getProtocol())) {
// 设置协议头injvm
URL local = URL.valueOf(url.toFullString()).setProtocol("injvm").setHost("127.0.0.1").setPort(0);
// push service
ServiceImplHolder.getInstance().pushServiceImpl(this.ref);
// 创建 Invoker,并导出服务,这里的 protocol 会在运行时调用 InjvmProtocol 的 export 方法
Exporter<?> exporter = protocol.export(proxyFactory.getInvoker(this.ref, this.interfaceClass, local));
this.exporters.add(exporter);
logger.info("Export dubbo service " + this.interfaceClass.getName() + " to local registry");
}
}
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// 省略无关代码
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
// 加载 ConfiguratorFactory,并生成 Configurator 实例,然后通过实例配置 url
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
String scope = url.getParameter(Constants.SCOPE_KEY);
// 如果 scope = none,则什么都不做
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
// scope != remote,导出到本地
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
exportLocal(url);
}
// scope != local,导出到远程
if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
if (registryURLs != null && !registryURLs.isEmpty()) {
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
// 加载监视器链接
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
// 将监视器链接作为参数添加到 url 中
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
String proxy = url.getParameter(Constants.PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
}
// 为服务提供类(ref)生成 Invoker
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
// DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
// 导出服务,并生成 Exporter
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
// 不存在注册中心,仅导出服务
} else {
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
}
}
this.urls.add(url);
}
服务注册,首先要创建注册中心,下图是注册中心的获取流程
zookeeper客户端通过SPI 提供
@SPI("zkclient")
public interface ZookeeperTransporter {
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
ZookeeperClient connect(URL url);
}
zkclient=com.alibaba.dubbo.remoting.zookeeper.zkclient.ZkclientZookeeperTransporter
curator=com.alibaba.dubbo.remoting.zookeeper.curator.CuratorZookeeperTransporter