1. 程式人生 > >7.2 服務本地暴露

7.2 服務本地暴露

mage equals extends fail cas gis provide pack per

服務暴露的流程其實就是下邊這樣(圖片來自:http://www.iteye.com/topic/1123039)

技術分享

簡單看一下服務暴露的偽代碼:

 1 /**
 2  * dubbo 服務暴露為代碼
 3  */
 4 public class DubboProviderSimpleCode {
 5     private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();
 6     /**
 7      * 獲取註冊中心url列表
 8      * [ registry://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&client=curator&dubbo=2.0.0&pid=2956&registry=zookeeper&timestamp=1507004600231 ]
9 */ 10 List<URL> registryURLs = loadRegistries();//獲取註冊中心url列表 11 for (ProtocolConfig protocolConfig : protocols) { 12 /** 13 * 創建協議url 14 * dubbo://10.243.62.133:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=2956&side=provider&timestamp=1507004625957
15 */ 16 URL url = new URL(name, host, port, path, map); 17 /** 18 * 本地暴露 19 */ 20 if (<dubbo:service scope!=remote>) { 21 /** 22 * 構造injvm協議的url 23 * injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=2956&side=provider&timestamp=1507004625957
24 */ 25 URL local = URL.valueOf(url.toFullString()) 26 .setProtocol(Constants.LOCAL_PROTOCOL) 27 .setHost(NetUtils.LOCALHOST) 28 .setPort(0); 29 Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, local); 30 Exporter<?> exporter = protocol.export(invoker); 31 exporters.add(exporter); 32 } 33 /** 34 * 遠程暴露 35 */ 36 if (<dubbo:service scope!=local>) { 37 for (URL registryURL : registryURLs) { 38 Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); 39 Exporter<?> exporter = protocol.export(invoker); 40 exporters.add(exporter); 41 } 42 } 43 } 44 }

本地暴露:

  • 通過JavassistProxyFactory(默認)將具體的實現類包裝成AbstractProxyInvoker實例
  • InjvmProtocol將上述的AbstractProxyInvoker實例轉換成Exporter

遠程暴露:

  • 通過JavassistProxyFactory(默認)將具體的實現類包裝成AbstractProxyInvoker實例
  • DubboProtocol將上述的AbstractProxyInvoker實例轉換成Exporter

本節來看本地暴露。首先給出整個本地服務暴露的調用鏈

 1 ServiceBean.onApplicationEvent(ApplicationEvent event)
 2 -->ServiceConfig.export()
 3    -->doExport()
 4       -->doExportUrls()
 5          -->loadRegistries(boolean provider) //
 6          -->doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs)
 7             -->Wrapper getWrapper(Class<?> c)
 8                -->makeWrapper(Class<?> c)
 9             -->new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map)
10             
11             <!--   本地暴露   -->
12             -->exportLocal(url)
13                -->構造injvm協議的url:injvmUrl
14                <!--  1 將ref包裝成Invoker   -->
15                -->ProxyFactory$Adaptive.getInvoker(ref實例, interfaceClass, injvmUrl)
16                   -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension("javassist")
17                   -->StubProxyFactoryWrapper.getInvoker(T proxy, Class<T> type, URL url)
18                      -->JavassistProxyFactory.getInvoker(T proxy, Class<T> type, URL url)
19                         -->Wrapper getWrapper(Class<?> c)
20                            -->makeWrapper(Class<?> c)
21                         -->new AbstractProxyInvoker<T>(ref實例, interfaceClass, injvmUrl)
22                <!--  2 將Invoker暴露為Exporter   -->
23                -->Protocol$Adaptive.export(Invoker<T> invoker)
24                   -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension("injvm")
25                   -->ProtocolListenerWrapper.export(Invoker<T> invoker)
26                      -->ProtocolFilterWrapper.export(Invoker<T> invoker)
27                         -->buildInvokerChain(final Invoker<T> invoker, key:"service.filter", group:"provider")
28                            -->ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(injvmUrl, "service.filter", "provider")
29                         -->InjvmProtocol.export(Invoker<T> invoker)
30                            -->new InjvmExporter(Invoker<T> invoker, key:"com.alibaba.dubbo.demo.DemoService", Map<String, Exporter<?>> exporterMap)
31                               -->InjvmExporter.exporterMap({"com.alibaba.dubbo.demo.DemoService" -> "injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3207&side=provider&timestamp=1507009133930"})
32                   -->new ListenerExporterWrapper(Exporter<T> exporter, List<ExporterListener> listeners) : 使用listener包裝invoker
33                <!--  3 將Invoker暴露為Exporter   -->
34                -->ServiceConfig#exporters.add(exporter) 

