服務發佈
啓動流程
1.ServiceConfig#export
服務提供方在啓動部署時,dubbo會調用ServiceConfig#export
來激活服務發佈流程,如下所示:
- Java API:
// 1. 創建ServiceConfig實例
ServiceConfig serviceConfig = new ServiceConfig<>();
// 2. 設置應用程序配置
serviceConfig.setApplication(new ApplicationConfig(“deep-in-dubbo-first-provider”));
// 3. 設置註冊中心
RegistryConfig registryConfig = new RegistryConfig(“zookeeper://127.0.0.1:2181/”);
serviceConfig.setRegistry(registryConfig);
// 4. 設置接口和實現類
// 5. 設置服務分組和版本
// dubbo中,服務接口+服務分組+服務版本 唯一的確定一個服務,同一個接口可以有不同版本,方便維護升級
serviceConfig.setInterface(IGreetingService.class);
serviceConfig.setRef(new GreetingServiceImpl());
serviceConfig.setVersion(“1.0.0”);
serviceConfig.setGroup(“dubbo-sxzhongf-group”);
RpcContext.getContext().setAttachment(“age”,“18”);
// 7. 導出服務,啓動Netty監聽鏈接請求,並將服務註冊到註冊中心
serviceConfig.export();
// 8. 掛起線程,避免服務停止
System.out.println("api provider service is started...");
System.in.read();
```
- XML
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- provider's application name, used for tracing dependency relationship -->
<dubbo:application name="first-xml-provider"/>
<!-- use multicast registry center to export service -->
<dubbo:registry address="zookeeper://127.0.0.1:2181/"/>
<!-- use dubbo protocol to export service on port 20880 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- service implementation, as same as regular local bean -->
<bean id="demoService" class="com.sxzhongf.deep.in.dubbo.provider.service.impl.GreetingServiceImpl"/>
<!-- declare the service interface to be exported -->
<dubbo:service interface="com.sxzhongf.deep.in.dubbo.api.service.IGreetingService"
ref="demoService" version="1.0.0" group="dubbo-sxzhongf-group">
<dubbo:method name="sayHello" async="false" timeout="0" retries="3"></dubbo:method>
<dubbo:method name="testGeneric" async="false" timeout="10000" retries="3"></dubbo:method>
</dubbo:service>
</beans>
查看export
源碼可知,總共有三種服務導出選項:
java public synchronized void export() { //1. 是否導出 if (!shouldExport()) { return; } ... //2.延遲導出 if (shouldDelay()) { DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS); } else { //3.立刻導出 doExport(); } }
2.ServiceConfig#doExport
此方法主要是根據設置的屬性進行合法性檢查,主要包含是否已被導出,doExportUrls();
3.doExportUrls
4.ConfigValidationUtils#loadRegistries
此方法用來加載所有的服務註冊中心對象,在dubbo中,一個service可以被註冊到多個註冊中心。
通過
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
5.doExportUrlsFor1Protocol
在此方法中會將所有的參數封裝成
org.apache.dubbo.common.URL
對象,然後執行具體的服務導出。
具體過程分爲:
-
1.解析MethodConfig配置(單獨的方法調用參數設置)
-
2.泛型調用類型設置
-
3.拼接URL參數
-
4.導出具體服務
導出又分爲四種範圍(
scope
):-
SCOPE_NONE = “none”,如果設定爲none,表示該服務不導出。
-
SCOPE_LOCAL = “local” ,如果設定爲local,表示該服務導出到本地(injvm–僞協議,實現類爲:
org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol
)- SCOPE_REMOTE = “remote”,如果設定爲remote,表示該服務導出到遠程。
-
如果有註冊中心,發佈到註冊中心
-
如果沒有註冊中心,則表示服務是直連方式
-
從
dubbo-2.7.0
開始,新增加了WritableMetadataService
來存儲dubbo 服務的元數據,元數據可以存儲在遠端配置中心和本地,默認是存儲在本地,通過設置:METADATA_KEY = "metadata"
-
DEFAULT_METADATA_STORAGE_TYPE = “local”
-
REMOTE_METADATA_STORAGE_TYPE = “remote”
WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE)); if (metadataService != null) { metadataService.publishServiceDefinition(url); }
-
不設置,導出到本地和遠端
-
-
最終執行導出的代碼如下
// 擴展適配類 private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); // 擴展適配類 private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); ... Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); Exporter<?> exporter = protocol.export(wrapperInvoker); exporters.add(exporter);
由於
protocol
和PROXY_FACTORY
都是擴展適配類,跟蹤代碼我們可以發現:-
執行
PROXY_FACTORY.getInvoker
的時候實際上首先執行擴展接口ProxyFactory
的適配類ProxyFactory$Adaptive
的getInvoker
方法,根據URL
中參數proxy
的設置類型選擇具體的代理工廠,默認使用的是javassist
,因此又調用了org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory#getInvoker
來獲取代理實現類,代碼如下:public class JavassistProxyFactory extends AbstractProxyFactory { ... @Override public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { // TODO Wrapper cannot handle this scenario correctly: the classname contains '$' // 這裏使用javassist動態代理生成serviceImpl實現類的包裝類`Wraaper...` final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type); return new AbstractProxyInvoker<T>(proxy, type, url) { @Override protected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable { return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); } }; } ... }
上面代碼有2個目的:
inal Wrapper wrapper = Wrapper.getWrapper(...);
用來生成具體serviceImpl
的包裝類,減少反射的性能損耗;return new AbstractProxyInvoker<T>...
返回了一個抽象的代理invoker
,並且重寫了doInvoker
方法,重寫之後使用包裝類中的invokeMethod
來調用方法。
-
-
經過上述2步,服務提供方就將具體的實現類轉換爲Invoker
代理。
- 然後,當執行
protocol.export()
,實際上也是調用了Protocol$Adaptive#export()
方法,同時也分爲兩種情況 - 如果爲遠程暴露,則執行
RegistryProtocol#export
- 如果爲本地暴露,則只需
InjvmProtocol#export
由於dubbo的增強SPI
特性支持,injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
,則在調用之前會一層一層調用,ProtocolFilterWrapper
->ProtocolListenerWrapper
->QosProtocolWrapper
,最後會調用export
方法,此方法會將Invoker
轉換爲Exporter
對象,在org.apache.dubbo.registry.integration.RegistryProtocol#export
方法中,org.apache.dubbo.registry.integration.RegistryProtocol#doLocalExport
方法啓NettyServer
來監聽服務,org.apache.dubbo.registry.integration.RegistryProtocol#register
將當前的服務註冊到註冊中心。 doLocalExport
是如何啓動NettyServer
呢?
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
String key = getCacheKey(originInvoker);
return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
});
}
此時URL
中的protocol
類型爲默認的dubbo
,因此會執行DubboProtocol#export
進行轉換,如下:
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
String key = serviceKey(url);
// invoker->exporter
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
//export an stub service for dispatching event
Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice) {
String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
if (logger.isWarnEnabled()) {
logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
} else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
}
//創建server
openServer(url);
//序列化提示
optimizeSerialization(url);
return exporter;
}
可以看到代碼執行到openServer
,因爲key=getAddress()=ip+port
,因此,同一臺機器只會開啓一個NettyServer
.
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(IS_SERVER_KEY, true);
if (isServer) {
ProtocolServer server = serverMap.get(key);
if (server == null) {
synchronized (this) {
server = serverMap.get(key);
if (server == null) {
serverMap.put(key, createServer(url));
}
}
} else {
// server supports reset, use together with override
server.reset(url);
}
}
}
對於org.apache.dubbo.remoting.Transporter
的適配類選擇有三種:MinaTransporter
、 NettyTransporter
、GrizzlyTransporter
,關於JavaNIO:Apache Mina、JBoss Netty、Sun Grizzly 框架對比:傳送門
- NettyServer啓動之後,回到
org.apache.dubbo.registry.integration.RegistryProtocol#export
方法,繼續執行將服務註冊到註冊中心,我們以Zookeeper
爲例: - 1.首先查找所有註冊中心
final Registry registry = getRegistry(originInvoker);
...
protected Registry getRegistry(final Invoker<?> originInvoker) {
URL registryUrl = getRegistryUrl(originInvoker);
return registryFactory.getRegistry(registryUrl);
}
因爲RegistryFactory
是一個SPI擴展接口,代碼中設置的爲zookeeper
,因此這裏調用的是ZookeeperRegistryFactory
,繼承自:org.apache.dubbo.registry.support.AbstractRegistryFactory#getRegistry(org.apache.dubbo.common.URL)
,在此方法中調用了createRegistry
,但是ZookeeperRegistryFactory
重寫了createRegistry
,因此具體調用的是ZookeeperRegistryFactory#createRegistry
,該方法返回了一個new ZookeeperRegistry(url, zookeeperTransporter)
實例對象。
- 2.開始註冊,
RegistryProtocol#register
方法執行註冊動作,首先獲取到我們在上一步找到的註冊中心ZookeeperRegistry
,ZookeeperRegistry
執行父類org.apache.dubbo.registry.support.FailbackRegistry#register
,在該方法中會調用抽象方法:doRegister
,ZookeeperRegistry
重寫了改方法,則執行ZookeeperRegistry#doRegister
,如下:
@Override
public void doRegister(URL url) {
try {
zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
- 3.
toUrlPath
方法會把org.apache.dubbo.common.URL
轉換格式後存儲到zookeeper,如下:
dubbo://172.16.44.21:20880/com.sxzhongf.deep.in.dubbo.api.service.IGreetingService?anyhost=true&application=deep-in-dubbo-first-provider&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=dubbo-sxzhongf-group&interface=com.sxzhongf.deep.in.dubbo.api.service.IGreetingService&methods=sayHello,testGeneric&pid=8480&release=2.7.5&revision=1.0.0&side=provider×tamp=1582872610313&version=1.0.0
-----------------------轉換------------------------
/dubbo/com.sxzhongf.deep.in.dubbo.api.service.IGreetingService/providers/dubbo%3A%2F%2F172.16.44.21%3A20880%2Fcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%3Fanyhost%3Dtrue%26application%3Ddeep-in-dubbo-first-provider%26default%3Dtrue%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Ddubbo-sxzhongf-group%26interface%3Dcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%26methods%3DsayHello%2CtestGeneric%26pid%3D8480%26release%3D2.7.5%26revision%3D1.0.0%26side%3Dprovider%26timestamp%3D1582872610313%26version%3D1.0.0
轉換之後的格式其實就是我們在zookeeper中看到的一樣了,不過有幾個目錄:
- dubbo
- com.sxzhongf.deep.in.dubbo.api.service.IGreetingService
- providers
[zk: localhost:2181(CONNECTED) 2] ls /dubbo/com.sxzhongf.deep.in.dubbo.api.service.IGreetingService/providers
[dubbo%3A%2F%2F172.16.44.21%3A20880%2Fcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%3Fanyhost%3Dtrue%26application%3Ddeep-in-dubbo-first-provider%26default%3Dtrue%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Ddubbo-sxzhongf-group%26interface%3Dcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%26methods%3DsayHello%2CtestGeneric%26pid%3D15716%26release%3D2.7.5%26revision%3D1.0.0%26side%3Dprovider%26timestamp%3D1582872850187%26version%3D1.0.0]
至此,服務消費端就可以從註冊中心獲取服務提供service進行調用了,下節我們繼續來分析,消費端是如何從註冊中心拉取service進行處理的。