当前位置 : 主页 > 大数据 > 区块链 >

RPC框架Dubbo深入分析

来源:互联网 收集:自由互联 发布时间:2021-06-22
1,背景 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进 单一
1,背景

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进

单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本

此时,用于简化增删改查工作量的?数据访问框架(ORM)?是关键

垂直应用架构?当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率此时,用于加速前端页面开发的?Web框架(MVC)?是关键分布式服务架构?当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求此时,用于提高业务复用及整合的?分布式服务框架(RPC)?是关键分布式服务RPC框架

按业务线拆分

部署分离

每次发布只部署部分服务器

每个节点可根据不同需求伸缩扩展

每个应用之间更新,部署,运行不影响

团队分离

数据分离

停止RPC滥用,垂直业务内优先通过本地jar调用,跨业务才采用RPC调用

正确的识别业务逻辑的归属,让各个模块最大化内聚,从性能,可用性和维护性上减少耦合

流动计算架构?当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键

2,需求

在大规模服务化之前,应用可能只是通过RMI或Hessian等工具,简单的暴露和引用远程服务,通过配置服务的URL地址进行调用,通过F5等硬件进行负载均衡

当服务越来越多时,服务URL配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大

此时需要一个服务注册中心,动态的注册和发现服务,使服务的位置透明

并通过在消费方获取服务提供方地址列表,实现软负载均衡和Failover,降低对F5硬件负载均衡器的依赖,也能减少部分成本

当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系

这时,需要自动画出应用间的依赖关系图,以帮助架构师理清理关系

接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器??为了解决这些问题,第一步,要将服务现在每天的调用量,响应时间,都统计出来,作为容量规划的参考指标其次,要可以动态调整权重,在线上,将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间到达阀值,记录此时的访问量,再以此访问量乘以机器数反推总容量

3,Dubbo架构

Dubbo分层

层次结构

Business

Service

RPC

Config

Proxy

Registry

Cluster

Monitor

Protocol

Remoting

Exchange

Transport

Serialize

层说明

config(配置层 )

对外配置接口

以ServiceConfig,?ReferenceConfig为中心,可以直接new配置类,也可以通过spring解析配置生成配置类

proxy(服务代理层)

服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton

以ServiceProxy为中心,扩展接口为ProxyFactory

选择

Javassist?ProxyFactory

Jdk?ProxyFactory

registry( 注册中心层)

封装服务地址的注册与发现

以服务URL为中心,扩展接口为RegistryFactory,?Registry,?RegistryService

选择

Zookeeper

支持基于网络的集群方式,有广泛周边开源产品,建议使用dubbo-2.3.3以上版本(推荐使用)

依赖于Zookeeper的稳定性

Redis

支持基于客户端双写的集群方式,性能高

要求服务器时间同步,用于检查心跳过期脏数据

Multicast

去中心化,不需要安装注册中心

依赖于网络拓普和路由,跨机房有风险

Simple

Dogfooding,注册中心本身也是一个标准的RPC服务

没有集群支持,可能单点故障

cluster( 路由层)

封装多个提供者的路由及负载均衡,并桥接注册中心

以Invoker为中心,扩展接口为Cluster,?Directory,?Router,?LoadBalance

Cluster选择

Failover

失败自动切换,当出现失败,重试其它服务器,通常用于读操作(推荐使用)

重试会带来更长延迟

Failfast

快速失败,只发起一次调用,失败立即报错,通常用于非幂等性的写操作

如果有机器正在重启,可能会出现调用失败

Failsafe

失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作

调用信息丢失

Failback

失败自动恢复,后台记录失败请求,定时重发,通常用于消息通知操作

不可靠,重启丢失

Forking

并行调用多个服务器,只要一个成功即返回,通常用于实时性要求较高的读操作

需要浪费更多服务资源

Broadcast

广播调用所有提供者,逐个调用,任意一台报错则报错,通常用于更新提供方本地状态

速度慢,任意一台报错则报错

