分散式服務框架dubbo--服務引用的過程

近期公司新產品按照互聯網模式進行創新開發,為提升產品的並發處理能力以及服務自治、後續的可擴容性,決定採用阿里開源框架dubbo。致力於提供高性能和透明化的RPC遠程服務調用方案,以及SOA服務治理方案。

2015年做外包的時候,客戶基於dubbo開發的分散式服務框架,以領域驅動模型進行開發,當時接觸的時候是第一次接觸互聯網架構,涉及知識點太多包括dubbo,redis,rocketmq,分散式事物,bpm,angulajs,bootstrap。對於各個知識點了解薄弱。趁本次東風,仔細研究下dubbo實現原理。

本篇是介紹服務引用的過程,不針對服務暴露做詳細解釋,後續文章介紹。

廢話少說,進入正題。我們首先看看dubbo基於spring配置的服務暴露和引用的配置方式:

介面定義為

public interface IService <P, V> { V get(P params);}public interface IUserService extends IService<Params, User> {}

介面實現

public class UserServiceImpl implements IUserService { public User get(Params params) { return new User(1, "charles"); }}

服務暴露配置:

<bean id="userserviceimpl" class="com.alibaba.dubbo.examples.generic.impl.UserServiceImpl" /> <dubbo:service interface="com.alibaba.dubbo.examples.generic.api.IUserService" ref="userserviceimpl" > </dubbo:service>

消費者引用代碼

public static void main(String[] args) throws Exception { String config = GenericConsumer.class.getPackage().getName().replace(., /) + "/generic-consumer.xml"; ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(config); context.start(); IUserService userservice = (IUserService) context.getBean("userservice"); User user = userservice.get(new Params("a=b")); System.out.println(user); System.in.read(); }

引用bean配置如下

<dubbo:reference id="userservice" interface="com.alibaba.dubbo.examples.generic.api.IUserService" />

從一個c++轉java的人看來,這個過程我只有介面類,為什麼通過配置以後,消費者就可以調用服務了呢? 到底spring做了什麼? dubbo又做了哪些事情呢?

  1. 第一個疑問:消費者獲取的bean是什麼?

  2. 第二個疑問:消費者調用方法是什麼?

我們首先看<dubbo:reference/>的工作原理。spring 提供了一個可擴展spring.schemas支持,為開發系統提供可配置化支持。

實現spring.schemas擴展,需要以下步驟:設計配置屬性和JavaBean;編寫XSD文件;編寫NamespaceHandler和BeanDefinitionParser完成解析工作;編寫spring.handlers和spring.schemas串聯起所有部件.

dubbo自定義xsd在dubbo-config-spring工程下:

dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd 文件定義了 applicaiton,protocol,provider,consumer,service,refernce。

在其同目錄定義了spring.handler和spring.schema分別為:

http://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandlerhttp://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd

指定定義為dubbo.xsd. beandefinition處理類為com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

我們接著分析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 DubboBeanDefinitionParser(AnnotationBean.class, true)); }

我們看到其中一個註冊; reference 定義解析類為DubboBeanDefinitionParser,bean 對應類為ReferenceBean.class。

ReferenceBean繼承ReferenceConfig,實現spring的FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean 介面。

分析DubboBeanDefinitionParser的主要工作,需要了解spring Bean載入的過程,即從配置到spring容器中的bean要經歷配置->BeanDefinition->可用的Bean這幾個階段,Bean的所有創建信息,都放在了BeanDefinition對象中。

