Dubbo简介
Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各层之间解耦合(或者组大限度的松耦合)。从服务模型的角度来看,Dubbo采用的是一种非常简单的模型,要么是提供方提供服务,要么是消费方消费服务,所以基于这一点可以抽象出服务提供者(provider)和服务消费方(consumer)两个角色。关于注册中心、协议支持、服务监控等内容,详见后面描述。
Dubbo的总体架构,如图所示:
Dubbo框架设计一共划分了10层,而最上面的Service层是留给实际想要使用Dubbo开发分布式服务的开发者实现业务逻辑的接口层。图中左边淡蓝色背景的为服务消费方使用的接口,右边淡绿色的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
下面,结合Dubbo官方文档,我们分别理解一下框架分层架构中,各个层次的设计要点:
- 服务接口层(Service): 该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现。
- 配置层(Config): 对外配置接口,以ServiceConfig和ReferenceConfig为中心,可以直接new配置类,也可以通过spring解析配置生成配置类
- 服务代理层(Proxy): 服务接口透明代理,生成服务的客户端Stub和服务端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory
- 服务注册层(Registry): 封装服务地址的注册与发现,以服务URL为中心,扩展接口
- 集群层(Cluster): 封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster、Directory、Router和LoadBalance。将多个服务提供方组合为一个服务提供方,实现对服务消费方来透明,只需要与一个服务提供方进行交互。
- 监控层(Monitor): RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory、Monitor和MonitorService.
- 远程调用层(Protocol): 封装RPC调用,以Invocation和Result为中心,扩展接口为Protocol、Invoker和Exporter。Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠拢,或转换成它,它代表一个可执行体,可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
- 信息交换层(Exchange): 封装请求响应模式,同步转异步,以Request和Response为中心,扩展接口为Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。
- 网络传输层(Transport): 抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、Transporter、Client、Server和Codec。
-
数据序列化层(Serialize): 可复用的一些工具,扩展接口为Serialization、ObjectInput、ObjectOutput和ThreadPool。
Dubbo目录支持的协议:
- Dubbo协议
- Hessian协议
- HTTP协议
- WebService协议
- Thrift协议
- Memcached协议
- Redis协议
SPI机制
在Dubbo中,SPI是一个非常核心的机制,贯穿在几乎所有的流程中。搞懂这块内容,是接下来了解Dubbo更多源码的关键因素。
Dubbo是基于Java原生SPI机制思想的一个改进,所以,先从Java SPI机制开始了解,然后再去学习Dubbo的SPI,就比较容易了。
关于Java的SPI机制
SPI全称(service provider interface),是JDK内置的一种服务提供发现机制,目前市面上有很多框架都是用它来做服务的扩展发现,大家耳熟能详的如JDBC、日志框架都有用到;
简单来说,它是一种动态替换发现的机制。举个简单的例子,如果我们定义了一个规范,需要第三方厂商去实现,那么对于我们应用方来说,只需要集成对应厂商的插件,即可以完成对应规范的实现机制。形成一种插拔式的扩展手段。
实现一个SPI机制的流程图如下:
SPI规范总结
实现SPI,就需要按照SPI本身定义的规范来进行配置,SPI规范如下:
需要再classpath下创建一个目录,该目录命名必须是:META-INF/services
在该目录下创建一个properties文件,该文件需要满足以下几个条件
- 文件名必须是扩展的接口的全路径名称
- 文件内部描述的是该扩展接口的所有实现类
- 文件的编码格式是UTF-8
通过
java.util.ServiceLoader
的加载机制来实现
SPI的实际应用
SPI在很多地方有应用,大家可以看看最常用的java.sql.Driver驱动。JDK官方提供了java.sql.Driver这个驱动扩展点,但是你们并没有看到JDK中有对应的Driver实现。那在哪里实现呢?
以连接Mysql为例,我们需要添加mysql-connector-java依赖。然后,你们可以在这个jar包中找到SPI的配置信息。如下图,所以java.sql.Driver由各个数据库厂商自行实现。这就是SPI的实际应用。当然除了这个意外,大家在spring的保重也可以看到相应的痕迹
SPI的缺点
- JDK标准的SPI会一次性加载实例化扩展点的所有实现,什么意思呢?就是如果你在META-INF/services下的文件里面加了N个实现类,那么JDK启动的时候都会一次性全部加载。那么如果有的扩展点实现初始化很耗时或者如果有些实现类并没有用到,那么会浪费资源
- 如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到原因
Dubbo优化后的SPI实现
基于Dubbo提供的SPI规范实现自己的扩展
Dubbo的SPI机制规范
大部分的思想都是和SPI是一样,只是下面两个地方有差异。
- 需要再resource目录下配置META-INF/dubbo或者META-INF/dubbo/internal或者META-INF/services,并基于SPI接口去创建一个文件
- 文件名称和接口名称保持一致,文件内容和SPI有差异,内容是KEY=VALUE
配置优先级
以timeout为例,显示了配置的查找顺序,其它retries,loadbalance等类似
- 方法级优先,接口级次之,全局配置再次之
- 如果级别一样,则消费方优先,提供方次之。
其中,服务提供方配置,通过URL经由注册中心传递给消费方。
建议由服务提供方设置超时,因为一个方法需要执行多长时间,服务提供方更清楚,如果一个消费方通知引用多个服务,就不需要关系每个服务的超时设置
容错策略
我们使用Dubbo做分布式服务需要了解Dubbo服务容错策略,Dubbo官网总共提出总共有六种容错策略,下面将详细介绍这六种容错策略区别。
-
Failover Cluster模式(缺省)
- 失败自动切换,当出现失败,重试其它服务器。
- 通常用于读操作,但重试会带来更长延迟。
- 可通过retries="2"来设置重试次数(不含第一次)。
-
Failfast Cluster
- 快速失败,只发起一次调用,失败立即报错。
- 通常用于非幂等性的写操作,比如新增记录。
-
Failsafe Cluster
- 失败安全,出现异常时,直接忽略。
- 通常用于写入审计日志等操作。
-
Failback Cluster
- 失败自动恢复,后台记录失败请求,定时重发。
- 通常用于消息通知操作。
-
Forking Cluster
- 并行调用多个服务器,只要一个成功即返回。
- 通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过forks="2"来设置最大并行数。
-
Broadcast Cluster
- 广播调用所有提供者,逐个调用,任意一台报错则报错。(2.1.0开始支持)
- 通常用于通知所有提供者更新缓存或日志等本地资源信息。
Dubbo容错策略成熟粒度:
总结:在实际引用中,查询语句容错策略建议使用默认Failover Cluster,而增删改建议使用Failfast Cluster或者使用Failover Cluster(retries=0)策略,防止出现数据重复添加等等其它问题。建议在设计接口时候把查询接口方法单独做一个接口来提供查询。
服务降级
降级的目的是为了保证核心服务可用。
降级可用有几个层面的分类:自动降级和人工降级;按照功能可用分为:读服务降级和写服务降级。
- 对一些非核心服务进行人工降级,在大促之前通过降级开关关闭哪些推荐内容、评价等对主流程没有影响的功能
- 故障降级,比如调用的远程服务挂了,网络故障、或者RPC服务返回异常。那么可以直接降级,降级的方案比如设置默认值、采用兜底数据(系统推荐的行为广告挂了,可以提前准备静态页面做返回)等等
- 限流降级,在秒杀这种流量比较集中并且流量特别大的情况下,因为突发访问量特别大可能会导致系统支撑不了。这个时候可以采用限流来限制访问量。当达到阀值时,后续的请求被降级,比如进入排队页面,比如跳转到错误页(活动太火爆,稍后重试等)
dubbo的降级方式:Mock
实现步骤:
- 在client端创建一个TestMock类,实现对应ITest的接口(需要对哪个接口进行mock,就实现哪个),名称必须以Mock结尾
- 在client端的xml配置文件中,添加如下配置,增加一个mock属性指向创建的TestMock
- 模拟错误(设置timeout),模拟超时异常,运行测试代码即可访问到TestMock这个类。当服务端故障解除以后,调用过程将恢复正常