dubbo

本文深入剖析Dubbo的SPI机制,解释如何按需加载扩展实现。接着,详细介绍了Dubbo服务的导出流程,包括服务启动、网络服务开启与注册中心注册。此外,探讨了服务调用的原理,涵盖负载均衡策略与容错机制。最后,讨论了Dubbo协议选择、同步与异步调用以及服务分组等关键概念。
摘要由CSDN通过智能技术生成

Dubbo的SPI机制解析

Dubbo与Spring整合原理

Dubbo服务导出原理

Dubbo服务引入原理

Dubbo服务调用原理

Dubbo负载均衡配置

Dubbo同步调用

Dubbo异步调用

Dubbo服务分组

DubboJvm调用

Dubbo泛化调用

Dubbo显示回调

Dubbo框架事件

Dubbo本地执行

Dubbo隐式传参

DubboSTUB & mock

 

Dubbo的SPI机制解析

1.jdk的spi,举个例子,jdbc驱动连数据库,其实就是读的mysql驱动包里一个文件,文件写了连接的实现类,实现的接口就是jdbc提供的,换成oracle就是读o的文件,其实就是把实现类的名字写到了文件里,然后去加载具体的实现类.jdk的它会加载文件里接口所有的实现类,不管我们需不需要

2.dubbo的spi做了优化,按需加载,举个例子协议接口protocol,dubbo不是有很多协议吧,所以协议实现类都是实现了这个接口,在文件是keyvalue的形式,比如dubbo=..rest=..这样就根据你配置文件的key配的协议再去文件里加载具体的实现类包括过滤器等等都是这么做的

3.dubbo使用ExtensionLoader<Protocol>表示协议接口的扩展点加载器,通过extensionLoader.getExtension("dubbo");去加载dubbo协议的实现类,在getExtension方法里,创建实例的时候为了防止并发,先使用了双重检查锁的单例模式,防止多个线程同时创建接口的扩展实例,这里使用了一个Holder<Object>对象去做加锁的对象,这么做的原因是这个时候还不存在扩展对象的实例,当多个线程加载不同的实例的时候,是可以允许并发,但是加载同一个实例是必须保证是单例的,但是此时实例对象为null,只能为每种实例对象先创建一个Holder对象,用来加锁。

然后就是真正的创建扩展实例的方法 createExtension(name),通过loadResource方法,再根据接口的全限定名去META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/这些目录下寻找对应的文件,此时会返回一个Map, key就是文件里的别名,value就是通过文件中的全限定类名反射除了的实例,比如dubbo协议的具体实现类,接着通过传入的实例别名去map取出我们要找的实例,反射得到它。这个地方还会缓存文件里,这个接口的所有包装类

5.对实例进行依赖注入,假如一个A扩展实例,里面有个属性B,是个接口对象,这个时候如果是整合了spring,就先从ioc容器中拿到对应的bean进行注入,如果没有找到,判断如果属性B的实现类,有标注的@Adaptive注解,默认就是注入那个实现类,如果都没有,那么会生成代理对象给属性B注入。对于要代理的方法,也需要加上@Adaptive注解,入参要加上URL,通过URL传参,在具体执行方法的时候再判断使用的是哪个具体的实现类。

6.对实例进行AOP代理,解析文件时候得到的实例包装类全部放入一个set集合中。把当前接口类的所有的Wrapper全部一层一层包裹在实例对象上,每包裹一个Wrapper后,也会对Wrapper对象进行依赖注入,返回最终的Wrapper对象

 

 

Dubbo服务导出原理

1.服务导出主要是做两件事,就是启动网络服务来接收请求和注册自己的dubbo服务到注册中心

服务导出入口为ServiceBean的export方法,当spring启动完后,也是通过监听spring的容器刷新事件来触发export方法。

2.首先就是拿到配置项的参数值,优先级最高的是在启动参数中设置的,接着就是把参数,我们的配置注册中心,协议等等转换成url,然后每个协议每个注册中心都会对应起来,进行遍历,遍历的过程中,会把两个url组合成l类似Registry://... zookerper://...dubbo://这样的url,拿到spi加载的代理工厂,默认使用的是javasister,去获取可执行的invoker,参数就是ref,也就是服务的具体实现类,还有注册中心和当前服务的url等,这个时候得到的是AbstractProxyInvoker,然后使用了protocol的真正的开始暴露invoker,怎么知道,这个时候用哪个protocol的实现类呢,就是通过export方法中传入的invoker,找到url中配置的协议,通过spi加载,因为协议头是Registry://, 会拿到RegistryProtocol的实现类,然后在RegistryProtocol的export方法中,就是真正完成服务导出的两件事,第一件事就是拿到服务的url,比如dubbo://,将AbstractProxyInvoker包装成DelegateProviderMetaDataInvoker,DelegateProviderMetaDataInvoker包括了服务的提供者和服务的配置, 再次调用protocol的export(invoker)方法,参数就是这个invoker,这次肯定是找到了dubbo协议的实现类