public class DubboBeanDefinitionParser implements BeanDefinitionParser { private final Class<?> beanClass; private final boolean required; public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) { this.beanClass = beanClass; this.required = required; } public BeanDefinition parse(Element element, ParserContext parserContext) { return parse(element, parserContext, beanClass, required); } @SuppressWarnings("unchecked") private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) { RootBeanDefinition beanDefinition = new RootBeanDefinition(); beanDefinition.setBeanClass(beanClass); beanDefinition.setLazyInit(false); String id = element.getAttribute("id"); /** 下面略*/

我看到parse方法中, beanDefinition.setBeanClass(beanClass),直接使用的傳入的beanClass,即ReferenceBean。從以上分析可以得知,dubbo服務引用都是生成的ReferenceBean。那麼和我們配置的介面有什麼關係呢?介面內部方法在ReferenceBean可是不存在的,消費端調用的到底是什麼呢?

上面提到ReferenceBean繼承於ReferenceConfig, 分析下這兩個類都做了哪些事情。

首先看看ReferenceConfig定義的屬性

public class ReferenceConfig<T> extends AbstractReferenceConfig { private static final long serialVersionUID = -5864351140409987595L; private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); private static final Cluster cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension(); private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); // 介面類型 private String interfaceName; private Class<?> interfaceClass; // 客戶端類型 private String client; // 點對點直連服務提供地址 private String url; // 方法配置 private List<MethodConfig> methods; // 預設配置 private ConsumerConfig consumer; private String protocol; // 介面代理類引用 private transient volatile T ref; private transient volatile Invoker<?> invoker; private transient volatile boolean initialized; private transient volatile boolean destroyed; /**略**/ public void setInterface(String interfaceName) { this.interfaceName = interfaceName; if (id == null || id.length() == 0) { id = interfaceName; } }

可以看到通過interfaceName存儲了介面類名。其中重點注意下interfaceClass,T ref,invoker,proxyFactory變數,後邊的實現與這些息息相關,也是我們問題的終結者。

首先分析下ref是如何獲取值,以及如何使用的?

private void init() {/***略**/ 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); }/***略***/ StaticContext.getSystemContext().putAll(attributes); ref = createProxy(map); } public synchronized T get() { if (destroyed){ throw new IllegalStateException("Already destroyed!"); } if (ref == null) { init(); } return ref; }

從上述代碼可以看到,ref在Bean初始化時賦值,具體內容後續分析。再分析get方法中返回ref。get方法又是如何使用的, 我們繼續分析ReferenceBean,看到getObject方法。

public Object getObject() throws Exception { return get(); }

到這裡估計很多看官就了解了,spring獲取創建的bean即是這個(在Spring容器中,會利用BeanDefinition對象信息來初始化創建之後的Bean對象。Bean的類實現FactoryBean介面,沒錯,Dubbo中就是用這種方法來插手Bean對象創建這一步,所以顯然ReferenceConfig實現了FactoryBean介面,而getObject()正是FactoryBean介面中定義的),所以得知在真正調用的時候是用的T ref,但是內存中存在的服務引用類卻是ReferenceBean。

那麼問題又來了?T ref 到底是個什麼鬼? 我們從來沒有定義過這個玩意?它是哪來的?

我們上文看到T ref = createProxy(map),我們繼續分析,createProxy到底做了什麼。

@SuppressWarnings({ "unchecked", "rawtypes", "deprecation" }) private T createProxy(Map<String, String> map) { URL tmpUrl = new URL("temp", "localhost", 0, map); final boolean isJvmRefer;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) { // 用戶指定URL,指定的URL可能是對點對直連地址,也可能是註冊中心URL 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 { // 通過註冊中心配置拼裝URL List<URL> us = loadRegistries(false); if (us != null && us.size() > 0) { 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 == null || urls.size() == 0) { 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; // 用了最後一個registry url } } if (registryURL != null) { // 有 註冊中心協議的URL // 對有註冊中心的Cluster 只用 AvailableCluster URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME); invoker = cluster.join(new StaticDirectory(u, invokers)); } else { // 不是 註冊中心的URL invoker = cluster.join(new StaticDirectory(invokers)); } } }/***略**/ // 創建服務代理 return (T) proxyFactory.getProxy(invoker);

新的重要角色登場了,proxyFactory,invoker。

關於invoker文章只做簡單介紹,我們知道在從註冊中心獲取到provider的連接信息後,會通過連接創建Invoker。後續文章介紹。

在dubbo中,proxyFactory將對外開放的服務進行封裝。這裡使用到代理的方式。ProxyFactory介面有兩個不同的實現類:JavassistProxyFactory和JdkProxyFactory。JdkProxyFactory是基於JDK的動態代理機制實現的。只能針對方法級別的代理。JavassistProxyFactory針對Class級別,可以在代碼行前後添加代碼,將一個包含代碼的String對象轉化成Class對象。JavassistProxyFactory比JdkProxyFactory性能要好一點。

我們以默認使用的javassit作為動態代理分析如下例子。

@SPI("javassist")public interface ProxyFactory { /** * create proxy. * * @param invoker * @return proxy */ @Adaptive({Constants.PROXY_KEY}) <T> T getProxy(Invoker<T> invoker) throws RpcException; /** * create invoker. * * @param <T> * @param proxy * @param type * @param url * @return invoker */ @Adaptive({Constants.PROXY_KEY}) <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;}/** * AbstractProxyFactory * * @author william.liangf */public abstract class AbstractProxyFactory implements ProxyFactory { public <T> T getProxy(Invoker<T> invoker) throws RpcException { Class<?>[] interfaces = null; String config = invoker.getUrl().getParameter("interfaces"); if (config != null && config.length() > 0) { String[] types = Constants.COMMA_SPLIT_PATTERN.split(config); if (types != null && types.length > 0) { interfaces = new Class<?>[types.length + 2]; interfaces[0] = invoker.getInterface(); interfaces[1] = EchoService.class; for (int i = 0; i < types.length; i ++) { interfaces[i + 1] = ReflectUtils.forName(types[i]); } } } if (interfaces == null) { interfaces = new Class<?>[] {invoker.getInterface(), EchoService.class}; } return getProxy(invoker, interfaces); } public abstract <T> T getProxy(Invoker<T> invoker, Class<?>[] types);}public class JavassistProxyFactory extends AbstractProxyFactory { @SuppressWarnings("unchecked") public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) { return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker)); } public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { // TODO Wrapper類不能正確處理帶$的類名 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); } }; }}

從類關係上以及proxyFactory.createProxy(invoker),可以知道代理類是有JavassistProxyFactory#getProxy生成的。繼續分析該方法內容:

Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker))

