2、dubbo的架构思路
2.1 dubbo框架设计
dubbo官网的架构设计 提供了一张总体的框架图,10个层级看起来挺吓人的。可是其核心总结起来就是:Microkernel + Plugin(微内核+插件) 。
官网介绍的架构设计思想是两点:
采用 URL 做为配置信息的统一格式,全部扩展点都经过传递 URL 携带配置信息;
采用 Microkernel + Plugin 模式,Microkernel 只负责组装 Plugin,Dubbo 自身的功能也是经过扩展点实现的,也就是 Dubbo 的全部功能点均可被用户自定义扩展所替换。
对于第一点比较容易理解,由于是分布式环境,各系统之间的参数传递基于URL来携带配置信息,全部的参数都封装成 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(); }
SPI的解决方案就呼之欲出了,一个接口对应有多个实现类的时候该怎样指定么?若是采用上述设计是很糟糕的,用if else
来写死本身的服务发现,若是新增一种协议则还须要去修改代码,针对此类问题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工具。
2.2dubbo原理、与Spring融合
dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。既然是分布式 那就意味着:一个业务分拆多个子业务,部署在不一样的服务器上,既然各服务是部署在不一样的服务器上,那服务间的调用就是要经过网络通讯。既然涉及到了网络通讯,那么服务消费者调用服务以前,都要写各类网络请求,编解码之类的相关代码,明显是很不友好的.dubbo所说的透明,就是指,让调用者对网络请求,编解码之类的细节透明,让咱们像调用本地服务同样调用远程服务,甚至感受不到本身在调用远程服务。
public class ProxyFactory implements InvocationHandler { private Class interfaceClass; public ProxyFactory(Class interfaceClass) { this.interfaceClass = interfaceClass; }
项目引入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融合》
2.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地址、端口、接口以及配置信息等。
这时会执行到一个接口方法getInvoker()
,这是一个注解了@Adaptive的方法,该方法的具体实现类是运行中生成动态编译的Adaptive类,把java编译出来的动态类贴出来debug以下,恍然大悟,原来他就是几个if判断,来告诉程序我这个url参数配置的是哪一种协议,我如今就动态的去调用这个扩展点服务(dubbo-spi),动态编译的好处就是不用将代码写死,在协议会扩展的状况下,我根据你配置的协议来动态的生成个人extensionLoader,再来加载我所须要的Invoker。
上图引用的是本地服务的暴露执行,如果远程服务的暴露,arg2参数的开头则会是registry://192.168.0.1:2181/com.alibaba.dubbo.** / **。从exporter对象里包含的invoker属性能够看出,invoker包含的携带ip、端口、接口以及配置信息的url。
如今开始进入到远程服务暴露的过程,通常来讲这部分是应用和考察最多的点,经过配置的协议将服务暴露给外部调用。dubbo所支持的协议有多重,默认推荐dubbo协议,因而在动态代理的时候会生成Protocol$Adpative代理类,该代理类实现了RPC 协议接口 ,再经过扩展机制将服务加载进来。
关键步骤4-Protocol$Adpative代理类
加载了实现类后方法会顺着调用链路进入到dubbo协议中的export()方法中来,能够再DubboProtocol类中设置断点观察方法执行,此处完成了一个绑定,将暴露的接口+DubboExporter进行关联放入map中缓存。
后面的步骤再也不一一展开来说,愈来愈贴近底层和网络通讯,咱们在调用dubbo接口的时候dubbo都为了咱们作了这样的工做,可是对开发人员来讲都是透明无感知的:
exchange 信息交换层。封装请求响应模式,同步转异步,以 Request, Response 为中心。
transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心。
serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool
这里引用一张肥朝博客 的总结图,来总结服务暴露所干的事情: 首先是经过动态代理店的方式将暴露的接口组装成url形式的invoker,而后再根据url的配置信息来指定传输协议、交换方式、序列化方式等等,因为dubbo采用了自定义的SPI扩展,各层之间都是相互独立的,只有在调用的时候才知道所调用的具体扩展实现,这里仍是以jdk或者javasisit的方式来动态代理实现。
2.4 服务引用
首先 ReferenceConfig
类的init
方法调用 Protocol 的 refer
方法生成 Invoker 实例(如上图中的红色部分),这是服务消费的关键。接下来把 Invoker 转换为客户端须要的接口(如:HelloWorld)。关于每种协议如 RMI/Dubbo/Web service
等它们在调用 refer
方法生成Invoker
实例的细节和上一章节所描述的相似。
上述图和文字是摘自官方文档的原话(地址在这里 ),总结来讲就是干了两件事情:一、将spring的schemas标签信息转换bean,而后经过这个bean的信息,链接、订阅zookeeper节点信息建立一个invoker。二、将invoker的信息建立一个动态代理对象。贴一张服务应用的时序图:
这里又一次出现了Invoker
,这个抽象的概念真是无处不在呀,dubbo中最重要的两种 Invoker
:服务提供 Invoker
和服务消费 Invoker
。Invoker从类的设计信息上是封装了 Provider和Consumer地址及 Service 接口信息 ,咱们在本身的子系统调用远程接口的时候,会像调用本身的方法同样,好比在消费端这里用注解@Autowirted
自动注入一个远程接口进来,这个远程接口就是上图中服务消费端的 proxy
,可是远程接口是须要网络通讯、编码解码等等一系列工做的,要封装这个通讯细节,让用户像以本地调用方式调用远程服务,就必须使用代理,而后说到动态代理,用户代码经过这个 proxy
调用其对应的 Invoker
,而该 Invoker
实现了真正的远程服务调用。
3、Dubbo实战应用
实战应用主要是从应用层面讲引入dubbo框架后如何作一些关键配置
3.1 Dubbo 支持四种配置方式:
XML 配置:基于 Spring 的 Schema 和 XML 扩展机制实现(推荐) 属性配置:加载 classpath 根目录下的 dubbo.properties API 配置:经过硬编码方式配置(不推荐使用,可学习加深源码理解) 注解配置:经过注解方式配置(Dubbo-2.5.7及以上版本支持,不推荐使用)
3.2 集群容错
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
Invoker 是 Provider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息
Directory 表明多个 Invoker,能够把它当作 List<Invoker> ,但与 List 不一样的是,它的值多是动态变化的,好比注册中心推送变动
Cluster 将 Directory 中的多个 Invoker 假装成一个 Invoker,对上层透明,假装过程包含了容错逻辑,调用失败后,重试另外一个
Router 负责从多个 Invoker 中按路由规则选出子集,好比读写分离,应用隔离等
LoadBalance 负责从多个 Invoker 中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,须要重选。
集群调用的配置可从以下列表中选择:
<dubbo:service cluster="failsafe" />
集群模式
说明
Failfast Cluster
快速失败,只发起一次调用,失败当即报错。一般用于非幂等性的写操做,好比新增记录。
Failsafe Cluster
失败安全,出现异常时,直接忽略。一般用于写入审计日志等操做。
Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。一般用于消息通知操做。
Forking Cluster
并行调用多个服务器,只要一个成功即返回。一般用于实时性要求较高的读操做,但须要浪费更多服务资源。可经过 forks="2" 来设置最大并行数。
Broadcast Cluster
广播调用全部提供者,逐个调用,任意一台报错则报错 [2]。一般用于通知全部提供者更新缓存或日志等本地资源信息。
3.3 负载均衡
Random LoadBalance
随机 ,按权重设置随机几率。
在一个截面上碰撞的几率高,但调用量越大分布越均匀,并且按几率使用权重后也比较均匀,有利于动态调整提供者权重。
RoundRobin LoadBalance
轮询 ,按公约后的权重设置轮询比率。
存在慢的提供者累积请求的问题,好比:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,长此以往,全部请求都卡在调到第二台上。
LeastActive LoadBalance
最少活跃调用数 ,相同活跃数的随机,活跃数指调用先后计数差。
使慢的提供者收到更少请求,由于越慢的提供者的调用先后计数差会越大。
ConsistentHash LoadBalance
一致性 Hash ,相同参数的请求老是发到同一提供者。
当某一台提供者挂时,本来发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引发剧烈变更。
算法参见:http://en.wikipedia.org/wiki/Consistent_hashing
缺省只对第一个参数 Hash,若是要修改,请配置 <dubbo:parameter key="hash.arguments" value="0,1" />
缺省用 160 份虚拟节点,若是要修改,请配置 <dubbo:parameter key="hash.nodes" value="320" />
SPI
一、你是否了解SPI,讲一讲什么是SPI,为何要使用SPI? SPI具体约定: 当服务的提供者,提供了服务接口的一种实现以后,在jar包的META-INF/services/
目录里同时建立一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能经过该jar包META-INF/services/
里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入(从使用层面来讲,就是运行时,动态给接口添加实现类 )。 基于这样一个约定就能很好的找到服务接口的实现类,而不须要再代码里制定(不须要在代码里写死)。
这样作的好处: java设计出SPI目的是为了实如今模块装配的时候能不在程序里动态指明,这就须要一种服务发现机制。这样程序运行的时候,该机制就会为某个接口寻找服务的实现,有点相似IOC的思想,就是将装配的控制权移到程序以外,在模块化设计中这个机制尤为重要。例如,JDBC驱动,能够加载MySQL、Oracle、或者SQL Server等,目前有很多框架用它来作服务的扩张发现。回答这个问题能够延伸一下和API的对比,API是将方法封装起来给调用者使用的,SPI是给扩展者使用的。
二、对类加载机制了解吗,说一下什么是双亲委托模式,他有什么弊端,这个弊端有没有什么咱们熟悉的案例,解决这个弊端的原理又是怎么样的? 扩展延生的一道题。
三、Dubbo的SPI和JDK的SPI有区别吗?有的话,究竟有什么区别? Dubbo 的扩展点加载是基于JDK 标准的 SPI 扩展点发现机制加强而来的,Dubbo 改进了 JDK 标准的 SPI 的如下问题:
JDK 标准的 SPI 会一次性实例化扩展点全部实现,若是有扩展实现初始化很耗时,但若是没用上也加载,会很浪费资源。
增长了对扩展点 IoC 和 AOP 的支持,一个扩展点能够直接 setter 注入其它扩展点。
上文已提供。另外在博客中也单独对此写了一篇《Dubbo内核之SPI机制》 、《跟我学Dubbo系列之Java SPI机制简介》
四、Dubbo中SPI也增长了IoC,先讲讲Spring的IoC,而后再讲讲Dubbo里面又是怎么作的 五、Dubbo中SPI也增长了AOP,那你讲讲这用到了什么设计模式,Dubbo又是如何作的.
Dubbo原理
一、Dubbo角色和设计是怎么样的,原理是怎么样的?请简单谈谈?
二、有没有考虑过本身实现一个相似dubbo的RPC框架,若是有,请问你会若是着手实现?(面试高频题,区分度高) 可从两个方面去入手,考虑接口扩展性,改造JDK的SPI机制来实现本身的扩展SPI机制 。另外就是从动态代理入手,从网络通讯、编码解码这些步骤以动态代理的方式植入远程调用方法中,实现透明化的调用。
三、用过mybatis是否知道Mapper接口的原理吗?(若是回答得不错,而且提到动态代理这个关键词会继续往下问,那这个动态代理又是如何经过依赖注入到Mapper接口的呢?)
四、服务发布过程当中作了哪些事? 暴露本地服务、暴露远程服务、启动netty、链接zookeeper、到zookeeper注册、监听zookeeper
五、dubbo都有哪些协议,他们之间有什么特色,缺省值是什么? dubbo支持多种协议,默认使用的是dubbo
协议,具体介绍官方文档写得很清楚,传送地址:相关协议介绍 ,重点是掌握好推荐dubbo协议。Dubbo 缺省协议采用单一长链接和 NIO 异步通信,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的状况。
六、什么是本地暴露和远程暴露,他们的区别? 在dubbo中咱们一个服务可能既是Provider,又是Consumer,所以就存在他本身调用本身服务的状况,若是再经过网络去访问,那天然是舍近求远,所以他是有本地暴露服务的这个设计.从这里咱们就知道这个二者的区别
本地暴露是暴露在JVM中,不须要网络通讯.
远程暴露是将ip,端口等信息暴露给远程客户端,调用时须要网络通讯.
七、服务暴露中远程暴露的整体过程,画图和文字方式说明 详见上述说明
zookeeper
一、通常选择什么注册中心,还有别的选择吗? zk为默认推荐,其他还有Multicast、redis、Simple等注册中心。
二、dubbo中zookeeper作注册中心,若是注册中心集群都挂掉,那发布者和订阅者还能通讯吗?(面试高频题) zookeeper的信息会缓存到服务器本地做为一个cache缓存文件,而且转换成properties对象方便使用,每次调用时,按照本地存储的地址进行调用,可是没法从注册中心去同步最新的服务列表,短时间的注册中心挂掉是没关系的,但必定要尽快修复。因此挂掉是没关系的,但前提是你没有增长新的服务,若是你要调用新的服务,则是不能办到的。
三、项目中有使用过多线程吗?有的话讲讲你在哪里用到了多线程?(面试高频题) 以dubbo为例,这里的作法是:创建线程池,定时的检测并链接注册中心,若是失败了就重连,其实也就是一个定时任务执行器 。可能作了两三年java还没真正在项目中开启过线程,问到这个问题时菊花一紧,可是定时任务执行器这种需求在项目中仍是很常见的,好比失败重连、轮询执行任务等等,能够参考这个例子,把大家的定时任务场景和这里的多线程用法套在一块儿。
四、zookeeper的java客户端你使用过哪些? zookeeper是支持ZkClient和Curator两种,关于zk的使用场景,除了以dubbo做为注册中心之外,zk在分布式环境做为协调服务器有许多应用场景,能够尝试用java来调用zk服务作一些协调服务,如负载均衡、数据订阅与发布等等。SnailClimb写了一篇优秀的博客《多是全网把ZK概念讲的最清楚的一篇文章》
五、服务提供者能实现失效踢出是什么原理(高频题) 在分布式系统中,咱们经常须要知道某个机器是否可用,传统的开发中,能够经过Ping某个主机来实现,Ping得通说明对方是可用的,相反是不可用的,ZK 中咱们让全部的机器都注册一个临时节点,咱们判断一个机器是否可用,咱们只须要判断这个节点在ZK中是否存在就能够了,不须要直接去链接须要检查的机器,下降系统的复杂度。
六、zookeeper的有哪些节点,他们有什么区别?讲一下应用场景 zookeeper中节点是有生命周期的.具体的生命周期取决于节点的类型.节点主要分为持久节点(Persistent)和临时节点(Ephemeral),可是更详细的话还能够加上时序节点(Sequential),建立节点中每每组合使用,所以也就是4种:持久节点、持久顺序节点、临时节点、临时顺序节点。
所谓持久节点,是指在节点建立后,就一直存在,直到有删除操做来主动清除这个节点,也就是说不会由于建立该节点的客户端会话失效而消失。
临时节点的生命周期和客户端会话绑定,也就是说,若是客户端会话失效,那么这个节点就会自动被清除掉。
七、在dubbo中,何时更新本地的zookeeper信息缓存文件?订阅zookeeper信息的总体过程是怎么样的? dubbo向zk发送了订阅请求之后,会去监听zk的回调,(若是zk有回调就回去调用notify方法),接着会去建立接口配置信息的持久化节点,同时dubbo也设置了对该节点的监听,zk节点若是发生了变化那么会触发回调方法,去更新zk信息的缓存文件,同时注册服务在调用的时候会去对比最新的配置信息节点,有差异的话会以最新信息为准从新暴露。《dubbo源码解析-zookeeper订阅》
服务引用 一、描述一下dubbo服务引用的过程,原理 上文已提供。
二、既然你提到了dubbo的服务引用中封装通讯细节是用到了动态代理,那请问建立动态代理经常使用的方式有哪些,他们又有什么区别?dubbo中用的是哪种?(高频题) jdk、cglib还有javasisit,JDK的动态代理代理的对象必需要实现一个接口,而针对于没有接口的类,则可用CGLIB。要明白二者区别必需要了解原理,明白了原理天然一通百通,CGLIB其原理也很简单,对指定的目标类生成一个子类,并覆盖其中方法实现加强,但因为采用的是继承,因此不能对final修饰的类进行代理。除了以上两种你们都很熟悉的方式外,其实还有一种方式,就是javassist生成字节码来实现代理(dubbo多处用到了javassist)。
集群容错 一、dubbo提供了集中集群容错模式? 二、谈谈dubbo中的负载均衡算法及特色?最小活跃数算法中是如何统计活跃数的?简单谈谈一致性哈希算法 这部分能够多结合官方文档进行学习,并且涉及到了负载均衡的多个重要算法,也是高频的考察热点。
三、怎么经过dubbo实现服务降级的,降级的方式有哪些,又有什么区别? 当网站处于高峰期时,并发量大,服务能力有限,那么咱们只能暂时屏蔽边缘业务,这里面就要采用服务降级策略了。首先dubbo中的服务降级分红两个:屏蔽(mock=force)、容错(mock=fail)。
mock=force:return+null
表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
mock=fail:return+null
表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
要生效须要在dubbo后台进行配置的修改:
四、dubbo监控平台可以动态改变接口的一些设置,其原理是怎样的? 改变注册在zookeeper上的节点信息,从而zookeeper通知从新生成invoker(这些具体细节在zookeeper建立节点,zookeeper链接,zookeeper订阅中都详细讲了,这里再也不重复)。