3.在dubbo协议的export方法中,不会先执行dubbo协议的export方法,而是执行DubboProtocol的包装类ProtocolFilterWrapper的export方法,这个方法是对DelegateProviderMetaDataInvoker再次包装,构造过滤器链,当调用的时候在执行DelegateProviderMetaDataInvoker之前,就会执行先过滤器链。

4.然后再是真正的DubboProtocol的export(invoker)方法通过端口号,服务名,分组,版本号这些信息构造一个key,和它对应的invoker去生成一个exporter,缓存在exportmap中,后面有请求的时候可以直接找到对应的exporter,最后就是通过openServer去开启网络服务,在这个里面首先会构造一个exchangehandler,exchangehandler方法的reply方法就是 处理调用逻辑的地方。exchangehandler再经过一系列的handler的构造包装,成为一个责任链,最外层的就是NettyServerHandler,会把netty接收i请求后,也是从NettyServerHandler再一层层的往里面的handler调用,比如HeartbeatHandler和心跳机制有关,AllchannelHandler,跟Dubbo线程模型有关,最后调用回ExchangeHandler,

第二个是写入注册中心,通过刚才传入的url中,拿到真正的注册中心的url,比如zookeeper://,根据这个url拿到真正的注册中心的实现类,然后创建zk节点写入服务的url就ok了。

 

Dubbo服务调用原理

服务端的nettyserverhandler接收到数据后,会经过一系列的handler,比如HeartbeatHandler心跳handler. AllChannelHandler等,一直到最后的HeaderExchangeHandler:处理Request数据,⾸先构造⼀个Response对象,然后调⽤ExchangeHandlerAdapter得到⼀个CompletionStage future,然后给future通过whenComplete绑定⼀个回调函数,当future执⾏完了之后,就可以从回调函数中得到ExchangeHandlerAdapter的执⾏结果,并把执⾏结果设置给Response对象,通过channel发送出去,在ExchangeHandlerAdapter中的reply方法中,会根据请求中的key在ExporterMap中找到对应的

Invoker,这个invoker就是ProtocolFilterWrapper$CallbackRegistrationInvoker,执行它的过滤器链后会执行它包装的invoker,也就是DelegateProviderMetaDataInvoker,DelegateProviderMetaDataInvoker会执行它的包装的下一个invoker,也就是真正用javasister生成AbstractProxyInvoker,调用服务实现类对象的方法,得到结果返回。

 

首先ReferenceConfig类的init方法调用Protocol的refer方法生成Invoker实例(如上图中的红色部分),这是服务消费的关键。接下来把Invoker转换为客户端需要的接口(如:HelloWorld)。

关于每种协议如RMI/Dubbo/Web service等它们在调用refer方法生成Invoker实例的细节和上一章节所描述的类似

1578B282E1784460972777F1F23C76DD-wps1DB8.tmp.jpeg

 

 

 

 

AD714C83E4234EBD9964F4F7B790552D-wpsBEE2.tmp.jpeg

 

171880F9ACBC40DDAB07D77CD14784C6-clipboard.png

 

 

 

A8AE97B5A72C4CBA86E0D4BF9D180261-clipboard.png

 

Dubbo负载均衡

随机:按权重设置随机概率。此为默认算法.

轮循:按公约后的权重设置轮循比率。

最少活跃调用数:相同活跃数的随机,活跃数指调用前后计数差。

 一致性Hash:相同的参数总是发到同一台机器负载均衡

 

Dubbo容错机制

失败自动切换:调用失败后基于retries=“2” 属性重试其它服务器 

快速失败:快速失败,只发起一次调用,失败立即报错。

勿略失败:失败后勿略,不抛出异常给客户端。

失败重试:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作

并行调用: 只要一个成功即返回,并行调用指定数量机器,可通过 forks="2" 来设置最大并行数。

 

