以前人们计算个数是1+1+1+1...+1,简单有效,等到每次计算个数并且量越来越大的时候,这种计算模型显然是不合适的,于是就有人发明了乘法,把冗长的加法问题用一个简单的乘法符号所代替,这是概念的不断抽象带来的好处,当然我就是举个例子。在Dubbo所处的领域里面,到处都是A服务的方法method1去调用某个B服务的method2,这个method2的方法名可能会变,可能没有参数,可能有参数,而且参数的个数也可能发生变化,要对这样的远程调用做管理,那么必须抽象这种调用,必须用一种模型(概念)描述这种调用,然后基于这个抽象(概念)调用,架构出整个服务治理体系,如果抽象的不好,也就是没有涵盖每一种远程调用类型,那么你这套框架就是不完备的,必然存在特例情况,你要为这个特例情况付出沉痛代价,甚至推倒重新设计。
Dubbo里面的抽象(Java的接口)很多,我就不一一列举了,我把我认为重要的抽象说明下,然后再之后的博客都是围绕这些核心抽象构筑Dubbo整个体系,完成一个功能集合体。
- URL(Uniform Resource Locator 统一资源定位):这个不是接口,但是很重要,贯穿整个Dubbo体系,功能类似Java原生的URL,但Dubbo重写了它 ,Java原生的URL专注与互联网资源的定位,Dubbo需要携带更多的东西,甚至附加的属性也放到里面,有时候可以认为它就是一个资源配置与参数的包装体即可,Dubbo对此的解释是统一参数配置,规划配置,不要使用额外的类去携带,URL就是统一的消息总线,需要什么配置,什么参数,就放到上面好了,需要定制化的东西,想都不用想,取URL上的配置即可
- Node:从取名上看就是个节点,如果里面没有方法,那抽象的可太离谱了,万物皆节点都可以这么理解(好比Linux系统里的一切皆文档类似),不好意思,人家里面用方法定义
/** * get url. * * @return url. */ URL getUrl(); /** * is available. * * @return available. */ boolean isAvailable(); /** * destroy. */ void destroy();
加了方法,就相当于加了限定,我这个节点是能取出配置那种(getUrl),还可以判断是否有效(isAvailable),并且还能销毁(detroy),那肯定的,销毁了就出现有效失效的问题了,这就把万物皆节点的概念往小括,因为把【厕所】当成一个Node来说的话,明显超出Dubbo的管辖范围,概念是有作用域的,我得限制我的Node在服务治理这块。这个Node抽象程度就比较高了,如果它不是放到Dubbo里面,我完全有理由认为万物皆Node,那【厕所】可以归属于这个Node下,我destroy了厕所,你看看它还available不avaliable,getUrl么,不就是厕所是蹲坑式还是坐便,提不提供厕纸之类,很合理嘛
-
Protocol:字面意思是协议,就是需要一定的共识,我还写进Protocol里了,是Dubbo的核心承诺,大家看一下
/** * Protocol. (API/SPI, Singleton, ThreadSafe) */ @SPI("dubbo") public interface Protocol { /** * Get default port when user doesn't config the port. * * @return default port */ int getDefaultPort(); /** * Export service for remote invocation: <br> * 1. Protocol should record request source address after receive a request: * RpcContext.getContext().setRemoteAddress();<br> * 2. export() must be idempotent, that is, there's no difference between invoking once and invoking twice when * export the same URL<br> * 3. Invoker instance is passed in by the framework, protocol needs not to care <br> * * @param <T> Service type * @param invoker Service invoker * @return exporter reference for exported service, useful for unexport the service later * @throws RpcException thrown when error occurs during export the service, for example: port is occupied */ @Adaptive <T> Exporter<T> export(Invoker<T> invoker) throws RpcException; /** * Refer a remote service: <br> * 1. When user calls `invoke()` method of `Invoker` object which's returned from `refer()` call, the protocol * needs to correspondingly execute `invoke()` method of `Invoker` object <br> * 2. It's protocol's responsibility to implement `Invoker` which's returned from `refer()`. Generally speaking, * protocol sends remote request in the `Invoker` implementation. <br> * 3. When there's check=false set in URL, the implementation must not throw exception but try to recover when * connection fails. * * @param <T> Service type * @param type Service class * @param url URL address for the remote service * @return invoker service's local proxy * @throws RpcException when there's any error while connecting to the service provider */ @Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; /** * Destroy protocol: <br> * 1. Cancel all services this protocol exports and refers <br> * 2. Release all occupied resources, for example: connection, port, etc. <br> * 3. Protocol can continue to export and refer new service even after it's destroyed. */ void destroy(); }
我就不翻译注释了,我英文没那么好,况且强行翻译一波可能会导致偏离原作者的意图(其实是懒?),里面核心就两个方法,一个是暴露服务(export),另一个是引用服务(refer),返回结果也是两个接口(抽象概念,接下来会解释),这个Protocol打通了两端,服务提供方和服务消费方均需要一个“协议”来通讯,不然你打10086投书电信服务可能话务员会顺着电磁波打你,如果你看过源代码的话,Dubbo提供了几种协议实现,比如hessian或者http,还有很多,这里不展开。这个Protocol的抽象高于远程调用的抽象的,像个工厂一样,调用方想用何种形式调用提供方,只要提供方有用过相应的协议形式暴露过服务
-
Invoker:调用者,看这样子是消费方调用服务费的抽象,但是Dubbo赋予这个Invoker的含义远不止于此,它抽象一个调用过程,细想消费方不能像调用本地方法那样直接调用远程服务方法,必然会经过网络传输,简单理解就是一次远程调用,分两段,一段是消费方调用方法转成网络请求,另一段是网络请求转换为被调服务提供方的本地的执行,一次调用,两次转换,每次转换都是Invoker的一次invoke,看下源码
/** * Invoker. (API/SPI, Prototype, ThreadSafe) * * @see org.apache.dubbo.rpc.Protocol#refer(Class, org.apache.dubbo.common.URL) * @see org.apache.dubbo.rpc.InvokerListener * @see org.apache.dubbo.rpc.protocol.AbstractInvoker */ public interface Invoker<T> extends Node { /** * get service interface. * * @return service interface. */ Class<T> getInterface(); /** * invoke. * * @param invocation * @return result * @throws RpcException */ Result invoke(Invocation invocation) throws RpcException; }
返回接口(getInterface)不必说,暴露的服务必须是接口契约,不会暴露具体实现的,这个invoke方法便是我认为Dubbo整个体系的最核心的抽象方法,甚至向注册中心注册也被抽象成此方法,实话说,Dubbo的好多功能就是代理此方法或者装饰增强此方法实现的,好比SpringAOP中PointCut的存在,参数必然也得抽象出来
-
Invocation:为了能应付Java任何方法的调用,那么参数也得是泛化的,看下源码豁然开朗
/** * Invocation. (API, Prototype, NonThreadSafe) * * @serial Don't change the class name and package name. * @see org.apache.dubbo.rpc.Invoker#invoke(Invocation) * @see org.apache.dubbo.rpc.RpcInvocation */ public interface Invocation { /** * get method name. * * @return method name. * @serial */ String getMethodName(); /** * get parameter types. * * @return parameter types. * @serial */ Class<?>[] getParameterTypes(); /** * get arguments. * * @return arguments. * @serial */ Object[] getArguments(); /** * get attachments. * * @return attachments. * @serial */ Map<String, String> getAttachments(); void setAttachment(String key, String value); void setAttachmentIfAbsent(String key, String value); /** * get attachment by key. * * @return attachment value. * @serial */ String getAttachment(String key); /** * get attachment by key with default value. * * @return attachment value. * @serial */ String getAttachment(String key, String defaultValue); /** * get the invoker in current context. * * @return invoker. * @transient */ Invoker<?> getInvoker(); }
泛化调用参数其实就三个就行,方法名,方法参数类型列表,方法参数实体列表即可,如果用JDK反射的话,你还得知道被调用的实体对象,当然持有这个Invocation的Invoker对象的实体肯定是知道被调用者是谁的,设计也非常复杂(你应该想到了,各种代理)。然后还非常贴心的把获取调用方参数一些方法也定义了,并且把执行的Invoker也缓存了,可以做一些校验什么的
-
Exporter:这个是服务提供方使用协议Protocol暴露(export)服务后给的返回,看看有什么东西
/** * Exporter. (API/SPI, Prototype, ThreadSafe) * * @see org.apache.dubbo.rpc.Protocol#export(Invoker) * @see org.apache.dubbo.rpc.ExporterListener * @see org.apache.dubbo.rpc.protocol.AbstractExporter */ public interface Exporter<T> { /** * get invoker. * * @return invoker */ Invoker<T> getInvoker(); /** * unexport. * <p> * <code> * getInvoker().destroy(); * </code> */ void unexport(); }
上面说过,服务提供方的本地方法也是需要Invoker代理调用的,还支持反悔,不想给消费方调用了,unexport一下
-
IOC/DI:你没有看错,Dubbo是有IOC/DI的,有类似与Spring的Bean容器扩展点工厂
/** * ExtensionFactory */ @SPI public interface ExtensionFactory { /** * Get extension. * * @param type object type. * @param name object name. * @return object instance. */ <T> T getExtension(Class<T> type, String name); }
Dubbo是对Java原生的SPI机制进行了改进,给每个SPI接口的实现类配一个名字,那上面那个扩展点工厂举例
adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory
叫adaptive这个适配扩展点可以认为是默认实现,且一个SPI接口只有一个,有些SPI接口在方法有这个@Adaptive注解,这个需要特别说明下,一旦SPI接口的方法有这个注解,那么这个SPI的实现就得启用代理了,具体用字节码生成一个新类,然后把带有@Adaptive的方法的实现进行增强,具体就是@Adaptive方法的参数中获取那个消息总线URL中与当前SPI接口实例参数,允许在运行时使用指定的扩展来执行方法,举个例子:Protocol也是SPI接口,运行时有的服务提供方有的方法想用http协议暴露,有的想用hessian协议暴露,能不能Protocol实现类自己适配下,根据相应的协议调用相应协议具体实现类进行暴露,这就是ExtensionLoader.getAdaptiveExtension()返回的就是个自适应代理类,在(export)方法执行的时候,动态选取期望的实现类再再去执行(export)方法。Dubbo在获取扩展点实例的时候,如果候选实现中有当前SPI接口的构造方法,则认为该实现类是个Wrapper类,类似Spring的构造器注入,最后返回多个实现类那种装饰器模式的效果一个Wrapper类,当然也会检测SPI实现类中的setter方法,目前Dubbo只支持setter的注入,一个SPI实现类的setter方法的接口一般也是扩展点,那么就取出实例给你注入进去,实现了DI
以上我挑选出来的比较重要的类和接口,接下的博客来将围绕这些重要抽象看看Dubbo是怎么一步一步把远程调用串联起来的。