Router选择

Random

随机,按权重设置随机概率(推荐使用)

在一个截面上碰撞的概率高,重试时,可能出现瞬间压力不均

RoundRobin

轮循,按公约后的权重设置轮循比率

存在慢的机器累积请求问题,极端情况可能产生雪崩

LeastActive

最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差,使慢的机器收到更少请求

不支持权重,在容量规划时,不能通过权重把压力导向一台机器压测容量

ConsistentHash

一致性Hash,相同参数的请求总是发到同一提供者,当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动

压力分摊不均

路由规则

条件路由

基于条件表达式的路由规则,功能简单易用

有些复杂多分支条件情况,规则很难描述

脚本路由

基于脚本引擎的路由规则,功能强大

没有运行沙箱,脚本能力过于强大,可能成为后门

容器

Spring

自动加载META-INF/spring目录下的所有Spring配置

Jetty

启动一个内嵌Jetty,用于汇报状态

大量访问页面时,会影响服务器的线程和内存

Log4j

自动配置log4j的配置,在多进程启动时,自动给日志文件按进程分目录

用户不能控制log4j的配置,不灵活

monitor( 监控层)

RPC调用次数和调用时间监控

以Statistics为中心,扩展接口为MonitorFactory,?Monitor,?MonitorService

protocol( 远程调用层)

封装RPC调用

以Invocation,?Result为中心,扩展接口为Protocol,?Invoker,?Exporter

选择

Dubbo协议

采用NIO复用单一长连接,并使用线程池并发处理请求,减少握手和加大并发效率,性能较好(推荐使用)

适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况

Dubbo缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低

Dubbo协议缺省每服务每提供者每消费者使用单一长连接,如果数据量较大,可以使用多个连接

为防止被大量连接撑挂,可在服务提供方限制大接收连接数,以实现服务提供方自我保护

在大文件传输时,单一连接会成为瓶颈

总结

连接个数:单连接

连接方式:长连接

传输协议:TCP

传输方式:NIO异步传输

序列化:Hessian二进制序列化

适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用dubbo协议传输大文件或超大字符串。

适用场景:常规远程服务方法调用

Rmi协议

可与原生RMI互操作,基于TCP协议

偶尔会连接失败,需重建Stub

Hessian协议

可与原生Hessian互操作,基于HTTP协议

需hessian.jar支持,http短连接的开销大

Hessian协议用于集成Hessian的服务,Hessian底层采用Http通讯,采用Servlet暴露服务,Dubbo缺省内嵌Jetty作为服务器实现

可以和原生Hessian服务互操作

提供者用Dubbo的Hessian协议暴露服务,消费者直接用标准Hessian接口调用

或者提供方用标准Hessian暴露服务,消费方用Dubbo的Hessian协议调用

基于Hessian的远程调用协议

总结

连接个数:多连接

连接方式:短连接

传输协议:HTTP

传输方式:同步传输

序列化:Hessian二进制序列化

适用范围:传入传出参数数据包较大,提供者比消费者个数多,提供者压力较大,可传文件

适用场景:页面传输,文件传输,或与原生hessian服务互操作

约束

参数及返回值需实现Serializable接口

参数及返回值不能自定义实现List, Map, Number, Date, Calendar等接口,只能用JDK自带的实现,因为hessian会做特殊处理,自定义实现类中的属性值都会丢失

exchange( 信息交换层)

封装请求响应模式,同步转异步

以Request, Response为中心,扩展接口为Exchanger,?ExchangeChannel,?ExchangeClient,?ExchangeServer

transport( 网络传输层)

抽象mina和netty为统一接口

以Message为中心,扩展接口为Channel,?Transporter,?Client,?Server,?Codec

选择

Netty

性能较好(推荐使用)

一次请求派发两种事件,需屏蔽无用事件

Mina

老牌NIO框架,稳定

待发送消息队列派发不及时,大压力下,会出现FullGC

Grizzly

Sun的NIO框架,应用于GlassFish服务器中

