Dubbo中暴露服務的過程解析

搜雲庫技術團隊發表於2018-05-09

作者:大程熙
地址:cxis.me/2017/02/19/…

示例專案:github.com/souyunku/sp…

Dubbo會在Spring例項化完bean之後,在重新整理容器最後一步釋出ContextRefreshEvent事件的時候,通知實現了ApplicationListener的ServiceBean類進行回撥onApplicationEvent 事件方法,dubbo會在這個方法中呼叫ServiceBean父類ServiceConfig的export方法,而該方法真正實現了服務的(非同步或者非非同步)釋出。

載入dubbo配置

Spring容器在啟動的時候,會讀取到Spring預設的一些schema以及Dubbo自定義的schema,每個schema都會對應一個自己的NamespaceHandler,NamespaceHandler裡面通過BeanDefinitionParser來解析配置資訊並轉化為需要載入的bean物件。

遇到dubbo名稱空間 ,首先會呼叫DubboNamespaceHandler類的 init方法 進行初始化操作。

根據名稱空間去獲取具體的處理器NamespaceHandler。那具體的處理器是在哪定義的呢,在”META-INF/spring.handlers”檔案中,Spring在會自動載入該檔案中所有內容。

META-INF/spring.handlers

http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
複製程式碼

META-INF/spring.schemas

http\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
複製程式碼

根據不同的XML節點,會委託NamespaceHandlerSupport 類找出合適的BeanDefinitionParser,其中Dubbo所有的標籤都使用 DubboBeanDefinitionParser進行解析,基於一對一屬性對映,將XML標籤解析為Bean物件。

DubboNamespaceHandler.java 類程式碼如下

package com.alibaba.dubbo.config.spring.schema;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

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));
    }

}
複製程式碼

由於DubboBeanDefinitionParser 類中 parse轉換的過程程式碼還是比較複雜,只抽離出來bean的註冊這一塊的程式碼如下

DubboBeanDefinitionParser.java 類程式碼如下

package com.alibaba.dubbo.config.spring.schema;

public class DubboBeanDefinitionParser implements BeanDefinitionParser {

	@SuppressWarnings("unchecked")
	private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
	
	RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanClass);
        beanDefinition.setLazyInit(false);
        String id = element.getAttribute("id");
        //省略......
         if(id != null && id.length() > 0) {
            if(parserContext.getRegistry().containsBeanDefinition(id)) {
                throw new IllegalStateException("Duplicate spring bean id " + id);
            }
            //registerBeanDefinition 註冊Bean的定義
            //具體的id如下 applicationProvider.xml解析後的顯示 id,
            //如id="dubbo_provider"  beanDefinition = "ApplicationConfig"
            parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
            beanDefinition.getPropertyValues().addPropertyValue("id", id);
        }
	 }	
}
複製程式碼

通過DubboBeanDefinitionParser 類的 parse方法會將class資訊封裝成BeanDefinition,然後將BeanDefinition再放進DefaultListableBeanFactory的beanDefinitionMap中。

最後通過Spring bean 的載入機制進行載入。

服務暴露過程

Dubbo會在Spring例項化完bean之後,在重新整理容器最後一步釋出ContextRefreshEvent事件的時候,通知實現了ApplicationListener的ServiceBean類進行回撥onApplicationEvent 事件方法,dubbo會在這個方法中呼叫ServiceBean父類ServiceConfig的export方法,而該方法真正實現了服務的(非同步或者非非同步)釋出。

服務暴露入口

由服務配置類 ServiceConfig 進行初始化工作及服務暴露入口,首先進去執行該類的export()方法。

ServiceConfig.java 類的 export 方法

export的步驟簡介

  1. 首先會檢查各種配置資訊,填充各種屬性,總之就是保證我在開始暴露服務之前,所有的東西都準備好了,並且是正確的。
  2. 載入所有的註冊中心,因為我們暴露服務需要註冊到註冊中心中去。
  3. 根據配置的所有協議和註冊中心url分別進行匯出。
  4. 進行匯出的時候,又是一波屬性的獲取設定檢查等操作。
  5. 如果配置的不是remote,則做本地匯出。
  6. 如果配置的不是local,則暴露為遠端服務。
  7. 不管是本地還是遠端服務暴露,首先都會獲取Invoker。
  8. 獲取完Invoker之後,轉換成對外的Exporter,快取起來。

export方法先判斷是否需要延遲暴露(這裡我們使用的是不延遲暴露),然後執行doExport方法。

doExport方法先執行一系列的檢查方法,然後呼叫doExportUrls方法。檢查方法會檢測dubbo的配置是否在Spring配置檔案中宣告,沒有的話讀取properties檔案初始化。

doExportUrls方法先呼叫loadRegistries獲取所有的註冊中心url,然後遍歷呼叫doExportUrlsFor1Protocol方法。對於在標籤中指定了registry屬性的Bean,會在載入BeanDefinition的時候就載入了註冊中心。

ServiceConfig.java 類的 export 方法

package com.alibaba.dubbo.config;

public class ServiceConfig<T> extends AbstractServiceConfig {

public synchronized void export() {
	if (provider != null) {
		if (export == null) {
			export = provider.getExport();
		}
		if (delay == null) {
			delay = provider.getDelay();
		}
	}
	if (export != null && !export) {
		return;
	}

	if (delay != null && delay > 0) {
		delayExportExecutor.schedule(new Runnable() {
			public void run() {
				doExport();
			}
		}, delay, TimeUnit.MILLISECONDS);
	} else {
		doExport();
	}
}
複製程式碼

可以看出釋出釋出是支援延遲暴露釋出服務的,這樣可以用於當我們釋出的服務非常多,影響到應用啟動的問題,前提是應用允許服務釋出的延遲特性。

接下來就進入到 ServiceConfig.java 類的 doExport() 方法。

檢查DUBBO配置的合法性

ServiceConfig.java 類的 doExport方法。檢查DUBBO配置的合法性,並呼叫doExportUrls 方法。

package com.alibaba.dubbo.config;

public class ServiceConfig<T> extends AbstractServiceConfig {

protected synchronized void doExport() {
        if (unexported) {
            throw new IllegalStateException("Already unexported!");
        }
        if (exported) {
            return;
        }
        exported = true;
        if (interfaceName == null || interfaceName.length() == 0) {
            throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
        }
        checkDefault();
        if (provider != null) {
            if (application == null) {
                application = provider.getApplication();
            }
            if (module == null) {
                module = provider.getModule();
            }
            if (registries == null) {
                registries = provider.getRegistries();
            }
            if (monitor == null) {
                monitor = provider.getMonitor();
            }
            if (protocols == null) {
                protocols = provider.getProtocols();
            }
        }
        if (module != null) {
            if (registries == null) {
                registries = module.getRegistries();
            }
            if (monitor == null) {
                monitor = module.getMonitor();
            }
        }
        if (application != null) {
            if (registries == null) {
                registries = application.getRegistries();
            }
            if (monitor == null) {
                monitor = application.getMonitor();
            }
        }
        if (ref instanceof GenericService) {
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
                generic = Boolean.TRUE.toString();
            }
        } else {
            try {
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            checkInterfaceAndMethods(interfaceClass, methods);
            checkRef();
            generic = Boolean.FALSE.toString();
        }
        if (local != null) {
            if ("true".equals(local)) {
                local = interfaceName + "Local";
            }
            Class<?> localClass;
            try {
                localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if (!interfaceClass.isAssignableFrom(localClass)) {
                throw new IllegalStateException("The local implemention class " + localClass.getName() + " not implement interface " + interfaceName);
            }
        }
        if (stub != null) {
            if ("true".equals(stub)) {
                stub = interfaceName + "Stub";
            }
            Class<?> stubClass;
            try {
                stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if (!interfaceClass.isAssignableFrom(stubClass)) {
                throw new IllegalStateException("The stub implemention class " + stubClass.getName() + " not implement interface " + interfaceName);
            }
        }
        checkApplication();
        checkRegistry();
        checkProtocol();
        appendProperties(this);
        checkStubAndMock(interfaceClass);
        if (path == null || path.length() == 0) {
            path = interfaceName;
        }
        doExportUrls();
    }
}
複製程式碼

我們可以看出該方法的實現的邏輯包含了根據配置的優先順序將ProviderConfig,ModuleConfig,MonitorConfig,ApplicaitonConfig等一些配置資訊進行組裝和合並。還有一些邏輯是檢查配置資訊的合法性。最後又呼叫了doExportUrls方法。

服務多協議暴露過程

ServiceConfig.java 類的 doExportUrls() 方法

package com.alibaba.dubbo.config;

public class ServiceConfig<T> extends AbstractServiceConfig {

	@SuppressWarnings({"unchecked", "rawtypes"})
	private void doExportUrls() {
		List<URL> registryURLs = loadRegistries(true);
		for (ProtocolConfig protocolConfig : protocols) {
			doExportUrlsFor1Protocol(protocolConfig, registryURLs);
		}
	}
}
複製程式碼

該方法第一步是載入註冊中心列表

loadRegistries(true);載入註冊中心列表響應示例

registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-provider&dubbo=2.5.6&file=/data/dubbo/cache/dubbo-provider&pid=21448&registry=zookeeper&timestamp=1524134852031
複製程式碼

第二部是將服務釋出到多種協議的url上,並且攜帶註冊中心列表的引數,從這裡我們可以看出dubbo是支援同時將一個服務釋出成為多種協議的,這個需求也是很正常的,客戶端也需要支援多協議,根據不同的場景選擇合適的協議。

ServiceConfig.java 類的 doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) 方法。

package com.alibaba.dubbo.config;

public class ServiceConfig<T> extends AbstractServiceConfig {

