最好的文档:http://dubbo.apache.org/zh-cn/docs/user/quick-start.html
Dubbo SPI 跟Java SPI很相似,Java SPI是Java内置的一种服务提供发现功能,一种动态替换发现机制。
Java SPI使用方法:
- 在META-INF/services 目录下放置配置文件,文件名是接口全路径名,文件内部是要实现接口的实现类全路径名,编码用UTF-8
- 使用ServiceLoad.load(xx.class)调用
Dubbo比Java 增加了:
- 可以方便地获取某一个想要的扩展实现
- 对于扩展实现IOC依赖注入功能
- @SPI声明一个扩展接口,@Adaptive用在方法上,表示自动生成和编译一个动态的Adaptive类,如果用在类上表示一个装饰模式的类
Dubbo 通过ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()方法进行加载,每个SPI接口(@SPI注解的接口)都会产生一个ExtensionLoader扩展加载器实例,保存在名为EXTENSION_LOADERS的ConcureentMap中,通过扩展加载器调用getAdaptiveExtension()方法来获得自适应对象,并注入对象依赖属性
Dubbo扩展接口文件放在META-INF/dubbo 目录下,文件名是接口全路径名,文件内部是接口实现类的全路径名
-
基本原理
-
service 层,接口层,给服务提供者和消费者来实现的
-
config 层,配置层,主要是对 dubbo 进行各种配置的
-
proxy 层,服务代理层,无论是 consumer 还是 provider,dubbo 都会给你生成代理,代理之间进行网络通信
-
registry 层,服务注册层,负责服务的注册与发现
-
cluster 层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务
-
monitor 层,监控层,对 rpc 接口的调用次数和调用时间进行监控
-
protocal 层,远程调用层,封装 rpc 调用
-
exchange 层,信息交换层,封装请求响应模式,同步转异步
-
transport 层,网络传输层,抽象 mina 和 netty 为统一接口
-
serialize 层,数据序列化层
-
-
执行流程
- 服务容器Container负责启动加载运行服务提供者Provider,根据Provider配置文件根据协议发布服务,完成服务初始化
- 在Provider(服务提供者)启动时,根据配置中的Registry地址连接注册中心,将Provider的服务信息发布到注册中心
- Consumer(消费者)启动时,根据消费者配置文件中的服务引用信息,连接到注册中心,向注册中心订阅自己所需的服务
- 注册中心根据服务订阅关系,返回Provider地址列表给Consumer,如果有变更,注册中心会推送最新的服务地址信息给Consumer
- Consumer调用远程服务时,根据路由策略,先从缓存的Provider地址列表中选择一台进行,跨进程调用服务,假如调用失败,再重新选择其他调用
- 服务Provider和Consumer,会在内存中记录调用次数和调用时间,每分钟发送一次统计数据到Monitor
负载均衡策略:
- RoundRobinLoadBalance 权重轮询算法,按照公约后的权重设置轮询比例,把来自用户的请求轮流分配给内部的服务器
- LeastActiveLoadBalance 最少活跃调用数均衡算法,活跃数是指调用前后计数差,使慢的机器收到的更少
- ConsistenHashLoadBalance 一致性Hash算法,相同参数的请求总是发到同一个提供者
- RandomLoadBlance 随机均衡算法,按权重设置随机概率,如果每个提供者的权重都相同,那么随机选一个,如果权重不同,则先累加权重值,从0~累加权重值选一个随机数,判断该随机数落在哪个提供者上
集群容错策略:
- FailoverCluster: 失败转移,当出现失败时,重试其他服务器,通常用于读操作,但重试会带来更长延迟,默认集群策略
- FailfastCluster: 快速失败,只发起一次调用,失败立即报错,通常用于非幂等性操作
- FailbackCluster: 失败自动恢复, 对于Invoker调用失败,后台记录失败请求,任务定时重发,通常用于通知
- BroadcastCluster: 广播调用,遍历所有Invokers,如果调用其中某个invoker报错,则catch住异常,这样就不影响其他invoker调用
- AvailableCluster: 获取可用调用,遍历所有Invokers并判断Invoker.isAvalible,只要有一个为true就直接调用返回,不管成不成功
- FailsafeCluster: 失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作
- ForkingCluster: 并且调用,只要一个成功即返回,通常用于实时性要求较高的操作,但需要更多的服务资源
- MergeableCluster: 分组聚合,按组合并返回结果,比如某个服务接口有多种实现,可以用group区分,调用者调用多种实现并将得到的结果合并
可以。对于正在运行的 Consumer 调用 Provider 是不需要经过注册中心,所以不受影响。并且,Consumer 进程中,内存已经缓存了 Provider 列表。
那么,此时 Provider 如果下线呢?如果 Provider 是正常关闭,它会主动且直接对和其处于连接中的 Consumer 们,发送一条“我要关闭”了的消息。那么,Consumer 们就不会调用该 Provider ,而调用其它的 Provider 。
另外,因为 Consumer 也会持久化 Provider 列表到本地文件。所以,此处如果 Consumer 重启,依然能够通过本地缓存的文件,获得到 Provider 列表。
再另外,一般情况下,注册中心是一个集群,如果一个节点挂了,Dubbo Consumer 和 Provider 将自动切换到集群的另外一个节点上。
序列化,就是把数据结构或者是一些对象,转换为二进制串的过程,而反序列化是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。
dubbo 支持不同的通信协议
- dubbo 协议
默认就是走 dubbo 协议,单一长连接,进行的是 NIO 异步通信,基于 hessian 作为序列化协议。使用的场景是:传输数据量小(每次请求在 100kb 以内),但是并发量很高。
为了要支持高并发场景,一般是服务提供者就几台机器,但是服务消费者有上百台,可能每天调用量达到上亿次!此时用长连接是最合适的,就是跟每个服务消费者维持一个长连接就可以,可能总共就 100 个连接。然后后面直接基于长连接 NIO 异步通信,可以支撑高并发请求。
长连接,通俗点说,就是建立连接过后可以持续发送请求,无须再建立连接。
而短连接,每次要发送请求之前,需要先重新建立一次连接。
- rmi 协议
走 Java 二进制序列化,多个短连接,适合消费者和提供者数量差不多的情况,适用于文件的传输,一般较少用。
- hessian 协议
走 hessian 序列化协议,多个短连接,适用于提供者数量比消费者数量还多的情况,适用于文件的传输,一般较少用。
- http 协议
走 json 序列化。
- webservice
走 SOAP 文本序列化。
dubbo 支持的序列化协议
dubbo 支持 hession、Java 二进制序列化、json、SOAP 文本序列化多种序列化协议。但是 hessian 是其默认的序列化协议。
说一下 Hessian 的数据结构
Hessian 的对象序列化机制有 8 种原始类型:
- 原始二进制数据
- boolean
- 64-bit date(64 位毫秒值的日期)
- 64-bit double
- 32-bit int
- 64-bit long
- null
- UTF-8 编码的 string
另外还包括 3 种递归类型:
- list for lists and arrays
- map for maps and dictionaries
- object for objects
还有一种特殊的类型:
- ref:用来表示对共享对象的引用。
- 调用链路自动生成
一个大型的分布式系统,或者说是用现在流行的微服务架构来说吧,分布式系统由大量的服务组成。那么这些服务之间互相是如何调用的?调用链路是啥?说实话,几乎到后面没人搞的清楚了,因为服务实在太多了,可能几百个甚至几千个服务。
那就需要基于 dubbo 做的分布式系统中,对各个服务之间的调用自动记录下来,然后自动将各个服务之间的依赖关系和调用链路生成出来,做成一张图,显示出来,大家才可以看到对吧。
- 服务访问压力以及时长统计
需要自动统计各个接口和服务之间的调用次数以及访问延时,而且要分成两个级别。
- 一个级别是接口粒度,就是每个服务的每个接口每天被调用多少次,TP50/TP90/TP99,三个档次的请求延时分别是多少;
- 第二个级别是从源头入口开始,一个完整的请求链路经过几十个服务之后,完成一次请求,每天全链路走多少次,全链路请求延时的 TP50/TP90/TP99,分别是多少。
这些东西都搞定了之后,后面才可以来看当前系统的压力主要在哪里,如何来扩容和优化啊。
- 其它
- 服务分层(避免循环依赖)
- 调用链路失败监控和报警
- 服务鉴权
- 每个服务的可用性的监控(接口调用成功率?几个 9?99.99%,99.9%,99%)
比如说服务 A 调用服务 B,结果服务 B 挂掉了,服务 A 重试几次调用服务 B,还是不行,那么直接降级,走一个备用的逻辑,给用户返回响应。
举个栗子,我们有接口 HelloService
。HelloServiceImpl
有该接口的具体实现。
public interface HelloService {
void sayHello();
}
public class HelloServiceImpl implements HelloService {
public void sayHello() {
System.out.println("hello world......");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="dubbo-provider" />
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:service interface="com.zhss.service.HelloService" ref="helloServiceImpl" timeout="10000" />
<bean id="helloServiceImpl" class="com.zhss.service.HelloServiceImpl" />
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="dubbo-consumer" />
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<dubbo:reference id="fooService" interface="com.test.service.FooService" timeout="10000" check="false" mock="return null">
</dubbo:reference>
</beans>
我们调用接口失败的时候,可以通过 mock
统一返回 null。
mock 的值也可以修改为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规则是 “接口名称+Mock
” 后缀。然后在 Mock 类里实现自己的降级逻辑。
public class HelloServiceMock implements HelloService {
public void sayHello() {
// 降级逻辑
}
}
所谓失败重试,就是 consumer 调用 provider 要是失败了,比如抛异常了,此时应该是可以重试的,或者调用超时了也可以重试。配置如下:
<dubbo:reference id="xxxx" interface="xx" check="true" async="false" retries="3" timeout="2000"/>
举个栗子。
某个服务的接口,要耗费 5s,你这边不能干等着,你这边配置了 timeout 之后,我等待 2s,还没返回,我直接就撤了,不能干等你。
可以结合你们公司具体的场景来说说你是怎么设置这些参数的:
timeout
:一般设置为200ms
,我们认为不能超过200ms
还没返回。retries
:设置 retries,一般是在读请求的时候,比如你要查询个数据,你可以设置个 retries,如果第一次没读到,报错,重试指定的次数,尝试再次读取。