作者:大程熙
地址:cxis.me/2017/02/19/…
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的步驟簡介
- 首先會檢查各種配置資訊,填充各種屬性,總之就是保證我在開始暴露服務之前,所有的東西都準備好了,並且是正確的。
- 載入所有的註冊中心,因為我們暴露服務需要註冊到註冊中心中去。
- 根據配置的所有協議和註冊中心url分別進行匯出。
- 進行匯出的時候,又是一波屬性的獲取設定檢查等操作。
- 如果配置的不是remote,則做本地匯出。
- 如果配置的不是local,則暴露為遠端服務。
- 不管是本地還是遠端服務暴露,首先都會獲取Invoker。
- 獲取完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®istry=zookeeper×tamp=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
複製程式碼
本地暴露和遠端暴露
- 如果服務配置的scope是釋出範圍,配置為none不暴露服務,則會停止釋出操作;
- 如果配置不是remote的情況下先做本地暴露,則呼叫本地暴露exportLocal方法;
- 如果配置不是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兩個處理邏輯。
- 獲得代理工廠建立的Invoker代理,呼叫getInvoker方法,根據URL中的proxy引數選擇具體的代理工廠,預設選擇JavassistProxyFactory代理工廠進行處理。
- 呼叫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,大概的步驟是:
- 根據上面生成的proxyFactory方法呼叫具體的ProxyFactory實現類的getInvoker方法獲取Invoker。
- getInvoker的過程是,首先對實現類做一個包裝,生成一個包裝後的類。
- 然後新建立一個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處理過程
大概的步驟是:
- 經過兩個不用做任何處理的Wrapper類,然後到達RegistryProtocol中。
- 通過具體的協議匯出Invoker為Exporter。
- 註冊服務到註冊中心。
- 訂閱註冊中心的服務。
- 生成一個新的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
:
- 會先經過
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)));
}
}
複製程式碼
- 再經過
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進行通訊。