	private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
			String name = protocolConfig.getName();
			if (name == null || name.length() == 0) {
				name = "dubbo";
			}

			String host = protocolConfig.getHost();
			//host = 10.4.81.95
			if (provider != null && (host == null || host.length() == 0)) {
				host = provider.getHost();
			}
			boolean anyhost = false;
			if (NetUtils.isInvalidLocalHost(host)) {
				anyhost = true;
				try {
					host = InetAddress.getLocalHost().getHostAddress();
				} catch (UnknownHostException e) {
					logger.warn(e.getMessage(), e);
				}
				if (NetUtils.isInvalidLocalHost(host)) {
					if (registryURLs != null && registryURLs.size() > 0) {
						for (URL registryURL : registryURLs) {
							try {
								Socket socket = new Socket();
								try {
									SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
									socket.connect(addr, 1000);
									host = socket.getLocalAddress().getHostAddress();
									break;
								} finally {
									try {
										socket.close();
									} catch (Throwable e) {
									}
								}
							} catch (Exception e) {
								logger.warn(e.getMessage(), e);
							}
						}
					}
					if (NetUtils.isInvalidLocalHost(host)) {
						host = NetUtils.getLocalHost();
					}
				}
			}

			Integer port = protocolConfig.getPort();
			if (provider != null && (port == null || port == 0)) {
				port = provider.getPort();
			}
			final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
			if (port == null || port == 0) {
				port = defaultPort;
			}
			if (port == null || port <= 0) {
				port = getRandomPort(name);
				if (port == null || port < 0) {
					port = NetUtils.getAvailablePort(defaultPort);
					putRandomPort(name, port);
				}
				logger.warn("Use random available port(" + port + ") for protocol " + name);
			}

			Map<String, String> map = new HashMap<String, String>();
			if (anyhost) {
				map.put(Constants.ANYHOST_KEY, "true");
			}
			map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
			map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
			map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
			if (ConfigUtils.getPid() > 0) {
				map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
			}
			appendParameters(map, application);
			appendParameters(map, module);
			appendParameters(map, provider, Constants.DEFAULT_KEY);
			appendParameters(map, protocolConfig);
			appendParameters(map, this);
			if (methods != null && methods.size() > 0) {
				for (MethodConfig method : methods) {
					appendParameters(map, method, method.getName());
					String retryKey = method.getName() + ".retry";
					if (map.containsKey(retryKey)) {
						String retryValue = map.remove(retryKey);
						if ("false".equals(retryValue)) {
							map.put(method.getName() + ".retries", "0");
						}
					}
					List<ArgumentConfig> arguments = method.getArguments();
					if (arguments != null && arguments.size() > 0) {
						for (ArgumentConfig argument : arguments) {
							//型別自動轉換.
							if (argument.getType() != null && argument.getType().length() > 0) {
								Method[] methods = interfaceClass.getMethods();
								//遍歷所有方法
								if (methods != null && methods.length > 0) {
									for (int i = 0; i < methods.length; i++) {
										String methodName = methods[i].getName();
										//匹配方法名稱,獲取方法簽名.
										if (methodName.equals(method.getName())) {
											Class<?>[] argtypes = methods[i].getParameterTypes();
											//一個方法中單個callback
											if (argument.getIndex() != -1) {
												if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
													appendParameters(map, argument, method.getName() + "." + argument.getIndex());
												} else {
													throw new IllegalArgumentException("argument config error : the index attribute and type attirbute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
												}
											} else {
												//一個方法中多個callback
												for (int j = 0; j < argtypes.length; j++) {
													Class<?> argclazz = argtypes[j];
													if (argclazz.getName().equals(argument.getType())) {
														appendParameters(map, argument, method.getName() + "." + j);
														if (argument.getIndex() != -1 && argument.getIndex() != j) {
															throw new IllegalArgumentException("argument config error : the index attribute and type attirbute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
														}
													}
												}
											}
										}
									}
								}
							} else if (argument.getIndex() != -1) {
								appendParameters(map, argument, method.getName() + "." + argument.getIndex());
							} else {
								throw new IllegalArgumentException("argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
							}

						}
					}
				} // end of methods for
			}

			if (ProtocolUtils.isGeneric(generic)) {
				map.put("generic", generic);
				map.put("methods", Constants.ANY_VALUE);
			} else {
				String revision = Version.getVersion(interfaceClass, version);
				if (revision != null && revision.length() > 0) {
					map.put("revision", revision);
				}

				String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
				if (methods.length == 0) {
					logger.warn("NO method found in service interface " + interfaceClass.getName());
					map.put("methods", Constants.ANY_VALUE);
				} else {
					map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
				}
			}
			if (!ConfigUtils.isEmpty(token)) {
				if (ConfigUtils.isDefault(token)) {
					map.put("token", UUID.randomUUID().toString());
				} else {
					map.put("token", token);
				}
			}
			if ("injvm".equals(protocolConfig.getName())) {
				protocolConfig.setRegister(false);
				map.put("notify", "false");
			}
			// 服務釋出 map 中的一些關鍵配置資訊
			/*
			map = {HashMap@6276}  size = 17
			 0 = {HashMap$Node@6516} "side" -> "provider"
			 1 = {HashMap$Node@6517} "default.version" -> "1.0"
			 2 = {HashMap$Node@6518} "methods" -> "sayHello"
			 3 = {HashMap$Node@6519} "dubbo" -> "2.5.6"
			 4 = {HashMap$Node@6520} "threads" -> "500"
			 5 = {HashMap$Node@6521} "pid" -> "21448"
			 6 = {HashMap$Node@6522} "interface" -> "io.ymq.dubbo.api.DemoService"
			 7 = {HashMap$Node@6523} "threadpool" -> "fixed"
			 8 = {HashMap$Node@6524} "generic" -> "false"
			 9 = {HashMap$Node@6525} "default.retries" -> "0"
			 10 = {HashMap$Node@6526} "delay" -> "-1"
			 11 = {HashMap$Node@6527} "application" -> "dubbo-provider"
			 12 = {HashMap$Node@6528} "default.connections" -> "5"
			 13 = {HashMap$Node@6529} "default.delay" -> "-1"
			 14 = {HashMap$Node@6530} "default.timeout" -> "10000"
			 15 = {HashMap$Node@6531} "anyhost" -> "true"
			 16 = {HashMap$Node@6532} "timestamp" -> "1524135271940"
			*/
			// 匯出服務
			String contextPath = protocolConfig.getContextpath();
			if ((contextPath == null || contextPath.length() == 0) && provider != null) {
				contextPath = provider.getContextpath();
			}
			URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
			// url
			/*
			dubbo://10.4.81.95:20880/io.ymq.dubbo.api.DemoService?
			anyhost=true&
			application=dubboprovider&
			default.connections=5&
			default.delay=-1&
			default.retries=0&
			default.timeout=10000&
			default.version=1.0&
			delay=-1&
			dubbo=2.5.6&
			generic=false&
			interface=io.ymq.dubbo.api.DemoService&
			methods=sayHello&
			pid=21448&
			side=provider&
			threadpool=fixed&
			threads=500&
			timestamp=1524135271940
			*/
			
			if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
					.hasExtension(url.getProtocol())) {
				url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
						.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
			}

			String scope = url.getParameter(Constants.SCOPE_KEY);
			//配置為none不暴露
			if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

				//配置不是remote的情況下做本地暴露 (配置為remote,則表示只暴露遠端服務)
				if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
					exportLocal(url);
				}
				//如果配置不是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);
					}
				}
			}
			this.urls.add(url);
		}
}
	
複製程式碼

拼裝dubbo服務URL

該方法的邏輯是先根據服務配置、協議配置、釋出服務的伺服器資訊、方法列表、dubbo版本等等資訊組裝成一個釋出的URL物件。

主要根據之前map裡的資料組裝成URL。

例如

dubbo://10.4.81.95:20880/io.ymq.dubbo.api.DemoService?
anyhost=true&
application=dubbo-provider&
default.connections=5&
default.delay=-1&
default.retries=0&
default.timeout=10000&
default.version=1.0&
delay=-1&
dubbo=2.5.6&
generic=false&
interface=io.ymq.dubbo.api.DemoService&
methods=sayHello&
pid=21448&
side=provider&
threadpool=fixed&
threads=500&
timestamp=1524135271940
複製程式碼

