Directory:

集群目录服务Directory, 代表多个Invoker, 可以看成List<Invoker>,它的值可能是动态变化的比如注册中心推送变更。集群选择调用服务时通过目录服务找到所有服务

StaticDirectory: 静态目录服务, 它的所有Invoker通过构造函数传入, 服务消费方引用服务的时候, 服务对多注册中心的引用,将Invokers集合直接传入 StaticDirectory构造器,再由Cluster伪装成一个Invoker;StaticDirectory的list方法直接返回所有invoker集合;

RegistryDirectory: 注册目录服务, 它的Invoker集合是从注册中心获取的, 它实现了NotifyListener接口实现了回调接口notify(List<Url>)

通俗的来说,就是一个缓存和更新缓存的过程

Directory目录服务的更新过程

RegistryProtocol.doRefer方法,也就是消费端在初始化的时候,这里涉及到了RegistryDirectory这个类。然后执行cluster.join(directory)方法。这些代码在上篇博客有分析过。

cluster.join其实就是将Directory中的多个Invoker伪装成一个Invoker, 对上层透明,包含集群的容错机制

  1. private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
  2. RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);//对多个invoker进行组装
  3. directory.setRegistry(registry); //ZookeeperRegistry
  4. directory.setProtocol(protocol); //protocol=Protocol$Adaptive
  5. //url=consumer://192.168.111....
  6. URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
  7. //会把consumer://192... 注册到注册中心
  8. if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
  9. && url.getParameter(Constants.REGISTER_KEY, true)) {
  10. //zkClient.create()
  11. registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
  12. Constants.CHECK_KEY, String.valueOf(false)));
  13. }
  14. directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
  15. Constants.PROVIDERS_CATEGORY
  16. + "," + Constants.CONFIGURATORS_CATEGORY
  17. + "," + Constants.ROUTERS_CATEGORY));
  18. //Cluster$Adaptive
  19. return cluster.join(directory);
  20. }

  directory.subscribe:

订阅节点的变化,

1. 当zookeeper上指定节点发生变化以后,会通知到RegistryDirectory的notify方法

2. 将url转化为invoker对象

调用过程中invokers的使用