Dubbo协议

1. dubbo:单一长连接和NIO异步通讯,适合大并发小数据量的服务调用,以及消费者远大于提供者。传输协议TCP,异步,Hessian序列化;

2. rmi: 采用JDK标准的rmi协议实现,传输参数和返回参数对象需要实现Serializable接口,使用java标准序列化机制,使用阻塞式短连接,传输数据包大小混合,消费者和提供者个数差不多,可传文件,传输协议TCP。多个短连接,TCP协议传输,同步传输,适用常规的远程服务调用和rmi互操作。在依赖低版本的Common-Collections包,java序列化存在安全漏洞;

3. webservice: 基于WebService的远程调用协议,集成CXF实现,提供和原生WebService的互操作。多个短连接,基于HTTP传输,同步传输,适用系统集成和跨语言调用;

 http: 基于Http表单提交的远程调用协议,使用Spring的HttpInvoke实现。多个短连接,传输协议HTTP,传入参数大小混合,提供者个数多于消费者,需要给应用程序和浏览器JS调用;

 hessian: 集成Hessian服务,基于HTTP通讯,采用Servlet暴露服务,Dubbo内嵌Jetty作为服务器时默认实现,提供与Hession服务互操作。多个短连接,同步HTTP传输,Hessian序列化,传入参数较大,提供者大于消费者,提供者压力较大,可传文件;

 memcache: 基于memcached实现的RPC协议

 redis: 基于redis实现的RPC协议 dubbo: 单一长连接和NIO异步通讯,适合大并发小数据量的服务调用,以及消费者远大于提供者。传输协议TCP,异步,Hessian序列化;

 rmi: 采用JDK标准的rmi协议实现,传输参数和返回参数对象需要实现Serializable接口,使用java标准序列化机制,使用阻塞式短连接,传输数据包大小混合,消费者和提供者个数差不多,可传文件,传输协议TCP。

多个短连接,TCP协议传输,同步传输,适用常规的远程服务调用和rmi互操作。在依赖低版本的Common-Collections包,java序列化存在安全漏洞;

 webservice: 基于WebService的远程调用协议,集成CXF实现,提供和原生WebService的互操作。多个短连接,基于HTTP传输,同步传输,适用系统集成和跨语言调用;

 http: 基于Http表单提交的远程调用协议,使用Spring的HttpInvoke实现。多个短连接,传输协议HTTP,传入参数大小混合,提供者个数多于消费者,需要给应用程序和浏览器JS调用;

 hessian: 集成Hessian服务,基于HTTP通讯,采用Servlet暴露服务,Dubbo内嵌Jetty作为服务器时默认实现,提供与Hession服务互操作。多个短连接,同步HTTP传输,Hessian序列化,传入参数较大,提供者大于消费者,提供者压力较大,可传文件;

 memcache: 基于memcached实现的RPC协议

 redis: 基于redis实现的RPC协议

 

1.基本的使用,服务发布与服务调用

服务调用

<!--定义了提供方应用信息,用于计算依赖关系;在 dubbo-admin 或 dubbo-monitor 会显示这个名字,方便辨识-->

<dubbo:application name="demo-consumer"/>

<dubbo:registry address="zookeeper://localhost:2181"/>

<dubbo:reference id="demoService" check="true" interface="com.zyl.api.Api"/>

bbb

 

2、dubbo运行时,突然所有的zookeeper全部宕机,dubbo是否还会继续提供服务?

 

 

 

Dubbo 同步、异步调用的几种方式

1.同步调用是一种阻塞式的调用方式,即 客户端代码一直阻塞等待,直到 Provider 端返回为止;通常,一个典型的同步调用过程如下:

1.1Consumer 业务线程调用远程接口,向 Provider 发送请求,同时当前线程处于阻塞状态;

1.2Provider 接到 Consumer 的请求后,开始处理请求,将结果返回给 Consumer;

1.3Consumer 收到结果后,当前线程继续往后执行。

这里有 2 个问题:

Consumer 业务线程是怎么进入阻塞状态的,还有就是Consumer 收到结果后,如果唤醒业务线程往后执行的?

其实,Dubbo 的底层 IO 操作都是异步的。Consumer 端发起调用后,得到一个 Future 对象。对于同步调用,业务线程通过Future#get(timeout),阻塞等待 Provider 端将结果返回;timeout则是 Consumer 端定义的超时时间。当结果返回后,会设置到此 Future,并唤醒阻塞的业务线程;当超时时间到结果还未返回时,业务线程将会异常返回