本地暴露和遠端暴露

  1. 如果服務配置的scope是釋出範圍,配置為none不暴露服務,則會停止釋出操作;
  2. 如果配置不是remote的情況下先做本地暴露,則呼叫本地暴露exportLocal方法;
  3. 如果配置不是local則暴露為遠端服務,則註冊服務registryProcotol;
//配置為none不暴露
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

	//配置不是remote的情況下做本地暴露 (配置為remote,則表示只暴露遠端服務)
	if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
		exportLocal(url);
	}
	//如果配置不是local則暴露為遠端服務.(配置為local,則表示只暴露本地服務)
	if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
		省略更多
	}
}
複製程式碼

本地暴露服務

@SuppressWarnings({"unchecked", "rawtypes"})
private void exportLocal(URL url) {
	// 入參 URL =
	/*
	dubbo://10.4.81.95:20880/io.ymq.dubbo.api.DemoService?
	anyhost=true&
	application=dubbo-provider&
	default.connections=5&
	default.delay=-1&
	default.retries=0&
	default.timeout=10000&
	default.version=1.0&
	delay=-1&
	dubbo=2.5.6&
	generic=false&
	interface=io.ymq.dubbo.api.DemoService&
	methods=sayHello&
	pid=11104&
	scope=local&
	side=provider&
	threadpool=fixed&
	threads=500&
	timestamp=1524193840984
	*/
	if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
	
		URL local = URL.valueOf(url.toFullString())
				.setProtocol(Constants.LOCAL_PROTOCOL)
				.setHost(NetUtils.LOCALHOST)
				.setPort(0);
			
	//這時候轉成本地暴露的協議 url:			
	/* 
	injvm://127.0.0.1/io.ymq.dubbo.api.DemoService?
	anyhost=true&
	application=dubbo-provider&
	default.connections=5&
	default.delay=-1&
	default.retries=0&
	default.timeout=10000&
	default.version=1.0&
	delay=-1&
	dubbo=2.5.6&
	generic=false&
	interface=io.ymq.dubbo.api.DemoService&
	methods=sayHello&
	pid=11104&
	scope=local&
	side=provider&
	threadpool=fixed&
	threads=500&
	timestamp=1524193840984
	*/
	
	//首先還是先獲得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");
	}
}
複製程式碼
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();
複製程式碼

ServiceConfig.exportLocal(URL url)方法中對url進行本地暴露,首先將URL的協議更改為了“injvm”、IP更改為了本地埠,埠變更為0。

主要有代理工廠建立Invoker代理、Protocol暴露Invoker從而生成Exporter兩個處理邏輯。

  1. 獲得代理工廠建立的Invoker代理,呼叫getInvoker方法,根據URL中的proxy引數選擇具體的代理工廠,預設選擇JavassistProxyFactory代理工廠進行處理。
  2. 呼叫protocl.export()方法完成,在protocol物件的export(Invoker)方法中建立的Exporter物件存入ServiceConfig物件的List<Exporter<?>> exporters 屬性中,至此本地暴露過程完成。

暴露為遠端服務

接下來是暴露為遠端服務,跟本地暴露的流程一樣還是先獲取Invoker,然後匯出成Exporter。


private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();


//如果配置不是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);
			}

			// url 引數
			/**
			dubbo://10.4.81.95:20880/io.ymq.dubbo.api.DemoService?
			anyhost=true&
			application=dubbo-provider&
			default.connections=5&
			default.delay=-1&
			default.retries=0&
			default.timeout=10000&
			default.version=1.0&
			delay=-1&
			dubbo=2.5.6&
			generic=false&
			interface=io.ymq.dubbo.api.DemoService&methods=sayHello&
			monitor=dubbo%3A%2F%2F127.0.0.1%3A2181%2Fcom.alibaba.dubbo.registry.RegistryService%3Fapplication%3Ddubbo-provider%26dubbo%3D2.5.6%26file%3D%2Fdata%2Fdubbo%2Fcache%2Fdubbo-provider%26pid%3D26440%26protocol%3Dregistry%26refer%3Ddubbo%253D2.5.6%2526interface%253Dcom.alibaba.dubbo.monitor.MonitorService%2526pid%253D26440%2526timestamp%253D1524212561737%26registry%3Dzookeeper%26timestamp%3D1524212160841&
			pid=26440&
			side=provider&
			threadpool=fixed&
			threads=500&
			timestamp=1524212161760
			
			*/
			
			//根據服務具體實現,實現介面,以及registryUrl通過ProxyFactory將DemoService封裝成一個本地執行的Invoker代理
			//invoker是對具體實現的一種代理。
			//這裡proxyFactory是上面列出的生成的程式碼
			
			Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));

			//使用Protocol將invoker匯出成一個Exporter
			//暴露封裝服務invoker
			//呼叫Protocol生成的適配類的export方法
			//這裡的protocol是上面列出的生成的程式碼
 
			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);
	}
}
複製程式碼

關於Invoker,Exporter等的解釋參見最下面的內容

暴露遠端服務時的獲取Invoker過程

服務實現類轉換成Invoker,大概的步驟是:

  1. 根據上面生成的proxyFactory方法呼叫具體的ProxyFactory實現類的getInvoker方法獲取Invoker。
  2. getInvoker的過程是,首先對實現類做一個包裝,生成一個包裝後的類。
  3. 然後新建立一個Invoker例項,這個Invoker中包含著生成的Wrapper類,Wrapper類中有具體的實現類。
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
複製程式碼
package com.alibaba.dubbo.rpc.proxy.wrapper;

public class StubProxyFactoryWrapper implements ProxyFactory {

    private final ProxyFactory proxyFactory;

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException {
        return proxyFactory.getInvoker(proxy, type, url);
    }
}
複製程式碼

這行程式碼中包含服務實現類轉換成Invoker的過程,其中proxyFactory是上面列出的動態生成的程式碼,其中getInvoker的程式碼為(做了精簡)。

JavassistProxyFactory.java 類的 getInvoker(T proxy, Class<T> type, URL url) 方法。

package com.alibaba.dubbo.rpc.proxy.javassist;

public class JavassistProxyFactory extends AbstractProxyFactory {

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
	
		// TODO Wrapper類不能正確處理帶$的類名
		//第一步封裝一個包裝類(wrapper)
		//該類是手動生成的
		//如果類是以$開頭,就使用介面型別獲取,其他的使用實現類獲取

        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
		
		//返回一個Invoker例項,doInvoke方法中直接返回上面wrapper的invokeMethod
		//關於生成的wrapper,請看下面列出的生成的程式碼,其中invokeMethod方法中就有實現類對實際方法的呼叫
	
        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);
            }
        };
    }

}
複製程式碼

Wrapper.java 類的 getWrapper(Class<?> c) 方法。

生成包裝類(wrapper)的過程,首先看getWrapper方法

package com.alibaba.dubbo.common.bytecode;

public abstract class Wrapper {

	//快取包裝類
    private static final Map<Class<?>, Wrapper> WRAPPER_MAP = new ConcurrentHashMap<Class<?>, Wrapper>(); //class wrapper map
	
	public static Wrapper getWrapper(Class<?> c) {
		while( ClassGenerator.isDynamicClass(c) ) // can not wrapper on dynamic class.
			c = c.getSuperclass();
		//Object型別的
		if( c == Object.class )
			return OBJECT_WRAPPER;
		//先去Wrapper快取中查詢 
		// c 等於 class io.ymq.dubbo.provider.service.DemoServiceImpl
		Wrapper ret = WRAPPER_MAP.get(c);
		if( ret == null ) {
			//快取中不存在,生成Wrapper類,放到快取
			ret = makeWrapper(c);
			WRAPPER_MAP.put(c,ret);
		}
		return ret;
	}
}
複製程式碼

生成完Wrapper以後,返回一個AbstractProxyInvoker例項。至此生成Invoker的步驟就完成了。 可以看到Invoker執行方法的時候,會呼叫Wrapper的invokeMethod,這個方法中會有真實的實現類呼叫真實方法的程式碼

JavassistProxyFactory.java 類的 getInvoker(T proxy, Class<T> type, URL url) 方法。

package com.alibaba.dubbo.rpc.proxy.javassist;

public class JavassistProxyFactory extends AbstractProxyFactory {

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
	
		// TODO Wrapper類不能正確處理帶$的類名
		//第一步封裝一個包裝類(wrapper)
		//該類是手動生成的
		//如果類是以$開頭,就使用介面型別獲取,其他的使用實現類獲取

        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
		
		//返回一個Invoker例項,doInvoke方法中直接返回上面wrapper的invokeMethod
		//關於生成的wrapper,請看下面列出的生成的程式碼,其中invokeMethod方法中就有實現類對實際方法的呼叫
	
        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);
            }
        };
    }

}
複製程式碼

暴露遠端服務時匯出Invoker為Exporter

Invoker匯出為Exporter分為兩種情況,第一種是Registry型別的Invoker,第二種是其他協議型別的Invoker,分開解析。

