Dubbo RPC源码解读

https://yq.aliyun.com/articles/272405#27

 

本文代码摘录的时候,将一些与本流程无关的内容去掉了,如有需要请看源码。

一、闲言碎语

使用rpc框架已经多年了,虽然之前有研究过rpc的过程,但是却不曾详细阅读过dubbo的源码,探究过其中的设计思路与亮点。所以抽时间阅读了一下dubbo的源码,分享出来和大家一起学习。

 

二、目标与示例

1. 目标

l   探究dubbo rpc实现原理。

l   探究rpc从发出请求到收到返回结果这整个过程的详细过程。

l   学习rpc的负载均衡原理。

l   学习服务暴露、服务发现的原理以及实现细节。

l   多线程中dubbo是如何做到将返回结果和每个线程一一对应的。

 

本文重点为源码分析和模型实现分析,如果对dubbo的概念和使用不熟悉,情移步官网。

本文的所有分析均基于dubbo 2.5.3版本。

本文假定使用zookeeper管理服务。

 

2. 示例代码

以下的分析基于以下配置方式。不同的配置方式并不会影响本文所需要解决的几个问题,只是一下方式配置会比较便于理解,所以这里依次做为示例。

1) consumer

 

<bean id="rpcServiceRef" class="com.alibaba.dubbo.config.spring.ReferenceBean"> <property name="interface" value="com.wzf.service.RpcService"/> <property name="application" ref="dubboApplicationConfig"/> <property name="registry" ref="dubboRegistryConfig"/> <property name="version" value="dev"/> <property name="timeout" value="3000"/> <property name="retries" value="0"/> <property name="check" value="false"/> </bean>

 

 

 

2) provider

 

<bean id="rpcServiceExport" class="com.alibaba.dubbo.config.spring.ServiceBean"> <property name="interface" value="com.wzf.funny.service.RpcService"/> <property name="ref" ref="rpcServiceImpl"/> <property name="application" ref="dubboApplicationConfig"/> <property name="registry" ref="dubboRegistryConfig"/> <property name="protocol" ref="dubboProtocolConfig"/> <property name="version" value="dev"/> <property name="timeout" value="0"/> <property name="retries" value="0"/> </bean>

 

 

三、  模型

1. dubbo的模块模型

99a978dea16c5ceeeeded827675e541e11f97c08

dubbo的模块模型有些复杂,不太容易看懂,如果你也有同感的话,可以看一下本文后面的几部分,他们详细讲述了dubbo中rpc的调用链,其中包括了核心的几个类,比较便于理解。

 

2. 服务调用关系模型

28338f8c34c47f7b8e1befb893e262d50cf94078

 

如图所示,dubbo的RPC调用模型分为registry、provider、consumer、monitor这几个部分。此图展示了从服务注册、发现、调用的全过程,但dubbo是如何做到的呢?其实这个问题包括了以下几个问题:provider如何注册服务到zookeeper;consumer如何从zookeeper拉取provider信息;provider变化以后,zookeeper如何告知consumer;consumer如何调用provider。另外,监控逻辑很简单本文暂时不做分析。

 

3. Provider

从源码上看ServiceBean主要完成以下几件工作:服务暴露,取消暴露服务。

1) 暴露服务

服务暴露开始于ServiceBean的afterPropertiesSet方法,此方法在ServiceBean的所有属性都被赋值以后被BeanFactory调用。服务暴露的调用链是: ServiceConfig#export -> ServiceConfig#doExport -> ServiceConfig#doExportUrls -> ServiceConfig#doExportUrlsFor1Protocol -> ServiceConfig#exportLocal(URL url)。 暴露服务其实包括两个类容:

l   将Invoker存入AbstractProtocol#exporterMap,调用服务时从次map中取出Invoker直接使用。

 

protected final Set<Invoker<?>> invokers = new ConcurrentHashSet<Invoker<?>>();

 

其中key为:com.wzf.funny.service.ArticleService:dev, value为invoker对象

l   将url注册到zookeeper。

此过程的入口在RegistryProtocol#export方法中,调用链为:

RegistryProtocol#export -> FailbackRegistry#register -> AbstractRegistry#register -> ZookeeperRegistry#doRegister -> ZookeeperClient#create -> AbstractZookeeperClient#create

 

2) 服务发现

ZookeeperRegistry是服务发现的核心类之一,实现了《服务调用关系模型》中的register、subscribe、notify。以下分析一下几个主要的方法。

l  构造函数

从以下代码中可以看到,zkClient创建成功以后,会监听RECONNECTED事件,recover方法主要做一件事:将需要暴露的url放在failedRegistered(Set<URL>)中,将需要订阅的服务放在failedSubscribed(Set<URL>)中。说明RECONNECTED时,因为所有需要暴露的服务都需要重新注册,所以其实是将需要暴露、订阅的url都放到failedRegistered、failedSubscribed中。

 

public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) { super(url); //其他代码省略 this.root = group; zkClient = zookeeperTransporter.connect(url); zkClient.addStateListener(new StateListener() { public void stateChanged(int state) { if (state == RECONNECTED) { try { recover(); } catch (Exception e) { logger.error(e.getMessage(), e); } } } }); }

 

创建zkclient的url示例如下:

zookeeper://localhost:2181/com.alibaba.dubbo.registry.RegistryService?application=funny&dubbo=2.5.3&interface=com.alibaba.dubbo.registry.RegistryService&pid=38796&timestamp=1502594657663

 

l  register(URL url)

注册url代表的服务到zookeeper

l  unregister(URL url) 

从zookeeper中删除之前注册的服务

l  subscribe(URL url, NotifyListener listener)

订阅url的服务

l  unsubscribe(URL url, NotifyListener listener)

取消订阅url对应的服务

l  notify(URL url, NotifyListener listener, List<URL> urls)

通知

l  retry()

上面提到过,在recover()中将需要暴露的服务放到failedRegistered(Set<URL>)中,将需要订阅的服务放在failedSubscribed(Set<URL>)中,并没有真正的重新暴露服务或者订阅服务,这个工作是放在retry()中的,另外notify、doUnsubscribe,failedUnregistered也都放在此方法中处理。retry()方法的主要逻辑如下(为了方便阅读,我删掉了部分代码),retry被一个定时线程调用:

 

    protected void retry() { if (! failedRegistered.isEmpty()) { for (URL url : failed) { doRegister(url); failedRegistered.remove(url); } } if(! failedUnregistered.isEmpty()) { for (URL url : failed) { doUnregister(url); failedUnregistered.remove(url); } } if (! failedSubscribed.isEmpty()) { for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) { URL url = entry.getKey(); Set<NotifyListener> listeners = entry.getValue(); for (NotifyListener listener : listeners) { doSubscribe(url, listener); listeners.remove(listener); } } } if (! failedUnsubscribed.isEmpty()) { for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) { URL url = entry.getKey(); Set<NotifyListener> listeners = entry.getValue(); for (NotifyListener listener : listeners) { doUnsubscribe(url, listener); listeners.remove(listener); } } } if (! failedNotified.isEmpty()) { for (Map<NotifyListener, List<URL>> values : failed.values()) { for (Map.Entry<NotifyListener, List<URL>> entry:values.entrySet()) { NotifyListener listener = entry.getKey(); List<URL> urls = entry.getValue(); listener.notify(urls); values.

转载于:https://www.cnblogs.com/wujinsen/p/9854295.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值