Dubbo源代码实现三:注册中心Registry
我们知道,对于服务治理框架来说,服务通信(RPC)和服务管理两部分必不可少,而服务管理又分为服务注册、服务发现和服务人工介入,我们来看看Dubbo框架的结构图(来源网络):
图中可以看出,服务提供者Provider往服务注册中心Registry注册服务,而的消费者Consumer从服务注册中心订阅它需要的服务,而不是全部服务,当有新的Provider出现,或者现有Provider宕机,注册中心Registry都应该能尽早发现,并将新的Provider列表推送给对应的Consumer,有了这样的机制,Dubbo才能做到Failover,而Failover的时效性,由注册中心Registry的实现决定。
Dubbo线上支持三种注册中心:自带的SimpleRegistry、Redis和Zookeeper,当然,最常用的还是Zookeeper作为注册中心,因为太多分布式的中间件需要依赖Zookeeper作为协作者。那么怎么才能让Dubbo知道我们使用哪个实现作为注册中心呢?我们只需要在dubbo的xml配置文件中配置dubbo:registry节点即可:
<dubbo:registry id="dubboRegistry"protocol="zookeeper"address="${dubbo.registry.address}"/>
没错,protocol就指明了注册中心的实现。
要想做到服务的可靠,避免分布式系统的单点问题,除了Provider可以集群部署外,注册中心的弱依赖也是必须的,注册中心的宕机,不会影响现有服务的运行,只是不能注册新的服务和进行服务发现,Failover还是可以做的,比如Consumer可以通过服务调用来简单判断当前的Provier是否可用。如果某个Consumer宕机了,当它重启后,发现注册中心也挂了,那咋办?为了防止这种问题出现,Dubbo的Consumer会将自己需要的Provider列表在本地保存一份,当然,里面也包括自己暴露的服务信息(即自己也作为Provider),我们可以看看AbstractRegistry中的实现:
public AbstractRegistry(URL url) {
setUrl(url);
// 启动文件保存定时器
syncSaveFile= url.getParameter(Constants.REGISTRY_FILESAVE_SYNC_KEY,false);
String filename =url.getParameter(Constants.FILE_KEY, System.getProperty("user.home") +"/.dubbo/dubbo-registry-"+ url.getHost() +".cache");
File file = null;
if (ConfigUtils.isNotEmpty(filename)) {
file = newFile(filename);
if(! file.exists() &&file.getParentFile() !=null&&! file.getParentFile().exists()){
if(! file.getParentFile().mkdirs()){
throw new IllegalArgumentException("Invalid registry store file "+ file +", cause: Failed to create directory" + file.getParentFile()+"!");
}
}
}
this.file= file;
loadProperties();
notify(url.getBackupUrls());
}
注意看黄底代码部分,如果没有在属性文件中配置file(Constants.FILE_KEY),就将在用户的当前用户目录/.dubbo/目录下新建一个dubbo-registry开头的保存所有URL信息的Cache文件,通常来说一个应用可以在多个注册中心暴露自己的服务,也可以从多个注册中心订阅不同的服务,所以这里的Cache文件名加入了注册中心的主机名。还有一个lock文件,用来防止不同的JVM进程同时修改Cache文件,注意,这里只是防止,所以意味着同一目录的Cache文件可以由多个JVM进程共享,当多个JVM进程恰巧同时修改Cache文件时,将会有一个进程获取lock文件的锁失败,见保存Cache的过程的AbstractRegistry#doSaveProperties方法的片段:
FileChannel channel = raf.getChannel();
try {
FileLocklock = channel.tryLock();
if (lock == null) {
thrownew IOException("Can not lock theregistry cache file "+file.getAbsolutePath() + ", ignore and retrylater, maybe multi java process use the file, please config:dubbo.registry.file=xxx.properties");
}
这将导致某个URL更新到Cache文件失败,但Dubbo提供了重试机制,以保证Cache文件中信息能和内存中的信息最终一致。但不要认为Cache文件中的Provider和Consumer列表是和当前运行的服务一致,因为当一个服务部署多个应用时,Cache文件被多个JVM同时写的概率还是很大的,所以这时总有JVM进程度lock文件获取锁失败(即FileChannel#tryLock()失败),这时它只能乖乖稍后重试了。写Cache的方式也很简单粗暴,即先读取整个Cache文件,然后再往其写入当前处理的URL,然后再全量写入,可见,如果某个服务(URL)已经不再使用,它有可能一直存在于Cache文件中。
保存Cache还分为同步保存和异步保存,我们知道内存中服务列表的更新相对于服务调用来说肯定是异步的,但为啥保存Cache文件还要分同步和异步呢?因为在Dubbo中,服务(或者叫URL)是一个个来更新的,也就是说,当服务比较多时,使用异步保存Cache文件能使应用启动和服务更新速度更快,而整个更新过程是由AbstractRegistry#notify来触发的。
我们再来看看如果选择使用Zookeeper用来做Dubbo的注册中心,那么Provider和Consumer的数据在上是怎么存储的。Dubbo在ZK的所有数据都在/dubbo节点下,如下图:
/dubbo
/com.manzhizhen.user.Service1
/consumers
/routers
/providers
/configurators
/com.manzhizhen.user.Service2
/consumers
/routers
/providers
/configurators
/com.manzhizhen.user.Service3
/consumers
/routers
/providers
/configurators
我们可以看到,每个服务(URL)在dubbo节点下都会有一个对应的ZK持久化节点,而每个服务节点下面都会有四个持久化子节点,代表消费者(consumer)、路由(routers)、提供者(providers)和配置(configurators),consumer和providers节点好理解,放的就是该URL下消费者和提供者的URL全部信息,而routers和configurators主要用于控制路由规则,这在正常情况下是用的比较少的,所以这两个节点数据通常为空。
现在我们说说和服务注册相关的两个异常信息, 先给出Dubbo的集群容错图:
一个常见的异常信息是"Forbid consumer XXXXXaccess service XXXXX from registry XXXXX use dubbo version 2.5.3, Please checkregistry access list (whitelist/blacklist).",当我们需要调用服务时,会先从本地的注册目录也就是RegistryDirectory来拿取调用(Invoker)列表,见上图Directory节点,RegistryDirectory#doList代码片段如下:
public List<Invoker<T>> doList(Invocation invocation) {
if (forbidden) {
thrownew RpcException(RpcException.FORBIDDEN_EXCEPTION,"Forbid consumer "+ NetUtils.getLocalHost() + " access service" +getInterface().getName() + " from registry "+ getUrl().getAddress() +" use dubbo version " + Version.getVersion() + ", Please check registry access list(whitelist/blacklist).");
}
List<Invoker<T>> invokers = null;
Map<String, List<Invoker<T>>>localMethodInvokerMap =this.methodInvokerMap;// local reference
可见,当forbidden为false时,会抛出该异常信息,当注册中心给它推送最新的Provider列表时,上面的forbidden的值已经变成了false,见RegistryDirectory#refreshInvoker代码片段:
private void refreshInvoker(List<URL>invokerUrls){
if(invokerUrls !=null&&invokerUrls.size() ==1&& invokerUrls.get(0) !=null
&& Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
this.forbidden=true;//禁止访问
this.methodInvokerMap=null; // 置空列表
destroyAllInvokers(); // 关闭所有Invoker
} else{
this.forbidden=false;//允许访问
Map<String, Invoker<T>>oldUrlInvokerMap =this.urlInvokerMap;// local reference
if (invokerUrls.size() ==0&&this.cachedInvokerUrls!=null){
invokerUrls.addAll(this.cachedInvokerUrls);
} else{
this.cachedInvokerUrls=new HashSet<URL>();
this.cachedInvokerUrls.addAll(invokerUrls);//缓存invokerUrls列表,便于交叉对比
}
从上面代码可以看出,当该URL协议为empty时,说明该URL已经被禁止(forbidden)了,那什么时候URL的协议会被设置成empty呢?我们看看ZookeeperRegistry#toUrlsWithEmpty方法:
private List<URL> toUrlsWithEmpty(URLconsumer, String path, List<String> providers) {
List<URL> urls = toUrlsWithoutEmpty(consumer, providers);
if (urls == null || urls.isEmpty()) {
int i = path.lastIndexOf('/');
String category = i < 0? path : path.substring(i +1);
URL empty = consumer.setProtocol(Constants.EMPTY_PROTOCOL).addParameter(Constants.CATEGORY_KEY, category);
urls.add(empty);
}
return urls;
}
可见,当providers列表为空时,也就是某个URL下没有活着的Provider时,Consumer会将本地的invokerUrl的协议设置成empty,而toUrlsWithEmpty是在ZookeeperRegistry订阅方法doSubscribe中被调用的,这里不再给出代码。
另一个是"Failed to invoke the method XXXXXin the service XXXXX. No provider available for the service XXXXX from registryXXXXX on the consumer XXXXX using the dubbo version 2.5.3. Please check if theproviders have been started and registered.",因为每次调用时都会去检查调用列表,如果列表有多个可用服务(即多个Provider),将会使用配置的负载均衡方式来选择一个服务来调用,但如果服务列表为空,则会抛异常,也就是在上图的Invoker节点抛出异常,这种情况一般是说明当前没有可用的Provider,见AbstractClusterInvoker#checkInvokers代码:
protected void checkInvokers(List<Invoker<T>> invokers, Invocation invocation) {
if (invokers == null|| invokers.size() ==0) {
thrownew RpcException("Failed to invokethe method "
+ invocation.getMethodName() +" in the service "+ getInterface().getName()
+ ". No provideravailable for the service "+directory.getUrl().getServiceKey()
+ " from registry" + directory.getUrl().getAddress()
+ " on the consumer" + NetUtils.getLocalHost()
+ " using the dubboversion " + Version.getVersion()
+ ". Please check ifthe providers have been started and registered.");
}
}
对于这两个异常的直接结论是,如果某个URL去注册中心注册过,但后来该URL下没有Provider了,那么此时Consumer调用Provider将报第一种异常;如果Consumer调用了一个从未去注册中心注册过的URL,则会报第二种异常。
需要明确一点的是,注册中心的两个重要目的是服务发现和服务人工介入,线上的Provider和Consumer都不能强依赖注册中心,哪怕注册中心是双机部署,但要做到对注册中心的弱依赖,Consumer端需要有简单的负载均衡和Failover机制。
本文转自:http://blog.csdn.net/manzhizhen/article/details/53025666
Dubbo源代码实现三:注册中心Registry的更多相关文章
- Dubbo+ZK与Eureka注册中心比较
Eureka可以很好的应对网络故障导致部分节点失去联系的情况,而不会像zk那样因为选举导致整个集群不可用 dubbo + zk 当向注册中心查询服务注册列表时,可以容忍注册中心返回的是几分钟以前的注册 ...
- Dubbo源码学习--注册中心分析
相关文章: Dubbo源码学习--服务是如何发布的 Dubbo源码学习--服务是如何引用的 注册中心 关于注册中心,Dubbo提供了多个实现方式,有比较成熟的使用zookeeper 和 redis 的 ...
- dubbo入门学习 四 注册中心 zookeeper入门
一.Dubbo支持的注册中心 1. Zookeeper 1.1 优点:支持网络集群 1.2 缺点:稳定性受限于Zookeeper 2. Redis 2.1 优点:性能高. 2.2 缺点:对服务器环境要 ...
- dubbo 学习(5) dubbo多协议和多注册中心
转载 http://blog.csdn.net/songjinbin/article/details/49498431 一.配置dubbo多协议模式 1.默认协议 Dubbo缺省协议采用单一长连接和N ...
- dubbo 多协议和多注册中心
一.配置dubbo多协议模式 1.默认协议 Dubbo缺省协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况.Dubbo缺省协议不适合 ...
- Dubbo环境搭建-ZooKeeper注册中心
场景 Dubbo简介与基本概念: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/103555224 注: 博客: https:// ...
- Dubbo(二):zookeeper 注册中心
zookeeper 注册中心 Zookeeper 是 Apacahe Hadoop 的子项目,是一个树型的目录服务,支持变更推送,适合作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境,并 ...
- 微服务化的大坑之一:当dubbo神器碰上共用注册中心和错误的暴露接口
dubbo是国内用比较多的微服务化系统,非侵入(意思就是说不用自己写代码,把xml配置好就可以用了,这个xml的引用注解就注在springboot的开启main类里面就可以了),提供好用的均衡和容错机 ...
- 使用dubbo中间件的zookeeper注册中心时报错
在项目中搭建soa项目时,使用dubbo服务中间件时需要在虚拟机中创建一个zookeeper注册中心,在配置都没有问题的时候,如果服务端启动成功,但是消费端启动报错并且看不出据地位置时,一定要注意你的 ...
随机推荐
- jquery操作CSS样式全记录
$(this).click(function(){ if($(this).hasClass(“zxx_fri_on”)){ $(this).removeClass(“zxx_fri_on”); ...
- 更改DNS轻松访问google.com,FaceBook,Youtube等
将默认的Dns更改为42.120.21.30即可打开 https://www.google.com/ https://www.facebook.com/ https://www.youtube.com ...
- 不同版本(2.3,2.4,2.5,3.0)的Servlet web.xml 头信息
不同版本(2.3,2.4,2.5,3.0)的Servlet web.xml 头信息 学习了:https://blog.csdn.net/z69183787/article/details/360080 ...
- 广州高清卫星地图 用百度卫星地图server下载 含标签、道路数据叠加 可商用
广州高清卫星地图的地图展示图片各自是15级别.17级别.19级别的地图.一般来说17级别的地图图片就行用于商用.地图包包括一整张高级别的图片,如要全图浏览请用专业图片处理软件PS等打开. 一般来说互联 ...
- GIS中要素的捕捉以及C++实现
这篇文章早在去年就写出来了,但是由于当时毕业论文有一段是直接引用了我的这篇文章,怕引起查重的麻烦就删掉了,在此,重新挂出来和大家一起分享. 要素的选择,也称为要素的捕捉,在CAD.计算机图形学和地理信 ...
- 首都医科大学附属北京安贞医院全院级PACS系统采购项目[转]
项目名称:首都医科大学附属北京安贞医院全院级PACS系统采购项目 项目编号:TC140VCF0 采购人名称:首都医科大学附属北京安贞医院 采购人地址:北京市朝阳区安贞里 采购人联系方式:010-644 ...
- 算法笔记_136:交替字符串(Java)
目录 1 问题描述 2 解决方案 1 问题描述 输入三个字符串s1.s2和s3,判断第三个字符串s3是否由前两个字符串s1和s2交错而成且不改变s1和s2中各个字符原有的相对顺序. 2 解决方案 ...
- c++初始化函数列表
以下三种情况下需要使用初始化成员列表: 一,需要初始化的数据成员是对象的情况: 二,需要初始化const修饰的类成员: 三,需要初始化引用成员数据: 原因: C++可以定义引用类型的成员变量,引用类型 ...
- [odroid-pc] ubuntu12.04 64bit Android4.0.3 源码编译报错及解决的方法
第一个错误: host Executable: cmu2nuance (out/host/linux-x86/obj/EXECUTABLES/cmu2nuance_intermedia ...
- 【Espruino】NO.17 使用平板电脑调试Espruino(OTG方式)
http://blog.csdn.net/qwert1213131/article/details/38068379 本文属于个人理解,能力有限,纰漏在所难免,还望指正! [小鱼有点电] [Espru ...