程式碼入口:

Exporter<?> exporter = protocol.export(invoker);
複製程式碼

Registry型別的Invoker處理過程

大概的步驟是:

  1. 經過兩個不用做任何處理的Wrapper類,然後到達RegistryProtocol中。
  2. 通過具體的協議匯出Invoker為Exporter。
  3. 註冊服務到註冊中心。
  4. 訂閱註冊中心的服務。
  5. 生成一個新的Exporter例項,將上面的Exporter進行引入,然後返回。

rotocol是上面列出的動態生成的程式碼,會先呼叫ProtocolListenerWrapper,這個Wrapper負責初始化暴露和引用服務的監聽器。對於Registry型別的不做處理,程式碼如下:

ProtocolFilterWrapper.java 類的 export(Invoker<T> invoker) 方法

package com.alibaba.dubbo.rpc.protocol;

public class ProtocolFilterWrapper implements Protocol {
	
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
		
		// REGISTRY_PROTOCOL = "registry";
		// 協議為 registry型別的Invoker,不需要做處理
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
		
        return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
    }
}
複製程式碼

消費者端和提供者端形成呼叫鏈都呼叫了同一段程式碼:buildInvokerChain() 通過last->next的方式統一新增,然後再新生成的Invoker中呼叫next然後最後呼叫last就是我們真正執行的Invoker,呼叫鏈分類(預設Filter), 程式碼如下:

ProtocolFilterWrapper.java 類的 buildInvokerChain(final Invoker<T> invoker, String key, String group) 方法。

package com.alibaba.dubbo.rpc.protocol;

public class ProtocolFilterWrapper implements Protocol {

	private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
		Invoker<T> last = invoker;
		 //只獲取滿足條件的Filter
		List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
		if (filters.size() > 0) {
		
			// filter從最後一個開始依次封裝,最終形成一個鏈,呼叫順序為filters的順序    
			for (int i = filters.size() - 1; i >= 0; i--) {
				final Filter filter = filters.get(i);
				final Invoker<T> next = last;
				last = new Invoker<T>() {

					public Class<T> getInterface() {
						return invoker.getInterface();
					}

					public URL getUrl() {
						return invoker.getUrl();
					}

					public boolean isAvailable() {
						return invoker.isAvailable();
					}

					public Result invoke(Invocation invocation) throws RpcException {
						return filter.invoke(next, invocation);
					}

					public void destroy() {
						invoker.destroy();
					}

					@Override
					public String toString() {
						return invoker.toString();
					}
				};
			}
		}
		return last;
	}
}
複製程式碼

RegistryProtocol.java 類的 export(final Invoker<T> originInvoker) 方法:

這裡我們先解析的是Registry型別的Invoker,接著就會呼叫RegistryProtocol的export方法,RegistryProtocol負責註冊服務到註冊中心和向註冊中心訂閱服務。程式碼如下:

package com.alibaba.dubbo.registry.integration;

public class RegistryProtocol implements Protocol {

	public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
		//export invoker
		//這裡就交給了具體的協議去暴露服務(先不解析,留在後面,可以先去後面看下匯出過程)
		final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
		
		//registry provider
		//根據invoker中的url獲取Registry例項
		//並且連線到註冊中心
		//此時提供者作為消費者引用註冊中心核心服務RegistryService
		final Registry registry = getRegistry(originInvoker);
		
		//註冊到註冊中心的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, originInvoker);
		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);
				}
			}
		};
	}
}
複製程式碼

交給具體的協議去暴露服務

先不解析,留在後面,可以先去後面看下匯出過程,然後再回來接著看註冊到註冊中心的過程。具體協議暴露服務主要是開啟伺服器和埠,進行監聽。

註冊到註冊中心

具體的協議進行暴露並且返回了一個ExporterChangeableWrapper之後,接下來看下一步連線註冊中心並註冊到註冊中心,程式碼是在RegistryProtocol的export方法:

RegistryProtocol.java 類的 export(final Invoker<T> originInvoker) 方法

package com.alibaba.dubbo.registry.integration;

public class RegistryProtocol implements Protocol {

	//省略很多。。。
	
	public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
			
		//此步已經分析完
		final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
		//得到具體的註冊中心,連線註冊中心,此時提供者作為消費者引用註冊中心核心服務RegistryService
		final Registry registry = getRegistry(originInvoker);
		//獲取要註冊到註冊中心的url
		final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
		//呼叫遠端註冊中心的register方法進行服務註冊
		//若有消費者訂閱此服務,則推送訊息讓消費者引用此服務
		registry.register(registedProviderUrl);
		//提供者向註冊中心訂閱所有註冊服務的覆蓋配置
		registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
		//返回暴露後的Exporter給上層ServiceConfig進行快取
		return new Exporter<T>() {。。。}
	}
}
複製程式碼

RegistryProtocol.java 類的 getRegistry(final Invoker<?> originInvoker) 方法,會發現該方法會在 AbstractRegistryFactory.java 類中實現:

package com.alibaba.dubbo.registry.integration;

public class RegistryProtocol implements Protocol {

	 /**
     * 根據invoker的地址獲取registry例項
     *
     * @param originInvoker
     * @return
     */
    private Registry getRegistry(final Invoker<?> originInvoker) {
		
		//獲取invoker中的registryUrl
        URL registryUrl = originInvoker.getUrl();
		
		if (Constants.REGISTRY_PROTOCOL.equals(registryUrl.getProtocol())) {
			//獲取registry的值,這裡獲得是zookeeper,預設值是dubbo
            String protocol = registryUrl.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_DIRECTORY);
			
            registryUrl = registryUrl.setProtocol(protocol).removeParameter(Constants.REGISTRY_KEY);
			
			// registryUrl
			
			/*
			 zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?
			 application=dubbo-provider&
			 dubbo=2.5.6&
			 export=dubbo://10.4.81.95:20880/io.ymq.dubbo.api.DemoService?
			 anyhost=true&
			 application=dubbo-provider&
			 default.connections=5&
			 default.delay=-1&default.retries=0&
			 default.timeout=10000&
			 default.version=1.0&
			 delay=-1&
			 dubbo=2.5.6&
			 generic=false&
			 interface=io.ymq.dubbo.api.DemoService&
			 methods=sayHello&
			
			 monitor=dubbo://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService%3F
			 application=dubbo-provider&
			 dubbo=2.5.6&
			 file=/data/dubbo/cache/dubbo-provider&
			 pid=3D29100%26
			 protocol%3D
			 registry%26refer%3D
			 dubbo%253D2.5.6%2526
			 interface%253Dcom.alibaba.dubbo.monitor.MonitorService%2526
			 pid%253D29100%2526
			 timestamp%253D1524465704266%26
			 registry%3Dzookeeper%26
			 timestamp%3D1524465673571&
            
			 pid=29100&
			 side=provider&threadpool=fixed&threads=500&
			 timestamp=1524465674601&
			 file=/data/dubbo/cache/dubbo-provider&
			 pid=29100&
			 timestamp=1524465673571
			 */

        }
		
		//根據SPI機制獲取具體的Registry例項,這裡獲取到的是ZookeeperRegistry
        return registryFactory.getRegistry(registryUrl);
    }
}
複製程式碼

先看下 AbstractRegistryFactory.java 類的 getRegistry(URL url) 方法。

package com.alibaba.dubbo.registry.support;

public abstract class AbstractRegistryFactory implements RegistryFactory {

	public Registry getRegistry(URL url) {
		url = url.setPath(RegistryService.class.getName())
				 .addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
				 .removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
		// 入參url =	 
		// zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?
		// application=dubbo-provider&
		// dubbo=2.5.6&
		// file=/data/dubbo/cache/dubbo-provider&
		// interface=com.alibaba.dubbo.registry.RegistryService&
		// pid=30112&
		// timestamp=1524469125241

		//這裡key為:
		// zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService
		String key = url.toServiceString();
		// 鎖定註冊中心獲取過程,保證註冊中心單一例項
		LOCK.lock();
		try {
			//先從快取中獲取Registry例項
			Registry registry = REGISTRIES.get(key);
			if (registry != null) {
				return registry;
			}
			
			
			//建立registry,會直接new一個ZookeeperRegistry返回
			
			//********具體建立例項是子類來實現的********
			registry = createRegistry(url);
			if (registry == null) {
				throw new IllegalStateException("Can not create registry " + url);
			}
		   // 建立後的registry 
		   
			// zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?
			// application=dubbo-provider&
			// dubbo=2.5.6&
			// file=/data/dubbo/cache/dubbo-provider&
			// interface=com.alibaba.dubbo.registry.RegistryService&
			// pid=30344&
			// timestamp=1524473152100
			
			//放到快取中
			REGISTRIES.put(key, registry);
			return registry;
		} finally {
			// 釋放鎖
			LOCK.unlock();
		}
	}
}
複製程式碼

ZookeeperRegistryFactory.java 類的 createRegistry(URL url) 方法

