Dubbo扩展点重构
转于自己在公司的Blog:
http://pt.alibaba-inc.com/wp/dev_related_1283/dubbo-extension.html
随着服务化的推广,网站对Dubbo服务框架的需求逐渐增多,
Dubbo的现有开发人员能实现的需求有限,很多需求都被delay,
而网站的同学也希望参与进来,加上领域的推动,
所以平台计划将部分项目对公司内部开放,让大家一起来实现,
Dubbo为试点项目之一。
既然要开放,那Dubbo就要留一些扩展点,
让参与者尽量黑盒扩展,而不是白盒的修改代码,
否则分支,质量,合并,冲突都会很难管理。
先看一下Dubbo现有的设计:
这里面虽然有部分扩展接口,但并不能很好的协作,
而且扩展点的加载和配置都没有统一处理,所以下面对它进行重构。
第一步,微核心,插件式,平等对待第三方。
即然要扩展,扩展点的加载方式,首先要统一,
微核心+插件式,是比较能达到OCP原则的思路,
由一个插件生命周期管理容器,构成微核心,
核心不包括任何功能,这样可以确保所有功能都能被替换,
并且,框架作者能做到的功能,扩展者也一定要能做到,以保证平等对待第三方,
所以,框架自身的功能也要用插件的方式实现,不能有任何硬编码。
通常微核心都会采用Factory,IoC,OSGi等方式管理插件生命周期,
考虑Dubbo的适用面,不想强依赖Spring等IoC容器,
自已造一个小的IoC容器,也觉得有点过度设计,
所以打算采用最简单的Factory方式管理插件,
最终决定采用的是JDK标准的SPI扩展机制,参见:java.util.ServiceLoader
也就是扩展者在jar包的META-INF/services/目录下放置与接口同名的文本文件,
内容为接口实现类名,多个实现类名用换行符分隔,
比如,需要扩展Dubbo的协议,只需在xxx.jar中放置:
文件:META-INF/services/com.alibaba.dubbo.rpc.Protocol
内容为:com.alibaba.xxx.XxxProtocol
Dubbo通过ServiceLoader扫描到所有Protocol实现。
并约定所有插件,都必须标注:@Extension("name"),
作为加载后的标识性名称,用于配置选择。
第二步,每个扩展点只封装一个变化因子,最大化复用。
每个扩展点的实现者,往往都只是关心一件事,
现在的扩展点,并没有完全分离,
比如:Failover, Route, LoadBalance, Directory没有完全分开,全由RoutingInvokerGroup写死了。
再比如,协议扩展,扩展者可能只是想替换序列化方式,或者只替换传输方式,
并且Remoting和Http也能复用序列化等实现,
这样,需为传输方式,客户端实现,服务器端实现,协议头解析,数据序列化,都留出不同扩展点。
拆分后,设计如下:
第三步,全管道式设计,框架自身逻辑,均使用截面拦截实现。
现在很多的逻辑,都是放在基类中实现,然后通过模板方法回调子类的实现,
包括:local, mock, generic, echo, token, accesslog, monitor, count, limit等等,
可以全部拆分使用Filter实现,每个功能都是调用链上的一环。
比如:(基类模板方法)
public abstract AbstractInvoker implements Invoker {public Result invoke(Invocation inv) throws RpcException {// 伪代码active ++;if (active > max)wait();doInvoke(inv);active --;notify();}protected abstract Result doInvoke(Invocation inv) throws RpcException}
public abstract LimitFilter implements Filter {public Result invoke(Invoker chain, Invocation inv) throws RpcException { // 伪代码active ++;if (active > max)wait();chain.invoke(inv);active --;notify();}}
Result invoke(Invocation invocation) throws RpcException;
Object invoke(Method method, Object[] args) throws Throwable;
export(String url)createExporter(String host, int port, Parameters params);
export(URL url)createExporter(URL url);