线程池不可扩展,Filter不能拦截下一Filter

serialize( 数据序列化层)

可复用的一些工具

扩展接口为Serialization,?ObjectInput,?ObjectOutput,?ThreadPool

选择

Hessian

性能较好,多语言支持(推荐使用)

Hessian的各版本兼容性不好,可能和应用使用的Hessian冲突,Dubbo内嵌了hessian3.2.1的源码

Dubbo

通过不传送POJO的类元信息,在大量POJO传输时,性能较好

当参数对象增加字段时,需外部文件声明

Json

纯文本,可跨语言解析,缺省采用FastJson解析

性能较差

Java

Java原生支持

性能较差

关系说明

在RPC中,Protocol是核心层,也就是只要有Protocol + Invoker + Exporter就可以完成非透明的RPC调用,然后在Invoker的主过程上Filter拦截点。

图中的Consumer和Provider是抽象概念,只是想让看图者更直观的了解哪些类分属于客户端与服务器端,不用Client和Server的原因是Dubbo在很多场景下都使用Provider, Consumer, Registry, Monitor划分逻辑拓普节点,保持统一概念。

而Cluster是外围概念,所以Cluster的目的是将多个Invoker伪装成一个Invoker,这样其它人只要关注Protocol层Invoker即可,加上Cluster或者去掉Cluster对其它层都不会造成影响,因为只有一个提供者时,是不需要Cluster的。

Proxy层封装了所有接口的透明化代理,而在其它层都以Invoker为中心,只有到了暴露给用户使用时,才用Proxy将Invoker转成接口,或将接口实现转成Invoker,也就是去掉Proxy层RPC是可以Run的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。

而Remoting实现是Dubbo协议的实现,如果你选择RMI协议,整个Remoting都不会用上,Remoting内部再划为Transport传输层和Exchange信息交换层,Transport层只负责单向消息传输,是对Mina,Netty,Grizzly的抽象,它也可以扩展UDP传输,而Exchange层是在传输层之上封装了Request-Response语义。

Registry和Monitor实际上不算一层,而是一个独立的节点,只是为了全局概览,用层的方式画在一起

Dubbo模块分包?模块

dubbo-common 公共逻辑模块,包括Util类和通用模型。

dubbo-remoting 远程通讯模块,相当于Dubbo协议的实现,如果RPC用RMI协议则不需要使用此包。

dubbo-rpc 远程调用模块,抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理。

dubbo-cluster 集群模块,将多个服务提供方伪装为一个提供方,包括:负载均衡,?容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。

dubbo-registry 注册中心模块,基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。

dubbo-monitor 监控模块,统计服务调用次数,调用时间的,调用链跟踪的服务。

dubbo-config 配置模块,是Dubbo对外的API,用户通过Config使用Dubbo,隐藏Dubbo所有细节。

dubbo-container 容器模块,是一个Standlone的容器,以简单的Main加载Spring启动,因为服务通常不需要Tomcat/JBoss等Web容器的特性,没必要用Web容器去加载服务

与分层的不同点在于

container为服务容器,用于部署运行服务,没有在层中画出。

protocol层和proxy层都放在rpc模块中,这两层是rpc的核心,在不需要集群时(只有一个提供者),可以只使用这两层完成rpc调用。

transport层和exchange层都放在remoting模块中,为rpc调用的通讯基础。

serialize层放在common模块中,以便更大程度复用

模型?Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现Invocation是会话域,它持有调用过程中的变量,比如方法名,参数等基本原则?采用Microkernel + Plugin模式,Microkernel只负责组将Plugin,Dubbo自身的功能也是通过扩展点实现的,也就是Dubbo的所有功能点都可被用户自定义扩展所替换采用URL作为配置信息的统一格式,所有扩展点都通过传递URL携带配置信息扩展点加载?Dubbo的扩展点加载从JDK标准的SPI(Service Provider Interface)扩展点发现机制加强而来在扩展类的jar包内,放置扩展点配置文件:META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔注意:这里的配置文件是放在你自己的jar包内,不是dubbo本身的jar包内,Dubbo会全ClassPath扫描所有jar包内同名的这个文件,然后进行合并Provider暴露服务的过程?具体服务到Invoker的转换

