Dubbo是一个分布式服务框架,致力于提高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。简单来说,Dubbo就是个远程服务调用的分布式框架。
Dubbo具备的功能有:服务提供、服务调用、连接处理、通信协议、序列化方式、服务发现、服务路由、日志输出。
Dubbo核心部分包括:
远程通讯
:提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型序列化,以及“请求-响应”模式的信息交换方式。集群容错
:提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡、失败容错、地址路由、动态配置等集群支持。自动发现
:基于注册中心目录服务,使服务消费方能动态的查找服务器提供方,使地址透明,使服务提供方可以平滑增加或减少机器。
Dubbo能做什么?
透明化的远程方法调用
,就像调用本地方法一样调用远程方法,只需简单配置,没有任何API入侵。软负载均衡及容错机制
,可在内网替代F5等硬件负载均衡器,降低成本。服务自动注册与发现
,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能平滑添加或删除服务提供者。- Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo配置即可,Dubbo基于Spring的Schema扩展进行加载。
Dubbo默认使用NIO Netty通信框架。因此Dubbo不需要Web容器,如果硬要用Web容器,只会增加复杂性,也浪费资源。
在大规模服务化之前,应用可能只是通过RMI等工具,简单的暴露和引用远程服务,通过配置服务的URL地址进行调用,通过F5等硬件进行负载均衡。
当服务越来越多时,服务URL配置管理变得非常困难, F5硬件负载均衡器的单点压力也越来越大。此时需要一个服务注册中心,动态的注册和发现服务,使服务的位置透明。并通过在消费方获取服务提供方地址列表,实现软负载均衡和Failover,降低对F5硬件负载均衡器的依赖,也能减少部分成本。
当进一步发展,服务间依赖关系变得错综复杂,甚至分不清哪个应用要在哪个应用之前启动。这时,需要画出应用间的依赖关系图,以帮助理清应用之间的服务调用关系。
接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要用多少机器支撑?什么时候该加机器?为了解决这些问题,首先要将服务现在每天的调用量、响应时间等信息都统计出来,作为容量规划的参考指标。其次,要可以动态调整权重,在线上将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间达到阈值,记录此时的访问量,再依次访问量乘以机器数反推总容量。
这就是Dubbo最基本的几个需求。概括起来,需要服务治理的几个原因:
过多的服务URL配置困难;
负载均衡分配节点压力过大的情况下也需要部署集群;
服务依赖混乱,启动顺序不清晰;
过多服务导致性能指标分析难度较大,需要监控。
Dubbo是SOA时代的产物,它的关注点主要在于服务的调用,流量分发、流量监控和熔断。而Spring Cloud诞生于微服务架构时代,考虑的是微服务治理的方方面面,另外由于依托了Spirng、Spirng Boot的优势之上,两个框架在开始目标就不一致,Dubbo定位服务治理、Spirng Cloud是一个生态。
Dubbo | Spring Cloud | |
---|---|---|
服务注册中心 | Zookeeper | Spring Cloud Netflix Eureka |
服务调用方式 | RPC | REST API |
服务网关 | 无 | Spring Cloud Netflix Zuul |
断路器 | 不完善 | Spring Cloud Netflix Hystrix |
分布式配置 | 无 | Spring CLoud Config |
服务跟踪 | 无 | Spring Cloud Sleuth |
消息总线 | 无 | Spring CLoud Bus |
数据流 | 无 | Spring Cloud Stream |
批量任务 | 无 | Spring Cloud Task |
Dubbo
底层是使用Netty这样的NIO框架,是基于TCP协议传输的,配合以Hession序列化完成RPC通信
。
SpringCloud是基于Http协议+Rest接口调用远程过程的通信
,相对来说,Http请求会有更大的报文,占的带宽也会更多。但是REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖。
节点角色:
节点 | 角色说明 |
---|---|
Provider | 暴露服务的服务提供方 |
Consumer | 调用远程服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次调和调用时间的监控中心 |
Container | 服务运行容器 |
Dubbo服务器注册与发现的流程:
Consumer与Provider解偶,双方都可以横向增减节点数。
注册中心对本身可做对等集群,可动态增减节点,并且任意一台宕掉后,将自动切换到另一台。
去中心化,双方不直接依懒注册中心,即使注册中心全部宕机短时间内也不会影响服务的调用。
服务提供者无状态,任意一台宕掉后,不影响使用。
如果事件处理的逻辑能迅速完成,并且不会发起新的IO请求,比如只是在内存中记个标识,则直接在IO线程上处理更快,因为减少了线程池调度。
但如果事件处理逻辑较慢,或者需要发起新的IO请求,比如需要查询数据库,则必须派发到线程池,否则IO线程阻塞,将导致不能接收其它请求。
如果用IO线程处理事件,又在事件处理过程中发起新的IO请求,比如在连接事件中发起登录请求,会报“可能引发死锁”异常,但不会真死锁。
需要通过不同的派发策略和不同的线程池配置的组合来应对不同的场景:
<dubbo:protocol name="dubbo" dispatcher="all"
threadpool="fixed" threads="100" />
all
:所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。direct
:所有消息都不派发到线程池,全部在IO线程上直接执行。message
:只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在IO线程上执行。execution
:只请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在IO线程上执行。connection
:在IO线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。fixed
:固定大小线程池,启动时建立线程,不关闭,一直持有(缺省)。cached
:缓存线程池,空闲一分钟自动删除,需要时重建。limited
:可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。 Dubbo默认使用dubbo协议。支持的协议有:dubbo://(推荐)、rmi://、hessian://、http://、webservice://、thrift://、memcached://、redis://、rest://。
Dubbo的设计目的是为了满足高并发小数据量的rpc调用,在大数据量下的性能表现并不好,建议使用rmi或http 协议。
这些协议对应的默认端口号:
dubbo:20880
http:80
hessian:80
rmi:80
连接个数:单连接;
连接方式:长连接;
传输协议:TCP;
传输方式:NIO异步传输;
序列化:Hessian二进制序列化。
Dubbo缺省协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。
反之,Dubbo缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。
数据通讯 | 情况 | 结果 |
---|---|---|
A -> B | 类A多一种属性(或者说类B少一种属性) | 不抛异常,A多的那个属性的值,B没有,其他正常 |
A -> B | 枚举A多一种雷剧(或者说B少一种枚举),A使用多出来的枚举进行传输 | 抛异常 |
A -> B | 枚举A多一种雷剧(或者说B少一种枚举),A不使用多出来的枚举进行传输 | 不抛异常,B正常接收数据 |
A -> B | A和B的属性名相同,但类型不同 | 抛异常 |
A -> B | 序列化ID不同 | 正常传输 |
接口增加方法,对客户端无影响,如果该方法不是客户端需要的,客户端不需要重新部署。
输入参数和结果集中增加属性,对客户端无影响,如果客户端并不需要新属性,不用重新部署。
输入参数和结果集属性名变化,对客户端序列化无影响,但是如果客户端不重新部署,不管输入还是输出,属性名变化的属性值是获取不到的。
总结:服务器端和客户端对领域对象并不需要完全一致,而是按照最大匹配原则。
List
,但与List不同的是,它的值可能是动态变化的,比如注册中心推送变更。 Dubbo的默认集群容错方案是Failover Cluster
。
Dubbo的集群容错方案:
<dubbo:service retries="2" cluster="failover"/>
或者:
<dubbo:reference retries="2" cluster="failover"/>
cluster="failover"可以不用写,因为默认就是failover。
<dubbo:service cluster="failfast" />
或者:
<dubbo:reference cluster="failfast" />
cluster="failfast"和 把 cluster=“failover”、retries="0"是一样的效果,retries="0"就是不重试。
<dubbo:service cluster="failsafe" />
或者:
<dubbo:reference cluster="failsafe" />
<dubbo:service cluster="failback" />
或者:
<dubbo:reference cluster="failback" />
或者:
<dubbo:service interface="..." loadbalance="roundrobin" />
客户端服务级别:
<dubbo:reference interface="..." loadbalance="roundrobin" />
服务端方法级别:
<dubbo:service interface="..."> <dubbo:method name="..." loadbalance="roundrobin" />
客户端方法级别:
<dubbo:reference interface="..."> <dubbo:method name="..." loadbalance="roundrobin" />
Dubbo负载均衡在客户端。
Dubbo默认采用Random LoadBalance策略
。
<dubbo:parameter key="hash.arguments" value="0,1" />
缺省用160份虚拟节点,如果要修改,配置示例:
<dubbo:parameter key="hash.nodes" value="320" />
Consumer端在发起调用之前会先走filter链;provider端在接收到请求时也是先走filter链,然后才进行真正的业务逻辑处理。默认情况下,在consumer和provider的filter链中都会有Monitorfilter。
当一个接口有多种实现时,可以用group区分。
服务示例:
<dubbo:service group="feedback" interface="com.xxx.IndexService" />
<dubbo:service group="member" interface="com.xxx.IndexService" />
示例:
<dubbo:reference id="feedbackIndexService" group="feedback"
interface="com.xxx.IndexService" />
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
- 在低压力时间段,先升级一半提供者为新版本;
- 再将所有消费者升级为新版本;
- 然后将剩下的一半提供者升级为新版本。
老版本服务提供者配置示例:
<dubbo:service interface="com.foo.BarService" version="1.0.0" />
新版本服务提供者示例:
<dubbo:service interface="com.foo.BarService" version="2.0.0" />
老版本服务消费者示例:
<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />
新版本服务消费者示例:
<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />
基于NIO的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。
在调用之前、调用之后、出现异常时,会触发oninvoke、onreturn、onthrow三个事件,可以配置当事件发生时,通知哪个类的哪个方法 。
服务消费者Callback配置示例:
<bean id ="demoCallback" class = "com.alibaba.dubbo.callback.implicit.NofifyImpl" />
<dubbo:reference id="demoService"
interface="com.alibaba.dubbo.callback.implicit.IDemoService" version="1.0.0" group="cn" >
<dubbo:method name="get" async="true"
onreturn = "demoCallback.onreturn" onthrow="demoCallback.onthrow" />
dubbo:reference>
callback与async功能正交分解, async=true表示结果是否马上返回,onreturn表示是否需要回调。
两者叠加存在以下几种组合情况 :
异步回调模式: async=true onreturn=“xxx”
同步回调模式: async=false onreturn=“xxx”
异步无回调 : async=true
同步无回调 : async=false
本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过Mock数据返回授权失败。
在Spring配置文件中按以下方式配置:
<dubbo:service interface="com.foo.BarService" mock="true" />
<dubbo:service interface="com.foo.BarService" mock="com.foo.BarServiceMock" />
如果服务的消费方经常需要try-catch捕获异常,如:
Offer offer = null;
try {
offer = offerService.findOffer(offerId);
} catch (RpcException e) {
logger.error(e);
}
请考虑改为Mock实现,并在Mock实现中return null。如果只是想简单的忽略异常,在2.0.11以上版本可用:
<dubbo:service interface="com.foo.BarService" mock="return null" />
通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者。图示:
可以全局设置开启令牌验证:
<dubbo:provider interface="com.foo.BarService" token="true" />
<dubbo:provider interface="com.foo.BarService" token="123456" />
也可在服务级别设置:
<dubbo:service interface="com.foo.BarService" token="true" />
<dubbo:service interface="com.foo.BarService" token="123456" />
还可在协议级别设置:
<dubbo:protocol name="dubbo" token="true" />
<dubbo:protocol name="dubbo" token="123456" />
向注册中心写入路由规则的操作通常由监控中心或治理中心的页面完成。
condition://
:表示路由规则的类型,支持条件路由规则和脚本路由规则,可扩展,必填。
0.0.0.0
:表示对所有IP地址生效,如果只想对某个IP的生效,请填入具体IP,必填。
com.foo.BarService
:表示只对指定服务生效,必填。
category=routers
:表示该数据为动态配置类型,必填。
dynamic=false
:表示该数据为持久数据,当注册方退出时,数据依然保存在注册中心,必填。
enabled=true
:覆盖规则是否生效,可不填,缺省生效。
force=false
:当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路
由规则将自动失效,可不填,缺省为flase 。
runtime=false
:是否在每次调用时执行路由规则,否则只在提供者地址列表变更时预先执行并缓存结果,调用时直接从缓存中获取路由结果。如果用了参数路由,必须设为true,需要注意设置会影响调用的性能,可不填,缺省为flase 。
priority=1
路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为
0 。
rule=URL.encode("host = 10.20.153.10 => host = 10.20.153.11")
:表示路由规则的内容,必填。
Dubbo是通过JDK的ShutdownHook来完成优雅停机的,所以如果用户使用kill -9 PID等强制关闭指令,是不会执行优雅停机的,只有通过kill PID时,才会执行。
<dubbo:application ...>
<dubbo:parameter key="shutdown.timeout" value="60000" />
dubbo:application>
服务提供者启动时:向/dubbo/com.foo.BarService/providers目录下写入自己的 URL地址。
服务消费者启动时:订阅/dubbo/com.foo.BarService/providers目录下的提供者 URL地址。并向/dubbo/com.foo.BarService/consumers目录下写入自己的URL地址监控中心。
启动时:订阅/dubbo/com.foo.BarService目录下的所有提供者和消费者URL地址。
时,记录失败注册和订阅请求,后台定时重试。可通过
设置zookeeper登录信息。可通过
设置zookeeper的根节点,不设置将使用无根树支持 * 号通配符
,可订阅服务的所有分组和所有版本的提供者使用。 Dubbo默认采用Zookeeper注册中心
。
JVM -D参数:当部署或启动应用时,可以重写配置,比如改变Dubbo协议端口。
XML:XML中的当前配置会重写dubbo.properties中的;
Properties:默认配置,仅仅作用域以上两者都没有配置时。
4种:
JVM System Properties,-D参数;
Externalized Configuration,外部化配置;
ServiceConfig、ReferenceConfig等编程接口采集的配置;
本地配置文件dubbo.properties。
xml配置示例:
<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService"
url="dubbo://localhost:20890"/>
-D配置示例:
java -D com.alibaba.xxx.XxxService=dubbo://localhost:20890
properties配置示例:
java -D dubbo.resolve.file=xxx.properties
com.alibaba.xxx.XxxService=dubbo://localhost:20890
Spring容器在启动的时候,会读取到Spring默认的一些schema以及Dubbo自定义的schema,每个schema都会对应一个自己的NamespaceHandler,NamespaceHandler里面通过BeanDefinitionParser来解析配置信息并转化为需要加载的bean对象。
。
- threads:服务线程池大小。
- executes:一个服务提供者并行执行请求上限,即当Provider对一个服务的并发调用到上限后,新调用会Wait,这个时候Consumer可能会超时。在方法上配置dubbo:method则并发限制针对方法,在接口上配置dubbo:service ,则并发限制针对服务。
- 表:避免出现A服务关联B服务的表的数据操作;服务一旦划分了,那么数据库即便没分开,也要当成db表分开了来进行编码;否则AB服务难以进行垂直拆库。
- 避免服务耦合度高,依赖调用;如果出现,考虑服务调优。
- 避免分布式事务,不要拆分过细。
默认使用Hessian序列化
,还有Duddo、FastJson、Java自带序列化。
Dubbo超时时间设置有两种方式:
Dubbo在调用服务不成功时,默认是会重试两次的。
Dubbo通过Token令牌防止用户绕过注册中心直连,然后在注册中心上管理授权。Dubbo还提供服务黑白名单,来控制服务所允许的调用方。
Dubbo2.2.0以上版本支持。
服务失效踢出基于Zookeeper的临时节点原理。(服务机器会在zk上注册一个临时节点,服务失效则临时节点被删除)。
Dubbo可以使用Pinpoint和Apache Skywalking(Incubator)实现分布式服务追踪,当然还有其他很多方案。比如可以结合zipkin实现分布式服务追踪。
读操作建议使用Failover失败自动切换,默认重试两次其他服务器。
写操作建议使用Failfast快速失败,发一次调用失败就立即报错。
管理控制台主要包含:路由规则,动态配置,服务降级,访问控制,权重调整,负载均衡等管理功能。
Dubbo会在 Spring实例化完bean之后,在刷新容器最后一步发布ContextRefreshEvent事件的时候,通知实现了ApplicationListener的ServiceBean类进行回调onApplicationEvent事件方法,Dubbo会在这个方法中调用ServiceBean父类ServiceConfig的export方法,而该方法真正实现了服务的(异步或者非异步)发布。
当一个接口有多种实现时,可以用group属性来分组,服务提供方和消费方都指定同一个group即可。
可以用版本号(version)过渡,多个不同版本的服务注册到注册中心,版本号不同的服务相互间不引用。这个和服务分组的概念有一点类似。
可以,Dubbo提供了声明式缓存,用于加速热门数据的访问速度,以减少用户加缓存的工作量。示例:
<dubbo:reference cache="true" />
其实比普通的配置文件就多了一个标签 cache=“true”。
默认是同步等待结果阻塞的,支持异步调用。
Dubbo是基于 NIO的非阻塞实现并行调用,客户端不不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个Future对象。异步调用流程图:
可以配置环境点对点直连,绕过注册中心,将以服务接口为单位,忽略注册中心的提供者列列表。
Dubbox和Dubbo本质上没有区别,Dubbox扩展了Dubbo而已,以下扩展出来的功能:
- 支持 REST 风格远程调用(HTTP + JSON/XML);
- 支持基于Kryo和FST的Java高效序列化实现;
- 支持基于Jackson的JSON序列化;
- 支持基于嵌入式Tomcat的HTTP remoting体系;
- 升级Spring至3.x;
- 升级ZooKeeper客户端;
- 支持完全基于Java代码的Dubbo配置。
JDK1.6。
默认是同步等待结果阻塞的,同时也支持异步调用。
Dubbo是基于NIO的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个Future对象。
CPU个数+1。
可以通过dubbo:reference中设置mock=“return null”。mock的值也可以修改为true,然后再跟接口同一个路径下实现一个Mock类,命名规则是“接口名称+Mock”后缀。然后在Mock类里实现自己的降级逻辑。
private static final Protocol protocol =
ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Dubbo里有很多这种代码。这也是一种工厂模式,只是实现类的获取采用了JDK SPI的机制。这么实现的优点是可扩展性强,想要扩展实现,只需要在classpath下增加个文件就可以了,代码零侵入。另外,像上面的Adaptive实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。
为了提高数据访问的速度。Dubbo 提供了声明式缓存,以减少用户加缓存的工作量:
<dubbo:reference cache="true" />
其实比普通的配置文件就多了一个标签cache=“true”。
1)Spring配置方式;2)Java API配置方式。
Dubbo缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止Spring初始化完成,默认check=“true”,可以通过check="false"关闭检查。
1)timeout:方法调用超时。
2)retries:失败重试次数,默认重试2次。
3)loadbalance:负载均衡算法,默认随机。
4)actives 消费者端,最大并发调用限制。
Dubbo框架在初始化和通信过程中使用了多种设计模式,可灵活控制类加载、权限控制等功能。
private static final Protocol protocol =
ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Dubbo里有很多这种代码。这也是一种工厂模式,只是实现类的获取采用了JDKSPI的机制。这么实现的优点是可扩展性强,想要扩展实现,只需要在classpath下增加个文件就可以了,代码零侵入。另外,像上面的Adaptive实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。
EchoFilter -> ClassLoaderFilter -> GenericFilter -> ContextFilter ->
ExecuteLimitFilter -> TraceFilter -> TimeoutFilter -> MonitorFilter ->
ExceptionFilter
更确切地说,这里是装饰器和责任链模式的混合使用。例如,EchoFilter的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现。而像ClassLoaderFilter则只是在主功能上添加了功能,更改当前线程的ClassLoader,这是典型的装饰器模式。
可以通信的,启动Dubbo时,消费者会从zk拉取注册的生产者的地址接口等数据,缓存在本地。每次调用时,按照本地存储的地址进行调用;任一一台宕机后,将会切换到另一台。注册中心全部宕机后,服务的提供者和消费者仍能本地缓存通讯。服务提供者无状态,任一台宕机后,不影响使用;服务提供者全部宕机,服务消费者会无法使用,并无限次重连等待服务者恢复。