方法是在子類中實現的,這裡的子類是ZookeeperRegistry.java。

首先需要經過AbstractRegistry 類的構造方法。AbstractRegistry類的構造器初始化完,接著呼叫FailbackRegistry類的構造器初始化,最後回到ZookeeperRegistry類的構造初始化。

package com.alibaba.dubbo.registry.zookeeper;

public class ZookeeperRegistryFactory extends AbstractRegistryFactory {

    private ZookeeperTransporter zookeeperTransporter;

    public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
        this.zookeeperTransporter = zookeeperTransporter;
    }

    public Registry createRegistry(URL url) {
        return new ZookeeperRegistry(url, zookeeperTransporter);
    }

}
複製程式碼

ZookeeperRegistry.java 類的 ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) 構造方法

package com.alibaba.dubbo.registry.zookeeper;

public class ZookeeperRegistry extends FailbackRegistry {

    public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
	
	    super(url);
        if (url.isAnyHost()) {
            throw new IllegalStateException("registry address == null");
        }
        String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
        if (!group.startsWith(Constants.PATH_SEPARATOR)) {
            group = Constants.PATH_SEPARATOR + group;
        }
        this.root = group;
        zkClient = zookeeperTransporter.connect(url);
        zkClient.addStateListener(new StateListener() {
            public void stateChanged(int state) {
                if (state == RECONNECTED) {
                    try {
                        recover();
                    } catch (Exception e) {
                        logger.error(e.getMessage(), e);
                    }
                }
            }
        });
	}
}
複製程式碼

FailbackRegistry.java 類的 AbstractRegistry 構造器初始化完,接著呼叫FailbackRegistry 類的 FailbackRegistry(URL url) 構造器初始化:

package com.alibaba.dubbo.registry.support;

public abstract class FailbackRegistry extends AbstractRegistry {

	 public FailbackRegistry(URL url) {
        super(url);
        int retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);
        this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                // 檢測並連線註冊中心
                try {
                    retry();
                } catch (Throwable t) { // 防禦性容錯
                    logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);
                }
            }
        }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
    }
}
複製程式碼

AbstractRegistry.java 類的 AbstractRegistry(URL url) 構造器初始化完,接著呼叫 FailbackRegistry 類的 FailbackRegistry(URL url) 構造器初始化:

package com.alibaba.dubbo.registry.support;

public abstract class AbstractRegistry implements Registry {

	public AbstractRegistry(URL url) {
		// 儲存url
		// zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?
		// application=dubbo-provider&
		// dubbo=2.5.6&
		// file=/data/dubbo/cache/dubbo-provider&
		// interface=com.alibaba.dubbo.registry.RegistryService&
		// pid=28536&
		// timestamp=1524471115982

		setUrl(url);
		// 啟動檔案儲存定時器
		
		syncSaveFile = url.getParameter(Constants.REGISTRY_FILESAVE_SYNC_KEY, false);
		String filename = url.getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getHost() + ".cache");
		// 儲存的檔案為:/data/dubbo/cache/dubbo-provider
		File file = null;
		if (ConfigUtils.isNotEmpty(filename)) {
			file = new File(filename);
			if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
				if (!file.getParentFile().mkdirs()) {
					throw new IllegalArgumentException("Invalid registry store file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
				}
			}
		}
		this.file = file;
		// 載入檔案中的屬性
		loadProperties();
		
		//通知訂閱
		notify(url.getBackupUrls());
	}
	
}
複製程式碼

AbstractRegistry.java 類的 notify(List<URL> urls) 方法:

package com.alibaba.dubbo.registry.support;

public abstract class AbstractRegistry implements Registry {

	protected void notify(List<URL> urls) {
		if(urls == null || urls.isEmpty()) return;
		//getSubscribed()方法獲取訂閱者列表
		//訂閱者Entry裡每個URL都對應著n個NotifyListener
		for (Map.Entry<URL, Set<NotifyListener>> entry : getSubscribed().entrySet()) {
			URL url = entry.getKey();

			if(! UrlUtils.isMatch(url, urls.get(0))) {
				continue;
			}

			Set<NotifyListener> listeners = entry.getValue();
			if (listeners != null) {
				for (NotifyListener listener : listeners) {
					try {
						//通知每個監聽器
						notify(url, listener, filterEmpty(url, urls));
					} catch (Throwable t) {}
				}
			}
		}
	}
}
複製程式碼

通知每個監聽器

AbstractRegistry.java 類的 notify(URL url, NotifyListener listener, List<URL> urls) 方法:

package com.alibaba.dubbo.registry.support;

public abstract class AbstractRegistry implements Registry {

	protected void notify(URL url, NotifyListener listener, List<URL> urls) {
		
		// 省略。。。
		
		Map<String, List<URL>> result = new HashMap<String, List<URL>>();
		for (URL u : urls) {
			if (UrlUtils.isMatch(url, u)) {
			    //分類
				String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
				List<URL> categoryList = result.get(category);
				if (categoryList == null) {
					categoryList = new ArrayList<URL>();
					result.put(category, categoryList);
				}
				categoryList.add(u);
			}
		}
		if (result.size() == 0) {
			return;
		}
		Map<String, List<URL>> categoryNotified = notified.get(url);
		if (categoryNotified == null) {
			notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
			categoryNotified = notified.get(url);
		}
		for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
			String category = entry.getKey();
			List<URL> categoryList = entry.getValue();
			categoryNotified.put(category, categoryList);
			
			//儲存到主目錄下的.dubbo目錄下
			saveProperties(url);
			//上面獲取到的監聽器進行通知
			listener.notify(categoryList);
		}
	}
}
複製程式碼

FailbackRegistry.java 類的 AbstractRegistry 類的構造器初始化完,接著呼叫FailbackRegistry 類的構造器初始化:

public abstract class FailbackRegistry extends AbstractRegistry {

	public FailbackRegistry(URL url) {
		super(url);
		//重試時間,預設5000ms
		int retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);
		//啟動失敗重試定時器
		this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {
			public void run() {
				// 檢測並連線註冊中心
				try {
					//獲取到註冊失敗的,然後嘗試註冊
					retry();
				} catch (Throwable t) { // 防禦性容錯
					logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);
				}
			}
		}, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
	}
}
複製程式碼

最後回到ZookeeperRegistry.java 類的構造初始化:

package com.alibaba.dubbo.registry.zookeeper;

public class ZookeeperRegistry extends FailbackRegistry {
		
	public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
		super(url);
		if (url.isAnyHost()) {
			throw new IllegalStateException("registry address == null");
		}
		//獲得到註冊中心中的分組,預設dubbo
		String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
		if (!group.startsWith(Constants.PATH_SEPARATOR)) {
			group = Constants.PATH_SEPARATOR + group;
		}
		
		//註冊到註冊中心的節點
		this.root = group;
		
		//使用zookeeperTansporter去連線
		//ZookeeperTransport這裡是生成的自適應實現,預設使用ZkClientZookeeperTransporter
		//ZkClientZookeeperTransporter的connect去例項化一個ZkClient例項
		//並且訂閱狀態變化的監聽器subscribeStateChanges
		//然後返回一個ZkClientZookeeperClient例項
		zkClient = zookeeperTransporter.connect(url);
		
		//ZkClientZookeeperClient新增狀態改變監聽器
		zkClient.addStateListener(new StateListener() {
			public void stateChanged(int state) {
				if (state == RECONNECTED) {
					try {
						recover();
					} catch (Exception e) {
						logger.error(e.getMessage(), e);
					}
				}
			}
		});
	}
}
複製程式碼

註冊到註冊中心 部分重要程式碼

package com.alibaba.dubbo.registry.integration;

public class RegistryProtocol implements Protocol {

	public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
		//export invoker
		final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
		//registry provider
		final Registry registry = getRegistry(originInvoker);

		//取要註冊到註冊中心的url
		final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);

		
		// URL 下面
		/*
		dubbo://10.4.81.95:20880/io.ymq.dubbo.api.DemoService?
		anyhost=true&
		application=dubbo-provider&
		default.connections=5&
		default.delay=-1&
		default.retries=0&
		default.timeout=10000&
		default.version=1.0&
		delay=-1&
		dubbo=2.5.6&
		generic=false&
		interface=io.ymq.dubbo.api.DemoService&
		methods=sayHello&
		pid=30344&
		side=provider&
		threadpool=fixed&
		threads=500&
		timestamp=1524473153090
		*/
	
	   // 省略很多。。。
		   
	   return new Exporter<T>() {
	   // 省略很多。。。
	   }
		 
	}
}
複製程式碼

獲取到了Registry,Registry例項中儲存著連線到了zookeeper的zkClient例項之後,下一步獲取要註冊到註冊中心的url(在RegistryProtocol中)。

取要註冊到註冊中心的url 的 RegistryProtocol.java 類的 getRegistedProviderUrl(final Invoker<?> originInvoker) 方法

package com.alibaba.dubbo.registry.integration;