本地暴露的代碼如下:

 1     /**
 2      * 本地暴露
 3      * 1 根據傳進來的url(例如dubbo協議的url)構造injvm協議的url
 4      * injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=2999&side=provider&timestamp=1507005507343
 5      *
 6      * 2 將ref包裝為AbstractProxyInvoker實例
 7      * 3 將AbstractProxyInvoker實例轉換為InjvmExporter
 8      *
 9      * @param url dubbo://10.243.62.133:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=2999&side=provider&timestamp=1507005507343
10      */
11     @SuppressWarnings({"unchecked", "rawtypes"})
12     private void exportLocal(URL url) {
13         if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
14             URL local = URL.valueOf(url.toFullString()).setProtocol(Constants.LOCAL_PROTOCOL).setHost(NetUtils.LOCALHOST).setPort(0);
15             Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, local);
16             Exporter<?> exporter = protocol.export(invoker);
17             exporters.add(exporter);
18             logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
19         }
20     }

為了清晰,這裏對exportLocal(URL url)做了稍稍的改動。整體流程如下:

1 首先將dubbo協議的url,改成了injvm協議的url:local;

2 將具體服務類ref通過proxyFactory包裝成AbstractProxyInvoker實例;

3 將AbstractProxyInvoker實例轉化為Exporter實例;

4 最後將生成的Exporter實例存放在ServiceConfig的List<Exporter> exporters中。

一 具體服務包裝成AbstractProxyInvoker實例

Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, local);

具體服務:com.alibaba.dubbo.demo.provider.DemoServiceImpl。調用鏈如下:

1                <!--  1 將ref包裝成Invoker   -->
2                -->ProxyFactory$Adaptive.getInvoker(ref實例, interfaceClass, injvmUrl)
3                   -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension("javassist")
4                   -->StubProxyFactoryWrapper.getInvoker(T proxy, Class<T> type, URL url)
5                      -->JavassistProxyFactory.getInvoker(T proxy, Class<T> type, URL url)
6                         -->Wrapper getWrapper(Class<?> c)
7                            -->makeWrapper(Class<?> c)
8                         -->new AbstractProxyInvoker<T>(ref實例, interfaceClass, injvmUrl)

1 ProxyFactory$Adaptive.getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2)

 1     public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws com.alibaba.dubbo.rpc.RpcException {
 2         if (arg2 == null)
 3             throw new IllegalArgumentException("url == null");
 4         com.alibaba.dubbo.common.URL url = arg2;
 5         String extName = url.getParameter("proxy", "javassist");
 6         if(extName == null)
 7             throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
 8         com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
 9         return extension.getInvoker(arg0, arg1, arg2);
10     }
  • arg0: com.alibaba.dubbo.demo.provider.DemoServiceImpl 實例
  • arg1: interface com.alibaba.dubbo.demo.DemoService Class對象
  • arg2: injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3275&side=provider&timestamp=1507010529605

這裏,首先是參數檢查和賦值。之後獲取key為javassist的ProxyFactory實現類:JavassistProxyFactory,該類會在spi進行aop的時候包裹在StubProxyFactoryWrapper中,最終調用鏈為:

ProxyFactory$Adaptive -> StubProxyFactoryWrapper -> JavassistProxyFactory

2 StubProxyFactoryWrapper.getInvoker(T proxy, Class<T> type, URL url)

1     public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException {
2         return proxyFactory.getInvoker(proxy, type, url);
3     }

這裏的proxyFactory就是JavassistProxyFactory實例了。

3 JavassistProxyFactory.getInvoker(T proxy, Class<T> type, URL url)

 1     public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
 2         // TODO Wrapper類不能正確處理帶$的類名
 3         final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf(‘$‘) < 0 ? proxy.getClass() : type);
 4         return new AbstractProxyInvoker<T>(proxy, type, url) {
 5             @Override
 6             protected Object doInvoke(T proxy, String methodName,
 7                                       Class<?>[] parameterTypes,
 8                                       Object[] arguments) throws Throwable {
 9                 return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
10             }
11         };
12     }

