- 阿里分布式服務框架 dubbo現在已成為了外面很多中小型甚至一些大型互聯網公司作為服務治理的一個首選或者考慮方案,相信大家在日常工作中或多或少都已經用過或者接觸過dubbo了。但是我搜了一下網上關於dubbo框架原理方面的解析還是比較少,大多數都是介紹一個大概的框架結構和一些配置的用法。一些低層原理的介紹還是比較少。於是我去github拉了dubbo的源碼下來嘗試整理出來了一些相關的內容,希望對想了解dubbo相關開發發員有所幫助。今天就給大家介紹一下dubbo是怎么實現服務注冊的
dubbo的一些簡單介紹:
dubbo的官方首頁:http://dubbo.io/
源碼地址:https://github.com/alibaba/dubbo
架構

|
|
角色說明 |
| Provider |
暴露服務的服務提供方 |
| Consumer |
調用遠程服務的服務消費方 |
| Registry |
服務注冊與發現的注冊中心 |
| Monitor |
統計服務的調用次調和調用時間的監控中心 |
| Container |
服務運行容器 |
調用關系說明
-
服務容器負責啟動,加載,運行服務提供者。
-
服務提供者在啟動時,向注冊中心注冊自己提供的服務。
-
服務消費者在啟動時,向注冊中心訂閱自己所需的服務。
-
注冊中心返回服務提供者地址列表給消費者,如果有變更,注冊中心將基於長連接推送
變更數據給消費者。
-
服務消費者,從提供者地址列表中,基於軟負載均衡算法,選一台提供者進行調用,如
果調用失敗,再選另一台調用。
-
服務消費者和提供者,在內存中累計調用次數和調用時間,定時每分鍾發送一次統計數
據到監控中心。
上面都是一些從官網摘抄下來的一些簡介,更具體的信息大家可以去瀏覽官網。
下面就進入正題。主要分兩部分:第一spring如何加載生成dubbo對象,第二dubbo服務如何注冊到注冊中心
1、spring如何加載生成dubbo對象
下面是一段provider的xml配置
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" 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"> <!-- 提供方應用信息,用於計算依賴關系 --> <dubbo:application name="demo-provider"/> <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181" /> <!-- 用dubbo協議在20880端口暴露服務 --> <dubbo:protocol name="dubbo" port="20880"/> <!-- 和本地bean一樣實現服務 --> <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/> <!-- 聲明需要暴露的服務接口 --> <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/> </beans>
在很多情況下,我們需要為系統提供可配置化支持,簡單的做法可以直接基於Spring的標准Bean來配置,但配置較為復雜或者需要更多豐富控制的時候,會顯得非常笨拙。一般的做法會用原生態的方式去解析定義好的xml文件,然后轉化為配置對象,這種方式當然可以解決所有問題,但實現起來比較繁瑣,特別是是在配置非常復雜的時候,解析工作是一個不得不考慮的負擔。Spring提供了可擴展Schema的支持,這是一個不錯的折中方案,完成一個自定義配置一般需要以下步驟:
- 設計配置屬性和JavaBean
- 編寫XSD文件
- 編寫NamespaceHandler和BeanDefinitionParser完成解析工作
- 編寫spring.handlers和spring.schemas串聯起所有部件
- 在Bean文件中應用
同樣的dubbo為了解決這個問題,起了一個叫dubbo-config-spring的模塊。這個模塊的下面的resources/META-INF文件下面有三個這樣的文件 dubbo.xsd 、spring.handlers、spring.schemas 如下圖