再调用过程中,AbstractClusterInvoker.invoke方法中:其中list(invocation) 就是获取directory中所缓存的 invoker。调用AbstrctDirectory的list方法,再转由调用RegisteryDirectory的doList,拿到成员变量methodInvokerMap里的值。

  1. public Result invoke(final Invocation invocation) throws RpcException {
  2.  
  3. checkWhetherDestroyed();
  4.  
  5. LoadBalance loadbalance;
  6.  
  7. List<Invoker<T>> invokers = list(invocation);
  8. if (invokers != null && invokers.size() > 0) {
  9. loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
  10. .getMethodParameter(invocation.getMethodName(),Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
  11. } else {
  12. loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
  13. }
  14. RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
  15. return doInvoke(invocation, invokers, loadbalance);
  16. }

负载均衡LoadBalance: 

  LoadBalance负载均衡, 负责从多个 Invokers中选出具体的一个Invoker用于本次调用,调用过程中包含了负载均衡的算法。

  在AbstractClusterInvoker.invoke中代码如下,通过名称获得指定的扩展点。RandomLoadBalance:

  1. public Result invoke(final Invocation invocation) throws RpcException {
  2.  
  3. checkWhetherDestroyed();
  4.  
  5. LoadBalance loadbalance;
  6.  
  7. List<Invoker<T>> invokers = list(invocation);
  8. if (invokers != null && invokers.size() > 0) {//默认拓展点是随机算法@SPI(RandomLoadBalance.NAME)
  9. loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
  10. .getMethodParameter(invocation.getMethodName(),Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
  11. } else {
  12. loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
  13. }
  14. RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
  15. return doInvoke(invocation, invokers, loadbalance);
  16. }

AbstractClusterInvoker.doselect

  调用LoadBalance.select方法,讲invokers按照指定算法进行负载

  1. private Invoker<T> doselect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
  2. if (invokers == null || invokers.size() == 0)
  3. return null;
  4. if (invokers.size() == 1)
  5. return invokers.get(0);
  6. // 如果只有两个invoker,退化成轮循
  7. if (invokers.size() == 2 && selected != null && selected.size() > 0) {
  8. return selected.get(0) == invokers.get(0) ? invokers.get(1) : invokers.get(0);
  9. }
  10. Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);
  11.  
  12. //如果 selected中包含(优先判断) 或者 不可用&&availablecheck=true 则重试.
  13. if( (selected != null && selected.contains(invoker))
  14. ||(!invoker.isAvailable() && getUrl()!=null && availablecheck)){
  15. try{
  16. Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
  17. if(rinvoker != null){
  18. invoker = rinvoker;
  19. }else{
  20. //看下第一次选的位置,如果不是最后,选+1位置.
  21. int index = invokers.indexOf(invoker);
  22. try{
  23. //最后在避免碰撞
  24. invoker = index <invokers.size()-1?invokers.get(index+1) :invoker;
  25. }catch (Exception e) {
  26. logger.warn(e.getMessage()+" may because invokers list dynamic change, ignore.",e);
  27. }
  28. }
  29. }catch (Throwable t){
  30. logger.error("clustor relselect fail reason is :"+t.getMessage() +" if can not slove ,you can set cluster.availablecheck=false in url",t);
  31. }
  32. }
  33. return invoker;
  34. }

  通过调用 AbstrctLoadBalance 的loadbalance.select(invokers, getUrl(), invocation) 转向具体的实现类,这里就是随机算法负载的 doSelect:

  1. protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
  2. int length = invokers.size(); // 总个数
  3. int totalWeight = 0; // 总权重
  4. boolean sameWeight = true; // 权重是否都一样
  5. for (int i = 0; i < length; i++) {
  6. int weight = getWeight(invokers.get(i), invocation);
  7. totalWeight += weight; // 累计总权重
  8. if (sameWeight && i > 0
  9. && weight != getWeight(invokers.get(i - 1), invocation)) {
  10. sameWeight = false; // 计算所有权重是否一样
  11. }
  12. }
  13. if (totalWeight > 0 && ! sameWeight) {
  14. // 如果权重不相同且权重大于0则按总权重数随机
  15. int offset = random.nextInt(totalWeight);
  16. // 并确定随机值落在哪个片断上
  17. for (int i = 0; i < length; i++) {
  18. offset -= getWeight(invokers.get(i), invocation);
  19. if (offset < 0) {
  20. return invokers.get(i);
  21. }
  22. }
  23. }
  24. // 如果权重相同或权重为0则均等随机
  25. return invokers.get(random.nextInt(length));
  26. }

 配置权重可以在配置文件中再service中可以配置weight 来确定随机的倾向

Random LoadBalance

  • 随机,按权重设置随机概率。
  • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

RoundRobin LoadBalance

  • 轮循,按公约后的权重设置轮循比率。
  • 存在慢的提供者累积请求问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

LeastActive LoadBalance

  • 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
  • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

ConsistentHash LoadBalance

  • 一致性Hash,相同参数的请求总是发到同一提供者。
  • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
  • 缺省只对第一个参数Hash,如果要修改,请配置<dubbo:parameter key="hash.arguments" value="0,1" />
  • 缺省用160份虚拟节点,如果要修改,请配置<dubbo:parameter key="hash.nodes" value="320" />

配置方法:

  1. 服务端服务级别:
  2. <dubbo:service interface="..." loadbalance="roundrobin" />
  3. 服务端方法级别:
  4. <dubbo:service interface="...">
  5. <dubbo:method name="..." loadbalance="roundrobin"/>
  6. </dubbo:service>
  7.  
  8. 客户端服务级别:
  9. <dubbo:reference interface="..." loadbalance="roundrobin" />
  10. 客户端方法级别:
  11. <dubbo:reference interface="...">
  12. <dubbo:method name="..." loadbalance="roundrobin"/>
  13. </dubbo:reference>