ServiceConfig:ref对外提供服务实际类

ProxyFactory:getInvoker()

JavassistProxyFactory

JdkProxyFactory

Invoker转换为Exporter

Invoker:AbstractProxyInvoker的实例

Protocal:export()

DubboProtocol

Dubbo协议的Invoker转为Exporter发生在DubboProtocol类的export方法,它主要是打开socket侦听服务,并接收客户端发来的各种请求,通讯细节由Dubbo自己实现

HessianProtocol

InjvmProtocol

它通过Spring或Dubbo或JDK来实现RMI服务,通讯细节这一块由JDK底层来实现,这就省了不少工作量

RmiProtocol

WebServiceProtocol

Export

Consumer消费服务的过程?把远端服务转为Invoker

ReferenceConfig

Protocol:refer()

DubboProtocol

HessianProtocol

InjvmProtocol

RmiProtocol

WebServiceProtocol

把Invoker转为客户端需要的接口

Invoker

DubboInvoker

HessianInvoker

InjvmInvoker

RmiInvoker

WebServiceInvoker

ProxyFactory:getProxy()

JavassistProxyFactory

JdkProxyFactory

ref

过程:首先ReferenceConfig类的init方法调用Protocol的refer方法生成Invoker实例(如上图中的红色部分),这是服务消费的关键。接下来把Invoker转换为客户端需要的接口

无处不在的Invoker?由于Invoker是Dubbo领域模型中非常重要的一个概念,很多设计思路都是向它靠拢服务消费者Invoker

用户代码通过这个proxy调用其对应的Invoker(DubboInvoker、 HessianRpcInvoker、 InjvmInvoker、 RmiInvoker、 WebServiceInvoker中的任何一个),而该Invoker实现了真正的远程服务调用

服务提供者Invoker

被封装成为一个AbstractProxyInvoker实例,并新生成一个Exporter实例。这样当网络通讯层收到一个请求后,会找到对应的Exporter实例,并调用它所对应的AbstractProxyInvoker实例,从而真正调用了服务提供者的代码

线程模型?过程

Proxy

Client

Transporter

Header ->?Codec

Body ->?Serialization

Server

Dispatcher

ThreadPool

Implementation

Dispatcher

all?所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等

direct?所有消息都不派发到线程池,全部在IO线程上直接执行

message?只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在IO线程上执行

execution?只请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在IO线程上执行

connection?在IO线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池

ThreadPool

fixed?固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)

cached?缓存线程池,空闲一分钟自动删除,需要时重建

limited?可伸缩线程池,但池中的线程数只会增长不会收缩。(为避免收缩时突然来了大流量引起的性能问题)

4,增强功能

并发控制

连接控制: 连接数控制

分组聚合: 分组聚合返回值,用于菜单聚合等服务

泛化引用: 泛化调用,无需业务接口类进行远程调用,用于测试平台,开放网关桥接等

异步调用

延迟暴露: 延迟暴露服务,用于等待应用加载warmup数据,或等待spring加载完成

延迟连接: 延迟建立连接,调用时建立

隐私传参: 附加参数

5,Dubbo扩展

方法

OSGI

Equinox

Eclipse, HSF

META-INF/MANIFEST.MF

IoC

Spring

META-INF/spring/beans.xml

SPI

java.util.ServiceProvider

JDBC, MessageDigest, ScriptEngine

META-INF/services/com.xx.Xxx

Dubbo SPI?Microkernel & SPIProtocol & ProxyFactory & FilterCluster & Directory & Router & LoadBalanceTransporter & Serialization & ThreadPoolTelnetHandler & StatusChecker

6,Dubbo设计原则

模块分包原则

复用度

