[dubbo 原始碼之 ]1. 服務提供方如何釋出服務
服務釋出
啟動流程
1.ServiceConfig#export
服務提供方在啟動部署時,dubbo會呼叫ServiceConfig#export
來啟用服務釋出流程,如下所示:
- Java API:
// 1. 建立ServiceConfig例項 ServiceConfig<IGreetingService> 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"
java /** * @since 2.7.0 * ServiceData Store */ 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(); /** * A {@link ProxyFactory} implementation that will generate a exported service proxy,the JavassistProxyFactory is its * default implementation */ // 擴充套件適配類 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
來獲取代理實現類,程式碼如下:/** * JavaassistRpcProxyFactory */ 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進行處理的