赵玉伟的博客

dubbo服务发布源码分析

dubbo作为一款非常优秀rpc框架,提供了非常友好的使用方式,在服务化的过程中,可以非常方便的将我们的接口做成“远程接口”,仿佛像是在用spring。 由于dubbo开源、而且提供了非常全面的功能,当前很多公司的服务化基于dubbo实现。

dubbo提供的功能有序列化选择、协议选择、连接数配置、线程池配置、借助其他服务,比如zookeeper完成服务暴露、服务的路由机制等等,具体可以参考官网 http://dubbo.io/ 提供的资料。而且, dubbo的作者很好的利用了spring的扩展功能,把dubbo框架“插”到spring的扩展点上; 内部实现上划分层次,每一层做专注的事情,从底到高,从单纯的序列化到一个具有容错能力、集群负载、被监控能力等等的服务接口。

官网上对于dubbo的整体设计图打不开,所以,从网上找了一张比较详细的设计图,在该图中除了标识出每层中重要的类之外, 还把调用,继承等关系做了标注

我从做的项目中,摘录出一段基本的配置,如下:

1
2
3
4
5
6
7
8
9
10
<!-- global config -->
<dubbo:application name="${dubbo.application}"/>
<dubbo:registry address="${dubbo.backend.service.registry}" />
<dubbo:protocol name="dubbo" port="${dubbo.backend.service.port}" serialization="hessian2" queues="0" iothreads="10" buffer="8192" accepts="1000" payload="8388608" charset="UTF-8" threadpool="fixed" server="netty" client="netty" threads="20"/>
<!-- poster api -->
<dubbo:service interface="com.doshr.xmen.backend.client.goodposter.api.PosterApi" ref="posterApi"
group="${dubbo.backend.service.group}" timeout="${dubbo.backend.service.timeout}" version="${dubbo.backend.service.version}"/>

也可以这么配置:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- provider -->
<dubbo:application name="qiakr-product-provider" owner="programmer" organization="qiakr" />
<dubbo:registry address="${dubbo.registry.address}"/>
<dubbo:annotation package="com.yiguo.qiakr.product.facade" />
<dubbo:protocol name="dubbo" serialization="kryo" port="${dubbo.protocol.port}" />
</beans>

然后在某个类或者接口上用注解“标注 ”成一个服务

1
@Service(version="1.0", protocol={"dubbo","rest"}, group = "annotationConfig", timeout=2000, connections=20, validation="true")

该注解的值,和在xml中配置的dubbo:service节点的属性起到了相同的作用,只是在加载的时候, 分成了注解和xml两种处理方式,不过最后都是殊途同归。

那么服务的发布过程是怎样的呢? 带着这个问题,阅读了部分dubbo的源码。

关于服务注册的入口, 在META-INF/spring.handlers 中,做了以下配置,

1
http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

也就是名称空间 http\://code.alibabatech.com/schema/dubbo 配置的元素,由DubboNamespaceHandler解析,那么可以从DubboNamespaceHandler作为入口, DubboNamespaceHandler的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/*
* Copyright 1999-2011 Alibaba Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.dubbo.config.spring.schema;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import com.alibaba.dubbo.common.Version;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ConsumerConfig;
import com.alibaba.dubbo.config.ModuleConfig;
import com.alibaba.dubbo.config.MonitorConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.AnnotationBean;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.alibaba.dubbo.config.spring.ServiceBean;
/**
* DubboNamespaceHandler
*
* @author william.liangf
* @export
*/
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
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));
}
}

通过init方法,可以确定每个标签被解析成哪个类, 对应关系如下:

标签
application ApplicationConfig.class
module ModuleConfig.class
registry RegistryConfig.class
monitor MonitorConfig.class
provider ProviderConfig.class
consumer ConsumerConfig.class
protocol ProtocolConfig.class
service ServiceBean.class
reference ReferenceBean.class
annotation AnnotationBean.class

我们重点看service的解析,一个service被解析成了一个ServiceBean对象,所以, 有多少个服务类, 就有多少个ServiceBean。
我们先看一下ServiceBean的类图

可以看出,servicebean实现了很多spring扩展的接口,然后在合适的时候,把需要做的事情(也就是导出成一个服务)插入到spring容器的启动过程中,

以servicebean的方法afterPropertiesSet作为入口,用一张简图,描述如下:

几个备注:
①:[registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?
application=qiakr-product-provider&dubbo=2.8.4&organization=qiakr&owner=programmer&pid=1844&registry=zookeeper&timestamp=1493087966584]

②:annotationConfig/com.yiguo.qiakr.product.service.ProductBrandService:1.0:18610