public class RegistryProtocol implements Protocol {
	/**
	 * 返回註冊到註冊中心的URL,對URL引數進行一次過濾
	 *
	 * @param originInvoker
	 * @return
	 */
	private URL getRegistedProviderUrl(final Invoker<?> originInvoker) {
		URL providerUrl = getProviderUrl(originInvoker);
		//註冊中心看到的地址
		final URL registedProviderUrl = providerUrl.removeParameters(getFilteredKeys(providerUrl)).removeParameter(Constants.MONITOR_KEY);
		return registedProviderUrl;
	}
}
複製程式碼

然後呼叫 registry.register(registedProviderUrl) 註冊到註冊中心(在RegistryProtocol中)。register方法的實現在FailbackRegistry中:

FailbackRegistry.java 類的 register(URL url) 方法:

package com.alibaba.dubbo.registry.support;

public abstract class FailbackRegistry extends AbstractRegistry {

    @Override
    public void register(URL url) {
        if (destroyed.get()){
            return;
        }
        super.register(url);
        failedRegistered.remove(url);
        failedUnregistered.remove(url);
        try {
            // 向伺服器端傳送註冊請求
             (url);
        } catch (Exception e) {
            Throwable t = e;

            // 如果開啟了啟動時檢測,則直接丟擲異常
            boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                    && url.getParameter(Constants.CHECK_KEY, true)
                    && !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
            boolean skipFailback = t instanceof SkipFailbackWrapperException;
            if (check || skipFailback) {
                if (skipFailback) {
                    t = t.getCause();
                }
                throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
            } else {
                logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
            }

            // 將失敗的註冊請求記錄到失敗列表,定時重試
            failedRegistered.add(url);
        }
    }
}
複製程式碼

doRegister(url);在這裡是ZookeeperRegistry中具體實現的,這裡將會註冊到註冊中心:

ZookeeperRegistry.java 類的 doRegister(URL url) 方法

package com.alibaba.dubbo.registry.zookeeper;

public class ZookeeperRegistry extends FailbackRegistry {
	
	protected void doRegister(URL url) {
		
		try {
			
			//這裡zkClient就是我們上面呼叫構造的時候生成的
			//ZkClientZookeeperClient
			//儲存著連線到Zookeeper的zkClient例項
			//開始註冊,也就是在Zookeeper中建立節點
			
			//這裡toUrlPath獲取到的path為:
			
		
			/**
			/dubbo
				/io.ymq.dubbo.api.DemoService
					/providers
						/dubbo://169.254.8.253:20880/io.ymq.dubbo.api.DemoService?
						anyhost=true&
						application=dubbo-provider&
						default.connections=5&
						default.delay=-1&
						default.retries=0&
						default.timeout=10000&
						default.version=1.0&
						delay=-1&
						dubbo=2.5.6&
						generic=false&
						interface=io.ymq.dubbo.api.DemoService&
						methods=sayHello&
						pid=16856&
						side=provider&
						threadpool=fixed&
						threads=500&
						timestamp=1525419864880
			**/
			//預設建立的節點是臨時節點 
			zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
		} catch (Throwable e) {
			throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
		}
	}
}
複製程式碼

經過這一步之後,Zookeeper中就有節點存在了,具體節點為:

/dubbo
	/io.ymq.dubbo.api.DemoService
		/providers
			/dubbo://169.254.8.253:20880/io.ymq.dubbo.api.DemoService?
			anyhost=true&
			application=dubbo-provider&
			default.connections=5&
			default.delay=-1&
			default.retries=0&
			default.timeout=10000&
			default.version=1.0&
			delay=-1&
			dubbo=2.5.6&
			generic=false&
			interface=io.ymq.dubbo.api.DemoService&
			methods=sayHello&
			pid=16856&
			side=provider&
			threadpool=fixed&
			threads=500&
			timestamp=1525419864880
複製程式碼

訂閱註冊中心的服務

在註冊到註冊中心之後,registry會去訂閱覆蓋配置的服務,這一步之後就會在/dubbo/io.ymq.dubbo.api.DemoService節點下多一個configurators節點。(具體過程暫先不解析)。

返回新Exporter例項

最後返回Exporter新例項,返回到ServiceConfig中。服務的釋出就算完成了。

交給具體的協議進行服務暴露

這裡也就是非Registry型別的Invoker的匯出過程。主要的步驟是將本地ip和20880埠開啟,進行監聽。最後包裝成exporter返回。

RegistryProtocol.java 類的 doLocalExport(invoker) 方法

package com.alibaba.dubbo.registry.integration;

public class RegistryProtocol implements Protocol {

    //用於解決rmi重複暴露埠衝突的問題,已經暴露過的服務不再重新暴露
    //providerurl <--> exporter
    private final Map<String, ExporterChangeableWrapper<?>> bounds = new ConcurrentHashMap<String, ExporterChangeableWrapper<?>>();
	
	
    @SuppressWarnings("unchecked")
    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&dubbo=2.5.6&
		export=dubbo://169.254.8.253:20880/io.ymq.dubbo.api.DemoService?
		anyhost=true&
		application=dubbo-provider&
		default.connections=5&
		default.delay=-1&
		default.retries=0&
		default.timeout=10000&
		default.version=1.0&
		delay=-1&
		dubbo=2.5.6&
		generic=false&
		interface=io.ymq.dubbo.api.DemoService&
		methods=sayHello&
		
		monitor=dubbo%3A%2F%2F127.0.0.1%3A2181%2F
		com.alibaba.dubbo.registry.RegistryService%3Fapplication%3D
		dubbo-provider%26dubbo%3D2.5.6%26file%3D%2Fdata%2Fdubbo%2F
		cache%2Fdubbo-provider%26pid%3D2972%26protocol%3D
		registry%26refer%3Ddubbo%253D2.5.6%2526interface%253D
		com.alibaba.dubbo.monitor.MonitorService%2526
		pid%253D2972%2526timestamp%253D1525421418972%26
		registry%3Dzookeeper%26timestamp%3D1525421395956&
		pid=2972&
		side=provider&
		threadpool=fixed&
		threads=500&
		timestamp=1525421396922&
		file=/data/dubbo/cache/dubbo-provider&pid=2972&
		registry=zookeeper&
		timestamp=1525421395956
	
		//從原始的invoker中得到的key:
		
		dubbo://169.254.8.253:20880/io.ymq.dubbo.api.DemoService?
		anyhost=true&
		application=dubbo-provider&
		default.connections=5&
		default.delay=-1&
		default.retries=0&
		default.timeout=10000&
		default.version=1.0&
		delay=-1&
		dubbo=2.5.6&
		generic=false&
		interface=io.ymq.dubbo.api.DemoService&
		methods=sayHello&
		
		monitor=dubbo://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?
		application=dubbo-provider&
		dubbo=2.5.6&
		file=/data/dubbo/cache/dubbo-provider&
		pid=4668&
		protocol=registry&
		refer=dubbo%3D2.5.6%26
		interface%3Dcom.alibaba.dubbo.monitor.MonitorService%26pid%3D4668%26
		timestamp%3D1525422343947&
		registry=zookeeper&
		timestamp=1525422334635&
		pid=4668&
		side=provider&
		threadpool=fixed&
		threads=500&
		timestamp=1525422335033
	
		*/
		
        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 exporter;
    }
}
複製程式碼

這裡protocol.export(invokerDelegete)就要去具體的 DubboProtocol.java 中執行了,DubboProtocol.java的外面包裹著ProtocolFilterWrapper.java,再外面還包裹著ProtocolListenerWrapper.java。會先經過ProtocolListenerWrapper.java

  1. 會先經過 ProtocolListenerWrapper.java 類的 export(Invoker<T> invoker) 方法
package com.alibaba.dubbo.rpc.protocol;

public class ProtocolListenerWrapper implements Protocol {

    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
        return new ListenerExporterWrapper<T>(protocol.export(invoker),
                Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
                        .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
    }

}
複製程式碼
  1. 再經過 ProtocolFilterWrapper.java 類的 export(Invoker<T> invoker) 方法
package com.alibaba.dubbo.rpc.protocol;
	
public class ProtocolFilterWrapper implements Protocol {
	
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
		
		// REGISTRY_PROTOCOL = "registry";
		// 協議為 registry型別的Invoker,不需要做處理
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
		
		 //其他具體協議型別的Invoker
		//先構建Filter鏈,然後再匯出
	
        return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
    }
}
複製程式碼

消費者端和提供者端形成呼叫鏈都呼叫了同一段程式碼:buildInvokerChain() 通過last->next的方式統一新增,然後再新生成的Invoker中呼叫next然後最後呼叫last就是我們真正執行的Invoker,呼叫鏈分類(預設Filter)

ProtocolFilterWrapper.java 類的 buildInvokerChain(final Invoker<T> invoker, String key, String group) 方法 生成新的 Invoker

package com.alibaba.dubbo.rpc.protocol;

public class ProtocolFilterWrapper implements Protocol {

	private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
		