包中的类应该有同样的重用可能性

紧密协作的类应该放在一个包

对于变化因子,包中的类应全改或全不改

变化应在包内终止,而不传播到其它包

发布的粒度和复用度相同

稳定度

被依赖的包应该总是比依赖者更稳定

不要让一个稳定的包依赖于不稳定包

单向依赖,无环依赖

抽象度

越稳定的包应该越抽象

稳定的包不抽象将导致扩展性极差

抽象的包不稳定将导致其依赖包跟随变化

框架扩展原则?微核 +插件体系

OSGI

IoC

SPI

平等对待第三方?

Dogfooding

框架自己的功能也要扩展点实现

甚至微核的加载方式也可以扩展

Autowire

装配逻辑由扩展点之间互助完成

杜绝硬编码的桥接和中间代码

Cascading

层叠扩展粒度,逐级细分

由大的扩展点加载小的扩展点

Law of Demeter

只与×××的扩展点交互,间接转发

保持行为单一,输入输出明确

外置生命周期

API传入参数,SPI扩展点实例

尽量引用外部对象的实例,而不类元

正确:userInstance.xxx()

错误:Class.forName(userClass).newInstance().xxx()

尽量使用IoC注入,减少静态工厂方法调用

正确:setXxx(xxx)

错误:XxxFactory.getXxx();?applicationContext.getBean(“xxx”)

最少化概念模型一致性数据模型

Dubbo统一URL模型

所有配置信息都转换成URL的参数

所有的元信息传输都采用URL

所有接口都可以获取到URL

领域划分原则?服务域

指产品主要功能入口,同时负责实体域和会话域的生命周期管理。

Velocity的Engine

Spring的BeanFactory

实体域

表示你要操作的对象模型,不管什么产品,总有一个核心概念,大家都绕围它转。

Velocity的Template

Spring的Bean

会话域

表示每次操作瞬时状态,操作前创建,操作后销毁。

Velocity的Context

Spring的Invocation

领域模型划分优势

结构清晰,可直接套用

充血模型,实体域带行为

可变与不可变状态分离,可变状态集中

所有领域线程安全,不需要加锁

领域模型线程安全性

服务域

通常服务域是无状态,或者只有启动时初始化不变状态,所以天生线程安全,只需单一实例运行

实体域

通常设计为不变类,所有属性只读,或整个类引用替换,所以是线程安全的

会话域

保持所有可变状态,且会话域只在线程栈内使用,即每次调用都在线程栈内创建实例,调用完即销毁,没有竞争,所以线程安全

接口分离原则?API & SPI

声明式API(Dubbo API):描述需要什么

ServiceConfig

ReferenceConfig

RpcContext

过程式SPI(Dubbo SPI):描述怎么实现

Protocol

Transporter

LoadBalance

API可配置,一定可编程

配置用于简化常规使用

编程接口用于框架集成

API区分命令与查询

命令:无返回值表示命令,有副作用

查询:有返回值表示查询,保持幂等,无副作用

组件协作原则?管道 v.s. 派发

管道

组合行为

主功能以截面实现

比如:Servlet

派发

策略行为

主功能以事件实现

比如:?Swing

分布 v.s. 共享

分布

在行为交互为主的系统是适用

状态通过行为传递

共享

在以管理状态为主的系统中适用

状态通过仓库共享

主过程拦截

Web框架的请求响应流

ORM框架的SQL执行

Service框架的调用过程

反例:IBatis2在SQL执行过程中没有设拦截点,导致添加安全或日志拦截,执行前修改分页SQL等,不得不hack源代码

事件派发

过程

执行前后

触发附带非关键行为

状态

值的变化

触发状态观察者行为

关键路径

关键路径

采用拦截链分离职责

保持截面功能单一,不易出问题

非关键路径

采用后置事件派发

确保派发失败,不影响主过程运行

协作防御

可靠性分离

可靠操作

不可靠操作 (尽量缩小)

状态分离

无状态