所以我們知道dubbo是利用了Spring提供的可擴展Schema機制實現了dubbo的xml配置文件解析。
我在網上找了一個相關的的例子:http://blog.csdn.net/cutesource/article/details/5864562。
解決了spring解析dubbo配置xml的問題,下面就看spring怎么生成dubbo對象的。 看一下解析xml的DubboNamespaceHandler類
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));
}
}
從這里知道所有的dubbo的標簽,都是統一由DubboBeanDefinitionParser來解析的,每一個標簽都會統一解析成對應的Bean對象。dubbo定義了以下Bean
com.alibaba.dubbo.config.ApplicationConfig com.alibaba.dubbo.config.ConsumerConfig com.alibaba.dubbo.config.MethodConfig com.alibaba.dubbo.config.ModuleConfig com.alibaba.dubbo.config.MonitorConfig com.alibaba.dubbo.config.ProtocolConfig com.alibaba.dubbo.config.ProviderConfig com.alibaba.dubbo.config.ReferenceConfig com.alibaba.dubbo.config.RegistryConfig com.alibaba.dubbo.config.spring.ServiceBean
而<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>對應的Bean就是 ServiceBean !!!!!!!!
// 接口類型 private String interfaceName; private Class<?> interfaceClass; // 接口實現類引用 private T ref; // 服務名稱 private String path; // 方法配置
以上是ServiceBean里面的一些屬性截圖, 所以知道為什么要ref=demoService了吧。
解決了spring加載生成dubbo對象的問題。接下來進入第二部分:服務注冊,看下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 final transient Service service;
private transient ApplicationContext applicationContext;
private transient String beanName;
private transient boolean supportedApplicationListener;
public ServiceBean() {
super();
this.service = null;
}
public ServiceBean(Service service) {
super(service);
this.service = service;
}
public static ApplicationContext getSpringContext() {
return SPRING_CONTEXT;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
SpringExtensionFactory.addApplicationContext(applicationContext);
if (applicationContext != null) {
SPRING_CONTEXT = applicationContext;
try {
Method method = applicationContext.getClass().getMethod("addApplicationListener", new Class<?>[]{ApplicationListener.class}); // 兼容Spring2.0.1
method.invoke(applicationContext, new Object[]{this});
supportedApplicationListener = true;
} catch (Throwable t) {
if (applicationContext instanceof AbstractApplicationContext) {
try {
Method method = AbstractApplicationContext.class.getDeclaredMethod("addListener", new Class<?>[]{ApplicationListener.class}); // 兼容Spring2.0.1
if (!method.isAccessible()) {
method.setAccessible(true);
}
method.invoke(applicationContext, new Object[]{this});
supportedApplicationListener = true;
} catch (Throwable t2) {
}
}
}
}
}
public void setBeanName(String name) {
this.beanName = name;
}
/**
* Gets associated {@link Service}
*
* @return associated {@link Service}
*/
public Service getService() {
return service;
}
public void onApplicationEvent(ApplicationEvent event) {
if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) {
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
}
private boolean isDelay() {
Integer delay = getDelay();
ProviderConfig provider = getProvider();
if (delay == null && provider != null) {
delay = provider.getDelay();
}
return supportedApplicationListener && (delay == null || delay == -1);
}
@SuppressWarnings({"unchecked", "deprecation"})
public void afterPropertiesSet() throws Exception {
if (getProvider() == null) {
Map<String, ProviderConfig> providerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class, false, false);
if (providerConfigMap != null && providerConfigMap.size() > 0) {
Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
if ((protocolConfigMap == null || protocolConfigMap.size() == 0)
&& providerConfigMap.size() > 1) { // 兼容舊版本
List<ProviderConfig> providerConfigs = new ArrayList<ProviderConfig>();
for (ProviderConfig config : providerConfigMap.values()) {
if (config.isDefault() != null && config.isDefault().booleanValue()) {
providerConfigs.add(config);
}
}
if (providerConfigs.size() > 0) {
setProviders(providerConfigs);
}
} else {
ProviderConfig providerConfig = null;
for (ProviderConfig config : providerConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (providerConfig != null) {
throw new IllegalStateException("Duplicate provider configs: " + providerConfig + " and " + config);
}
providerConfig = config;
}
}
if (providerConfig != null) {
setProvider(providerConfig);
}
}
}
}
if (getApplication() == null
&& (getProvider() == null || getProvider().getApplication() == null)) {
Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
ApplicationConfig applicationConfig = null;
for (ApplicationConfig config : applicationConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (applicationConfig != null) {
throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
}
applicationConfig = config;
}
}
if (applicationConfig != null) {
setApplication(applicationConfig);
}
}
}
if (getModule() == null
&& (getProvider() == null || getProvider().getModule() == null)) {
Map<String, ModuleConfig> moduleConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ModuleConfig.class, false, false);
if (moduleConfigMap != null && moduleConfigMap.size() > 0) {
ModuleConfig moduleConfig = null;
for (ModuleConfig config : moduleConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (moduleConfig != null) {
throw new IllegalStateException("Duplicate module configs: " + moduleConfig + " and " + config);
}
moduleConfig = config;
}
}
if (moduleConfig != null) {
setModule(moduleConfig);
}
}
}
if ((getRegistries() == null || getRegistries().size() == 0)
&& (getProvider() == null || getProvider().getRegistries() == null || getProvider().getRegistries().size() == 0)
&& (getApplication() == null || getApplication().getRegistries() == null || getApplication().getRegistries().size() == 0)) {
Map<String, RegistryConfig> registryConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class, false, false);
if (registryConfigMap != null && registryConfigMap.size() > 0) {
List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
for (RegistryConfig config : registryConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
registryConfigs.add(config);
}
}
if (registryConfigs != null && registryConfigs.size() > 0) {
super.setRegistries(registryConfigs);
}
}
}
if (getMonitor() == null
&& (getProvider() == null || getProvider().getMonitor() == null)
&& (getApplication() == null || getApplication().getMonitor() == null)) {
Map<String, MonitorConfig> monitorConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MonitorConfig.class, false, false);
if (monitorConfigMap != null && monitorConfigMap.size() > 0) {
MonitorConfig monitorConfig = null;
for (MonitorConfig config : monitorConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (monitorConfig != null) {
throw new IllegalStateException("Duplicate monitor configs: " + monitorConfig + " and " + config);
}
monitorConfig = config;
}
}
if (monitorConfig != null) {
setMonitor(monitorConfig);
}
}
}
if ((getProtocols() == null || getProtocols().size() == 0)
&& (getProvider() == null || getProvider().getProtocols() == null || getProvider().getProtocols().size() == 0)) {
Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
if (protocolConfigMap != null && protocolConfigMap.size() > 0) {
List<ProtocolConfig> protocolConfigs = new ArrayList<ProtocolConfig>();
for (ProtocolConfig config : protocolConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
protocolConfigs.add(config);
}
}
if (protocolConfigs != null && protocolConfigs.size() > 0) {
super.setProtocols(protocolConfigs);
}
}
}
if (getPath() == null || getPath().length() == 0) {
if (beanName != null && beanName.length() > 0
&& getInterface() != null && getInterface().length() > 0
&& beanName.startsWith(getInterface())) {
setPath(beanName);
}
}
if (!isDelay()) {
export();
}
}
public void destroy() throws Exception {
unexport();
}
}
結合spring的生命周期管理我們知道了ServiceBean初始化完畢之后會調用一個afterPropertiesSet的方法。注意這個方法很重要!!!!!!!
因為這個方法里面調用了com.alibaba.dubbo.config.ServiceConfig#export這個方法。通過對這個方法進去對代碼進行跟蹤最終會跳到下面一段代碼:
//如果配置不是local則暴露為遠程服務.(配置為local,則表示只暴露本地服務)
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);
}
}
這一段就是dubbo實現服務注冊的真正代碼!!!!!!!其中最關鍵的兩句就是
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
Exporter<?> exporter = protocol.export(invoker);
上面的代碼可以用一張圖片作一個簡單化的描述.整個服務的暴露過程如下圖所示

然后再補上一消費者調用提供者的圖

所以終於知道了dubbo暴露出去其實是一個Exporter!!!!!。 至於Invoker和Exporter的介紹這里就暫時不做了,因為要解釋的話要講的東西實在太多了。
下面給出具體某一種協議的實現
假設配的協議是dubbo,注冊中心用的是zookeepr。那么代碼的調用過程大致是這樣:
1、調用JavassistProxyFactory 生成一個Invoker。 對應Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));這一段
2、用com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol#export方法 進行一個服務的暴露
3、DubboProtocol#export 方法最終會調用com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistryFactory#createRegistry
進行一個服務注冊 這時zookeepr相應的目錄下面就會有對應的內容.這時服務注冊就算完成了!!!
其實dubbo的服務注冊還牽涉到其它很多東西,實現的代碼也是很復雜,代碼之間的調用層次非常的多,我在這里也只能做一個大概的描述。希望這篇文章對想了解dubbo的人有所幫助吧。