		//我們要處理的那個Invoker作為處理鏈的最後一個
		Invoker<T> last = invoker;
		//根據key和group獲取自動啟用的Filter
		List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
		if (filters.size() > 0) {
		
			// 把所有的過濾器都挨個連線起來,最後一個是我們真正的Invoker,最終形成一個鏈,呼叫順序為filters的順序    
			for (int i = filters.size() - 1; i >= 0; i--) {
				final Filter filter = filters.get(i);
				final Invoker<T> next = last;
				last = new Invoker<T>() {

					public Class<T> getInterface() {
						return invoker.getInterface();
					}

					public URL getUrl() {
						return invoker.getUrl();
					}

					public boolean isAvailable() {
						return invoker.isAvailable();
					}

					public Result invoke(Invocation invocation) throws RpcException {
						return filter.invoke(next, invocation);
					}

					public void destroy() {
						invoker.destroy();
					}

					@Override
					public String toString() {
						return invoker.toString();
					}
				};
			}
		}
		return last;
	}
}
複製程式碼

接著就到了 DubboProtocol.java 類的 export(Invoker<T> invoker) 方法,這裡進行暴露服務:

package com.alibaba.dubbo.rpc.protocol.dubbo;

public class DubboProtocol extends AbstractProtocol {
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
	
        URL url = invoker.getUrl();
		
		/*
		入參: url
		
		dubbo://169.254.8.253:20880/io.ymq.dubbo.api.DemoService?
		anyhost=true&
		application=dubbo-provider&
		default.connections=5&
		default.delay=-1&
		default.retries=0&
		default.timeout=10000&
		default.version=1.0&
		delay=-1&
		dubbo=2.5.6&
		generic=false&
		interface=io.ymq.dubbo.api.DemoService&methods=sayHello&
		
		monitor=dubbo://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?
		application=dubbo-provider&
		dubbo=2.5.6&
		file=/data/dubbo/cache/dubbo-provider&
		pid=13576&
		protocol=registry&
		refer=dubbo%3D2.5.6%26interface%3Dcom.alibaba.dubbo.monitor.MonitorService%26pid%3D13576%26timestamp%3D1525425274459&
		registry=zookeeper&
		timestamp=1525425266251&
        
		pid=13576&
		side=provider&
		threadpool=fixed&
		threads=500&
		timestamp=1525425266793
		*/

        // export service.
		
		//key 由serviceName,port,version,group組成
		//當nio客戶端發起遠端呼叫時,nio服務端通過此key來決定呼叫哪個Exporter,也就是執行的Invoker。
		
		// io.ymq.dubbo.api.DemoService:1.0: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) {
                if (logger.isWarnEnabled()) {
                    logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) +
                            "], has set stubproxy support event ,but no stub methods founded."));
                }
            } else {
                stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
            }
        }

		//根據URL繫結IP與埠,建立NIO框架的Server
        openServer(url);

        return exporter;
    }
}
複製程式碼

上面得到的Exporter會被放到快取中去,key就是上面生成的,客戶端就可以發請求根據key找到Exporter,然後找到invoker進行呼叫了。接下來是建立伺服器並監聽埠。

接著呼叫openServer方法建立NIO Server進行監聽:

DubboProtocol.java 類的 openServer(URL url) 方法

package com.alibaba.dubbo.rpc.protocol.dubbo;

public class DubboProtocol extends AbstractProtocol {

    private void openServer(URL url) {
        // find server.
		 //key是IP:PORT
		//169.254.8.253: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支援reset,配合override功能使用
				
				//同協議的服務後來暴露服務的則使用第一次建立的同一Server
				//accept、idleTimeout、threads、heartbeat引數的變化會引起Server的屬性發生變化
				//這時需要重新設定Server
				
                server.reset(url);
            }
        }
    }
}
複製程式碼

繼續看createServer方法:

DubboProtocol.java 類的 createServer(URL url) 方法

package com.alibaba.dubbo.rpc.protocol.dubbo;

public class DubboProtocol extends AbstractProtocol {

    private ExchangeServer createServer(URL url) {
	
        //預設開啟server關閉時傳送readonly事件
        url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
        //預設開啟heartbeat
        url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
		//預設使用netty
        String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);

        if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
            throw new RpcException("Unsupported server type: " + str + ", url: " + url);

        url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
        ExchangeServer server;
        try {
		    //Exchangers是門面類,裡面封裝的是Exchanger的邏輯。
			//Exchanger預設只有一個實現HeaderExchanger.
			//Exchanger負責資料交換和網路通訊。
			//從Protocol進入Exchanger,標誌著程式進入了remote層。
			//這裡requestHandler是ExchangeHandlerAdapter
            server = Exchangers.bind(url, requestHandler);
        } catch (RemotingException e) {
            throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
        }
        str = url.getParameter(Constants.CLIENT_KEY);
        if (str != null && str.length() > 0) {
            Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
            if (!supportedTypes.contains(str)) {
                throw new RpcException("Unsupported client type: " + str);
            }
        }
        return server;
	}
}
複製程式碼

Exchangers.java 類的 ExchangeServer bind(URL url, ExchangeHandler handler) 方法:

package com.alibaba.dubbo.remoting.exchange;

public class Exchangers {
    public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handler == null) {
            throw new IllegalArgumentException("handler == null");
        }
        url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
		 //getExchanger方法根據url獲取到一個預設的實現HeaderExchanger
		//呼叫HeaderExchanger的bind方法
	
        return getExchanger(url).bind(url, handler);
    }
}
複製程式碼

HeaderExchanger.java 類的 bind(URL url, ExchangeHandler handler) 方法:

package com.alibaba.dubbo.remoting.exchange.support.header;

public class HeaderExchanger implements Exchanger {

    public static final String NAME = "header";

    public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
        return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
    }

    public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
		//直接返回一個HeaderExchangeServer
		//先建立一個HeaderExchangeHandler
		//再建立一個DecodeHandler
		//最後呼叫Transporters.bind
	
		return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
    }

}
複製程式碼

這裡會先建立一個 HeaderExchangerHandler ,包含著ExchangeHandlerAdapter,接著建立一個DecodeHandler,會包含前面的handler,接下來呼叫Transporters的bind方法,返回一個Server,接著用HeaderExchangeServer包裝一下,就返回給Protocol層了。

在HeaderExchangerServer包裝的時候會啟動心跳定時器startHeatbeatTimer();,暫不解析。

Transporters.java 類的 bind(URL url, ChannelHandler... handlers) 方法

package com.alibaba.dubbo.remoting;

public class Transporters {
    public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
		// 省略。。。
        ChannelHandler handler;
        if (handlers.length == 1) {
            handler = handlers[0];
        } else {
		   //如果有多個handler的話,需要使用分發器包裝下
            handler = new ChannelHandlerDispatcher(handlers);
        }
		//getTransporter()獲取一個Adaptive的Transporter
		//然後呼叫bind方法(預設是NettyTransporter的bind方法)
        return getTransporter().bind(url, handler);
    }
	
	
    public static Transporter getTransporter() {
        return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
    }
	
}

複製程式碼

NettyTransporter.java 類的 bind(URL url, ChannelHandler listener) 方法:

package com.alibaba.dubbo.remoting.transport.netty4;

public class NettyTransporter implements Transporter {

    public static final String NAME = "netty4";
    
    public Server bind(URL url, ChannelHandler listener) throws RemotingException {
		//建立一個Server
        return new NettyServer(url, listener);
    }

    public Client connect(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyClient(url, listener);
    }

}
複製程式碼

NettyServer.java 類的 NettyServer(URL url, ChannelHandler handler) 構造方法:

package com.alibaba.dubbo.remoting.transport.netty4;

public class NettyServer extends AbstractServer implements Server {

   // 省略。。。

    public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
		//handler先經過ChannelHandlers的包裝方法
		//然後再初始化
        super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
    }
複製程式碼

ChannelHandlers.wrap方法中會根據SPI擴充套件機制動態生成Dispatcher的自適應類,生成的程式碼不在列出,預設使用AllDispatcher處理,會返回一個AllChannelHandler,會把執行緒池和DataStore都初始化了。然後經過HeartbeatHandler封裝,再經過MultiMessageHandler封裝後返回。

NettyServer構造,會依次經過AbstractPeer,AbstractEndpoint,AbstractServer,NettyServer的初始化。重點看下AbstractServer的構造方法:

AbstractServer.java 類的 AbstractServer(URL url, ChannelHandler handler) 構造方法

package com.alibaba.dubbo.remoting.transport;

public abstract class AbstractServer extends AbstractEndpoint implements Server {
	
    public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
        super(url, handler);
        localAddress = getUrl().toInetSocketAddress();
        String host = url.getParameter(Constants.ANYHOST_KEY, false)
                || NetUtils.isInvalidLocalHost(getUrl().getHost())
                ? NetUtils.ANYHOST : getUrl().getHost();
        bindAddress = new InetSocketAddress(host, getUrl().getPort());
        this.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);
        this.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);
        try {
		
			//初始化的時候會開啟Server
			//具體實現這裡是NettyServer中
			
            doOpen();
            if (logger.isInfoEnabled()) {
                logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
            }
        } catch (Throwable t) {
            throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()
                    + " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);
        }
        //fixme replace this with better method
        DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
        executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
    }
}
複製程式碼