2.异步调用

基于 Dubbo 底层的异步 NIO 实现异步调用,对于 Provider 响应时间较长的场景是必须的,它能有效利用 Consumer 端的资源,相对于 Consumer 端使用多线程来说开销较小。

异步调用,对于 Provider 端不需要做特别的配置。下面的例子中,Provider 端接口定义如下:

public interface AsyncService { String goodbye(String name); }

Consumer 配置

<dubbo:reference id="asyncService" interface="com.alibaba.dubbo.samples.async.api.AsyncService"> <dubbo:method name="goodbye" async="true"/> </dubbo:reference>

需要异步调用的方法,均需要使用 <dubbo:method/>标签进行描述。

 

一些特殊场景下,为了尽快调用返回,可以设置是否等待消息发出:

  • sent="true" 等待消息发出,消息发送失败将抛出异常;
  • sent="false" 不等待消息发出,将消息放入 IO 队列,即刻返回。

默认为fase。配置方式如下:

<dubbo:method name="goodbye" async="true" sent="true" />

如果你只是想异步,完全忽略返回值,可以配置 return="false",以减少 Future 对象的创建和管理成本:

<dubbo:method name="goodbye" async="true" return="false"/>

此时,RpcContext.getContext().getFuture()将返回null。

 

dubbo 消费者和服务提供者分别注册到不同的注册中心

消费者服务和提供者服务分别注册到不同的注册中心,这样就必须在消费者和提供者的配置文件中分别配置dubbo:registry来指明具体的ZK地址

 

使用竖号分隔多个不同注册中心地址:

<dubbo:application name="world" /> <!-- 多注册中心配置,竖号分隔表示同时连接多个不同注册中心,同一注册中心的多个集群地址用逗号分隔 --> <dubbo:registry address="10.20.141.150:9090|10.20.154.177:9010" /> <!-- 引用服务 --> <dubbo:reference id="helloService" interface="com.alibaba.hello.api.HelloService" version="1.0.0" />

 

 

多集群配置

<dubbo:registry address="zookeeper://10.20.141.150:9090?backup=10.20.141.151,10.20.141.152"/>

 

多注册中心多集群配置

<dubbo:registry address="zookeeper://10.20.141.150:9090?backup=10.20.141.151,10.20.141.152|zookeeper://10.20.141.153:9090"/>

 

服务分组

当一个接口有多种实现时,可以用 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:reference id="memberIndexService" group="member" interface="com.xxx.IndexService" />

任意组 1:

<dubbo:reference id="barService" interface="com.foo.BarService" group="*" />

 

多版本

当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。

可以按照以下的步骤进行版本迁移:

在低压力时间段,先升级一半提供者为新版本

再将所有消费者升级为新版本

然后将剩下的一半提供者升级为新版本

老版本服务提供者配置:

<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" />

如果不需要区分版本,可以按照以下的方式配置 1:

<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />

 

泛化提供

是指不通过接口的方式直接将服务暴露出去。通常用于Mock框架或服务降级框架实现

 

泛化引用

是指不通过常规接口的方式去引用服务,通常用于测试框架。

 

隐示传参

是指通过非常方法参数传递参数,类似于http 调用当中添加cookie值。通常用于分布式追踪框架的实现。使用方式如下 :

//客户端隐示设置值

RpcContext.getContext().setAttachment("index", "1"); // 隐式传参,后面的远程调用都会隐

//服务端隐示获取值

String index = RpcContext.getContext().getAttachment("index"); 

 

dubbo 直连服务地址

开发、测试环境可通过指定Url方式绕过注册中心直连指定的服务地址,避免注册中心中服务过多,启动建立连接时间过长

  1. <dubbo:reference id="pService" 
  2. interface="org.jstudioframework.dubbo.service.PService" 
  3. url="dubbo://127.0.0.1:20014"/>  

 

 

可以用group属性来分,服务提供方和消费方都可以指定同一个group即可。

 

 

 

 

Dubbo可以对结果进行缓存吗?

可以,Dubbo提供了声明式缓存,用于加速热门数据的访问速度,以减少用户加载缓存的工作量

 

 

服务提供者能实现失效踢出是什么原理?

服务失效踢出基于Zookeeper的临时节点原理

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值