這裏首先會創建一個class com.alibaba.dubbo.demo.provider.DemoServiceImpl的包裝類Wrapper(該類後續去講)。之後創建一個AbstractProxyInvoker實例。

 1 package com.alibaba.dubbo.rpc.proxy;
 2 
 3 import com.alibaba.dubbo.common.URL;
 4 import com.alibaba.dubbo.rpc.Invocation;
 5 import com.alibaba.dubbo.rpc.Invoker;
 6 import com.alibaba.dubbo.rpc.Result;
 7 import com.alibaba.dubbo.rpc.RpcException;
 8 import com.alibaba.dubbo.rpc.RpcResult;
 9 
10 import java.lang.reflect.InvocationTargetException;
11 
12 public abstract class AbstractProxyInvoker<T> implements Invoker<T> {
13     private final T proxy;
14     private final Class<T> type;
15     private final URL url;
16 
17     public AbstractProxyInvoker(T proxy, Class<T> type, URL url) {
18         if (proxy == null) {
19             throw new IllegalArgumentException("proxy == null");
20         }
21         if (type == null) {
22             throw new IllegalArgumentException("interface == null");
23         }
24         if (!type.isInstance(proxy)) {
25             throw new IllegalArgumentException(proxy.getClass().getName() + " not implement interface " + type);
26         }
27         this.proxy = proxy;
28         this.type = type;
29         this.url = url;
30     }
31 
32     public Class<T> getInterface() {
33         return type;
34     }
35 
36     public URL getUrl() {
37         return url;
38     }
39 
40     public boolean isAvailable() {
41         return true;
42     }
43 
44     public void destroy() {
45     }
46 
47     public Result invoke(Invocation invocation) throws RpcException {
48         try {
49             return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));
50         } catch (InvocationTargetException e) {
51             return new RpcResult(e.getTargetException());
52         } catch (Throwable e) {
53             throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);
54         }
55     }
56 
57     /**
58      * 由創建的實例來復寫
59      * @param proxy
60      * @param methodName
61      * @param parameterTypes
62      * @param arguments
63      * @return
64      * @throws Throwable
65      */
66     protected abstract Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable;
67 
68     @Override
69     public String toString() {
70         return getInterface() + " -> " + getUrl() == null ? " " : getUrl().toString();
71     }
72 }

其中:

  • proxy: com.alibaba.dubbo.demo.provider.DemoServiceImpl 實例
  • type: interface com.alibaba.dubbo.demo.DemoService Class對象
  • url: injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3275&side=provider&timestamp=1507010529605

這樣,具體服務就被包裝成AbstractProxyInvoker實例了。

二 AbstractProxyInvoker實例轉換為Exporter

Exporter<?> exporter = protocol.export(invoker);

調用鏈如下:

 1                <!--  2 將Invoker暴露為Exporter   -->
 2                -->Protocol$Adaptive.export(Invoker<T> invoker)
 3                   -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension("injvm")
 4                   -->ProtocolListenerWrapper.export(Invoker<T> invoker)
 5                      -->ProtocolFilterWrapper.export(Invoker<T> invoker)
 6                         -->buildInvokerChain(final Invoker<T> invoker, key:"service.filter", group:"provider")
 7                            -->ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(injvmUrl, "service.filter", "provider")
 8                         -->InjvmProtocol.export(Invoker<T> invoker)
 9                            -->new InjvmExporter(Invoker<T> invoker, key:"com.alibaba.dubbo.demo.DemoService", Map<String, Exporter<?>> exporterMap)
10                               -->InjvmExporter.exporterMap({"com.alibaba.dubbo.demo.DemoService" -> "injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3207&side=provider&timestamp=1507009133930"})
11                   -->new ListenerExporterWrapper(Exporter<T> exporter, List<ExporterListener> listeners) : 使用listener包裝invoker

1 Protocol$Adaptive.export(Invoker<T> invoker)

 1     public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
 2         if (arg0 == null)
 3             throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
 4         if (arg0.getUrl() == null)
 5             throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
 6         com.alibaba.dubbo.common.URL url = arg0.getUrl();
 7         String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
 8         if(extName == null)
 9             throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
