Dubbo是基於Netty設計的
而Netty基於NIO
先簡單對NIO和Netty作簡單瞭解
1. NIO
NIO(Non-Blocking IO)起源於jdk1.4
NIO與BIO的作用是相同的,但使用方式完全不同
NIO支持面向緩衝區的、基於通道的IO操作,效率更高
NIO與BIO的區別:
-
BIO:面向流,阻塞
-
NIO:面向緩衝區,非阻塞,基於選擇器
-
使用NIO,在程序讀取文件時,建立的不再是BIO中的“流”,而是通道
-
數據傳輸的載體是緩衝區,這個緩衝區需要流動在通道里
- 可以將通道理解爲鐵路,緩衝區理解爲火車:鐵路本身不具備傳輸能力,但它卻是火車通行的必要條件
在實際高併發中,如果使用BIO,要爲每一個請求都創建一個Socket,而每個Socket又需要對應的IO流,每個IO流又需要單獨的線程,這樣就會導致大量線程阻塞,系統運行緩慢。
如果換用NIO,因爲所有的流都被換爲通道Channel,而通道中使用緩衝區Buffer傳輸數據。所有的通道都交給一個選擇器Selector(也被稱爲多路複用器),由它來監聽這些通道的事件,來分別執行不同的策略。
2. Netty
Netty是一個異步事件驅動的網絡應用程序框架
它通常用於開發可維護的高性能協議服務器和客戶端
使用Netty,可以極大簡化TCP和UDP的SocketServer等網絡編程
Netty基於NIO,主要的底層邏輯如下:
-
Netty服務器啓動,並監聽某一個端口
-
服務器啓動時,會初始化Netty的通道(NioServerSocketChannel),並註冊到Selector(暫且叫AcceptSelector)中,這個AcceptSelector只關心accept事件
-
AcceptSelector會輪詢Netty通道的accept事件
-
當監聽到accept事件後,該AcceptSelector會處理accept事件,並與客戶端建立起一個新的通道(NioSocketChannel)
-
之後,將這個新的通道註冊到另一個Selector(暫且叫ReadWriteSelector)中,這個ReadWriteSelector只感興趣read和write事件
-
ReadWriteSelector輪詢與客戶端連接的通道,當監聽到read就緒時,Netty會從通道中讀數據(基於任務隊列),write事件同理
-
當read和write完成預定目標後,整個流程結束
另外,Netty底層有一個Boss線程組,還有一個Worker線程組
-
Boss線程組負責監聽主線程Netty監聽的端口的連接就緒事件
-
Worker線程組負責具體的讀寫工作
-----
Dubbo是基於RPC調用的,也需要了解RPC的通信原理
3. RPC原理
一次完整的RPC調用過程需要經歷以下步驟
-
服務消費方以本地調用的方式,調用接口的方法(不是真正調用)
-
消費方RPC代理接收到調用請求後,將調用的方法、傳入的參數封裝(序列化)成可以進行網絡通信的消息體
-
消費方RPC代理找到服務地址,並將消息發送給服務提供方
-
提供方RPC代理收到消息後進行消息解碼
-
提供方RPC代理根據解碼後的消息,調用實際的本地服務(真正調用)
-
本地服務執行後將結果返回給提供方RPC代理
-
提供方RPC代理將返回的結果封裝(序列化)成消息體,傳送給消費方RPC代理
-
消費方RPC代理收到服務調用結果後進行消息解碼
-
服務消費方真正得到消息結果
對於實際開發中,只需要使用第1步和第9步,RPC屏蔽掉了2-8的步驟
4. Dubbo框架設計
官方文檔:http://dubbo.apache.org/zh-cn/docs/dev/design.html
4.1 Business部分
在Business部分,只有一個層面:Service
對於Service層,只是提供一個ServiceInterface,再在Provider中寫對應的ServiceImpl即可
也就是說,對於應用而言,到這裏就足以了
下面的部分全部都是Dubbo的底層原理
4.2 RPC部分
自上往下,依次有:
- config 配置層:對外配置接口
- 它是封裝配置文件中的配置信息
- 以ServiceConfig, ReferenceConfig爲中心
- 可以直接初始化配置類,也可以通過Spring解析配置生成配置類
- proxy 服務代理層:服務接口透明代理,生成服務的客戶端Stub和服務器端Skeleton
- 它實際上就是輔助生成RPC的代理對象
- 以ServiceProx爲中心,擴展接口爲ProxyFactory
- registry 註冊中心層:封裝服務地址的註冊與發現
- 這一層就是註冊中心的核心功能層(服務發現、服務註冊)
- 以服務URL爲中心,擴展接口爲RegistryFactory, Registry, RegistryService
- cluster 路由層:封裝多個提供者的路由及負載均衡,並橋接註冊中心
- 這一層的調用者Invoker可以保證多臺服務器的服務調用,以及實現負載均衡
- 以Invoker爲中心,擴展接口爲Cluster, Directory, Router, LoadBalance
- monitor 監控層:RPC調用次數和調用時間監控
- 以Statistics爲中心,擴展接口爲MonitorFactory, Monitor, MonitorService
- protocol 遠程調用層:封裝RPC調用
- 這一層是RPC的調用核心
- 以Invocation, Result爲中心,擴展接口爲Protocol, Invoker, Exporter
4.3 Remoting部分
自上往下,依次有:
- exchange 信息交換層:封裝請求響應模式,同步轉異步
- 這一層負責給服務提供方與服務消費方之間架起連接的管道
- 以Request, Response爲中心,擴展接口爲Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
- transport 網絡傳輸層:抽象mina和netty爲統一接口
- 這一層負責真正的網絡數據傳輸,Netty也就在這一層被封裝
- 以Message爲中心,擴展接口爲Channel, Transporter, Client, Server, Codec
- serialize 數據序列化層:可複用的一些工具
- 擴展接口爲Serialization, ObjectInput, ObjectOutput, ThreadPool
5. Dubbo啓動流程
因爲Dubbo的配置文件實際是Spring的配置文件
而Spring解析配置文件,最終都是迴歸到一個接口:BeanDefinitionParser
那就意味着,Dubbo肯定也提供了對應的BeanDefinitionParser:DubboBeanDefinitionParser
在BeanDefinitionParser中只定義了一個方法:
BeanDefinition parse(Element element, ParserContext parserContext);
下面剖析IOC容器啓動時標籤的解析機制
5.1 Dubbo的標籤解析機制
5.1.1 進入parse方法
可以看到parse方法僅有一句:
public BeanDefinition parse(Element element, ParserContext parserContext) {
return parse(element, parserContext, beanClass, required);
}
注意這裏的beanClass是成員變量而不是參數!
那難道說,每次解析標籤,都是一個全新的DubboBeanDefinitionParser?
5.1.2 自行聲明的parse方法
由於該方法太長,只取關鍵部分
if (ProtocolConfig.class.equals(beanClass)) {
for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
if (property != null) {
Object value = property.getValue();
if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
}
}
}
} else if (ServiceBean.class.equals(beanClass)) {
String className = element.getAttribute("class");
if (className != null && className.length() > 0) {
RootBeanDefinition classDefinition = new RootBeanDefinition();
classDefinition.setBeanClass(ReflectUtils.forName(className));
classDefinition.setLazyInit(false);
parseProperties(element.getChildNodes(), classDefinition);
beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
}
} else if (ProviderConfig.class.equals(beanClass)) {
parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
} else if (ConsumerConfig.class.equals(beanClass)) {
parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
}
這一部分在判斷beanClass的類型!
可Dubbo的配置文件中並沒有寫class=…之類的內容,它怎麼知道的?
那既然不知道beanClass從哪兒來的,就需要追到構造方法中了
5.1.3 DubboBeanDefinitionParser的構造方法
public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) {
this.beanClass = beanClass;
this.required = required;
}
構造方法很簡單,但這個Class<?>的參數從哪兒來的?
5.1.4 追溯到創建DubboBeanDefinitionParser的前一步
通過Debug可以發現,在這一步之前,還有一個調用過程,是調用了一個叫DubboNamespaceHandler的init方法
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 AnnotationBeanDefinitionParser());
}
從這個方法中,可以看到,這是把Dubbo配置文件中可能出現的標籤,全部轉化爲DubboBeanDefinitionParser了
也就是說,並不是解析一個標籤就創建一個DubboBeanDefinitionParser,而是把所有可能出現的標籤全部窮舉了,創建出對應的DubboBeanDefinitionParser,這樣遇到哪個標籤,就使用哪個標籤的解析器而已
而且注意到每次傳入的Class對象,要麼是xxxConfig,要麼是xxxBean
-
xxxConfig是標籤中的配置信息,只能出現一次
-
xxxBean是發佈的服務/引用的服務,每組標籤都會創建一個Bean
對於xxxConfig,只是把配置封裝到對應的Config類裏就可以了
但提供方發佈的服務、消費方的服務引用,是另外一種機制
5.2 Dubbo的服務發佈機制
所有的服務都被封裝爲ServiceBean,而ServiceBean類的定義信息比較複雜
public class ServiceBean<T> extends ServiceConfig<T>
implements InitializingBean, DisposableBean, ApplicationContextAware,
ApplicationListener<ContextRefreshedEvent>, BeanNameAware
這個ServiceBean實現了幾個接口:
-
InitializingBean:實現該接口後,Spring會在該Bean創建完畢後回調afterPropertiesSet方法做後置處理
-
ApplicationListener<ContextRefreshedEvent>:實現該接口後,當Spring的IOC容器初始化完畢後,會回調onApplicationEvent方法
5.2.1 實現InitializingBean的afterPropertiesSet方法
原源碼太長,取部分源碼:
public void afterPropertiesSet() throws Exception {
if (getProvider() == null) {
Map<String, ProviderConfig> providerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class, false, false);
if (providerConfigMap != null && providerConfigMap.size() > 0) {
Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
if ((protocolConfigMap == null || protocolConfigMap.size() == 0)
&& providerConfigMap.size() > 1) { // backward compatibility
List<ProviderConfig> providerConfigs = new ArrayList<ProviderConfig>();
for (ProviderConfig config : providerConfigMap.values()) {
if (config.isDefault() != null && config.isDefault().booleanValue()) {
providerConfigs.add(config);
}
}
if (!providerConfigs.isEmpty()) {
setProviders(providerConfigs);
}
} else {
ProviderConfig providerConfig = null;
for (ProviderConfig config : providerConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (providerConfig != null) {
throw new IllegalStateException("Duplicate provider configs: " + providerConfig + " and " + config);
}
providerConfig = config;
}
}
if (providerConfig != null) {
setProvider(providerConfig);
}
}
}
}
if (getApplication() == null
&& (getProvider() == null || getProvider().getApplication() == null)) {
Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
ApplicationConfig applicationConfig = null;
for (ApplicationConfig config : applicationConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (applicationConfig != null) {
throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
}
applicationConfig = config;
}
}
if (applicationConfig != null) {
setApplication(applicationConfig);
}
}
}
//……………(已忽略)
if (getPath() == null || getPath().length() == 0) {
if (beanName != null && beanName.length() > 0
&& getInterface() != null && getInterface().length() > 0
&& beanName.startsWith(getInterface())) {
setPath(beanName);
}
}
if (!isDelay()) {
export();
}
}
在每一個主幹if結構中,都能看到最後的一句話:
if (providerConfig!= null) {…}
如果判定成功,會把此前創建的那些Dubbo的配置標籤內容都存到這些ServiceBean中
5.2.2 實現ApplicationListener的onApplicationEvent方法
public void onApplicationEvent(ContextRefreshedEvent event) {
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
它調用了一個export方法
5.2.3 【關鍵】回調的export方法:暴露服務
該方法來源於父類ServiceConfig
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() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
前面還是一些判斷,關鍵的步驟在下面的起線程調用doExport方法
5.2.4 doExport方法
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 implementation 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 implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
}
}
checkApplication();
checkRegistry();
checkProtocol();
appendProperties(this);
checkStubAndMock(interfaceClass);
if (path == null || path.length() == 0) {
path = interfaceName;
}
doExportUrls();
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
還是好多好多的判斷、檢查,到最後執行了一個doExportUrls方法
5.2.5 doExportUrls方法
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
這個方法就是暴露服務的URL地址的!
這個方法先加載了註冊中心的URL,之後for循環了一組protocols
這組protocols實際上是Dubbo配置文件中配置的protocol標籤
之後它拿到這個protocol配置和註冊中心的URL,執行了一個doExportUrlsFor1Protocol方法
5.2.6 doExportUrlsFor1Protocol方法
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
name = "dubbo";
}
Map<String, String> map = new HashMap<String, String>();
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
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.isEmpty()) {
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.isEmpty()) {
for (ArgumentConfig argument : arguments) {
// convert argument type
if (argument.getType() != null && argument.getType().length() > 0) {
Method[] methods = interfaceClass.getMethods();
// visit all methods
if (methods != null && methods.length > 0) {
for (int i = 0; i < methods.length; i++) {
String methodName = methods[i].getName();
// target the method, and get its signature
if (methodName.equals(method.getName())) {
Class<?>[] argtypes = methods[i].getParameterTypes();
// one callback in the method
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 attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
}
} else {
// multiple callbacks in the method
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 attribute 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(Constants.GENERIC_KEY, generic);
map.put(Constants.METHODS_KEY, 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(Constants.METHODS_KEY, Constants.ANY_VALUE);
} else {
map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(Constants.TOKEN_KEY, token);
}
}
if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
protocolConfig.setRegister(false);
map.put("notify", "false");
}
// export service
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
}
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = this.findConfigedPorts(protocolConfig, name, map);
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
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);
// don't export when none is configured
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
// export to local if the config is not remote (export to remote only when config is remote)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is 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.isEmpty()) {
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
// For providers, this is used to enable custom proxy to generate invoker
String proxy = url.getParameter(Constants.PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
}
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
} else {
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
}
}
this.urls.add(url);
}
前面的大量內容還是做配置,關鍵的暴露部分在最後的幾行
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass,
registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
先是拿到代理工廠,去獲取了一個Invoker執行器,從代碼層面也可以看出來,它其實是利用代理技術,將接口對應的實現類對象(ref),與該服務的暴露地址等信息封裝到一個新的代理對象中,這個對象就是Invoker執行器。
之後又調用了protocol的export方法
這個protocol對象,在上面可以看到它是使用瞭如下方法:
- ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
這個方法其實是用的Java的SPI技術
5.2.6.1 【擴展】Java的SPI技術
關於Java中原生的SPI技術,可以參照博客
https://www.cnblogs.com/zhongkaiuu/articles/5040971.html
5.2.6.2 【擴展】Dubbo的SPI技術
Dubbo沒有使用jdk原生的SPI技術,而是自己另寫了一套
它的ExtensionLoader就可以類比於jdk的ServiceLoader,它也是用來加載指定路徑下的接口實現
更詳細的Dubbo-SPI,可以參照博客
https://blog.csdn.net/qiangcai/article/details/77750541
5.2.7 protocol的export方法
在Dubbo發佈服務時,由於涉及到註冊中心和服務本身,也就意味着需要找Protocol的兩個實現類,去調用他們的export方法
通過查找Protocol的實現類,可以發現有兩個需要使用的實現類:
RegistryProtocol,DubboProtocol
實際在進行Debug的時候,發現先進RegistryProtocol
5.2.8 RegistryProtocol的export方法
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
URL registryUrl = getRegistryUrl(originInvoker);
//registry provider
final Registry registry = getRegistry(originInvoker);
final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);
//to judge to delay publish whether or not
boolean register = registeredProviderUrl.getParameter("register", true);
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);
if (register) {
register(registryUrl, registeredProviderUrl);
ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
}
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
}
這個方法傳入的參數就是上面的那個服務接口的實現類的代理對象
這裏面有兩個關鍵的步驟:doLocalExport和ProviderConsumerRegTable.registerProvider
按照執行順序,先執行doLocalExport方法
5.2.9 doLocalExport方法
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
String key = getCacheKey(originInvoker);
ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
synchronized (bounds) {
exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}
return exporter;
}
在這段中,一開始這個exporter是空的,需要使用雙檢鎖來構造一個exporter(懶漢單例模式的思路)
而創建的時候,是先構造了一個invokerDelegete,這個對象中包含兩份invoker,都是該服務的實現類代理
之後又調用了這個RegistryProtocol中聚合的另一個protocol的export方法
這個protocol就是上面提到的DubboProtocol
5.2.10 DubboProtocol的export方法
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
//export an stub service for dispatching event
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);
}
}
openServer(url);
optimizeSerialization(url);
return exporter;
}
其實這個DubboProtocol不是指這個Dubbo框架,而是指發佈服務的協議使用的是dubbo協議
此外,從上面的Protocol的實現類來看,還有http的,Hessian的,WebService的,等等等
所以這裏的Dubbo並不是指框架,而是dubbo協議
這個方法一開始先從Invoker中拿到了url,之後封裝成一個DubboExporter對象
之後進行一部分if檢查後來到了另外一個關鍵的方法:openServer
5.2.11 【關鍵】openServer方法
private void openServer(URL url) {
// find server.
String key = url.getAddress();
//client can export a service which's only for server to invoke
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
if (isServer) {
ExchangeServer server = serverMap.get(key);
if (server == null) {
serverMap.put(key, createServer(url));
} else {
// server supports reset, use together with override
server.reset(url);
}
}
}
在這裏,因爲一開始是沒有ExchangeServer的,需要調用createServer方法
5.2.12 createServer方法
private ExchangeServer createServer(URL url) {
// send readonly event when server closes, it's enabled by default
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
// enable heartbeat by default
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
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, DubboCodec.NAME);
ExchangeServer server;
try {
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;
}
在創建ExchangeServer時,核心是try-catch中的那一句Exchanges.bind方法,要對url和requestHandler進行綁定
5.2.13 【Netty】Exchanges.bind方法
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");
return getExchanger(url).bind(url, handler);
}
public static Exchanger getExchanger(URL url) {
String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
return getExchanger(type);
}
public static Exchanger getExchanger(String type) {
return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
}
從這裏開始,就一步一步導向Netty了
因爲這裏前面都是檢查,到最後return的時候,又是調用了另一個Exchanger的bind方法
這部分過程還是使用了SPI技術
使用IDE引導查看這個bind方法,會來到另外一個Exchanger:HeaderExchanger
5.2.13.1 HeaderExchanger的bind方法
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
在這個方法中,又是調用了Transporters的靜態方法bind
5.2.13.2 Transporters的bind方法
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handlers == null || handlers.length == 0) {
throw new IllegalArgumentException("handlers == null");
}
ChannelHandler handler;
if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
return getTransporter().bind(url, handler);
}
可以發現這裏面已經出現ChannelHandler了,也就是NIO相關的東西了!
最後一步還是bind,不過這次不再是Exchanger的bind,而是Transporter的bind
而這個Transporter,不再是某一個固定的實現類了,而是幾個實現類中的一個
也就是這個地方,出現了使用Netty
這也就說明了Dubbo的RPC底層使用的是Netty
到這兒,openServer方法就執行完畢了
這段源碼實際做的事情,是Netty底層啓動,並監聽服務暴露的地址
5.2.14 回到DubboProtocol的export方法
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// ………
openServer(url);
optimizeSerialization(url);
return exporter;
}
數據交換服務器啓動後,將url序列化一下,整段export方法就執行完畢了
5.2.15 回到RegistryProtocol的export方法
前面提到了export方法中第二個關鍵方法是ProviderConsumerRegTable.registerProvider,進入到該方法:
5.2.16 ProviderConsumerRegTable.registerProvider方法
public static ConcurrentHashMap<String, Set<ProviderInvokerWrapper>> providerInvokers = new ConcurrentHashMap<String, Set<ProviderInvokerWrapper>>();
public static ConcurrentHashMap<String, Set<ConsumerInvokerWrapper>> consumerInvokers = new ConcurrentHashMap<String, Set<ConsumerInvokerWrapper>>();
public static void registerProvider(Invoker invoker, URL registryUrl, URL providerUrl) {
ProviderInvokerWrapper wrapperInvoker = new ProviderInvokerWrapper(invoker, registryUrl, providerUrl);
String serviceUniqueName = providerUrl.getServiceKey();
Set<ProviderInvokerWrapper> invokers = providerInvokers.get(serviceUniqueName);
if (invokers == null) {
providerInvokers.putIfAbsent(serviceUniqueName, new ConcurrentHashSet<ProviderInvokerWrapper>());
invokers = providerInvokers.get(serviceUniqueName);
}
invokers.add(wrapperInvoker);
}
注意這個類的名:提供方-消費方的註冊表,也就是說,這個類會保存服務提供方和消費方的調用地址和代理對象
可以看到在ProviderConsumerRegTable類中保存了兩個Map,這兩個Map實際上是保存了服務URL以及執行這個服務的Invoker(這些Invoker裏面有被代理的真正的服務實現類對象)
等上述方法完全執行完畢後,Dubbo服務發佈完成。
5.3 Dubbo的服務消費機制
所有的服務引用都被封裝爲ReferenceBean,而ReferenceBean類的定義信息比較複雜
public class ReferenceBean<T> extends ReferenceConfig<T>
implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean
這個ReferenceBean實現了幾個接口:
-
FactoryBean:Spring的工廠Bean
-
InitializingBean:實現該接口後,Spring會在該Bean創建完畢後回調afterPropertiesSet方法做後置處理
5.3.1 實現FactoryBean的效果
因爲ReferenceBean實現了該接口,所以在Controller引用這些Service時,由於Spring要完成自動注入,所以要調用這個ReferenceBean的getObject方法
5.3.2 調用getObject方法初始化服務引用
public Object getObject() throws Exception {
return get();
}
//該方法來源於父類ReferenceConfig
public synchronized T get() {
if (destroyed) {
throw new IllegalStateException("Already destroyed!");
}
if (ref == null) {
init();
}
return ref;
}
它調用了父類的get方法,而在父類ReferenceConfig中定義了init方法
5.3.3 init方法初始化服務引用
private void init() {
if (initialized) {
return;
}
initialized = true;
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
}
// get consumer's global configuration
checkDefault();
appendProperties(this);
if (getGeneric() == null && getConsumer() != null) {
setGeneric(getConsumer().getGeneric());
}
if (ProtocolUtils.isGeneric(getGeneric())) {
interfaceClass = GenericService.class;
} else {
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
checkInterfaceAndMethods(interfaceClass, methods);
}
// ………………
//attributes are stored by system context.
StaticContext.getSystemContext().putAll(attributes);
ref = createProxy(map);
ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}
前面還是跟服務發佈一樣,大量的檢查判斷
到最後有一句:createProxy,它負責創建服務的引用代理
5.3.4 【關鍵】createProxy方法
private T createProxy(Map<String, String> map) {
URL tmpUrl = new URL("temp", "localhost", 0, map);
final boolean isJvmRefer;
if (isInjvm() == null) {
if (url != null && url.length() > 0) { // if a url is specified, don't do local reference
isJvmRefer = false;
} else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
// by default, reference local service if there is
isJvmRefer = true;
} else {
isJvmRefer = false;
}
} else {
isJvmRefer = isInjvm().booleanValue();
}
if (isJvmRefer) {
URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
invoker = refprotocol.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
} else {
if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (url.getPath() == null || url.getPath().length() == 0) {
url = url.setPath(interfaceName);
}
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
} else {
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else { // assemble URL from register center's configuration
List<URL> us = loadRegistries(false);
if (us != null && !us.isEmpty()) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
}
}
if (urls.isEmpty()) {
throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
}
}
if (urls.size() == 1) {
invoker = refprotocol.refer(interfaceClass, urls.get(0));
} else {
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
invokers.add(refprotocol.refer(interfaceClass, url));
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // use last registry url
}
}
if (registryURL != null) { // registry url is available
// use AvailableCluster only when register's cluster is available
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
invoker = cluster.join(new StaticDirectory(u, invokers));
} else { // not a registry url
invoker = cluster.join(new StaticDirectory(invokers));
}
}
}
Boolean c = check;
if (c == null && consumer != null) {
c = consumer.isCheck();
}
if (c == null) {
c = true; // default true
}
if (c && !invoker.isAvailable()) {
throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
}
if (logger.isInfoEnabled()) {
logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
}
// create service proxy
return (T) proxyFactory.getProxy(invoker);
}
這個方法傳入了一個Map,裏面存了註冊中心的相關配置、服務的相關信息(接口名、方法名、超時時間等)
到中間,有一句最關鍵的:
invoker = refprotocol.refer(interfaceClass, urls.get(0));
它要使用refProtocol去註冊中心遠程引用目標服務接口的代理引用(url的內容就是註冊中心的地址)
而這個Protocol又是跟上面一樣,先進RegistryProtocol,再進DubboProtocol
5.3.5 RegistryProtocol的refer方法
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
Registry registry = registryFactory.getRegistry(url);
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
// group="a,b" or group="*"
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
String group = qs.get(Constants.GROUP_KEY);
if (group != null && group.length() > 0) {
if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
|| "*".equals(group)) {
return doRefer(getMergeableCluster(), registry, type, url);
}
}
return doRefer(cluster, registry, type, url);
}
與上面調用export類似,最關鍵的方法還是以do開頭的:doRefer方法
5.3.6 doRefer方法
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
directory.setRegistry(registry);
directory.setProtocol(protocol);
// all attributes of REFER_KEY
Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));
}
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
Invoker invoker = cluster.join(directory);
ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
return invoker;
}
在整個方法調用中,最核心的是去註冊中心訂閱服務
也就是說,這一步就是負責去訂閱服務提供者暴露的服務
當執行cluster.join方法時,會跳轉到DubboProtocol的refer方法中(SPI)
5.3.7 DubboProtocol的refer方法
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
optimizeSerialization(url);
// create rpc invoker.
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
與服務發佈類似,不過在這裏創建的是DubboInvoker,而這裏面最關鍵的是那一句getClients
5.3.8 【關鍵】getClients方法
private ExchangeClient[] getClients(URL url) {
// whether to share connection
boolean service_share_connect = false;
int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
// if not configured, connection is shared, otherwise, one connection for one service
if (connections == 0) {
service_share_connect = true;
connections = 1;
}
ExchangeClient[] clients = new ExchangeClient[connections];
for (int i = 0; i < clients.length; i++) {
if (service_share_connect) {
clients[i] = getSharedClient(url);
} else {
clients[i] = initClient(url);
}
}
return clients;
}
這個方法跟上面的openServer類似
而這些客戶端從哪裏來,需要調用getSharedClient或initClient
5.3.9 getSharedClient方法與initClient方法
private ExchangeClient getSharedClient(URL url) {
String key = url.getAddress();
ReferenceCountExchangeClient client = referenceClientMap.get(key);
if (client != null) {
if (!client.isClosed()) {
client.incrementAndGetCount();
return client;
} else {
referenceClientMap.remove(key);
}
}
locks.putIfAbsent(key, new Object());
synchronized (locks.get(key)) {
if (referenceClientMap.containsKey(key)) {
return referenceClientMap.get(key);
}
ExchangeClient exchangeClient = initClient(url);
client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
referenceClientMap.put(key, client);
ghostClientMap.remove(key);
locks.remove(key);
return client;
}
}
private ExchangeClient initClient(URL url) {
// client type setting.
String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
// enable heartbeat by default
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
// BIO is not allowed since it has severe performance issue.
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
throw new RpcException("Unsupported client type: " + str + "," +
" supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
}
ExchangeClient client;
try {
// connection should be lazy
if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
client = new LazyConnectExchangeClient(url, requestHandler);
} else {
client = Exchangers.connect(url, requestHandler);
}
} catch (RemotingException e) {
throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
}
return client;
}
可以發現,getSharedClient方法最終也是調用的initClient方法,所以核心還是在initClient方法中 前面還是值的檢驗、判空等邏輯
在最後的try-catch結構中,針對是否爲延遲連接,有兩種策略:
-
if中是延遲連接,在應用啓動時不連接到註冊中心
-
else中是非延遲連接,應用啓動時就與註冊中心建立連接
由於大多屬配置都是預先連接,故繼續看Exchangers的connect方法
5.3.10 【Netty】Exchangers.connect方法
public static ExchangeClient connect(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");
return getExchanger(url).connect(url, handler);
}
在最後一步,看到了與上面bind方法極爲相似的源碼
這也就意味着,這裏面的底層也是Netty
5.3.11 回到RegistryProtocol的refer方法
服務訂閱完成後,返回的還是Invoker,裏面存的是兩份directory,每一份directory中存放着服務的信息、客戶端的信息等
最後一步,又是那個註冊表,這次它把服務消費方也註冊進去了(不再貼源碼)
最終回到init方法,初始化完畢。
6. Dubbo的服務調用流程
Dubbo的調用鏈在官方文檔中也有描述:http://dubbo.apache.org/zh-cn/docs/dev/design.html
以源碼解析爲標準,分析調用過程
xxxService.xxxMethod();
當遠程調用方法時,經歷瞭如下過程
6.1 代理對象調用服務
上述的xxxService其實是一個代理對象,它基於InvokerInvocationHandler
public class InvokerInvocationHandler implements InvocationHandler {
private final Invoker<?> invoker;
public InvokerInvocationHandler(Invoker<?> handler) {
this.invoker = handler;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
if ("toString".equals(methodName) && parameterTypes.length == 0) {
return invoker.toString();
}
if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
return invoker.hashCode();
}
if ("equals".equals(methodName) && parameterTypes.length == 1) {
return invoker.equals(args[0]);
}
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}
}
可以發現,在除了上面的幾個特殊的方法,剩下的都是統一的調用方案:
invoker.invoke(new RpcInvocation(method, args)).recreate();
在調用之前,要先把調用的方法和參數統一封裝成一個RpcInvocation對象
之後再調用invoke方法,它傳到了MockClusterInvoker類的invoke方法
(MockClusterInvoker是做本地僞裝,意爲如果建立連接後服務器宕機,客戶端不拋異常,而採取拋null)
(有關本地僞裝的配置可參照官方文檔:http://dubbo.apache.org/zh-cn/docs/user/demos/local-mock.html)
6.2 MockClusterInvoker的invoke
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
if (value.length() == 0 || value.equalsIgnoreCase("false")) {
//no mock
result = this.invoker.invoke(invocation);
} else if (value.startsWith("force")) {
if (logger.isWarnEnabled()) {
logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
}
//force:direct mock
result = doMockInvoke(invocation, null);
} else {
//fail-mock
try {
result = this.invoker.invoke(invocation);
} catch (RpcException e) {
if (e.isBiz()) {
throw e;
} else {
if (logger.isWarnEnabled()) {
logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e);
}
result = doMockInvoke(invocation, e);
}
}
}
return result;
}
在這裏面,第一個if結構裏又一個invoker.invoke方法,這個invoker實際上是FailoverClusterInvoker(集羣容錯與負載均衡的Invoker)
這就應該意識到這是層層包裝的結構了,類似於攔截器機制
再往裏走,因爲FailoverClusterInvoker有父類AbstractClusterInvoker,它重寫了invoke方法(FailoverClusterInvoker並沒有重寫)
6.3 AbstractClusterInvoker的invoke方法
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
LoadBalance loadbalance = null;
// binding attachments into invocation.
Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
if (contextAttachments != null && contextAttachments.size() != 0) {
((RpcInvocation) invocation).addAttachments(contextAttachments);
}
List<Invoker<T>> invokers = list(invocation);
if (invokers != null && !invokers.isEmpty()) {
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
.getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
}
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}
在中間,它要調用list方法來獲取一組Invoker,這個方法是去註冊中心找有多少個能遠程調用該服務的可用服務Invoker
——| 其實list方法裏只有一句:directory.list(invocation);
之後的if結構是負責處理負載均衡的
最後return的時候又去調doInvoke方法
由於在AbstractClusterInvoker中該方法爲模板方法,根據多態,又回到FailoverClusterInvoker中調用
6.4 FailoverClusterInvoker的doInvoke方法
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException{
List<Invoker<T>> copyinvokers = invokers;
checkInvokers(copyinvokers, invocation);
int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
if (len <= 0) {
len = 1;
}
// retry loop.
RpcException le = null; // last exception.
List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // invoked invokers.
Set<String> providers = new HashSet<String>(len);
for (int i = 0; i < len; i++) {
//Reselect before retry to avoid a change of candidate `invokers`.
//NOTE: if `invokers` changed, then `invoked` also lose accuracy.
if (i > 0) {
checkWhetherDestroyed();
copyinvokers = list(invocation);
// check again
checkInvokers(copyinvokers, invocation);
}
Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
invoked.add(invoker);
RpcContext.getContext().setInvokers((List) invoked);
try {
Result result = invoker.invoke(invocation);
if (le != null && logger.isWarnEnabled()) {
logger.warn("Although retry the method " + invocation.getMethodName()
+ " in the service " + getInterface().getName()
+ " was successful by the provider " + invoker.getUrl().getAddress()
+ ", but there have been failed providers " + providers
+ " (" + providers.size() + "/" + copyinvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost()
+ " using the dubbo version " + Version.getVersion() + ". Last error is: "
+ le.getMessage(), le);
}
return result;
} catch (RpcException e) {
if (e.isBiz()) { // biz exception.
throw e;
}
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
providers.add(invoker.getUrl().getAddress());
}
}
throw new RpcException(le != null ? le.getCode() : 0, "Failed to invoke the method "
+ invocation.getMethodName() + " in the service " + getInterface().getName()
+ ". Tried " + len + " times of the providers " + providers
+ " (" + providers.size() + "/" + copyinvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "
+ Version.getVersion() + ". Last error is: "
+ (le != null ? le.getMessage() : ""), le != null && le.getCause() != null ? le.getCause() : le);
}
在循環的try-catch中,又是invoker.invoke方法
但這裏面不再是ClusterInvoker了,根據調用流程圖,可以發現,接下來是一組Filter
把這組Filter走完之後,最終負責執行的是DubboInvoker
6.5 DubboInvoker的doInvoke方法
protected Result doInvoke(final Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
final String methodName = RpcUtils.getMethodName(invocation);
inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
inv.setAttachment(Constants.VERSION_KEY, version);
ExchangeClient currentClient;
if (clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
if (isOneway) {
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
RpcContext.getContext().setFuture(null);
return new RpcResult();
} else if (isAsync) {
ResponseFuture future = currentClient.request(inv, timeout);
RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
return new RpcResult();
} else {
RpcContext.getContext().setFuture(null);
return (Result) currentClient.request(inv, timeout).get();
}
} catch (TimeoutException e) {
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
} catch (RemotingException e) {
throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
第一組if-else中,需要拿到當前請求的客戶端,之後再try-catch的最後一個return中執行request方法
這個方法就是真正請求遠程調用的方法
6.6 【Netty】client.request方法
這個request經歷了兩次跳轉,最終來到HeaderExchangeChannel類的request方法
//ReferenceCountExchangeClient
public ResponseFuture request(Object request, int timeout) throws RemotingException {
return client.request(request, timeout);
}
//HeaderExchangeClient
public ResponseFuture request(Object request, int timeout) throws RemotingException {
return channel.request(request, timeout);
}
//HeaderExchangeChannel
public ResponseFuture request(Object request, int timeout) throws RemotingException {
if (closed) {
throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
}
// create request.
Request req = new Request();
req.setVersion(Version.getProtocolVersion());
req.setTwoWay(true);
req.setData(request);
DefaultFuture future = new DefaultFuture(channel, req, timeout);
try {
channel.send(req);
} catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}
在最後的try-catch中,用channel把請求發送出去
而這個Channel,就是底層調用Netty的Channel進行通信調用了。
最終獲取到結果後,判斷一次是否超時,如果沒有超時,完整的遠程服務調用就完成了。