1,Dubbo服务分为注册中心,服务提供者,消费者三个模块,而三个模块的启动都是分开的。
2,注册中心的 注册过程。
通常,注册中心的配置基本如下,具体源码在dubbo-registrt-simple包下面。
<beans xmlns
=
"http://www.springframework.org/schema/beans"
xmlns
:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns
:dubbo
=
"http://code.alibabatech.com/schema/dubbo"
xsi
:schemaLocation
=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"
>
<bean
class
=
"org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
>
<property name
=
"systemPropertiesModeName" value
=
"SYSTEM_PROPERTIES_MODE_OVERRIDE"
/>
<property name
=
"location" value
=
"classpath:dubbo.properties"
/>
bean
>
<dubbo
:application name
=
"${dubbo.application.name}" owner
=
"${dubbo.application.owner}"
/>
<dubbo
:protocol name
=
"dubbo" port
=
"${dubbo.protocol.port}" heartbeat
=
"180000"
/>
<dubbo
:service id
=
"registryServiceConfig"
interface
=
"com.alibaba.dubbo.registry.RegistryService" ref
=
"registryService" registry
=
"N/A" ondisconnect
=
"disconnect" callbacks
=
"1000"
>
<dubbo
:method name
=
"subscribe"
><dubbo
:argument index
=
"1" callback
=
"true"
/>dubbo
:method
>
<dubbo
:method name
=
"unsubscribe"
><dubbo
:argument index
=
"1" callback
=
"false"
/>dubbo
:method
>
dubbo
:service
>
<bean id
=
"registryService"
class
=
"com.alibaba.dubbo.registry.simple.SimpleRegistryService"
/>
beans
>
我们看到,通过此配置文件,dubbo制定了服务名,协议,以及Service,这点配置可以看出,registryService也是作为一个dubbo服务对外暴漏的。
我们查看对应SimpleRegistryService的源码,该类主要提供如下几个方法:
1,
public boolean isAvailable
(
)
2,
public List
<URL
> lookup
(URL url
)
3,
public void register
(URL url
)
4,
public void unregister
(URL url
)
5,
public void subscribe
(URL url
, NotifyListener listener
)
6,
public void unsubscribe
(URL url
, NotifyListener listener
)
具体解释,我们查看RegistryService接口。
public
interface RegistryService
{
/**
* 注册数据,比如:提供者地址,消费者地址,路由规则,覆盖规则,等数据。
*
* 注册需处理契约:
* 1. 当URL设置了check=false时,注册失败后不报错,在后台定时重试,否则抛出异常。
* 2. 当URL设置了dynamic=false参数,则需持久存储,否则,当注册者出现断电等情况异常退出时,需自动删除。
* 3. 当URL设置了category=routers时,表示分类存储,缺省类别为providers,可按分类部分通知数据。
* 4. 当注册中心重启,网络抖动,不能丢失数据,包括断线自动删除数据。
* 5. 允许URI相同但参数不同的URL并存,不能覆盖。
*
* @param url 注册信息,不允许为空,如:dubbo://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
*/
void register
(URL url
)
;
/**
* 取消注册.
*
* 取消注册需处理契约:
* 1. 如果是dynamic=false的持久存储数据,找不到注册数据,则抛IllegalStateException,否则忽略。
* 2. 按全URL匹配取消注册。
*
* @param url 注册信息,不允许为空,如:dubbo://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
*/
void unregister
(URL url
)
;
/**
* 订阅符合条件的已注册数据,当有注册数据变更时自动推送.
*
* 订阅需处理契约:
* 1. 当URL设置了check=false时,订阅失败后不报错,在后台定时重试。
* 2. 当URL设置了category=routers,只通知指定分类的数据,多个分类用逗号分隔,并允许星号通配,表示订阅所有分类数据。
* 3. 允许以interface,group,version,classifier作为条件查询,如:interface=com.alibaba.foo.BarService&version=1.0.0
* 4. 并且查询条件允许星号通配,订阅所有接口的所有分组的所有版本,或:interface=*&group=*&version=*&classifier=*
* 5. 当注册中心重启,网络抖动,需自动恢复订阅请求。
* 6. 允许URI相同但参数不同的URL并存,不能覆盖。
* 7. 必须阻塞订阅过程,等第一次通知完后再返回。
*
* @param url 订阅条件,不允许为空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
* @param listener 变更事件监听器,不允许为空
*/
void subscribe
(URL url
, NotifyListener listener
)
;
/**
* 取消订阅.
*
* 取消订阅需处理契约:
* 1. 如果没有订阅,直接忽略。
* 2. 按全URL匹配取消订阅。
*
* @param url 订阅条件,不允许为空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
* @param listener 变更事件监听器,不允许为空
*/
void unsubscribe
(URL url
, NotifyListener listener
)
;
/**
* 查询符合条件的已注册数据,与订阅的推模式相对应,这里为拉模式,只返回一次结果。
*
* @see com.alibaba.dubbo.registry.NotifyListener#notify(List)
* @param url 查询条件,不允许为空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
* @return 已注册信息列表,可能为空,含义同{@link com.alibaba.dubbo.registry.NotifyListener#notify(List)}的参数。
*/
List
<URL
> lookup
(URL url
)
;
}
通过以上方法实现对客户端对Dubbo服务的注册等操作。我们查看对应的注册操作:
public void register
(URL url
)
{
String client
= RpcContext
.getContext
(
)
.getRemoteAddressString
(
)
;
Set
<URL
> urls
= remoteRegistered
.get
(client
)
;
if
(urls
==
null
)
{
remoteRegistered
.putIfAbsent
(client
,
new ConcurrentHashSet
<URL
>
(
)
)
;
urls
= remoteRegistered
.get
(client
)
;
}
urls
.add
(url
)
;
super
.register
(url
)
;
registered
(url
)
;
}
protected void registered
(URL url
)
{
for
(Map
.Entry
<URL
, Set
<NotifyListener
>> entry
: getSubscribed
(
)
.entrySet
(
)
)
{
URL
key
= entry
.getKey
(
)
;
if
(UrlUtils
.isMatch
(
key
, url
)
)
{
List
<URL
>
list
= lookup
(
key
)
;
for
(NotifyListener listener
: entry
.getValue
(
)
)
{
listener
.notify
(
list
)
;
}
}
}
}
具体的注册操作在registered方法完成,同时发布NotifyListener监听事件。
public
interface NotifyListener
{
/**
* 当收到服务变更通知时触发。
*
* 通知需处理契约:
* 1. 总是以服务接口和数据类型为维度全量通知,即不会通知一个服务的同类型的部分数据,用户不需要对比上一次通知结果。
* 2. 订阅时的第一次通知,必须是一个服务的所有类型数据的全量通知。
* 3. 中途变更时,允许不同类型的数据分开通知,比如:providers, consumers, routers, overrides,允许只通知其中一种类型,但该类型的数据必须是全量的,不是增量的。
* 4. 如果一种类型的数据为空,需通知一个empty协议并带category参数的标识性URL数据。
* 5. 通知者(即注册中心实现)需保证通知的顺序,比如:单线程推送,队列串行化,带版本对比。
*
* @param urls 已注册信息列表,总不为空,含义同{@link com.alibaba.dubbo.registry.RegistryService#lookup(URL)}的返回值。
*/
void notify
(List
<URL
> urls
)
;
}
3,Zookeeper注册中心
一般情况下,我们在生产环境中,通过zookeeper来实现对Dubbo的注册,所以我们具体查看下ZookeeperRegistry的实现。
public ZookeeperRegistry
(URL url
, ZookeeperTransporter zookeeperTransporter
)
{
super
(url
)
;
if
(url
.isAnyHost
(
)
)
{
throw
new IllegalStateException
(
"registry address == null"
)
;
}
String group
= url
.getParameter
(Constants
.GROUP_KEY
, DEFAULT_ROOT
)
;
if
(
! group
.startsWith
(Constants
.PATH_SEPARATOR
)
)
{
group
= Constants
.PATH_SEPARATOR
+ group
;
}
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
)
;
}
}
}
}
)
;
}
ZookeeperRegistry与SimpleRegistryService类一样,继承了AbstractRegistry类。ZookeeperRegistry的注册方法,在其父类中实现。
@Override
public void register
(URL url
)
{
super
.register
(url
)
;
failedRegistered
.remove
(url
)
;
failedUnregistered
.remove
(url
)
;
try
{
// 向服务器端发送注册请求
doRegister
(url
)
;
} catch
(Exception e
)
{
Throwable t
= e
;
// 如果开启了启动时检测,则直接抛出异常
boolean check
= getUrl
(
)
.getParameter
(Constants
.CHECK_KEY
,
true
)
&& url
.getParameter
(Constants
.CHECK_KEY
,
true
)
&&
! Constants
.CONSUMER_PROTOCOL
.equals
(url
.getProtocol
(
)
)
;
boolean skipFailback
= t instanceof SkipFailbackWrapperException
;
if
(check
|| skipFailback
)
{
if
(skipFailback
)
{
t
= t
.getCause
(
)
;
}
throw
new IllegalStateException
(
"Failed to register "
+ url
+
" to registry "
+ getUrl
(
)
.getAddress
(
)
+
", cause: "
+ t
.getMessage
(
)
, t
)
;
}
else
{
logger
.error
(
"Failed to register "
+ url
+
", waiting for retry, cause: "
+ t
.getMessage
(
)
, t
)
;
}
// 将失败的注册请求记录到失败列表,定时重试
failedRegistered
.add
(url
)
;
}
}
而且具体实现,doRegister是在ZookeeperRegistry中实现的,此处实现了模板方法。
protected void doRegister
(URL url
)
{
try
{
zkClient
.create
(toUrlPath
(url
)
, url
.getParameter
(Constants
.DYNAMIC_KEY
,
true
)
)
;
} catch
(Throwable e
)
{
throw
new RpcException
(
"Failed to register "
+ url
+
" to zookeeper "
+ getUrl
(
)
+
", cause: "
+ e
.getMessage
(
)
, e
)
;
}
}
4,Dubbo监听器
通过以上代码我们可以看到,Dubbo的实现,离不开其监听器notifyListener,在注册服务等代码的具体实现都离不开对监听器的依赖。我们再次看监听器的注释。
/**
* 当收到服务变更通知时触发。
*
* 通知需处理契约:
* 1. 总是以服务接口和数据类型为维度全量通知,即不会通知一个服务的同类型的部分数据,用户不需要对比上一次通知结果。
* 2. 订阅时的第一次通知,必须是一个服务的所有类型数据的全量通知。
* 3. 中途变更时,允许不同类型的数据分开通知,比如:providers, consumers, routers, overrides,允许只通知其中一种类型,但该类型的数据必须是全量的,不是增量的。
* 4. 如果一种类型的数据为空,需通知一个empty协议并带category参数的标识性URL数据。
* 5. 通知者(即注册中心实现)需保证通知的顺序,比如:单线程推送,队列串行化,带版本对比。
*
* @param urls 已注册信息列表,总不为空,含义同{@link com.alibaba.dubbo.registry.RegistryService#lookup(URL)}的返回值。
*/
void notify
(List
<URL
> urls
)
;
我们查看ZookeeperRegistry中的doSubscribe方法,可以看到具体的监听器的实现。
protected void doSubscribe
(final URL url
, final NotifyListener listener
)
{
try
{
if
(Constants
.ANY_VALUE
.equals
(url
.getServiceInterface
(
)
)
)
{
String root
= toRootPath
(
)
;
ConcurrentMap
<NotifyListener
, ChildListener
> listeners
= zkListeners
.get
(url
)
;
if
(listeners
==
null
)
{
zkListeners
.putIfAbsent
(url
,
new ConcurrentHashMap
<NotifyListener
, ChildListener
>
(
)
)
;
listeners
= zkListeners
.get
(url
)
;
}
ChildListener zkListener
= listeners
.get
(listener
)
;
if
(zkListener
==
null
)
{
listeners
.putIfAbsent
(listener
,
new ChildListener
(
)
{
public void childChanged
(String parentPath
, List
<String
> currentChilds
)
{
for
(String child
: currentChilds
)
{
child
= URL
.decode
(child
)
;
if
(
! anyServices
.contains
(child
)
)
{
anyServices
.add
(child
)
;
subscribe
(url
.setPath
(child
)
.addParameters
(Constants
.INTERFACE_KEY
, child
,
Constants
.CHECK_KEY
, String
.valueOf
(
false
)
)
, listener
)
;
}
}
}
}
)
;
zkListener
= listeners
.get
(listener
)
;
}
zkClient
.create
(root
,
false
)
;
List
<String
> services
= zkClient
.addChildListener
(root
, zkListener
)
;
if
(services
!=
null
&& services
.size
(
)
>
0
)
{
for
(String service
: services
)
{
service
= URL
.decode
(service
)
;
anyServices
.add
(service
)
;
subscribe
(url
.setPath
(service
)
.addParameters
(Constants
.INTERFACE_KEY
, service
,
Constants
.CHECK_KEY
, String
.valueOf
(
false
)
)
, listener
)
;
}
}
}
该监听器实现了对服务的注册。
而具体的取消注册方法,是移除了对应的监听器。
protected void doUnsubscribe
(URL url
, NotifyListener listener
)
{
ConcurrentMap
<NotifyListener
, ChildListener
> listeners
= zkListeners
.get
(url
)
;
if
(listeners
!=
null
)
{
ChildListener zkListener
= listeners
.get
(listener
)
;
if
(zkListener
!=
null
)
{
zkClient
.removeChildListener
(toUrlPath
(url
)
, zkListener
)
;
}
}
}
具体是现在AbstractZookeeperClient:
public void removeChildListener
(String path
, ChildListener listener
)
{
ConcurrentMap
<ChildListener
, TargetChildListener
> listeners
= childListeners
.get
(path
)
;
if
(listeners
!=
null
)
{
TargetChildListener targetListener
= listeners
.remove
(listener
)
;
if
(targetListener
!=
null
)
{
removeTargetChildListener
(path
, targetListener
)
;
}
}
}
具体的监听器,缓存在AbstrackZookeeperClient:
private final ConcurrentMap
<String
, ConcurrentMap
<ChildListener
, TargetChildListener
>> childListeners
=
new ConcurrentHashMap
<String
, ConcurrentMap
<ChildListener
, TargetChildListener
>>
(
)
;
5,注册中心初始化
我们知道Spring的Bean都是通过一系列BeanFactory来完成的初始化的,Dubbo也一样,注册中心通过DubboRegistryFactory来完成Bean的初始化。其基类是RegistryFactory。
public
interface RegistryFactory
{
/**
* 连接注册中心.
*
* 连接注册中心需处理契约:
* 1. 当设置check=false时表示不检查连接,否则在连接不上时抛出异常。
* 2. 支持URL上的username:password权限认证。
* 3. 支持backup=10.20.153.10备选注册中心集群地址。
* 4. 支持file=registry.cache本地磁盘文件缓存。
* 5. 支持timeout=1000请求超时设置。
* 6. 支持session=60000会话超时或过期设置。
*
* @param url 注册中心地址,不允许为空
* @return 注册中心引用,总不返回空
*/
@Adaptive
(
{
"protocol"
}
)
Registry getRegistry
(URL url
)
;
}
AbstractRegistryFactory实现了大概的实现。
public Registry getRegistry
(URL url
)
{
url
= url
.setPath
(RegistryService
.
class
.getName
(
)
)
.addParameter
(Constants
.INTERFACE_KEY
, RegistryService
.
class
.getName
(
)
)
.removeParameters
(Constants
.EXPORT_KEY
, Constants
.REFER_KEY
)
;
String
key
= url
.toServiceString
(
)
;
// 锁定注册中心获取过程,保证注册中心单一实例
LOCK
.lock
(
)
;
try
{
Registry registry
= REGISTRIES
.get
(
key
)
;
if
(registry
!=
null
)
{
return registry
;
}
registry
= createRegistry
(url
)
;
if
(registry
==
null
)
{
throw
new IllegalStateException
(
"Can not create registry "
+ url
)
;
}
REGISTRIES
.put
(
key
, registry
)
;
return registry
;
} finally
{
// 释放锁
LOCK
.unlock
(
)
;
}
}
我们看到注册中心被锁定为单例模式,并且维护在该类的缓存中。
而具体的实现方式,由DubboRegistryFactory来实现。
public Registry createRegistry
(URL url
)
{
url
= getRegistryURL
(url
)
;
List
<URL
> urls
=
new ArrayList
<URL
>
(
)
;
urls
.add
(url
.removeParameter
(Constants
.BACKUP_KEY
)
)
;
String backup
= url
.getParameter
(Constants
.BACKUP_KEY
)
;
if
(backup
!=
null
&& backup
.length
(
)
>
0
)
{
String
[
] addresses
= Constants
.COMMA_SPLIT_PATTERN
.
split
(backup
)
;
for
(String address
: addresses
)
{
urls
.add
(url
.setAddress
(address
)
)
;
}
}
RegistryDirectory
<RegistryService
> directory
=
new RegistryDirectory
<RegistryService
>
(RegistryService
.
class
, url
.addParameter
(Constants
.INTERFACE_KEY
, RegistryService
.
class
.getName
(
)
)
.addParameterAndEncoded
(Constants
.REFER_KEY
, url
.toParameterString
(
)
)
)
;
Invoker
<RegistryService
> registryInvoker
= cluster
.
join
(directory
)
;
RegistryService registryService
= proxyFactory
.getProxy
(registryInvoker
)
;
DubboRegistry registry
=
new DubboRegistry
(registryInvoker
, registryService
)
;
directory
.setRegistry
(registry
)
;
directory
.setProtocol
(protocol
)
;
directory
.notify
(urls
)
;
directory
.subscribe
(
new URL
(Constants
.CONSUMER_PROTOCOL
, NetUtils
.getLocalHost
(
)
,
0
, RegistryService
.
class
.getName
(
)
, url
.getParameters
(
)
)
)
;
return registry
;
}
注册过程相对简单,通过URL,获取到对应的Registry数据,以及Invoker,生成Registry对象。Registry实现了RegistryService接口,即我们之前的ZookeeperRegistryService的父类。
而此处的DubboRegistry的构方法中,
public DubboRegistry
(Invoker
<RegistryService
> registryInvoker
, RegistryService registryService
)
{
super
(registryInvoker
.getUrl
(
)
)
;
this
.registryInvoker
= registryInvoker
;
this
.registryService
= registryService
;
// 启动重连定时器
int reconnectPeriod
= registryInvoker
.getUrl
(
)
.getParameter
(Constants
.REGISTRY_RECONNECT_PERIOD_KEY
, RECONNECT_PERIOD_DEFAULT
)
;
reconnectFuture
= scheduledExecutorService
.scheduleWithFixedDelay
(
new Runnable
(
)
{
public void run
(
)
{
// 检测并连接注册中心
try
{
connect
(
)
;
} catch
(Throwable t
)
{
// 防御性容错
logger
.error
(
"Unexpected error occur at reconnect, cause: "
+ t
.getMessage
(
)
, t
)
;
}
}
}
, reconnectPeriod
, reconnectPeriod
, TimeUnit
.MILLISECONDS
)
;
}
实现了对注册中心的注册。