dubbo源码解析目录-总结列表
一、dubbo架构设计
1、十层架构
第一层:service层,接口层,给服务提供者和消费者来实现的。
第二层:config层,配置层,主要是对dubbo进行各种配置的。
第三层:proxy层,服务代理层,透明生成客户端的stub和服务单的skeleton。
第四层:registry层,服务注册层,负责服务的注册与发现。
第五层:cluster层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务。
第六层:monitor层,监控层,对rpc接口的调用次数和调用时间进行监控
第七层:protocol层,远程调用层,封装rpc调用。
第八层:exchange层,信息交换层,封装请求响应模式,同步转异步。
第九层:transport层,网络传输层,抽象mina和netty为统一接口。
第十层:serialize层,数据序列化层。
Dubbo框架设计一共划分了10个层:
- 服务接口层(Service):该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现。
- 配置层(Config):对外配置接口,以ServiceConfig和ReferenceConfig为中心,可以直接new配置类,也可以通过spring解析配置生成配置类。
- 服务代理层(Proxy):服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory。
- 服务注册层(Registry):封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory、Registry和RegistryService。可能没有服务注册中心,此时服务提供方直接暴露服务。
- 集群层(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。
————————————————
2、Dubbo通信协议
2.3.1.1 默认协议
默认就是走dubbo协议的,单一长连接,NIO异步通信,基于hessian作为序列化协议。
适用的场景就是:传输数据量很小(每次请求在100kb以内),但是并发量很高。
为了要支持高并发场景,一般是服务提供者就几台机器,但是服务消费者有上百台,可能每天调用量达到上亿次!此时用长连接是最合适的,就是跟每个服务消费者维持一个长连接就可以,可能总共就100个连接。然后后面直接基于长连接NIO异步通信,可以支撑高并发请求。
否则如果上亿次请求每次都是短连接的话,服务提供者会扛不住。
而且因为走的是单一长连接,所以传输数据量太大的话,会导致并发能力降低。所以一般建议是传输数据量很小,支撑高并发访问。
2.3.1.2 其他协议:
Dubbo 默认协议:议采用单一长连接和 NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。
REST 协议: 基于标准的Java REST API实现的REST调用支持
Hessian 协议:Hessian 协议用于集成 Hessian 的服务,Hessian 底层采用 Http 通讯,采用
Servlet 暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现
HTTP 协议:需同时给应用程序和浏览器 JS 使用的服务。
dubbo rpc jsonrpc
WebService 协议:系统集成,跨语言调用
RMI 协议:RMI 协议采用 JDK 标准的 java.rmi.* 实现,采用阻塞式短连接和 JDK 标准序列化方式。
Thrift 协议:使用Thrift实现PRC协议
Redis 协议:基于redis实现RPC协议
Memcached 协议:基于Memcached实现RPC协议
这里,先给出META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol内容:
1 registry=com.alibaba.dubbo.registry.integration.RegistryProtocol 2 dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol 3 filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper 4 listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper 5 mock=com.alibaba.dubbo.rpc.support.MockProtocol 6 injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol 7 rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol 8 hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol 9 com.alibaba.dubbo.rpc.protocol.http.HttpProtocol 10 com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol 11 thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol 12 memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol 13 redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol
————————————————
3、序列化协议
序列化:序列化,是一种把对像流化的机制,即把对像写入二进制数组(byte[]),传输到另一个地方,将其读出来之后,进行反序列化,得到对象含状态(状态就是属性的值)。
为什么要序列化。方便在网络间进行传输,并且可以把对象持久化到数据库或者文件中。
序列化协议(6种)
dubbo-hessian-lite 是官方 hessian 的一个 Apache Dubbo 私有版本。
Apache Avro™ 是一个数据序列化系统
Jdk 序列化
JSON - fastjson:一个Java版本内的 JSON 解析器/生成器
FST: 一个快速的Java序列化工具
Kryo是一个Java版本的快速有效的二进制对象序列化框架
————————————————
4、远程调用的方式(4种)
1.RPC:采用C/S方式,跨语言跨平台,Apache Thrift是RPC的经典框架
2.webservice:请求应答机制,跨系统跨平台
3.RMI: 降低客户端与服务器的耦合性,java远程接口调用;跨虚拟机
4.JMS:JAVA消息服务,点对点和发布订阅模型 如ActiveMQ
————————————————
5、负载均衡策略
1)random(随机) load balance
默认情况下,dubbo是random(随机) load balance随机调用实现负载均衡,可以对provider不同实例设置不同的权重,会按照权重来负载均衡,权重越大分配流量越高,一般就用这个默认的就可以了。
2)roundrobin(轮询调度) loadbalance
还有roundrobin loadbalance,这个的话默认就是均匀地将流量打到各个机器上去,但是如果各个机器的性能不一样,容易导致性能差的机器负载过高。所以此时需要调整权重,让性能差的机器承载权重小一些,流量少一些。
3)leastactive loadbalance
这个就是自动感知一下,如果某个机器性能越差,那么接收的请求越少,越不活跃,此时就会给不活跃的性能差的机器更少的请求
4)consistanthash loadbalance
一致性Hash算法,相同参数的请求一定分发到一个provider上去,provider挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。如果你需要的不是随机负载均衡,是要一类请求都到一个节点,那就走这个一致性hash策略。
————————————————
6、集群容错策略:
1)failover cluster模式
失败自动切换,自动重试其他机器,默认就是这个,常见于读操作
2)failfast cluster模式
一次调用失败就立即失败,常见于写操作
3)failsafe cluster模式
出现异常时忽略掉,常用于不重要的接口调用,比如记录日志
4)failbackc cluster模式
失败了后台自动记录请求,然后定时重发,比较适合于写消息队列这种
5)forking cluster
并行调用多个provider,只要一个成功就立即返回
6)broadcacst cluster
逐个调用所有的provider
————————————————
7、SPI(Service provide interface)机制
SPI,Service Provider Interface,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。
SPI是JDK内置的一种服务提供发现机制。目前市面上很多框架都用它来做服务的扩展发现。简单的说,它是一种动态替换发现的机制。
举个简单的例子,我们想在运行时动态给它添加实现,你只需要添加一个实现,然后把新的实现描述给JDK知道就行了。大家耳熟能详的如JDBC,日志框架都有用到。
当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader。
加载机制:
dubbo的spi实现原理和java spi相似,只不过增强了一些功能和优化。java spi的是把所有的spi都加载到内存,但对于dubbo来说可能只需要加载用户指定的实现方式,而不需要全部加载进来,全部加载也会有性能问题,所以dubbo实现的是在有用到的时候去加载这些扩展组件。
ExtensionLoader可以类比为JDK-SPI中的ServiceLoader。
上述的方法分别从三个目录查找SPI文件并进行加载。在这里只有在META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory中有值,内容如下:
1 adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory 2 spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory 3 spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory
————————————————
8、服务治理
1)调用链路自动生成
一个大型的分布式系统,或者说是用现在流行的微服务架构来说吧,分布式系统由大量的服务组成。那么这些服务之间互相是如何调用的?调用链路是啥?说实话,几乎到后面没人搞的清楚了,因为服务实在太多了,可能几百个甚至几千个服务。
那就需要基于dubbo做的分布式系统中,对各个服务之间的调用自动记录下来,然后自动将各个服务之间的依赖关系和调用链路生成出来,做成一张图,显示出来,大家才可以看到对吧。
服务A -> 服务B -> 服务C
-> 服务E
-> 服务D
-> 服务F
-> 服务W
2)服务访问压力以及时长统计
需要自动统计各个接口和服务之间的调用次数以及访问延时,而且要分成两个级别。一个级别是接口粒度,就是每个服务的每个接口每天被调用多少次,TP50,TP90,TP99,三个档次的请求延时分别是多少;第二个级别是从源头入口开始,一个完整的请求链路经过几十个服务之后,完成一次请求,每天全链路走多少次,全链路请求延时的TP50,TP90,TP99,分别是多少。
这些东西都搞定了之后,后面才可以来看当前系统的压力主要在哪里,如何来扩容和优化啊
3)其他的
服务分层(避免循环依赖),调用链路失败监控和报警,服务鉴权,每个服务的可用性的监控(接口调用成功率?几个9?)99.99%,99.9%,99%
失败重试和超时重试
所谓失败重试,就是consumer调用provider要是失败了,比如抛异常了,此时应该是可以重试的,或者调用超时了也可以重试。
如果是超时了,timeout就会设置超时时间;如果是调用失败了自动就会重试指定的次数。
你就结合你们公司的具体的场景来说说你是怎么设置这些参数的,timeout,一般设置为200ms,我们认为不能超过200ms还没返回。
retries,3次,设置retries,还一般是在读请求的时候,比如你要查询个数据,你可以设置个retries,如果第一次没读到,报错,重试指定的次数,尝试再次读取2次。
————————————————
9、Dubbo适用于哪些场景
1.RPC分布式服务
当网站变大后,不可避免的需要拆分应用进行服务化,以提高开发效率,调优性能,节省关键竞争资源等。
比如:为了适用不断变化的市场需求,以及多个垂直应用之间数据交互方便,我们把公共的业务抽取出来作为独立的模块,为其他的应用提供服务,系统逐渐依赖于抽象和rpc远程服务调用。
2.配置管理
当服务越来越多时,服务的URL地址信息就会爆炸式增长,配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。
3.服务依赖
当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。
4.服务扩容
接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?等等……
在遇到这些问题时,都可以用Dubbo来解决。
————————————————
二、dubbo配置详解
dubbo配置之间的关系:
左边是服务提供方的相关配置,右边是服务消费方的相关配置。中间是两方的共享配置。下边是方法和方法参数的相关配置。
ReferenceConfig继承ConsumerConfig,ServiceConfig继承ProviderConfig。如果没有进行Reference和Service的配置,默认是Consumer和Provider的配置。
二、配置覆盖关系:
1、方法级优先,接口级次之,全局配置再次之。(级别小的优先)
2、如果级别一样,则消费方优先,提供方次之。
其中,服务提供方配置,通过URL经由注册中心传递给消费方。
(配置的查找顺序,其他retries,loadbalance,actives等类似)
三、标签:
四、举例
1、项目中的配置
dubbo.xml的配置如下:
<!-- 应用信息,用于计算依赖关系 --> <dubbo:application name="basicInfoservice"/> <!-- 使用zookeeper注册中心暴露服务地址 --> <dubbo:registry protocol="zookeeper" address="${dubbo.registry.address}" register="${dubbo.registry.register}"/> <!--使用dubbo协议,basicInfoservice应用的端口为20881--> <dubbo:protocol name="dubbo" port="20881"/> <!--提供方的超时时间为3s--> <dubbo:provider timeout="3000"/> <!--消费方的超时时间为3s--> <dubbo:consumer check="false" timeout="3000"/> <!--需要引用的服务--> <dubbo:reference id="dictionaryFacade" interface="com.dmsdbj.itoo.singleTableMaintain.facade.DictionaryFacade" /> <!--需要暴露的服务--> <dubbo:service id="studentFacade" interface="com.dmsdbj.itoo.basicInfo.facade.StudentFacade" /> <!--studentFacade服务的超时时间为30s,addStudent方法的超时时间为60s--> <dubbo:service id="studentFacade" interface="com.dmsdbj.itoo.basicInfo.facade.StudentFacade" timeout="30000" loadbalance="roundrobin" > <dubbo:method name="addStudent" timeout="60000"/> </dubbo:service> dubbo-server.properties配置如下: dubbo.registry.address=zookeeper://192.168.22.156:2181?backup=192.168.22.154:2181,192.168.22.156:2182 dubbo.basicInfo.group=basicInfo dubbo.basicInfo.version=1.0.0 dubbo.registry.register=false ———————————————
上述实例,我们的全局的超时时间为3s,负载均衡策略为随机,student服务的超时时间为30s,负载均衡策略为轮询。addStudent的超时时间为60s。
<dubbo:annotation package="com.dmsdbj.itoo.basicInfo.facade"/>
这段配置的作用是开启注解扫描。
开启注解之后,就可以使用@Reference和@Service来订阅服务或者暴露服务啦。需要注意的是@Service并不是Spring的注解,而是
dubbo的注解 import com.alibaba.dubbo.config.annotation.Service;
也可以说<dubbo:reference>标签+@Autowired等价于<dubbo:annotation package="">+@Reference。
————————————————
常用配置
application
应用级别的配置,用于配置应用信息
配置项 | 作用 |
---|---|
name | 应用的名称,用于标识哪个dubbo应用 |
version | 应用的版本 |
owner | 应用的维护者 |
logger | 指定日志框架,可选:slf4j, jcl, log4j, log4j2, jdk,默认log4j |
compiler | 指定字节码处理类,可选:jdk, javassist,默认Javassist |
environment | 指定所属环境,可选:develop, test, product |
module
用于配置当前模块信息,可选,用处不大,只是为了配置补充
配置项 | 作用 |
---|---|
name | 模块名称 |
version | 模块的版本 |
owner | 模块的维护者 |
isDefault | 是否默认配置,如果配置多个module则需要指定default |
registry
注册中心配置
配置项 | 作用 |
---|---|
address | 注册中心服务器地址,如果地址没有端口缺省为9090,同一集群内的多个地址用逗号分隔,如:ip:port,ip:port,不同集群的注册中心,请配置多个<<dubbo:registry>>标签 |
username | 登录注册中心用户名 |
password | 登录注册中心密码 |
port | 注册中心缺省端口,当address没有带端口时使用此端口做为缺省值,默认9090 |
protocol | 注册中心地址协议,支持dubbo, multicast, zookeeper, redis, consul(2.7.1), sofa(2.7.2), etcd(2.7.2), nacos(2.7.2)等协议 |
transporter | 网络传输方式,可选mina,netty,默认netty |
check | 注册中心不存在时,是否报错,默认true |
timeout | 注册中心请求超时时间(毫秒),默认5000 |
register | 是否向此注册中心注册服务,如果设为false,将只订阅,不注册,默认true |
subscribe | 是否向此注册中心订阅服务,如果设为false,将只注册,不订阅,默认true |
group | 服务注册分组,跨组的服务不会相互影响,也无法相互调用,适用于环境隔离。 |
protocol
服务提供者协议配置
配置项 | 作用 |
---|---|
name | 协议名称,默认dubbo |
host | 自动查找本机IP,多网卡选择或指定VIP及域名时使用,为空则自动查找本机IP |
port | 服务端口,dubbo协议缺省端口为20880,rmi协议缺省端口为1099,http和hessian协议缺省端口为80;如果没有配置port,则自动采用默认端口,如果配置为-1,则会分配一个没有被占用的端口 |
threadpool | 线程池类型,可选:fixed/cached,默认fixed |
threads | 服务线程池大小(固定大小),默认200 |
iothreads | io线程池大小(固定大小),默认cpu个数+1 |
accepts | 服务提供方最大可接受连接数,默认0 |
payload | 请求及响应数据包大小限制,单位:字节,默认8388608(=8M) |
codec | 协议编码方式,默认dubbo |
serialization | 协议编码方式,协议序列化方式,当协议支持多种序列化方式时使用,比如:dubbo协议的dubbo,hessian2,java,compactedjava,以及http协议的json等,dubbo协议缺省为hessian2,rmi协议缺省为java,http协议缺省为json |
accesslog | 设为true,将向logger中输出访问日志,也可填写访问日志文件路径,直接把访问日志输出到指定文件 |
transporter | 协议的服务端和客户端实现类型,比如:dubbo协议的mina,netty等,可以分拆为server和client配置,dubbo协议缺省为netty |
dispatcher | 协议的消息派发方式,用于指定线程模型,比如:dubbo协议的all, direct, message, execution, connection等,默认all |
monitor
监控中心配置
配置项 | 作用 |
---|---|
protocol | 监控中心协议,如果为protocol=“registry”,表示从注册中心发现监控中心地址,否则直连监控中心。默认dubbo |
address | 直连监控中心服务器地址,address=“10.20.130.230:12080” |
username | 用户名 |
password | 密码 |
provider
服务提供者缺省值配置
配置项 | 作用 |
---|---|
host | 服务主机名,多网卡选择或指定VIP及域名时使用 |
port | 服务提供者端口 |
threadpool | 线程池类型,可选:fixed/cached/limit(2.5.3以上)/eager(2.6.x以上),默认fixed |
threads | 服务线程池大小(固定大小),默认200 |
iothreads | io线程池大小(固定大小),默认cpu个数+1 |
timeout | 远程服务调用超时时间(毫秒),1000 |
retries | 远程服务调用重试次数,不包括第一次调用,不需要重试请设为0,默认2 |
loadbalance | 负载均衡策略,可选值:random,roundrobin,leastactive,分别表示:随机,轮询,最少活跃调用,默认 random |
stub | 设为true,表示使用缺省代理类名,即:接口名 + Local后缀。自定义扩展 |
mock | 设为true,表示使用缺省Mock类名,即:接口名 + Mock后缀。降级用 |
token | 令牌验证,为空表示不开启,如果为true,表示随机生成动态令牌 |
registry | 向指定注册中心注册,在多个注册中心时使用,值为 <<dubbo:registry>> 的id属性,多个注册中心ID用逗号分隔,如果不想将该服务注册到任何registry,可将值设为N/A |
weight | 服务权重 |
executes | 服务提供者每服务每方法最大可并行执行请求数 |
proxy | 生成动态代理方式,可选:jdk/javassist,默认javassist |
cluster | 集群方式,可选:failover/failfast/failsafe/failback/forking,默认failover |
deprecated | 服务是否过时,如果设为true,消费方引用时将打印服务过时警告error日志 |
async | 是否缺省异步执行,不可靠异步,只是忽略返回值,不阻塞执行线程,默认false |
consumer
服务消费者缺省值配置
配置项 | 作用 |
---|---|
timeout | 远程服务调用超时时间(毫秒),默认1000 |
retries | 远程服务调用重试次数,不包括第一次调用,不需要重试请设为0,仅在cluster为failback/failover时有效,默认2 |
loadbalance | 负载均衡策略,可选值:random,roundrobin,leastactive,分别表示:随机,轮询,最少活跃调用 |
async | 是否缺省异步执行,默认false |
generic | 是否缺省泛化接口,默认false |
check | 启动时检查提供者是否存在,true报错,false忽略,默认true |
cluster | 集群方式,可选:failover/failfast/failsafe/failback/forking,默认failover |
filter | 服务消费方远程调用过程拦截器名称,多个名称用逗号分隔 |
registry | 向指定注册中心注册,在多个注册中心时使用,值为<dubbo:registry>的id属性,多个注册中心ID用逗号分隔,如果不想将该服务注册到任何registry,可将值设为N/A |
init | 是否在afterPropertiesSet()时饥饿初始化引用,否则等到有人注入或引用该实例时再初始化,默认false |
cache | 以调用参数为key,缓存返回结果,可选:lru, threadlocal, jcache等 |
validation | 是否启用JSR303标准注解验证,如果启用,将对方法参数上的注解进行校验 |
async | 是否缺省异步执行,默认false |
corethreads | 线程池的核心线程数 |
threads | 线程池的最大线程数 |
injvm | 是否只暴露在本地jvm |
lazy | 是否在发起远程调用的时候初始化连接 |
mock | 设为true,表示使用缺省Mock类名,即:接口名 + Mock后缀。降级用 |
stub | 设为true,表示使用缺省代理类名,即:接口名 + Local后缀。自定义扩展 |
config-center
配置中心配置,平时基本没用
配置项 | 作用 |
---|---|
protocol | 使用哪个配置中心:apollo、zookeeper、nacos等。默认zookeeper |
address | 配置中心地址,以zookeeper为例 1. 指定 protocol,则 address 可以简化为127.0.0.1:2181;2. 不指定 protocol,则 address 取值为 zookeeper://127.0.0.1:2181 |
username | 配置中心用户名 |
password | 配置中心密码 |
timeout | 获取配置的超时时间,默认3000 |
check | 当配置中心连接失败时,是否终止应用启动,默认true |
metadata-report
元数据中心配置,平时基本没用
配置项 | 作用 |
---|---|
address | 元数据中心地址 |
username | 元数据中心用户名 |
password | 元数据中心密码 |
timeout | 超时时间 |
三、netty3基本使用
由于dubbo默认使用的是netty3进行通信的,这里简单的列出一个netty3通信的例子。
服务端步骤:
- 首先创建了NioServerSocketChannelFactory:创建boss线程池,创建worker线程池以及worker线程数。(boss线程数默认为1个)
- 创建ServerBootstrap server端启动辅助类
- 为ServerBootstrap设置ChannelPipelineFactory工厂,并为ChannelPipelineFactory将来创建出的ChannelPipeline设置编码器/解码器/事件处理器
- 使用ServerBootstrap绑定监听地址和端口
客户端步骤:(与Server几乎相同)
- 首先创建了NioClientSocketChannelFactory:创建boss线程池,创建worker线程池以及worker线程数。(boss线程数默认为1个)
- 创建ClientBootstrap client端启动辅助类
- 为ClientBootstrap设置ChannelPipelineFactory工厂,并为ChannelPipelineFactory将来创建出的ChannelPipeline设置编码器/解码器/事件处理器
- 使用ClientBootstrap连接Server端监听的地址和端口
- 属性
- scheduled:是一个有1个名字为dubbo-remoting-server-heartbeat的后台线程的定时线程池;
- server:之前创建出来的NettyServer实例;
- heartbeatTimer:心跳计时器
- heartbeat:心跳时间,该参数会在HeaderExchangeServer的构造器中进行赋值,60000
- heartbeatTimeout:心跳超时时间,超过该时间,会进行channel重连,180000
四、注册服务到zookeeper
远程服务的暴露总体步骤:
- 将ref封装为invoker
- 将invoker转换为exporter
- 启动netty
- 注册服务到zookeeper
- 订阅
- 返回新的exporter实例
本节实现第四步:注册服务到zk。总体代码如下:RegistryProtocol.export(final Invoker<T> originInvoker)
1 final Registry registry = getRegistry(originInvoker);//创建ZookeeperRegistry实例:创建CuratorClient,并启动会话。 2 final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);//获取真正要注册在zk上的url 3 registry.register(registedProviderUrl);//创建节点(即注册服务到zk上)
首先对originInvoker中的url进行处理:
- 将协议换成zookeeper
- 去掉registry=zookeeper的参数
来看一下originInvoker的url:(解码后的)
registry://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&client=curator&dubbo=2.0.0&export=dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=2791&side=provider×tamp=1507262031554&pid=2791®istry=zookeeper×tamp=1507262031521
本节实现第五步:订阅。总体代码如下:RegistryProtocol.export(final Invoker<T> originInvoker)
1 // 订阅override数据 2 // FIXME 提供者订阅时,会影响同一JVM即暴露服务,又引用同一服务的的场景,因为subscribed以服务名为缓存的key,导致订阅信息覆盖。 3 final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl); 4 final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker); 5 overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); 6 registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
说明:
- 第一句代码根据registedProviderUrl来获取overrideSubscribeUrl。
- 第二句代码创建overrideSubscribeListener
- 第三句代码将{ overrideSubscribeUrl : overrideSubscribeListener放入缓存 }
- 第四句代码实现真正的订阅与通知
五、客户端发起请求
客户端请求代码:
1 DemoService demoService = (DemoService) context.getBean("demoService"); // 获取远程服务代理 2 String hello = demoService.sayHello("world"); // 执行远程方法
总体流程:
- 将请求参数(方法名,方法参数类型,方法参数值,服务名,附加参数)封装成一个Invocation
- 附加参数中的path:即接口名,将会用于服务端接收请求信息后从exportMap中选取Exporter实例
- 方法名,方法参数类型,方法参数值:将用于JavassistProxyFactory$AbstractProxyInvoker执行对应的方法
- 使用Directory从Map<String, List<Invoker<T>>> methodInvokerMap中获取key为sayHello(指定方法名)的List<Invoker<T>>
- 使用Router对上述的List<Invoker<T>>再进行一次过滤,得到subList
- 使用LoadBalancer从subList中再获取一个Invoker,实际上是InvokerDelegete实例
- 使用InvokerDelegete实例执行真正的DubboInvoker的listener和filter链,然后执行到真正的DubboInvoker
- DubboInvoker使用NettyClient向服务端发出了请求
异步调用方式
服务提供方不变,调用方代码如下:
1 <dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService"> 2 <dubbo:method name="sayHello" async="true" timeout="60000"/> 3 <dubbo:method name="sayBye" async="true" timeout="60000"/> 4 </dubbo:reference>
配置里添加<dubbo:method name="xxx" async="true"/>,表示单个方法xxx使用异步方式;如果demoService下的所有方法都使用异步,直接配置为<dubbo:reference async="true"/>。
事件通知机制调用方式
两个服务:
- DemoService:真正要调用的服务
- Notify:事件通知服务(用在consumer端)
xml配置:
1 <bean id="notifyService" class="com.alibaba.dubbo.demo.consumer.eventnotify.NotifyService"/> 2 <dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService"> 3 <dubbo:method name="sayHello" timeout="60000" oninvoke="notifyService.oninvoke" onreturn="notifyService.onreturnWithoutParam" onthrow="notifyService.onthrow"/> 4 </dubbo:reference>
之后就可以运行Consumer启动类,之后调用demoService.sayHello(String name)了。
注意:
- oninvoke方法:
- 必须具有与真实的被调用方法sayHello相同的入参列表:例如,oninvoke(String name)
- onreturn方法:
- 至少要有一个入参且第一个入参必须与sayHello的返回类型相同,接收返回结果:例如,onreturnWithoutParam(String result)
- 可以有多个参数,多个参数的情况下,第一个后边的所有参数都是用来接收sayHello入参的:例如, onreturn(String result, String name)
- onthrow方法:
- 至少要有一个入参且第一个入参类型为Throwable或其子类,接收返回结果;例如,onthrow(Throwable ex)
- 可以有多个参数,多个参数的情况下,第一个后边的所有参数都是用来接收sayHello入参的:例如,onthrow(Throwable ex, String name)
- 如果是consumer在调用provider的过程中,出现异常时不会走onthrow方法的,onthrow方法只会在provider返回的RpcResult中含有Exception对象时,才会执行。(dubbo中下层服务的Exception会被放在响应RpcResult的exception对象中传递给上层服务)
dubbo的心跳机制:
- 目的:检测provider与consumer之间的connection连接是不是还连接着,如果连接断了,需要作出相应的处理。
- 原理:
- provider:dubbo的心跳默认是在heartbeat(默认是60s)内如果没有接收到消息,就会发送心跳消息,如果连着3次(180s)没有收到心跳响应,provider会关闭channel。
- consumer:dubbo的心跳默认是在60s内如果没有接收到消息,就会发送心跳消息,如果连着3次(180s)没有收到心跳响应,consumer会进行重连。
provider端:
1 <dubbo:service ...> 2 <dubbo:parameter key="heartbeat" value="3000"/> 3 </dubbo:service>
consumer端:
1 <dubbo:reference ...> 2 <dubbo:parameter key="heartbeat" value="3000"/> 3 </dubbo:reference>
六、dubbo线程模型
netty的线程模型
在netty中存在两种线程:boss线程和worker线程。
1 boss线程
作用:
- accept客户端的连接;
- 将接收到的连接注册到一个worker线程上
个数:
- 通常情况下,服务端每绑定一个端口,开启一个boss线程
2 worker线程
作用:
- 处理注册在其身上的连接connection上的各种io事件
个数:
- 默认是:核数+1
注意:
- 一个worker线程可以注册多个connection
- 一个connection只能注册在一个worker线程上
dubbo的事件派发策略和线程池
1、dubbo基于netty。有5种派发策略:
- 默认是all:所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。 即worker线程接收到事件后,将该事件提交到业务线程池中,自己再去处理其他事。
- direct:worker线程接收到事件后,由worker执行到底。
- message:只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO线程上执行
- execution:只请求消息派发到线程池,不含响应(客户端线程池),响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行
- connection:在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。
2、业务线程池:
- fixed:固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)
- coresize:200
- maxsize:200
- 队列:SynchronousQueue
- 回绝策略:AbortPolicyWithReport - 打印线程信息jstack,之后抛出异常
- cached:缓存线程池,空闲一分钟自动删除,需要时重建。
- limited:可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。
三 服务端
两种线程池:
- io线程池:netty的boss和worker线程池。
- boss:建立connection
- worker:处理注册在其身上的连接connection上的各种io事件
- 业务线程池:fixedThreadPool():“DubboServerHandler-10.10.10.11:20880” 见“二 dubbo的事件派发策略和线程池”
- 与worker配合处理各种请求
四 客户端
两种线程池:
- io线程池:netty的boss和worker线程池
- 同上
- 业务线程池:cachedThreadPool:“DubboClientHandler-10.10.10.10:20880”
- 与worker配合处理各种响应,最后得到响应后唤醒被阻塞的主线程
五 dubbo线程模型图
整体步骤:(受限于派发策略,以默认的all为例, 以netty4为例)
- 客户端的主线程发出一个请求后获得future,在执行get时进行阻塞等待;
- 服务端使用worker线程(netty通信模型)接收到请求后,将请求提交到server线程池中进行处理
- server线程处理完成之后,将相应结果返回给客户端的worker线程池(netty通信模型),最后,worker线程将响应结果提交到client线程池进行处理
- client线程将响应结果填充到future中,然后唤醒等待的主线程,主线程获取结果,返回给客户端
七、dubbo结果缓存机制
dubbo提供了三种结果缓存机制:
- lru:基于最近最少使用原则删除多余缓存,保持最热的数据被缓存
- threadlocal:当前线程缓存
- jcache:可以桥接各种缓存实现
lru使用方式
1 <dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService"> 2 <dubbo:method name="sayHello" timeout="60000" cache="lru"/> 3 </dubbo:reference>
添加cache配置。
ThreadLocal缓存源码解析
根据文章开头提到的bug,cache=""只能配置在服务级别。
1 <dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService" cache="threadlocal"/>
八、dubbo-monitor计数监控
监控总体图:
红色:监控中心 - dubbo-simple-monitor
黄色:provider
蓝色:consumer
统计总体流程:
- MonitorFilter向DubboMonitor发送数据
- DubboMonitor将数据进行聚合后(默认聚合1min中的统计数据)暂存到ConcurrentMap<Statistics, AtomicReference<long[]>> statisticsMap,然后使用一个含有3个线程(线程名字:DubboMonitorSendTimer)的线程池每隔1min钟,调用SimpleMonitorService遍历发送statisticsMap中的统计数据,每发送完毕一个,就重置当前的Statistics的AtomicReference<long[]>
- SimpleMonitorService将这些聚合数据塞入BlockingQueue<URL> queue中(队列大写为100000)
- SimpleMonitorService使用一个后台线程(线程名为:DubboMonitorAsyncWriteLogThread)将queue中的数据写入文件(该线程以死循环的形式来写)
- SimpleMonitorService还会使用一个含有1个线程(线程名字:DubboMonitorTimer)的线程池每隔5min钟,将文件中的统计数据画成图表
注意:
- SimpleMonitorService理解为一个服务提供者;而provider和consumer都是一个服务消费者,所以二者的DubboMonitor中的MonitorService实例都是一个代理实例。
- dubbo-monitor计数监控不支持异步调用下的数据监控
一、dubbo-monitor使用
在配置文件中添加:
1 <dubbo:monitor address="10.211.55.5:9090" />
即开启了monitor监控,并且指定了监控中心服务器为“10.211.55.5:9090”。
9090端口是Prometheus的默认端口,dubbo提供的监控中心比较简陋,我们后续会使用Prometheus作为监控中心来存储监控数据。