10         com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
11         return extension.export(arg0);
12     }
  • arg0:上邊創建出來的AbstractProxyInvoker實例。

這裏,首先是參數檢查和賦值。之後獲取key為injvm的Protocol實現類:InjvmProtocol,該類會在spi進行aop的時候被ProtocolFilterWrapper和ProtocolListenerWrapper遞歸包裹,最終調用鏈為:

ProxyFactory$Adaptive -> ProtocolListenerWrapper -> ProtocolFilterWrapper -> InjvmProtocol

2 ProtocolListenerWrapper.export(Invoker<T> invoker)

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

這裏先調用ProtocolFilterWrapper.export(Invoker<T> invoker),之後獲取listener,最後進行遞歸包裹。(這裏沒有listener)

3 ProtocolFilterWrapper.export(Invoker<T> invoker)

1     public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
2         if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
3             return protocol.export(invoker);
4         }
5         return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
6     }

這裏首先使用filter對invoker進行了遞歸包裹,之後使用InjvmProtocol將包裹後的invoker轉化為InjvmExporter。

buildInvokerChain(final Invoker<T> invoker, String key, String group)

 1     /**
 2      * 1 根據key從url中獲取相應的filter的values,再根據這個values和group去獲取類上帶有@Active註解的filter集合
 3      * 2 之後將這些filter對傳入的invoker進行遞歸包裝層invoker(就是一個鏈表)
 4      */
 5     private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
 6         Invoker<T> last = invoker;
 7         List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
 8         if (filters.size() > 0) {
 9             for (int i = filters.size() - 1; i >= 0; i--) {
10                 final Filter filter = filters.get(i);
11                 final Invoker<T> next = last;
12                 last = new Invoker<T>() {
13 
14                     public Class<T> getInterface() {
15                         return invoker.getInterface();
16                     }
17 
18                     public URL getUrl() {
19                         return invoker.getUrl();
20                     }
21 
22                     public boolean isAvailable() {
23                         return invoker.isAvailable();
24                     }
25 
26                     public Result invoke(Invocation invocation) throws RpcException {
27                         return filter.invoke(next, invocation);
28                     }
29 
30                     public void destroy() {
31                         invoker.destroy();
32                     }
33 
34                     @Override
35                     public String toString() {
36                         return invoker.toString();
37                     }
38                 };
39             }
40         }
41         return last;
42     }

這裏:

  • invoker:之前創建出來的AbstractProxyInvoker實例;
  • key:service.filter
  • group:provider
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);

這句代碼,是:根據key從url中獲取相應的filter的values,再根據這個values和group去獲取類上帶有@Active註解的filter集合。這一塊兒具體的代碼可以查看講解spi中的loadFile方法。最終會獲取到8個filter,關於filter,後續會說。

4 InjvmProtocol.export(Invoker<T> invoker)

1     public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
2         return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
3     }
 1 class InjvmExporter<T> extends AbstractExporter<T> {
 2     private final String key;
 3     private final Map<String, Exporter<?>> exporterMap;
 4 
 5     InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
 6         super(invoker);
 7         this.key = key;
 8         this.exporterMap = exporterMap;
 9         exporterMap.put(key, this);
10     }
11 
12     public void unexport() {
13         super.unexport();
14         exporterMap.remove(key);
15     }
16 }

最終的InjvmExporter實例

  • key = "com.alibaba.dubbo.demo.DemoService"
  • exporterMap: { "com.alibaba.dubbo.demo.DemoService" -> 當前的InjvmExporter實例 }
  • Invoker<T> invoker = 被filter進行遞歸包裹後的Invoker

最終的ServiceConfig的exporters列表:

  • List<Exporter<?>> exporters = [ 上邊的injvmExporter實例 ]

為什麽要有本地暴露?

同一個jvm中的服務,相互調用不需要通過遠程註冊中心,但是又想使用filter鏈,可以使用本地暴露。

https://dubbo.gitbooks.io/dubbo-user-book/demos/local-call.html:

“本地調用使用了 injvm 協議,是一個偽協議,它不開啟端口,不發起遠程調用,只在 JVM 內直接關聯,但執行 Dubbo 的 Filter 鏈。”

7.2 服務本地暴露