前面分别写了二篇文章,介绍dubbo的源码与模拟现场场景的结构与调用分析。目前还缺少注册与统计模块的分析,所以又抽空看了一下注册部分,越看越感觉
dubbo真是宝啊,几乎全面的java知识都整合在一起了,看懂了DUBBO再看其它源代码都非常简单了;而且触类旁通,一些策略算法很多地方都可以用到。最后思考如何在特定场景下实现一个简单的soa的管理的minidubbo。
说到分析源码,我一般先网上找几篇看看,可是总看的云里雾里,而且dubbo升级后有些变化,所以还是自己硬啃一下。时间仓促欢迎斧正,我看的源码是2.5.3,我认为分析源码就不用贴多少代码了,关键理解其设计思路,解决问题需要的也是思路。有了思路就是COPY,修改,调试了。
一、dubbo注册分析
网上的源码最大的问题是没有一个完整的思维模型引导,所以我先介绍我所了解的注册部分的思维模型。注册就相当于一个购物平台,总不能买卖双方总是直接调用吧,所以可以这么理解:调用方如果配置注册中心,就注册自己,并从平台查询,可以得到自己想到的实现服务的被调用方地址,这个是订阅过程,会不断返回一些被调用方的URL,而这些个URL就是真正的被调用方了,存起来并按一定负载规则选择一个当成直接调用就OK了。而被调用方也是注册自己,并从平台得到..些URL重新暴露。(这里没明白为要得到那些URL并重新doChangeLocalExport,反正也是订阅并传入一个监听器,但不影响整体理解)。而注册时的数据存放可以是zookeeper,radis,也可以是数据库,但注册,订阅等业务逻辑都是调用端与被调用端来实现的。
调用方的一个接口,从注册中心找到多个实现
当调用端直接配置被调用端时,用dubboProtocol的refer方法就生成invoker,而当配置为注册中心时,调用方的接口就要按registryProtocol提供的refer生成invoker,而且一个接口配置了多个注册中心,并且有多个提供方时,那就比较麻烦了。我买一件商品,那么多商家提供,那当然弱水三千只取一瓢了,但可以随机,也可以选择最便宜的。
registryProtocol与RegistryDirectory是两个核心的业务逻辑类,另外根据注册中心的不同还有些不同的注册器与注册器工厂,比如zookeeper,radis的。
我们看到,registryProtocol做refer的时候得到相应的注册器,这里是比较复杂的SPI方式,后面会详细介绍的。比如得到zookeeper的,再做dorefer的时候三件事,先是注册registry.register(*),再是订阅,订阅就是我扔个回调对象给你,你有事了通知回调对象就行了,我就知道了。代码就是directory.subscribe(*)内部是registry.subscribe(url, this);。最后就是cluster.join(directory);
总结一下,得到注册中心的地址后,调用方用zookeeper注册器来注册一下,并传入一个对象RegistryDirectory向注册器订阅可用的url,注册器会有一个线程不断的查找zookeeper的节点得到可用的url来通知RegistryDirectory。RegistryDirectory拿到这些url就一个个得到invokers,这些invokers的持有者RegistryDirectory会生成一个实现了invoker的代理对象。而客户端以后就调用这个invoker的代理对象了,代理对象会用策略从一堆真正的invoker中拿一个来用的。
----------------------《把URL转成invoker代码》--------------------
拿到一个注册中心的真正一堆url后,转化成一堆invoker的重要语句如下:
//把订阅的url转化为invoker就是RegistryDirectory类中的refreshInvoker方法中的这句
Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls) ;
//转化过程就是用注册中心告诉你的真正的一堆url,用各自的协议来refer一下,生成一堆invoker。
invoker = new InvokerDelegete<T>(protocol.refer(serviceType, url), url, providerUrl);
invoker代理对象找出一个invoker来用就是FailoverClusterInvoker中的下面这句:
Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
----------------------------《END》------------------------
疑问:有几个注册中心,如果每个中心都查到有实现,那就有几个代理对象吗?那又多一层选择吗?一个调用先选择一个代理对象,代理对象中又选择出一个真正的invoker?还是把这些全部合并到一起了?这个细节还没有看。发现ReferenceConfig中有:invoker = cluster.join(new StaticDirectory(u, invokers));也是集群策略。
解答:今天又看了一会代码,明白了。当我们从afterPropertiesSet()开始追踪调用端对象的诞生时,直到看这个方法createProxy(Map<String, String> map),最终生成调用端的代理对象。
-------------《多个注册中心返回的invokers集群代码》-----------
//加载所有的注册中心地址
List<URL> us = loadRegistries(false);
...
for (URL url : urls) {
//每一个注册中心生成一个invoker,放在一起。注意一个注册中心生成的是一个集群failover的invoker。
invokers.add(refprotocol.refer(interfaceClass, url));
}
...
//把每个注册中心的invoker再组成一个集群。这里不是failoverCluster了,是AvailableCluster了。
invoker = cluster.join(new StaticDirectory(u, invokers));
------------------------------《END》-------------------------
就是说:一个注册中心得到的是集群,而多个注册中心是集群的集群。但是集群策略不一样。其实之前已猜测到了,只是对refprotocol有点困惑,所有的注册中心怎么用同一个变量refprotocol来refer呢?不同的注册中心协议不一样啊,有zookeeper,radis等。当然我知道是spi进来的,但总不能用一个变量吧。突然想起之前的文章中我还分析过spi的特别之处。阿里的spi得到的是一个适配器,是用生成代码编译方式的,具体适配是那个类,要根据参数定的。那就对了,注册中心的URL是不一样的,所以得到不同的类。
此刻又一想,注册协议只有一个RegistryProtocol,不同的注册中心导致注册协议refer时用的注册器不一样,那是如何获取不同的注册器呢?RegistryProtocol中有一个属性是:RegistryFactory registryFactory;它有Set方法。我又回看了一下与SPI有关的ExtensionLoader类中有injectExtension(),对所有SPI加载进来的再注入一下,那注入的内容也可以又是SPI加载的东西。那一切都清楚了:
RegistryProtocol是protocol.class通过SPI加载的,而RegistryProtocol内部的RegistryFactory 又是通过SPI加载并注入的。而SPI注入的都是适配器类,代理了相关的实际操作类,具体代理谁是通过传入参数临时决定的,决定的时候再加载真正的扩展类(SPI或者SPING方式)。而参数都是URL中,所以URL决定了真正用什么协议,以及真正用什么注册器工厂。工厂确定了,那注册类就确定了。一切在运行时都搞定了。
protocol的SPI加载时是适配器,运行时如果恰好是RegistryProtocol,那它内部还有RegistryFactory、ProxyFactory也是SPI加载的适配器,具体运行时如果是DubboRegistryFactory ,它的内部又有Cluster是SPI的,如果运行时恰好是....那它内部.. 真是层层注入的加载类,实现的是一个微核心功能,太有创意了!其实sping的IOC容器不也是这样层层注入的嘛,可能DUBBO“抄袭”的就是这样的思路吧。真是忙了几天别的事,思维有点断,还好理顺了!
二、注册中的集群策略与负载均衡算法汇总与应用场景
DUBBO中的宝贝越挖越多,这也是发现的一批重要知识点,在分布式环境中用的非常多,都总结到一起了,可以对比学习使用。
1.负载均衡算法--只要有集群都需要,在电商业务中,比如对不同级别的用户,权重也可能有差别,很多地方用的到。
1.1 RandomLoadBalance:
如果权重一样,就是简单的random,如果有权重,那把权重累加起来,从中得到一个随机数,在循环中不断累减随机数,如果出现负数就是要的值。这个算法没有去找证明,但我相信没问题,碰到了直接用。
1.2 ConsistentHashLoadBalance:
利用TreeMap实现的一致性HASH算法
1.3 RoundRobinLoadBalance:即轮询调度算法。比如有ABCD四个invoker,依次调用ABCD ABCD ABCD ABCD A...。如果加上权重稍微复杂一点,比如权重AC是2,BD是1,那次序是:ABCDAC ABCDAC ABCDAC.....。
2.集群策略
2.1 AvailableCluster:这个简单,从invokers中随便拿一个isAvailable()的invoker.拿的时候不需要负载均衡策略。
2.2 FailoverCluster:从invokers中按照负载均衡策略拿出来,可用就OK。不可用的话循环拿其它没拿过的。通用用于读操作,这个失败了换下一个。
FailfastCluster:从invokers中按照负载均衡策略拿出来一个,就用它了。不行就抛出异常。常用于非幂等性的写操作。非幂等就是一次操作与多次操作结果不一样。
2.3 FailbackCluster:从invokers中按照负载均衡策略拿出来一个,如果执行不成功,那记录下来,产生一个定时线程不断的重试。一般用于消息通知。
2.4 ForkingCluster:从invokers中按照负载均衡策略拿出来多个来,谁先有结果就是谁,用线程池来执行,把结果写入一个LinkedBlockingQueue,如果都是异常,把最后一个异常写入。主线程等待一定时间后用poll来取第一个。
这是并行调用,只要一个成功即返回,通常用于实时性要求较高的操作,但需要浪费更多服务资源。
2.5 FailsafeCluster:有异常时,也返回一个正常的空对象new RpcResult()。这个是失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作。
2.6 BroadcastCluster:广播方式,就是从invokers中循环执行所有的invoker。大家一个接一个都来做一遍调用。
三、重点分析下在负载均衡中用treemap实现的一致性Hash算法:
一致性hash在分布式环节下非常重要的选择方式。比如自己开发redis分布式中间件,比如分表分库等操作中。都需要命中目标并且目标个数是可以扩展的。
com.alibaba.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance中有一个内部静态类ConsistentHashSelector。
3.1 属性如下:
private final TreeMap<Long, Invoker<T>> virtualInvokers;
private final int replicaNumber;
private final int identityHashCode;
private final int[] argumentIndex;
3.2 构造时方法如下:
for (Invoker<T> invoker : invokers) {
for (int i = 0; i < replicaNumber / 4; i++) {
byte[] digest = md5(invoker.getUrl().toFullString() + i);
for (int h = 0; h < 4; h++) {
long m = hash(digest, h);
virtualInvokers.put(m, invoker);
}
}
}
说明:treemap里放的是一些invoker,key是一个hash码,是long类型。比如有10个invorker,复制数为5,那一共放了50个key,value在treemap中。每5个key对应的invorker是一样的。一般想的是有几个invoker就放入tree中几个,但实际上为了均匀命中率会都多复制。
3.3 如何选择出invoker?
public Invoker<T> select(Invocation invocation) {
String key = toKey(invocation.getArguments());
byte[] digest = md5(key);
Invoker<T> invoker = sekectForKey(hash(digest, 0));
return invoker;
}
private Invoker<T> sekectForKey(long hash) {
Invoker<T> invoker;
Long key = hash;
if (!virtualInvokers.containsKey(key)) {
SortedMap<Long, Invoker<T>> tailMap = virtualInvokers.tailMap(key);
if (tailMap.isEmpty()) {
key = virtualInvokers.firstKey();
} else {
key = tailMap.firstKey();
}
}
invoker = virtualInvokers.get(key);
return invoker;
}
说明:传入的是一个invocation,那怎么找到invoker呢?首先invocation得到key,key再得到digest,digest 再得到hash值。重点来了,就是如何用这个hash值在treemap上找到特定的invoker。首先对原码中的select写成sekect的拼写错误不习惯。
根据上面得到的 hash值key,得到一个 tailMap = virtualInvokers.tailMap(key); tailMap是一个大于等于key的一部分树的数据,等于的几率很小,一般是比key大的,那取这个tailMap中的第一个,就等于取了比key大的之中最小的一个tree中存在的有效key了。当然也有可能取不到,tailMap为空,因为tree中最大的有效key也比参数key小,那当然返回treeMap中最小的喽,这样不就是一个接好的圆环了。
3.4 说说hash算法
有些md5,sha1之类的方法得到hash值。还有些位运算的处理,就不深入研究了。
- 大小: 384.6 KB
分享到:
相关推荐
通过对Dubbo源码的阅读和分析,我们可以更深入地理解服务治理的原理,学习到如何设计高可用、可扩展的微服务架构,这对于提升我们的Java编程技能和系统设计能力具有重要的实践意义。同时,Dubbo也是许多企业级项目的...
3. **负载均衡策略**:Dubbo提供了多种负载均衡算法,如随机、轮询、最少活跃调用数等,源码中可以学习这些策略的实现细节。 4. **集群容错策略**:包括失败切换、失败忽略、故障降级等,这些策略在源码中是如何被...
- **负载均衡算法**:Dubbo提供了多种负载均衡策略,源码里可以看到这些策略的具体实现。 - **服务调用的异步化处理**:Dubbo支持同步和异步调用,源码中可以学习到如何处理异步调用和回调机制。 - **服务限流与熔断...
Dubbo提供了多种负载均衡策略,如随机、轮询、最少活跃调用数等。这些策略在`LoadBalance`接口的实现中得以体现,如`RandomLoadBalance`和`RoundRobinLoadBalance`。在选择服务提供者时,这些策略会根据一定的算法...
- **负载均衡策略选择**:分析不同场景下适合采用哪种负载均衡算法。 - **服务容错机制**:介绍如何通过断路器模式、重试机制等方式提高系统的可用性。 - **性能优化技巧**:分享在调优过程中需要注意的关键点。 ##...
- **Consumer(服务消费者)**:启动时订阅服务,并从注册中心获取Provider的URL,之后根据负载均衡算法选择Provider并发起RPC调用。 - **Monitor(监控中心)**:统计服务调用次数和时间。虽然不是必须的,但监控...
`LoadBalance`接口则实现了负载均衡算法,如Random随机、RoundRobin轮询、LeastActive最少活跃调用数等,确保请求能够均匀地分发到各个服务实例。 5. **服务暴露与消费** 在服务提供者端,`Provider`类负责将服务...
3. **服务路由与负载均衡**:分析负载均衡算法的实现,以及如何根据服务元数据进行路由决策。 4. **失败重试与熔断机制**:了解Dubbo如何处理服务调用失败,包括重试策略、降级和熔断机制。 5. **服务治理**:探讨...
- **LoadBalance**:实现负载均衡算法。 **3. 实例演示** - **创建项目**:创建一个名为`dubbo-demo`的示例项目。 - **配置文件**:编写`dubbo-demo-provider.xml`和`dubbo-demo-consumer.xml`配置文件。 - **...
【源码分析】:压缩包内的Dubbo提供者和消费者源码可以用于深入理解Dubbo的工作原理。服务提供者源码会包含服务接口定义、实现类以及服务暴露的相关配置。服务消费者源码则包含服务引用、服务调用的逻辑以及消费端的...
Dubbo 是一款高性能、轻量级的开源服务框架,它提供了三大核心能力:面向接口的远程方法调用、智能容错和负载均衡以及服务自动注册与发现。Dubbo 的设计目标是使开发者能够快速、高效地开发分布式应用服务,而无需...
Duboo 可能扩展了这些策略,或者引入了新的负载均衡算法。通过源码,我们可以了解如何根据业务需求调整负载均衡策略。 4. **容错机制** Dubbo 支持 Failfast、Failover、Failsafe、Fallback、Retry 等容错策略。...
- **负载均衡算法**:分析RandomLoadBalance、RoundRobinLoadBalance等内置负载均衡策略的实现原理。 - **服务注册与发现机制**:了解Registry、RegistryFactory等类的作用,以及Zookeeper、Eureka等注册中心的...
Dubbo负载均衡策略分析 Dubbo服务调试之服务只订阅及服务只注册配置 Dubbo服务接口的设计原则(实战经验) Dubbo设计原理及源码分析 基于Dubbo构建大型分布式电商平台实战雏形 Dubbo容错机制及扩展性分析 ...
1. **负载均衡**:为了处理大量并发请求,我们需要设置负载均衡策略,如轮询、随机、最少连接等,以分散流量,避免单个服务器过载。 2. **限流与降级**:在秒杀开始时,瞬间流量可能会非常高,因此需要实施限流策略...
- 分析Dubbo或Spring Cloud中的服务发现和负载均衡算法,理解其工作原理。 通过上述内容,我们可以看到分布式Java应用涉及的领域广泛,涵盖了从通信、数据存储到计算和事务处理等多个方面。源码分析是深入理解这些...
1.3 负载均衡:通过Nginx等负载均衡器,实现对多个服务实例的请求分发,提高系统的并发处理能力。 1.4 消息队列:采用RabbitMQ或Kafka作为消息中间件,实现异步处理,降低系统延迟,提高系统吞吐量。 1.5 数据库...
Dubbo是阿里巴巴开源的一款高性能、轻量级的微服务框架,它提供了一整套微服务解决方案,包括服务注册与发现、负载均衡、服务代理、断路器等功能。 #### Dubbo的核心组件 - **Provider(服务提供者)**:暴露服务...
- **高可用性与高并发**:讲解如何构建高可用系统、负载均衡策略、集群部署、性能调优等。 #### 二、面试真题解析 - **知识点概览**:通过分析近年来Java高级架构面试真题,总结出高频考点和技术趋势。 - **核心...
- 根据实际场景选择合适的负载均衡策略(如轮询、随机、最少连接等)。 - 实现动态扩缩容功能,根据实时监控数据自动调整服务器数量。 #### 四、实战案例分析 假设我们正在开发一个电商平台的秒杀系统,预期每秒...