Dubbo学习
DUBBO生态
RPC核心
集群容错 failover ,failsafe , failfast,forking,failback
负载均衡 random,roundRobin,leastactive,consistenthash
协议 dubbo hessian,rmi ,http,ws,thrift ,rest
传输 netty ,mina ,jetty:// , grizzly ,p2p
序列化 hessian2 , json (fastjson) , kryo ,FST
rpc和http
-
http1.0,1.1,2.0
-
1.0是短连接
每次请求都要一次tcp连接
-
1.1长连接
-
2.0 长连接+io多路复用(五大io模型之一)
-
-
性能
- 主要性能差别在序列化和反序列化
- rpc一般是基于二进制的 thrift , hessian2,http 是json或者xml
-
传输协议
- rpc基于tcp或者http
- http只能是http
-
负载均衡
- rpc自带
- http需要结合nginx等
-
传输效率
- rpc可以自定义报文协议,报文比较小
- http协议特别是老版本,很多无用且会重复传输的东西(Header,keepalive time ,reffer ...)
-
通知
- rpc自动通知
- http需要自行配置
-
场景
- 系统对外一般是http
- rpc不太适合传输图片等
- 如果是公司内部上下游系统之间传输还可以使用MQ
服务治理
注册中心 (zk,nacos,etcd,consul)
配置中心 (zk,nacos,etcd,consul)
元数据中心(redis)
指标 (dubbo metrics)
断路器(sentinel , hystrix ,resilience4j)
管理控制台(dubbo admin)
微服务组件
OpenAPI (Swagger)
api 网关 (Kong,dubbo proxy, netflix 's zuul )
可靠性(sentinel , hystrix ,resilience4j)
事务 ( seata)
授权(Oauth)
事件 ( RocketMQ)
调度
可观测性
监控 Prometheus
追踪 zipkin ,opentracing ,pinpoint
日志 es ( elasticseach)
诊断 arthas
2.7升级特性
异步编程模型 - 消费端/提供端异步
服务治理规则增强
简化的注册模型
配置中心、元数据中心
package 重构
DUBBO SPI
getExtension流程
Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下。
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。下面来演示 Dubbo SPI 的用法:
public class DubboSPITest {
@Test
public void sayHello() throws Exception {
ExtensionLoader<Robot> extensionLoader =
ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();
}
}
ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。这其中,getExtensionLoader 方法用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则创建一个新的实例。该方法的逻辑比较简单,本章就不进行分析了。下面我们从 ExtensionLoader 的 getExtension 方法作为入口,对拓展类对象的获取过程进行详细的分析。
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
// 获取默认的拓展实现类
return getDefaultExtension();
}
// Holder,顾名思义,用于持有目标对象
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
// 双重检查
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 创建拓展实例
instance = createExtension(name);
// 设置实例到 holder 中
holder.set(instance);
}
}
}
return (T) instance;
}
// 上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。下面我们来看一下创建拓展对象的过程是怎样的。
private T createExtension(String name) {
// 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// 通过反射创建实例
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 向实例中注入依赖
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
// 循环创建 Wrapper 实例
for (Class<?> wrapperClass : wrapperClasses) {
// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
instance = injectExtension(
(T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("...");
}
}
/* createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:
通过 getExtensionClasses 获取所有的拓展类
通过反射创建拓展对象
向拓展对象中注入依赖
将拓展对象包裹在相应的 Wrapper 对象中
以上步骤中,第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。在接下来的章节中,将会重点分析 getExtensionClasses 方法的逻辑,以及简单介绍 Dubbo IOC 的具体实现
*/
getExtension-> getExtensionClasses-> loadExtensionClasses ->loadDirectory -> loadResource->loadClass
自己扩展 dubbo 中的组件
自己写个工程,要是那种可以打成 jar 包的,里面的 src/main/resources
目录下,搞一个 META-INF/services
,里面放个文件叫: com.alibaba.dubbo.rpc.Protocol
,文件里搞一个 my=com.bingo.MyProtocol
。自己把 jar 弄到 nexus 私服里去。
然后自己搞一个 dubbo provider
工程,在这个工程里面依赖你自己搞的那个 jar,然后在 spring 配置文件里给个配置:
<dubbo:protocol name=”my” port=”20000” />
provider 启动的时候,就会加载到我们 jar 包里的 my=com.bingo.MyProtocol
这行配置里,接着会根据你的配置使用你定义好的 MyProtocol 了,这个就是简单说明一下,你通过上述方式,可以替换掉大量的 dubbo 内部的组件,就是扔个你自己的 jar 包,然后配置一下即可。
dubbo 里面提供了大量的类似上面的扩展点,就是说,你如果要扩展一个东西,只要自己写个 jar,让你的 consumer 或者是 provider 工程,依赖你的那个 jar,在你的 jar 里指定目录下配置好接口名称对应的文件,里面通过 key=实现类
。
然后对于对应的组件,类似 `` 用你的那个 key 对应的实现类来实现某个接口,你可以自己去扩展 dubbo 的各种功能,提供你自己的实现。
Dubbo IOC
目前仅支持 setter 方式注入
Dubbo集群
cluster 接口 , cluster invoker
集群 Cluster 用途是将多个服务提供者合并为一个 Cluster Invoker,并将这个 Invoker 暴露给服务消费者
集群模块是消费者和服务提供者的中间层
集群组件
cluster , cluster invoker , directory , router , loadbalance
集群工作过程可分为两个阶段
第一个阶段是在服务消费者初始化期间,集群 Cluster 实现类为服务消费者创建 Cluster Invoker 实例
cluster - - ( merge 操作) --> [cluster]invoker
第二个阶段是在服务消费者进行远程调用时。以 FailoverClusterInvoker 为例,该类型 Cluster Invoker 首先会调用 Directory 的 list 方法列举 Invoker 列表(可将 Invoker 简单理解为服务提供者)。
Directory 的用途是保存 Invoker,可简单类比为 List
每次变化后,RegistryDirectory 会动态增删 Invoker,并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker。当 FailoverClusterInvoker 拿到 Directory 返回的 Invoker 列表后,它会通过 LoadBalance 从 Invoker 列表中选择一个 Invoker。最后 FailoverClusterInvoker 会将参数传给 LoadBalance 选择出的 Invoker 实例的 invoke 方法,进行真正的远程调用。
集群容错
Failover 失败自动切换
Failover Cluster,当出现失败,重试其它服务器 。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)
<dubbo:reference retries="2" />
<dubbo:reference>
<dubbo:method name="findFoo" retries="2" />
</dubbo:reference>
<dubbo:service retries="2" />
#### Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
#### Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数
#### Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。
整合Hystrix
- maven spring-cloud-starter-netflix-hystrix
- 服务端 @EnableHystrix , @HystrixCommand
- 消费端 @EnableHystrix , @HystrixCommand(fallbackMethod="foo")
负载均衡
职责是将网络请求均摊到服务器 软负载和硬负载*,开发中接触的基本是软负载 .
Dubbo的4种负载均衡
服务端服务级别
<dubbo:service interface="..." loadbalance="roundrobin" />
客户端服务级别
<dubbo:reference interface="..." loadbalance="roundrobin" />
服务端方法级别
<dubbo:service interface="..."> <dubbo:method name="..." loadbalance="roundrobin"/> </dubbo:service>
客户端方法级别
<dubbo:reference interface="..."> <dubbo:method name="..." loadbalance="roundrobin"/> </dubbo:reference>
-
权重随机 RandomLoadBalance
- 默认机制
- 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
-
加权轮询 RoundRobinLoadBalance
- 总体上是轮询的顺序,可以理解成在权重比的指导下依次按轮询顺序塞满服务器请求
- 按公约后的权重设置轮循比率。存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上
-
最少活跃调用 LeastActiveLoadBalance
-
活跃数指调用前后计数差
-
总是挑一个最快的服务器相同活跃数的随机
-
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大
-
-
一致性hash ConsistentHashLoadBalance
-
服务器不出现故障的情况下,相同参数请求会落到同一服务器上
-
所谓数据倾斜是指,由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况
-
为了避免数据倾斜问题,dubbo引入虚拟节点,默认有160个虚拟节点 , 比如 Invoker1-1,Invoker1-2,……, Invoker1-160
-
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
- 算法参见:http://en.wikipedia.org/wiki/Consistent_hashing
- 缺省只对第一个参数 Hash,如果要修改,请配置 <dubbo:parameter key="hash.arguments" value="0,1" />
- 缺省用 160 份虚拟节点,如果要修改,请配置 <dubbo:parameter key="hash.nodes" value="320" />
-
Dubbo服务多版本
从架构哪个角度考虑设计的
- 扩展性
- 可用性
发生在哪个阶段
- 升级重构
长什么样子
<dubbo:service id = "versionService" …… version = 1.0 >
<dubbo:reference id = "versionService" …… version = 1.0 >
Dubbo 服务降级
- 服务降级 是从架构的 可用性和稳定性考虑的方案
- 服务降级是从客户端配置的
屏蔽降级
- 为了保证核心服务的可用性,高性能。在业务高峰期间暂停掉一些不太重要的服务
- eg. 商品评论,论坛,退款等
容错降级
- 某些服务因某种原因不可用时,但是整个调用流程不能直接终止,需要本地(调用端)模拟服务端实现保证流程正常执行
Mock配置服务降级
public class ServiceAMock implements ServiceA {
public String method(String param){
return "mock" + param;
}
}
<dubbo:reference id="serviceA" mock = "true" interface = "....ServiceA"/>
## Dubbo服务分组
* 提高扩展性
* 可以针对性地提高性能
~~~xml
<dubbo:service id = "versionService" …… group = "groupA" >
<dubbo:reference id = "versionService" …… group = "groupA" >
Dubbo微服务核心流程
- 定义服务接口:暴露给外部服务的接口
- 实现接口,定义服务实现类
- 服务端配置 ,把服务注册到【注册中心】
- 客户端配置 ,注册地址,声明引用bean
与SpringBoot整合的三种方式
-
@EnableDubbo (开启基于注解的配置方式)
-
没有dubbo.xml只有application.properties
-
dubbo-starter
-
@Service , @Reference
-
-
dubbo-starter + dubbo.xml + @importResource(locations = )
-
使用注解API的方式
-
没有了xml文件,也可以详细针对method进行配置
-
@Configuration 标记配置类
-
ApplicationConfig,RegistryConfig , ServiceConfig , ProviderConfig,MethodConfig…… (每个标签对应一个配置类@Bean)
-
Zookeeper宕机
- dubbo直连
- 本地缓存
异步调用
- NIO future主动获取结果,返回结果放在RpcContext中
//需要注意的是,由于RpcContext是单例模式,所以每次调用完后,需要保存一个Future实例;如:
fooService.findFoo(fooId);
Future<Foo> fooFuture = RpcContext.getContext().getFuture();
barService.findBar(barId);
Future<Bar> barFuture = RpcContext.getContext().getFuture();
barService.findBar(barId);
Bar bar = barFuture.get();
-
通过回调(Callback)参数
Callback并不是dubbo内部类或接口,而是由应用自定义的、实现了Serializable的接口;
分两步:1)服务提供者需在方法中声明Callback参数,其后在Service实现中显示地调用Callback的方法;
<dubbo:service ..> <dubbo:method name="method1"> <dubbo:argument index="1" callback="true" /> #标识第二个参数是callback类型 </dubbo:method> </dubbo:service>
2)Callback接口的实现类在消费端,当方法发生调用时,消费端会自动export一个Callback服务,在Rpc调用完成后,不能立即结束线程。
<dubbo:reference ...> <dubbo:method name="method1" async="true"> </dubbo:reference>
3、事件通知(推荐)
这种方式更简单,对服务提供方来讲是透明的,包括配置和代码上,均无需做任何改动。
消费端定义一个“通知者”的Spring Bean,指定方法的onreturn和onthrow事件action就可以。
<bean id="notify" class="com.alibaba.dubbo.callback.implicit.NofifyImpl" /> <dubbo:reference > <dubbo:method name="method1" async="true" onreturn="notify.onreturn" onthrow="notify.onthrow" /> </dubbo:reference>
泛化调用
存在一些情况,例如客户端没有 jar 包,或者是跨语言的调用,这个时候,就需要客户端使用字符串进行泛化调用
还是根据官方的例子来看一下:SOFA rpc
ConsumerConfig<GenericService> consumerConfig = new ConsumerConfig<GenericService>()
.setInterfaceId("com.alipay.sofa.rpc.quickstart.HelloService")
.setGeneric(true);
GenericService testService = consumerConfig.refer();
String result = (String) testService.$invoke("sayHello", new String[] { "java.lang.String" },new Object[] { "1111" });
我们看到,实际上,设置接口 ID 是和普通的调用时类似的,只是需要增加一个 Generic 属性为 true。
然后就返回了一个 GenericService 类型的代理对象,通过这个对象,就可以对服务发起调用,而调用的方式,则是使用 GenericService 的 $invoke 方法,需要传递方法名称,参数类型,参数值。并指定返回值。
SOFA 是如何实现的呢?
灰度发布
客户端在发起RPC服务调用之前,在客户端首先从服务器列表中选择一个服务调用者,包含如下关键角色:
1、Directory
服务的动态发现,通常基于注册中心进行服务的动态注册与发现,其具体实现类为RegistryDirectory。
2、Router
路由实现,其含义是根据Directory发现的所有服务提供者列表中,进行路由选择,也就是根据一定的路由规则选择合适的服务提供者,为Directory发现的服务提供者列表子集,可以基于Condition或脚本(默认为JS脚本,其实现类为ScriptRouter)。
3、LoadBalance
负载均衡机制,其作用主要是根据负载均衡算法(随机、轮询)
等算法,从(Directory–>Router)中返回的服务提供者列表中选择一个服务提供者,进行本次的RPC服务调用。
4、Cluster
集群(容错机制),就是当从服务提供者列表中按照负载均衡算法选择一个服务提供者,进行RPC服务调用后,发送了异常后的策略,例如failover(重试)、failfast(快速失败)等。
服务的灰度发布,其目标是希望根据请求,某些请求走新版本服务器,某些请求走旧版本服务器,其本质就是路由机制,即通过一定的条件来缩小服务的服务提供者列表,正好与Dubbo的Router相吻合。
有关于Dubbo的Router机制,请参考官方文档第【46、47、48】页,如果想从源码的角度了解其实现机制,请参考博文:https://blog.csdn.net/prestigeding/article/details/80848594
有了理论支持,下文将根据上述理论进行实战。
3、方案具体实现示例
本示例代码需要完成的任务是,对DemoService#createUser服务,其用户机构ID(orgId)为1的走新版本(当前服务提取者列表的最后一台服务器),其他的请求走所有的服务器(除最后一台服务器)。
原文链接:https://blog.csdn.net/prestigeding/article/details/83057273
https://blog.csdn.net/hueason/article/details/81054093
Dubbo原理
框架设计
-
Service层
- 用户自己要实现定义的服务
-
RPC层
- Config 配置层 (收集配置信息,每个标签都对应一个config类)
- Proxy 代理层
- Registry(注册中心,subscribe,register)
- Cluster (调用 invoker, directory,路由 router,负载 LoadBalance,cluster )
- Monitor(监控层)
- Protocol(远程调用 invoker + protocol + Exporter)
-
Remoting
- Exchange 交换
- 在两个服务间架起Exchanger , ExchangClient ->(Exchanger ) -> ExchangeServer
- Transport 传输层
- 真正的传输层 Transporter
- Transporter真正的底层就是netty框架
- Serialize 负责序列化和反序列化
- Exchange 交换
启动加载配置
- 如果是XML方式,它本身就是一个spring的配置文件
- interface BeanDefinitionParser DubboBeanDefinitionParser
-
- 容器启动 解析配置文件
- DubboNameSpaceHandler 创建dubbo标签解析器
- DubboBeanDefinitionParser 解析dubbo标签,把信息放入到对应的config类中
- ApplicationConfig
- RegistryConfig
- ServiceConfig ...
- ServiceBean解析 ,暴露服务 ……
服务暴露
- ServiceBean implements InitializingBean , ApplicationListener
- InitializingBean--> 创建完对象时调用 afterPropertiesSet()
- ServiceBean -> afterPropertiesSet -> setProvider() ,setApplication() ……
- IOC容器创建完成时回调 onApplicationEvent ()
- ServiceBean -> onApplicationEvent->exporter()->ServiceConfig
- ->ServiceConfig -> doExporter()->doExportUrls() -> protocols
- ->ProxyFactory ->Invoker
服务引用
-
核心就是返回一个invoker代理(里面包含了能和远程服务建立连接的客户端)
-
ReferenceBean implements FactoryBean -> getObject() ReferenceConfig
-
ProtocolFactory -createProxy -> ( DubboProtocol , RegistryProtocol )
-
DubboProtocol -> refer() -> connect & getClients ()
-
RegistryProtocol -> doRefer() & subcribe service -> ProviderConsumerRegTable.registerConsumer
-
返回invoker
服务调用
- Proxy -> invoker.invoke(RpcInvocation)