有状态 (尽量缩小)

不可变类 (尽量final)

状态验证

尽早失败

前置断言 + 后置断言 + 不变式

异常防御,但不忽略异常

异常信息给出解决方案

日志信息包含环境信息

降低修改时的误解性,不埋雷

避免基于异常类型的分支流程

保持null和empty语义一致

功能演进原则?开闭原则

对扩展开放

对修改关闭

软件质量的下降,来源于修改

替换整个实现类,而不是修改其中的某行

增量式 v.s.扩充式

Dubbo增量式扩展

Remoting

Transport:

单向消息发送,抽象Mina/Netty

Exchange:

封装Request-Respose语义

调用两次单向消息发送完成

RPC

Portocol:

协议实现,不透明,点对点

Cluster:

将集群中多个提供者伪装成一个

Proxy:

透明化接口,桥接动态代理

在高阶附加功能

尽可能少的依赖低阶契约,用最少的抽象概念实现功能

当低阶切换实现时,高阶功能可以继续复用

7,Dubbo编码约定

异常和日志:

尽可能携带完整的上下文信息,比如出错原因,出错的机器地址,调用对方的地址,连的注册中心地址,使用Dubbo的版本等。

尽量将直接原因写在最前面,所有上下文信息,在原因后用键值对显示。

抛出异常的地方不用打印日志,由最终处理异常者决定打印日志的级别,吃掉异常必需打印日志。

打印ERROR日志表示需要报警,打印WARN日志表示可以自动恢复,打印INFO表示正常信息或完全不影响运行。

建议应用方在监控中心配置ERROR日志实时报警,WARN日志每周汇总发送通知。

RpcException是Dubbo对外的唯一异常类型,所有内部异常,如果要抛出给用户,必须转为RpcException。

RpcException不能有子类型,所有类型信息用ErrorCode标识,以便保持兼容。

配置和URL:?配置对象属性首字母小写,多个单词用驼峰命名(Java约定)。配置属性全部用小写,多个单词用"-"号分隔(Spring约定)。URL参数全部用小写,多个单词用"."号分隔(Dubbo约定)。尽可能用URL传参,不要自定义Map或其它上下文格式,配置信息也转成URL格式使用。尽量减少URL嵌套,保持URL的简洁性。单元和集成测试:?单元测试统一用JUnit和EasyMock,集成测试用TestNG,数据库测试用DBUnit。保持单元测试用例的运行速度,不要将性能和大的集成用例放在单元测试中。保持单元测试的每个用例都用try...finally或tearDown释放资源。减少while循环等待结果的测试用例,对定时器和网络的测试,用以将定时器中的逻辑抽为方法测试。对于容错行为的测试,比如failsafe的测试,统一用LogUtil断言日志输出。扩展点基类与AOP:?AOP类都命名为XxxWrapper,基类都命名为AbstractXxx。扩展点之间的组合将关系由AOP完成,ExtensionLoader只负载加载扩展点,包括AOP扩展。尽量采用IoC注入扩展点之间的依赖,不要直接依赖ExtensionLoader的工厂方法。尽量采用AOP实现扩展点的通用行为,而不要用基类,比如负载均衡之前的isAvailable检查,它是独立于负载均衡之外的,不需要检查的是URL参数关闭。对多种相似类型的抽象,用基类实现,比如RMI,Hessian等第三方协议都已生成了接口代理,只需将将接口代理转成Invoker即可完成桥接,它们可以用公共基类实现此逻辑。基类也是SPI的一部分,每个扩展点都应该有方便使用的基类支持。模块与分包:?基于复用度分包,总是一起使用的放在同一包下,将接口和基类分成独立模块,大的实现也使用独立模块。所有接口都放在模块的根包下,基类放在support子包下,不同实现用放在以扩展点名字命名的子包下。尽量保持子包依赖父包,而不要反向。

作者:欧阳海阳
链接:https://www.jianshu.com/p/1d17c639a4d6來源:简书简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

网友评论