③:dubbo://192.168.8.121:18610/com.yiguo.qiakr.product.service.ProductBrandService?anyhost=true&application=qiakr-product-provider&
channel.readonly.sent=true&codec=dubbo&connections=1&dubbo=2.8.4&generic=false&group=annotationConfig&heartbeat=60000&
interface=com.yiguo.qiakr.product.service.ProductBrandService&methods=getBrandsBySupplierId,getBrandCntBySupplierId,getBrandNamesByStatus,
createProductBrand,getBrandsByBrandIds,getBrandCntByKeyWord,getBrandsByStoreIdWithSupplyType,getCategoryBrandsWithKeyWord,getBrandsWithKeywordSpecScope,
getBrandsByCategoryFamilyId,getSupplierBrandsByKeyword,updateProductBrand,getAllBrands,getBrandsByBrandId,getBrandBySpecifiedName,getBrandsWithKeyword,
getBrandCntByCatCond&organization=qiakr&owner=programmer&pid=1844&revision=1.0&serialization=kryo&side=provider&timeout=2000&
timestamp=1493088404793&validation=true&version=1.0

④:zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=qiakr-product-provider&dubbo=2.8.4&
interface=com.alibaba.dubbo.registry.RegistryService&organization=qiakr&owner=programmer&pid=1844&timestamp=1493087919111

⑤:/dubbo/com.yiguo.qiakr.product.service.ProductBrandService/providers/dubbo://192.168.8.121:18610/com.yiguo.qiakr.product.service.ProductBrandService?
anyhost=true&application=qiakr-product-provider&connections=1&dubbo=2.8.4&generic=false&group=annotationConfig&
interface=com.yiguo.qiakr.product.service.ProductBrandService&methods=getBrandsBySupplierId,getBrandCntBySupplierId,getBrandNamesByStatus,createProductBrand,
getBrandsByBrandIds,getBrandCntByKeyWord,getBrandsByStoreIdWithSupplyType,getCategoryBrandsWithKeyWord,getBrandsWithKeywordSpecScope,getBrandsByCategoryFamilyId,
getSupplierBrandsByKeyword,updateProductBrand,getAllBrands,getBrandsByBrandId,getBrandBySpecifiedName,getBrandsWithKeyword,
getBrandCntByCatCond&organization=qiakr&owner=programmer&pid=1844&revision=1.0&
serialization=kryo&side=provider&timeout=2000&timestamp=1493088404793&validation=true&version=1.0

根据图中的流程,可以追踪服务发布的过程;有几个重要的点:
1、暴露的服务,在框架层面被包装成了Invoker,也就是说,框架层面看到的不是服务,而是Invoker,
在ServiiceConfig中,有下面的处理

1
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));

proxyFactory的一个实现JavassistProxyFactory,可以看到,返回的是一个AbstractProxyInvoker的一个具体实现, AbstractProxyInvoker是一个抽象类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*
* Copyright 1999-2011 Alibaba Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.dubbo.rpc.proxy.javassist;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.bytecode.Proxy;
import com.alibaba.dubbo.common.bytecode.Wrapper;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.proxy.AbstractProxyFactory;
import com.alibaba.dubbo.rpc.proxy.AbstractProxyInvoker;
import com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler;
/**
* JavaassistRpcProxyFactory
* @author william.liangf
*/
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 {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
}

debug出的参数如下:

com.yiguo.qiakr.product.facade.ProductBrandServiceRemotingImpl@750ed637

interface com.yiguo.qiakr.product.service.ProductBrandService

injvm://127.0.0.1/product/com.yiguo.qiakr.product.service.ProductBrandService?accepts=500&anyhost=true&
application=qiakr-product-provider&connections=1&dubbo=2.8.4&extension=com.yiguo.qiakr.rest.extension.TraceInterceptor,
com.yiguo.qiakr.rest.extension.TraceFilter, com.yiguo.qiakr.rest.extension.ClientTraceFilter, com.yiguo.qiakr.rest.extension.DynamicTraceBinding,
com.yiguo.qiakr.rest.extension.ConsumerExceptionMapper, com.alibaba.dubbo.rpc.protocol.rest.support.LoggingFilter&generic=false&
group=annotationConfig&interface=com.yiguo.qiakr.product.service.ProductBrandService&methods=getBrandsBySupplierId,getBrandCntBySupplierId,
createProductBrand,getBrandNamesByStatus,getBrandsByBrandIds,getBrandCntByKeyWord,getBrandsByStoreIdWithSupplyType,getCategoryBrandsWithKeyWord,
getBrandsWithKeywordSpecScope,getBrandsByCategoryFamilyId,getSupplierBrandsByKeyword,updateProductBrand,getAllBrands,getBrandsByBrandId,
getBrandBySpecifiedName,getBrandsWithKeyword,getBrandCntByCatCond&organization=qiakr&owner=programmer&pid=11132&revision=1.0&server=tomcat&
side=provider&threads=500&timeout=2000&timestamp=1493197942248&validation=true&version=1.0

所以,服务的方法调用,是通过invoker的代理实现的。

2、服务暴露的过程,是通过以下代码实现的

1
Exporter<?> exporter = protocol.export(invoker);

export对invoker做了封装,对export做了映射; 如下:

1
2
3
4
5
6
7
8
9
10
11
12
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) {
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
exporter = new ExporterChangeableWrapper<T>((Exporter<T>)protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}

然后, 将服务的路径发布到zookeeper中。

在以上的处理中,通过下面的代码,监听端口

1
exporter = new ExporterChangeableWrapper<T>((Exporter<T>)protocol.export(invokerDelegete), originInvoker);

可以读下DubboProtocol的export方法,最终通讯交由netty处理。