DUBBO研读总结

作者: 千淘萬漉 | 来源:发表于2018-09-28 02:03 被阅读18次

    研读dubbo源码已经有一段时间了,dubbo中有非常多优秀的设计模式和示例代码值得学习,但是dubbo的调用层级和方法链都较为繁杂,如果不对源码思路进行梳理则很容易忘却,因此总结一篇研读心得,从阅读源码的思路应用调配的参数以及面试准备上对此进行一个全面总结。

    一、dubbo的架构思路

    1.1dubbo框架设计

    dubbo官网的架构设计提供了一张整体的框架图,10个层级看起来挺吓人的。但是其核心总结起来就是:Microkernel + Plugin(微内核+插件)

    微内核+插件机制

    官网介绍的架构设计思想是两点:

    • 采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息;
    • 采用 Microkernel + Plugin 模式,Microkernel 只负责组装 Plugin,Dubbo 自身的功能也是通过扩展点实现的,也就是 Dubbo 的所有功能点都可被用户自定义扩展所替换。

    对于第一点比较容易理解,所有的参数都封装成 Dubbo 自定义的 URL 对象进行传递。URL 对象主要包括以下属性:

    String protocol
    String host
    int port
    String path
    Map<String, String> parameters
    

    第二点:系统里抽象的各个模块,往往有很多不同的实现方案,好的设计来说:模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码,例如:

    if(参数==“dubbo”){ 
        return new DubboProtocol(); }
    else if(参数 == "rmi"){ 
        return new RMIProtocol();
     }
    

    上述设计是很糟糕的,如果新增一种协议则还需要改代码,针对此类问题java本身提供了spi机制,可以做到服务发现和动态扩展,但是弊端就是一初始化就把所有实现类给加载进去,dubbo改进了spi并重新命名为ExtensionLoader(扩展点机制),按照用户配置来指定加载模块,只需要约定一下路径即可:

    private static final String SERVICES_DIRECTORY = "META-INF/services/";
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
    

    这部分源码可以考察知识点非常多,对使用者来说是透明的,而精华却良多,尤其结合java-spi,jvm以及spring等多方面对比、借鉴,因此理论上可以好好掌握,当然最好的学习方式就是按照极简的思路来实现一个简版RPC工具。

    1.2dubbo原理、与Spring融合

    dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。既然是分布式那就意味着:一个业务分拆多个子业务,部署在不同的服务器上,既然各服务是部署在不同的服务器上,那服务间的调用就是要通过网络通信。既然涉及到了网络通信,那么服务消费者调用服务之前,都要写各种网络请求,编解码之类的相关代码,明显是很不友好的.dubbo所说的透明,就是指,让调用者对网络请求,编解码之类的细节透明,让我们像调用本地服务一样调用远程服务,甚至感觉不到自己在调用远程服务。

    public class ProxyFactory implements InvocationHandler {
        private Class interfaceClass;
        public ProxyFactory(Class interfaceClass) {
            this.interfaceClass = interfaceClass;
        }
        //返回代理对象,此处用泛型为了调用时不用强转,用Object需要强转
        public <T> T getProxyObject(){
            return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(),//类加载器
                    new Class[]{interfaceClass},//为哪些接口做代理(拦截哪些方法)
                    this);//(把这些方法拦截到哪处理)
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println(method);
            System.out.println("进行编码");
            System.out.println("发送网络请求");
            System.out.println("将网络请求结果进行解码并返回");
            return null;
        }
    }
    

    项目引入dubbo的方法推荐用XML配置的方式引入,即便是老项目拆分改造,只要是Spring工程,这个都是比较好做的,可以想象自己如果开发一个中间件服务,如果把服务嵌入spring容器当中呢?作为高级开发人员这个也是一个进阶的既能项。XML 配置方式是基于 Spring 的 Schema 和 XML 扩展机制实现的。通过该机制,我们可以编写自己的 Schema,并根据自定义的 Schema 自定义标签来配置 Bean。

    使用 Spring 的 XML 扩展机制有以下几个步骤:

    • 定义 Schema(编写 .xsd 文件)
    • 定义 JavaBean
    • 编写 NamespaceHandler 和 BeanDefinitionParser 完成 Schema 解析
    • 编写 spring.handlers 和 spring.schemas 文件串联解析部件
    • 在 XML 文件中应用配置

    最好的学习效果是可以自己按照模板来一样画瓢来创作一个类似的xml配置。可参考《dubbo源码解析-简单原理、与spring融合》

    1.3 服务发布

    服务的发布总共做了以下几件事,这个也可以从日志log上看出来:

    • 暴露本地服务
    • 暴露远程服务
    • 启动netty
    • 连接zookeeper
    • 到zookeeper注册
    • 监听zookeeper

    贴出一张官方文档的服务发布图


    服务发布

    首先 ServiceConfig 类拿到对外提供服务的实际类 ref(如:HelloWorldImpl),然后通过 ProxyFactory 类的 getInvoker方法使用 ref 生成一个 AbstractProxyInvoker 实例,到这一步就完成具体服务到 Invoker 的转化。接下来就是 Invoker 转换到 Exporter 的过程。Dubbo 处理服务暴露的关键就在 Invoker 转换到 Exporter 的过程,上图中的红色部分。
    Dubbo 的实现
    Dubbo 协议的 Invoker 转为 Exporter 发生在 DubboProtocol 类的 export 方法,它主要是打开 socket 侦听服务,并接收客户端发来的各种请求,通讯细节由 Dubbo 自己实现。

    上面摘抄了官方文档(具体链接请戳),可能还是有点抽象,实际上从代码层面进行分析:
    此处就是将本地的需要暴漏的方法以url形式作为参数传入 exportLocal()方法,url之前已经提到过包含了ip地址、端口、接口以及配置信息等。

    关键步骤1-本地暴露

    这时会执行到一个接口方法getInvoker(),这是一个注解了@Adaptive的方法,该方法的具体实现类是运行中生成动态编译的Adaptive类,把java编译出来的动态类贴出来debug如下,恍然大悟,原来他就是几个if判断,来告诉程序我这个url参数配置的是哪种协议,我现在就动态的去调用这个扩展点服务(dubbo-spi),动态编译的好处就是不用将代码写死,在协议会扩展的情况下,我根据你配置的协议来动态的生成我的extensionLoader,再来加载我所需要的Invoker。

    关键步骤2-getInvoker()方法

    上图引用的是本地服务的暴露执行,若是远程服务的暴露,arg2参数的开头则会是registry://192.168.0.1:2181/com.alibaba.dubbo.** / **。从exporter对象里包含的invoker属性可以看出,invoker包含的携带ip、端口、接口以及配置信息的url。

    关键步骤3-invoker信息

    现在开始进入到远程服务暴露的过程,一般来说这部分是应用和考察最多的点,通过配置的协议将服务暴露给外部调用。dubbo所支持的协议有多重,默认推荐dubbo协议,于是在动态代理的时候会生成Protocol$Adpative代理类,该代理类实现了RPC 协议接口,再通过扩展机制将服务加载进来。

    关键步骤4-Protocol$Adpative代理类

    加载了实现类后方法会顺着调用链路进入到dubbo协议中的export()方法中来,可以再DubboProtocol类中设置断点观察方法执行,此处完成了一个绑定,将暴露的接口+DubboExporter进行关联放入map中缓存。

    关键步骤5-DubboProtocol

    后面的步骤不再一一展开来讲,越来越贴近底层和网络通信,我们在调用dubbo接口的时候dubbo都为了我们做了这样的工作,但是对开发人员来说都是透明无感知的:

    • exchange 信息交换层。封装请求响应模式,同步转异步,以 Request, Response 为中心。
    • transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心。
    • serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool

    这里引用一张肥朝博客的总结图,来总结服务暴露所干的事情:
    首先是通过动态代理店的方式将暴露的接口组装成url形式的invoker,然后再根据url的配置信息来指定传输协议、交换方式、序列化方式等等,由于dubbo采用了自定义的SPI扩展,各层之间都是相互独立的,只有在调用的时候才知道所调用的具体扩展实现,这里还是以jdk或者javasisit的方式来动态代理实现。

    服务暴露流程

    二、Dubbo实战应用

    Dubbo 支持四种配置方式:
    XML 配置:基于 Spring 的 Schema 和 XML 扩展机制实现(推荐)
    属性配置:加载 classpath 根目录下的 dubbo.properties
    API 配置:通过硬编码方式配置(不推荐使用)
    注解配置:通过注解方式配置(Dubbo-2.5.7及以上版本支持,不推荐使用)

    三、dubbo面经

    SPI

    1、你是否了解spi,讲一讲什么是spi,为什么要使用spi?
    2、对类加载机制了解吗,说一下什么是双亲委托模式,他有什么弊端,这个弊端有没有什么我们熟悉的案例,解决这个弊端的原理又是怎么样的?
    3、dubbo的spi和jdk的spi有区别吗?有的话,究竟有什么区别?
    4、dubbo中spi也增加了IoC,先讲讲Spring的IoC,然后再讲讲dubbo里面又是怎么做的
    5、dubbo中spi也增加了AOP,那你讲讲这用到了什么设计模式,dubbo又是如何做的.

    dubbo原理

    1、dubbo的原理是怎么样的?请简单谈谈?
    2、有没有考虑过自己实现一个类似dubbo的RPC框架,如果有,请问你会如果着手实现?(面试高频题,区分度高)
    3、用过mybatis是否知道Mapper接口的原理吗?(如果回答得不错,并且提到动态代理这个关键词会继续往下问,那这个动态代理又是如何通过依赖注入到Mapper接口的呢?)

    4、服务发布过程中做了哪些事?
    暴露本地服务、暴露远程服务、启动netty、连接zookeeper、到zookeeper注册、监听zookeeper

    5、dubbo都有哪些协议,他们之间有什么特点,缺省值是什么?
    dubbo支持多种协议,默认使用的是dubbo协议,具体介绍官方文档写得很清楚,传送地址:相关协议介绍,重点是掌握好推荐dubbo协议。Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。

    6、什么是本地暴露和远程暴露,他们的区别?
    在dubbo中我们一个服务可能既是Provider,又是Consumer,因此就存在他自己调用自己服务的情况,如果再通过网络去访问,那自然是舍近求远,因此他是有本地暴露服务的这个设计.从这里我们就知道这个两者的区别

    • 本地暴露是暴露在JVM中,不需要网络通信.
    • 远程暴露是将ip,端口等信息暴露给远程客户端,调用时需要网络通信.

    7、服务暴露中远程暴露的总体过程,画图和文字方式说明
    详见上述说明

    zookeeper

    1、一般选择什么注册中心,还有别的选择吗?
    zk为默认推荐,其余还有Multicast、redis、Simple等注册中心。

    2、dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,那发布者和订阅者还能通信吗?(面试高频题)
    zookeeper的信息会缓存到服务器本地作为一个cache缓存文件,并且转换成properties对象方便使用,每次调用时,按照本地存储的地址进行调用,但是无法从注册中心去同步最新的服务列表,短期的注册中心挂掉是不要紧的,但一定要尽快修复。所以挂掉是不要紧的,但前提是你没有增加新的服务,如果你要调用新的服务,则是不能办到的。

    3、项目中有使用过多线程吗?有的话讲讲你在哪里用到了多线程?(面试高频题)
    以dubbo为例,这里的做法是:建立线程池,定时的检测并连接注册中心,如果失败了就重连,其实也就是一个定时任务执行器。可能做了两三年java还没真正在项目中开启过线程,问到这个问题时菊花一紧,但是定时任务执行器这种需求在项目中还是很常见的,比如失败重连、轮询执行任务等等,可以参考这个例子,把你们的定时任务场景和这里的多线程用法套在一起。

    dubbo检测zk链接

    4、zookeeper的java客户端你使用过哪些?
    zookeeper是支持ZkClient和Curator两种,关于zk的使用场景,除了以dubbo作为注册中心以外,zk在分布式环境作为协调服务器有许多应用场景,可以尝试用java来调用zk服务做一些协调服务,如负载均衡、数据订阅与发布等等。SnailClimb写了一篇优秀的博客《可能是全网把ZK概念讲的最清楚的一篇文章》

    zookeeper知识点一览图

    5、服务提供者能实现失效踢出是什么原理(高频题)
    在分布式系统中,我们常常需要知道某个机器是否可用,传统的开发中,可以通过Ping某个主机来实现,Ping得通说明对方是可用的,相反是不可用的,ZK 中我们让所有的机器都注册一个临时节点,我们判断一个机器是否可用,我们只需要判断这个节点在ZK中是否存在就可以了,不需要直接去连接需要检查的机器,降低系统的复杂度。

    6、zookeeper的有哪些节点,他们有什么区别?讲一下应用场景
    zookeeper中节点是有生命周期的.具体的生命周期取决于节点的类型.节点主要分为持久节点(Persistent)和临时节点(Ephemeral),但是更详细的话还可以加上时序节点(Sequential),创建节点中往往组合使用,因此也就是4种:持久节点、持久顺序节点、临时节点、临时顺序节点。

    • 所谓持久节点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点,也就是说不会因为创建该节点的客户端会话失效而消失。
    • 临时节点的生命周期和客户端会话绑定,也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。

    学习框架三部曲:

    • 掌握基本使用
    • 看过源码,知道其中原理
    • 临摹源码,自己仿写一个简易的框架

    临摹源码的这个过程,依肥朝拙见,也需要分为三个过程,分别是入门版(用最简单的代码表达出框架原理),进阶版(加入设计模式等思想,在入门版的基础上优化代码),高级版(和框架代码基本一致).

    相关文章

      网友评论

        本文标题:DUBBO研读总结

        本文链接:https://www.haomeiwen.com/subject/kpmyoftx.html