這裡有經典的InvokerInvocationHandler實現了java的InvocationHandler,其內容如下;

public class InvokerInvocationHandler implements InvocationHandler { private final Invoker<?> invoker; public InvokerInvocationHandler(Invoker<?> handler){ this.invoker = handler; } 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(); }}

服務的實際調用都是,invoke操作是由invoker對象來完成的,這個才是dubbo服務引用核心處理。但是我們的代碼還未到這一步,突入其來的這個解釋,看官是否能串起來了?

繼續看創建代理類的Proxy類,這個是阿里自己實現的javassit 代理類生成偽位元組碼,com.alibaba.dubbo.common.bytecode.Proxy,Proxy#getProxy方式主要是對獲取介面類方法、參數、類名重新創建類。我們以實際的介面和生成的對象舉例。

比如我們要對如下介面生成代理

public interface DemoService { String sayHello(String name); String sayHelloAgain(Stringname);}

生成的代理對象

public class DemoService.proxy10001implements DemoService { public static Method[] methods =new Metod[]{sayHello, sayHelloAgain}; private InvocationHandlerhandler; public voidDemoService.proxy10001() {} public voidDemoService.proxy10001(InvocationHandler handler) { this.handler= handler;}public String sayHello(String name){ Objectret = handler.invoke(this, methods[0], new Object[]{name})}public String sayHelloAgain(String name){ Objectret = handler.invoke(this, methods[0], new Object[]{name})}}

生成創建代理對象的代理

public class Proxy10001 extends Proxy { public void Proxy10001(){} public ObjectnewInstance(InvocationHandler h) { returnnew DemoService.proxy10001(h);}}

Proxy.getProxy(DemoService).newInstance(newInvokerInvocationHndler(invoker))代碼最終創建了基於DemoService介面的代理對象, 即 ReferenceConfig 中的 T ref ,也即spring獲取bean通過ReferenceBean#getObject()獲取到的對象bean。

以上就是從服務引用配置,到最後實際的調用的形成的整個鏈路圖。

服務引用->配置 -> <dubbo:reference> ->ReferenceBean->JavassitProxyFactory->T ref -> InvokeInvactionHandler->Invoker->服務。

一個服務引用的整體鏈路就此完成。

其中Invoker是根據服務中心註冊的Provider生成,這個我們後續文章分析。

參考

spring.schemas和spring.handlers對xmlns配置文件作用_阿諾_新浪博客

Spring中的FactoryBean - 刺蝟的溫馴 - 博客園

Dubbo源代碼實現二:服務調用的動態代理和負載均衡 - 大步流星 - ITeye技術網站

6. Dubbo原理解析-代理之Javassist生成的偽代碼


推薦閱讀:

TAG:dubbo | 動態代理 | 分散式伺服器 |