dubbo源码之Directory与LoadBalance的更多相关文章

  1. Dubbo 源码分析 - 集群容错之 LoadBalance

    1.简介 LoadBalance 中文意思为负载均衡,它的职责是将网络请求,或者其他形式的负载"均摊"到不同的机器上.避免集群中部分服务器压力过大,而另一些服务器比较空闲的情况.通 ...

  2. Dubbo 源码解析四 —— 负载均衡LoadBalance

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 Dubbo 入门之二 --- 项目结构解析 Dubbo 源码分析系列之三 -- 架构原 ...

  3. 深度解剖dubbo源码

    -----------学习dubbo源码,能给你带来什么好处?----------- 1.提升SOA的微服务架构设计能力   通过读dubbo源码是一条非常不错的通往SOA架构设计之路,毕竟SOA的服 ...

  4. Dubbo 源码分析 - 服务调用过程

    注: 本系列文章已捐赠给 Dubbo 社区,你也可以在 Dubbo 官方文档中阅读本系列文章. 1. 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与引入.以及集群容错方面的代码.经过 ...

  5. Dubbo 源码分析 - 集群容错之 Cluster

    1.简介 为了避免单点故障,现在的应用至少会部署在两台服务器上.对于一些负载比较高的服务,会部署更多台服务器.这样,同一环境下的服务提供者数量会大于1.对于服务消费者来说,同一环境下出现了多个服务提供 ...

  6. Dubbo 源码分析 - 集群容错之 Router

    1. 简介 上一篇文章分析了集群容错的第一部分 -- 服务目录 Directory.服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由.上一篇文章关于服务路由相关逻辑没有 ...

  7. dubbo源码解析五 --- 集群容错架构设计与原理分析

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...

  8. 【Dubbo 源码解析】07_Dubbo 重试机制

    Dubbo 重试机制 通过前面 Dubbo 服务发现&引用 的分析,我们知道,Dubbo 的重试机制是通过 com.alibaba.dubbo.rpc.cluster.support.Fail ...

  9. 深度剖析Dubbo源码

    -----------------学习dubbo源码,能给你带来什么好处?----------- 1.提升SOA的微服务架构设计能力   通过读dubbo源码是一条非常不错的通往SOA架构设计之路,毕 ...

随机推荐

  1. Java中常见的锁分类以及对应特点

    对于 Java 锁的分类没有严格意义的规则,我们常说的分类一般都是依据锁的特性.锁的设计.锁的状态等进行归纳整理的,所以常见的分类如下: 公平锁和非公平锁:公平锁是多线程按照锁申请的顺序获取锁,非公平 ...

  2. linux如何查看端口号被哪个进程占用

    1.lsof -i:端口号 lsof(list open files) 2.netstat -tunlp |grep 端口号 t:tcp u:udp n:拒绝显示别名 l:仅显示listen的服务状态 ...

  3. table 如何不越过父级div

    设置table 被限制在外围div的方法: 设置样式: table { table-layout: fixed; /*fiexed 列宽由表格宽度和列宽度设定. 默认.列宽度由单元格内容设定.*/ w ...

  4. Python 13 简单项目-堡垒机

    本节内容 项目实战:运维堡垒机开发 前景介绍 到目前为止,很多公司对堡垒机依然不太感冒,其实是没有充分认识到堡垒机在IT管理中的重要作用的,很多人觉得,堡垒机就是跳板机,其实这个认识是不全面的,跳板功 ...

  5. python作业高级FTP

    转载自:https://www.cnblogs.com/sean-yao/p/7882638.html 作业需求: 1. 用户加密认证 2. 多用户同时登陆 3. 每个用户有自己的家目录且只能访问自己 ...

  6. mysql 原理 ~ DDL之mdl锁

    一 简介: MDL锁 二 具体 1 MDL锁   1 增删查改 申请MDL读锁   2 ddl语句       1. 拿MDL写锁      2. 降级成MDL读锁      3. 真正做DDL    ...

  7. IBM 3650 M3 yum upgrade后系统无法登陆问题

    一.背景 IBM 3650 M3安装了centos7.2操作系统 今天yum upgrade升级centos7.6,重启系统后发现开不了机,报错如下: Failed to set MokListRT: ...

  8. mysql数据库可以远程连接或者说用IP地址可以访问

    mysql数据库可以远程连接或者说用IP地址可以访问 一般情况不建议直接修改root的权限, 先看下,自己mysql数据库的用户级权限 mysql -u root -p----->用root登陆 ...

  9. python中用selenium调Firefox报错问题

    python在用selenium调Firefox时报错: Traceback (most recent call last):  File "G:\python_work\chapter11 ...

  10. Palindrome Number & Reverse Integer

    Determine whether an integer is a palindrome. Do this without extra space. 分析:把一个数倒过来,然后看两个数是否相同. pu ...