浅析dubbo原理和实现 转
出处: 浅析dubbo原理和实现
一、Duboo基本概念解释
Dubbo是一种分布式服务框架。 Webservice也是一种服务框架,但是webservice并不是分布式的服务框架,他需要结合F5实现负载均衡。因此,dubbo除了可以提供服务之外,还可以实现软负载均衡。它还提供了两个功能Monitor 监控中心和调用中心。这两个是可选的,需要单独配置。
Dubbo的计数架构图如下:
我们解释以下这个架构图:
Consumer服务消费者,Provider服务提供者。Container服务容器。消费当然是invoke提供者了,invoke这条实线按照图上的说明当然同步的意思了,多说一句,在实际调用过程中,Provider的位置对于Consumer来说是透明的,上一次调用服务的位置(IP地址)和下一次调用服务的位置,是不确定的。这个地方就是实现了软负载。
Monitor这是一个监控,图中虚线表明Consumer 和Provider通过异步的方式发送消息至Monitor,Consumer和Provider会将信息存放在本地磁盘,平均1min会发送一次信息。Monitor在整个架构中是可选的(图中的虚线并不是可选的意思),Monitor功能需要单独配置,不配置或者配置以后,Monitor挂掉并不会影响服务的调用
调用关系说明:
- 调用服务容器负责启动,加载,运行服务提供者
- 服务提供者在启动时,向注册中心注册自己提供的服务(接口为名称:服务地址信息)
- 服务消费者在启动时,像注册中心订阅自己所需要的服务(把接口名称取出在注册中心中查找)
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
- 服务消费者,从提供地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,在算另一台调用(注:这里是不经过注册中心的,而是Duboox框架)
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
Dubbox默认请求时间是1000ms,请求三次,如果在业务加载时间较长时可以设置请求超时时间
Duboox支持的通讯协议
dubbo协议:(如dubbo://192.168.0.1:20188),dubbo默认的协议,单一长连接,NIO异步通信,基于hessian作为序列化协议,适用于并发高,传输数据量小。
( 为了要支持高并发场景,一般是服务提供者就几台机器,但服务消费者有上百台,可能每天调用量就达到上亿次!此时使用长连接最合适就是跟每个服务消费者维持一个长连接,然后后面直接基于长连接NIO异步通信,可以支撑高并发请求 )
rmi协议:走java二进制序列化,多个短链接,适合消费者和提供者差不多的情况下,适合用于文件传输,一般较少使用
hessian协议:走hessian序列化协议,多个短链接,适用于提供者数量比消费者数量还多,适用与文件传输,一般较少使用
http协议:走json序列化
webservice协议:走soap文本序列化
二 分析源代码,基本原理如下:
1:client一个线程调用远程接口,生成一个唯一的ID(比如一段随机字符串,UUID等),Dubbo是使用AtomicLong从0开始累计数字的
2:将打包的方法调用信息(如调用的接口名称,方法名称,参数值列表等),和处理结果的回调对象callback,全部封装在一起,组成一个对象object
3:向专门存放调用信息的全局ConcurrentHashMap里面put(ID, object)
4:将ID和打包的方法调用信息封装成一对象connRequest,使用IoSession.write(connRequest)异步发送出去
5:当前线程再使用callback的get()方法试图获取远程返回的结果,在get()内部,则使用synchronized获取回调对象callback的锁, 再先检测是否已经获取到结果,如果没有,然后调用callback的wait()方法,释放callback上的锁,让当前线程处于等待状态。
6:服务端接收到请求并处理后,将结果(此结果中包含了前面的ID,即回传)发送给客户端,客户端socket连接上专门监听消息的线程收到消息,分析结果,取到ID,再从前面的ConcurrentHashMap里面get(ID),从而找到callback,将方法调用结果设置到callback对象里。
7:监听线程接着使用synchronized获取回调对象callback的锁(因为前面调用过wait(),那个线程已释放callback的锁了),再notifyAll(),唤醒前面处于等待状态的线程继续执行(callback的get()方法继续执行就能拿到调用结果了),至此,整个过程结束。
当前线程怎么让它“暂停”,等结果回来后,再向后执行?
答:先生成一个对象obj,在一个全局map里put(ID,obj)存放起来,再用synchronized获取obj锁,再调用obj.wait()让当前线程处于等待状态,然后另一消息监听线程等到服 务端结果来了后,再map.get(ID)找到obj,再用synchronized获取obj锁,再调用obj.notifyAll()唤醒前面处于等待状态的线程。
正如前面所说,Socket通信是一个全双工的方式,如果有多个线程同时进行远程方法调用,这时建立在client server之间的socket连接上会有很多双方发送的消息传递,前后顺序也可能是乱七八糟的,server处理完结果后,将结果消息发送给client,client收到很多消息,怎么知道哪个消息结果是原先哪个线程调用的?
答:使用一个ID,让其唯一,然后传递给服务端,再服务端又回传回来,这样就知道结果是原先哪个线程的了。
三、远程调用细节
服务提供者暴露一个服务的详细过程:
上图是服务提供者暴露服务的主过程:
首先ServiceConfig类拿到对外提供服务的实际类ref,然后将ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到invoker的转化。接下来就是Invoker转换到Exporter的过程。
Dubbo处理服务暴露的关键就在Invoker转换到Exporter的过程,下面我们以Dubbo和rmi这两种典型协议的实现来进行说明
1. Dubbo的实现:
Dubbo协议的Invoker转为Exporter发生在DubboProtocol类的export方法,它主要是打开socket侦听服务,并接收客户端发来的各种请求,通讯细节由dubbo自己实现。
2. Rmi的实现:
RMI协议的Invoker转为Exporter发生在RmiProtocol类的export方法,他通过Spring或Dubbo或JDK来实现服务,通讯细节由JDK底层来实现。
服务消费者消费一个服务的详细过程:
上图是服务消费的主过程:
首先ReferenceConfig类的init方法调用Protocol的refer方法生成Invoker实例。接下来把Invoker转为客户端需要的接口。
附:发布服务配置文件:
- 使用dubbo发布服务
<!-- 提供方应用信息,用于计算依赖关系 --> <dubbo:application name="some-manager" /> <dubbo:registry protocol="zookeeper" address="192.168.25.154:2181,192.168.25.154:2182,192.168.25.154:2183" /> <!-- 用dubbo协议在20880端口暴 露服务 --> <dubbo:protocol name="dubbo" port="20880" /> <!-- 声明需要暴露的服务接口 --> <dubbo:service interface="com.xx.service.ItemService" ref="itemServiceImpl" timeout=“3000”/>//设置服务超时时间:服务调用超时时间默认1秒
- (消费)调用服务:
<!-- 引用dubbo服务 --> <dubbo:application name="some-manager-web"/> <dubbo:registry protocol="zookeeper" address="192.168.25.154:2181,192.168.25.154:2182,192.168.25.154:2183"/> <dubbo:reference interface="com.xx.service.ItemService" id="itemService" />
接口要么是采用服务方的接口API依赖,要么是复制相同的引用接口API到消费端,以便调用。
- 添加dubbo的依赖 加入dubbo相关的jar包。服务层、表现层都添加。
<!-- dubbo相关 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <!-- 排除依赖 --> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> </exclusion> <exclusion> <groupId>org.jboss.netty</groupId> <artifactId>netty</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> </dependency> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> </dependency>