Dubbo设计上值得借鉴的地方

1、API和SPI分离

​ dubbo提供的API是稳定的,如果通过spring配置来暴露和引用服务,甚至接触不到API,不过如果选择通过代码的方式来暴露服务和引用服务,那就需要和ReferenceConfig和ServiceConfig这两个API交互,这两个API背后所依赖的一些组件都是可扩展和可替换的,比如选择以什么样的协议暴露服务(Protocol)、以什么样的方式生成代理(consumer端)和创建invoker(provider端)、用什么样的通信层(Transport),都是可以通过参数配置的,这给了框架开发和维护人员很大的灵活性和施展空间,同时让框架的扩展对用户透明。举个简单的例子,假设有一个框架提供了一个API如下:

public class Action {
    void execute();
} 

用来获取数据,执行业务计算,最后输出结果。这个API有很多种实现,比如有从DB获取数据的实现,有从文件获取数据的实现,还有输出结果到屏幕的实现和输出结果到DB的实现。如果为每一种实现都创建一个子类,让业务方选择用某一个子类实现自己的业务功能,就是耦合API和SPI的一个例子。如果哪一天框架又新增了几种实现,那么业务方必须替换原有的实现类才能使用新功能,这无疑是一种很差的设计。合理的做法是,将获取数据的方式抽象成一个SPI接口DataInput,将输出结果的方式抽象成另一个接口DataRender,然后为这两个SPI提供多种实现,而Action类将这两个工作代理给这两个SPI接口,业务方始终只和Action交互,至于Action要用SPI的哪一种实现去完成工作,这个由配置项在启动时决定或者运行时动态决定。

​ 这其也符合尽可能使用组合而不是继承的思想。

2、框架需要提供拦截和通知机制

​ 没有任何一种框架可以满足用户全部的需求(哪怕当前满足了,未来还会有新需求),因此框架的设计者都会预留出扩展的余地,dubbo也不例外,除了功能强大的SPI机制,还有另外两种扩展的方式,它们是调用拦截和重要事件的通知机制。

1、调用拦截

​ ORM框架有sql执行过程的拦截,web框架有请求处理过程的拦截,rpc框架也有调用拦截。dubbo的调用拦截机制是通过Filter扩展点来实现的,服务提供端来说,在服务实现类真正处理请求之前,请求需要依次经过如下filter的处理:

EchoFilter --> ClassLoaderFilter --> GenericFilter --> ContextFilter --> ExceptionFilter -->
TimeoutFilter --> MonitorFilter --> TraceFilter

这些filter在服务暴露的时候,通过ProtocolFilterWrapper的buildInvokerChain形成一个filter链:

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
    Invoker<T> last = invoker;
    //FIXME  key => service.filter   group => provider  add by jileng
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
    if (filters.size() > 0) {
        for (int i = filters.size() - 1; i >= 0; i --) {
            final Filter filter = filters.get(i);
            final Invoker<T> next = last;
            last = new Invoker<T>() {
                public Class<T> getInterface() {
                    return invoker.getInterface();
                }
                public URL getUrl() {
                    return invoker.getUrl();
                }
                public boolean isAvailable() {
                    return invoker.isAvailable();
                }
                public Result invoke(Invocation invocation) throws RpcException {
                    return filter.invoke(next, invocation);
                }
                public void destroy() {
                    invoker.destroy();
                }
                @Override
                public String toString() {
                    return invoker.toString();
                }
            };
        }
    }
    return last;
}

一般需要放在filter里面实现的功能,都是对主流程有影响的功能,甚至可以提前中断整个执行流程,以达到类似AOP的效果。要想达到让框架自动加载用户自己实现的filter的目的,一般通过SPI机制。调用拦截机制的另一个形式是pipeline和valve机制,不同的是,一般pipeline里面有哪些valve是通过配置显示指定的。

2、事件通知

​ 框架的执行可能会有一些比较重要的状态变更,比如收到请求了、请求处理完了,容器初始化完成了等等。dubbo也定义了一些事件,并提供了一些Listener接口,留给用户进行扩展,比如监听服务暴露事件的ExporterListener、监听引用服务的InvokerListener,监听服务变更的NotifyListener等。dubbo框架也使用了Listener机制来实现框架自身的功能,比如使用NotifyListener感知服务提供者发生了变更,然通知服务订阅方更新服务提供者列表。

​ 和filter拦截主链路不同,listener机制通常是旁路,它不影响执行主链路,更多是被动的接受通知,然后去做一些响应,作用是让框架的用户能够观察到框架内部状态的变化。

3、动态代理模式的使用

​ 我们平时写业务代码,大部分时候要调什么方法,new什么对象,编译期就知道了,如果编译期不知道,那就使用反射机制,通过method.invoke和Class.newInstance之类的方式来达到同样的效果。但是反射是很耗性能的,应该尽量避免使用,那如果避免不了怎么办呢?比如RPC框架服务端怎么根据请求中的接口名和方法名去调用实现类呢?答案是利用字节码工具,动态生成一个类根据相应的参数去调真正的实现类。举个简单的例子,如果服务端暴露了一个服务 com.mogujie.service.Foo,实现类是 com.mogujie.service.FooImpl,Foo定义如下:

public interface Foo {
    void method1();
    void method2(int i);
}

那么服务暴露的时候,可以动态生成一个Wrapper类,Wrapper类的源码是根据Foo类利用反射动态生成的:

public class Wrapper {
    private Foo ref;
    Object invokeMethod(String mn, Object[] args) {
        if ("method1".equals(mn)) {
            ref.method1();
        } else if ("method2".equals(mn)) {
            ref.method2(args);
        } else {
            throw new NoSuchMethodException();
        }
    }
}

然后将源码编译成Class并加载,运行时收到调用请求后根据请求信息获取对应的Wrapper类,执行invokeMethod方法就能调到服务实现类了,这样避免了反射调用。

​ dubbo为每一个暴露的服务生成了这样一个Wrapper类,内部根据方法名用很多if去找到真正的方法进行调用,然后将接口签名和Wrapper构造成一个Map,相比较而言,感觉我们tesla的实现更高效一些——为每个方法生成一个MethodCaller,然后将方法签名和MethodCaller构造成Map,运行时直接拿到MethodCaller去执行,避免了dubbo的很多if判断。dubbo和tesla默认都是使用javassist来生成代理类的。

​ 如果我们的业务中有一些不得不使用反射来实现的功能,不妨考虑下这种代理模式。不过这种模式也不是万能的,使用它有一个前提,就是调用的方法名在编译期是要能穷举的,不然无法构造出能够通过编译的源码,也就无法生成代理类了,这种情况下,只能使用反射了。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值