然後呼叫doOpen方法

NettyServer.java 類的 doOpen() 方法

package com.alibaba.dubbo.remoting.transport.netty4;

public class NettyServer extends AbstractServer implements Server {
	
	protected void doOpen() throws Throwable {
		NettyHelper.setNettyLoggerFactory();
		//boss執行緒池
		ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
		//worker執行緒池
		ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
		//ChannelFactory,沒有指定工作者執行緒數量,就使用cpu+1
		ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
		bootstrap = new ServerBootstrap(channelFactory);

		final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
		channels = nettyHandler.getChannels();
		bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
			public ChannelPipeline getPipeline() {
				NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec() ,getUrl(), NettyServer.this);
				ChannelPipeline pipeline = Channels.pipeline();
				pipeline.addLast("decoder", adapter.getDecoder());
				pipeline.addLast("encoder", adapter.getEncoder());
				pipeline.addLast("handler", nettyHandler);
				return pipeline;
			}
		});
		// bind之後返回一個Channel
		channel = bootstrap.bind(getBindAddress());
	}
}
複製程式碼

doOpen方法建立Netty的Server端並開啟,具體的事情就交給Netty去處理了,Netty的過程,原理,程式碼有時間再另行研究。

  • NIO框架接受到訊息後,先由NettyCodecAdapter解碼,再由NettyHandler處理具體的業務邏輯,再由NettyCodecAdapter編碼後傳送。
  • NettyServer既是Server又是Handler。
  • HeaderExchangerServer只是Server。
  • MultiMessageHandler是多訊息處理Handler。
  • HeartbeatHandler是處理心跳事件的Handler。
  • AllChannelHandler是訊息派發器,負責將請求放入執行緒池,並執行請求。
  • DecodeHandler是編解碼Handler。
  • HeaderExchangerHandler是資訊交換Handler,將請求轉化成請求響應模式與同步轉非同步模式。
  • RequestHandler是最後執行的Handler,會在協議層選擇Exporter後選擇Invoker,進而執行Filter與Invoker,最終執行請求服務實現類方法。
  • Channel直接觸發事件並執行Handler,Channel在有客戶端連線Server的時候觸發建立並封裝成NettyChannel,再由HeaderExchangerHandler建立HeaderExchangerChannel,負責請求響應模式的處理。
  • NettyChannel其實是個Handler,HeaderExchangerChannel是個Channel,
  • 訊息的序列化與反序列化工作在NettyCodecAdapter中發起完成。

當有客戶端連線Server時的連線過程

  • NettyHandler.connected()
  • NettyServer.connected()
  • MultiMessageHandler.connected()
  • HeartbeatHandler.connected()
  • AllChannelHandler.connected()
  • DecodeHandler.connected()
  • HeaderExchangerHandler.connected()
  • requestHandler.connected()
  • 執行服務的onconnect事件的監聽方法

名詞解釋

Invoker

可執行的物件,執行具體的遠端呼叫,能夠根據方法名稱,引數得到相應的執行結果。

Invocation,包含了需要執行的方法,引數等資訊。目前實現類只有RpcInvocation。

有三種型別的Invoker

  • 本地執行類的Invoker。
  • 遠端通訊執行類的Invoker。
  • 多個遠端通訊執行類的Invoker聚合成叢集版的Invoker。

以sayHello為例:

  • 本地執行類的Invoker:在Server端有DemoServiceImpl實現,要執行該介面,只需要通過反射執行對應的實現類即可。
  • 遠端通訊執行類的Invoker:在Client端要想執行該介面的實現方法,需要先進行遠端通訊,傳送要執行的引數資訊給Server端,Server端利用本地執行Invoker的方式執行,最後將結果傳送給Client。
  • 叢集版的Invoker:Client端使用的時候,通過叢集版的Invoker操作,Invoker會挑選一個遠端通訊型別的Invoker來執行。

提供者端的Invoker封裝了服務實現類,URL,Type,狀態都是隻讀並且執行緒安全。通過發起invoke來具體呼叫服務類。

ProxyFactory

在服務提供者端,ProxyFactory主要服務的實現統一包裝成一個Invoker,Invoker通過反射來執行具體的Service實現物件的方法。預設的實現是JavassistProxyFactory,程式碼如下:

JavassistProxyFactory.java 類的 getInvoker(T proxy, Class<T> type, URL url) 方法

package com.alibaba.dubbo.rpc.proxy.javassist;

public class JavassistProxyFactory extends AbstractProxyFactory {

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
	
		// TODO Wrapper類不能正確處理帶$的類名
		//第一步封裝一個包裝類(wrapper)
		//該類是手動生成的
		//如果類是以$開頭,就使用介面型別獲取,其他的使用實現類獲取

        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
		
		//返回一個Invoker例項,doInvoke方法中直接返回上面wrapper的invokeMethod
		//關於生成的wrapper,請看下面列出的生成的程式碼,其中invokeMethod方法中就有實現類對實際方法的呼叫
	
        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);
            }
        };
    }

}
複製程式碼

Protocol

服務地址的釋出和訂閱。

Protocol是dubbo中的服務域,只在服務啟用時載入,無狀態,執行緒安全,是實體域Invoker暴露和引用的主功能入口,負責Invoker的生命週期管理,是Dubbo中遠端服務呼叫層。

Protocol根據指定協議對外公佈服務,當客戶端根據協議呼叫這個服務時,Protocol會將客戶端傳遞過來的Invocation引數交給Invoker去執行。

Protocol加入了遠端通訊協議,會根據客戶端的請求來獲取引數Invocation。

package com.alibaba.dubbo.rpc;

@Extension("dubbo")
public interface Protocol {

    int getDefaultPort();

    //對於服務提供端,將本地執行類的Invoker通過協議暴漏給外部
    //外部可以通過協議傳送執行引數Invocation,然後交給本地Invoker來執行
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    //這個是針對服務消費端的,服務消費者從註冊中心獲取服務提供者釋出的服務資訊
    //通過服務資訊得知服務提供者使用的協議,然後服務消費者仍然使用該協議構造一個Invoker。這個Invoker是遠端通訊類的Invoker。
    //執行時,需要將執行資訊通過指定協議傳送給服務提供者,服務提供者接收到引數Invocation,然後交給服務提供者的本地Invoker來執行
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    void destroy();

}
複製程式碼

關於RegistryProtocol和DubboProtocol的疑惑

以下是官方文件說明 :

暴露服務:

(1) 只暴露服務埠

在沒有註冊中心,直接暴露提供者的情況下,即:

<dubbo:service regisrty="N/A" /> or <dubbo:registry address="N/A" />

ServiceConfig解析出的URL的格式為:

dubbo://service-host/com.foo.FooService?version=1.0.0

基於擴充套件點的Adaptive機制,通過URL的”dubbo://”協議頭識別,直接呼叫DubboProtocol的export()方法,開啟服務埠。

(2) 向註冊中心暴露服務

在有註冊中心,需要註冊提供者地址的情況下,即:

<dubbo:registry address="zookeeper://10.20.153.10:2181" />

ServiceConfig解析出的URL的格式為:

registry://registry-host/com.alibaba.dubbo.registry.RegistryService?export=URL.encode("dubbo://service-host/com.foo.FooService?version=1.0.0")

基於擴充套件點的Adaptive機制,通過URL的”registry://”協議頭識別,就會呼叫RegistryProtocol的export()方法,將export引數中的提供者URL,先註冊到註冊中心,再重新傳給Protocol擴充套件點進行暴露:

dubbo://service-host/com.foo.FooService?version=1.0.0

基於擴充套件點的Adaptive機制,通過提供者URL的”dubbo://”協議頭識別,就會呼叫DubboProtocol的export()方法,開啟服務埠。

RegistryProtocol,註冊中心協議整合,裝飾真正暴露引用服務的協議,增強註冊釋出功能。

ServiceConfig中的protocol是被多層裝飾的Protocol,是DubboProtocol+RegistryProtocol+ProtocolListenerWrapper+ProtocolFilterWrapper。

  • ProtocolFilterWrapper負責初始化invoker所有的Filter。
  • ProtocolListenerWrapper負責初始化暴露或引用服務的監聽器。
  • RegistryProtocol負責註冊服務到註冊中心和向註冊中心訂閱服務。
  • DubboProtocol負責服務的具體暴露與引用,也負責網路傳輸層,資訊交換層的初始化,以及底層NIO框架的初始化。

Exporter

負責invoker的生命週期,包含一個Invoker物件,可以撤銷服務。

Exchanger

負責資料交換和網路通訊的元件。每個Invoker都維護了一個ExchangeClient的 引用,並通過它和遠端server進行通訊。

Dubbo中暴露服務的過程解析

相關文章