【dubbo源码解析】 --- dubbo集群容错(cluster)、负载均衡(loadbalance)底层原理探析 + 扩展自己的集群容错、负载均衡组件(转载)

文章目录
1 集群容错和负载均衡的概念
2 dubbo集群容错 + 负载均衡底层原理
3 简单测试
4 自己扩展一个dubbo集群容错组件和负载均衡组件
4.1 扩展一个集群容错组件(Cluster)
4.2 扩展一个loadbalance组件
4.3 不要忘了在MATE-INF/dubbo文件夹下指定这些SPI扩展组件
4.3 测试
1 集群容错和负载均衡的概念
摘自dubbo官方博客:http://dubbo.apache.org/zh-cn/blog/dubbo-loadbalance.html
在这里插入图片描述
在这里插入图片描述

摘自dubbo官方博客:http://dubbo.apache.org/zh-cn/blog/dubbo-loadbalance.html
在这里插入图片描述

2 dubbo集群容错 + 负载均衡底层原理
首先应该明确的是集群容错 + 负载均衡其实都是由消费端的逻辑。

由《【dubbo源码解析】— dubbo的服务暴露+服务消费(RPC调用)底层原理深入探析》文章的知识可知,在不引入注册中心时,在消费端, protocol.refer 得到 invoker 对象, 并拿着该Invoker做一个【可以调用服务端的服务】的代理对象作为@Reference标注的对象。

那加入注册中心逻辑后,dubbo是如何在RegistryProtocol对象中将集群容错功能挂入到Dubbo的RPC链条中的呢?

Dubbo 在这里玩了个心眼。 真正的过程走得百绕千回, 看如下这段代码:
RegistryProtocol. doRefer 方法:

private Invoker doRefer(Cluster cluster, Registry registry, Class type, URL url) {
RegistryDirectory directory = new RegistryDirectory(type, url);
directory.setRegistry(registry);
directory.setProtocol(protocol);
//。。。。省略部分代码

Invoker invoker = cluster.join(directory);
//。。。。省略部分代码
return invoker;

}
1
2
3
4
5
6
7
8
9
10
原来的 protocol 被传进RegistryDirectory 类中去了, doRefer 返回的 invoker对象, 是 cluster.join(directory)返回的 invoker。

而cluster 是一个扩展接口, 因此, 这个接口方法最终执行的对象, 是根据容错策略自适配出来的对象, 如果 url 中不指定则默认是 failover 再看 failover 实现类的逻辑, 非常简单, 只是返回一个FailoverClusterInvoker 对象:

public class FailoverCluster implements Cluster {
public static final String NAME = “failover”;

public FailoverCluster() {
}

public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
    return new FailoverClusterInvoker(directory);
}

}
1
2
3
4
5
6
7
8
9
10
再看下FailoverClusterInvoker中invoke方法调用后的的具体实现代码doInvoke的逻辑:

public Result doInvoke(Invocation invocation, List<Invoker> invokers, LoadBalance loadbalance) throws RpcException {
//… (省略部分代码)
//容错次数
int len = this.getUrl().getMethodParameter(invocation.getMethodName(), “retries”, 2) + 1;
if (len <= 0) {
len = 1;
}

  //...... (省略部分代码)
  for(int i = 0; i < len; ++i) {
  		//容错策略
	    //Reselect before retry to avoid a change of candidate `invokers`.
        //NOTE: if `invokers` changed, then `invoked` also lose accuracy.
        if (i > 0) { 
            checkWhetherDestroyed();
            copyinvokers = list(invocation);
            // check again
            checkInvokers(copyinvokers, invocation);
        }
      Invoker<T> invoker = this.select(loadbalance, invocation, copyinvokers, invoked);
      //记录已经被选择的Invoker --> 用作进行负载判断
      invoked.add(invoker);
      //...... (省略部分代码)
      try {
          Result result = invoker.invoke(invocation);
		 //...... (省略部分代码)
          Result var12 = result;
          return var12;
      }
       //...... (省略部分代码)
  }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
此 invoker 的逻辑:
(1)按重试次数 for 循环, 只要不是正常返回, 则再试一次
(2)调用 select 方法,大致逻辑为:先通过SPI机制取得一个 loadbalance 对象—>然后根据获取的loadbalance 对象select出来一个封装了【可以调用服务端的服务】的Invoker —> 然后执行此 invoker 对象得到结果。

需要注意的是:
此 select 方法, 是从一组 invoker(即文章《【dubbo源码解析】— dubbo的服务注册与发现机制底层原理探析》中讲到的RegistryDirectory对象的urlInvokerMap容器 中缓存的根据每一个服务端的URL生成的Invoker对象) 中选择出来的一个 invoker。

画个简图来描述上诉具体逻辑如下:
在这里插入图片描述

3 简单测试
注意: 如果消费端未配置集群容错 + 负载均衡策略的话,消费端会通过注册中心获取到服务端配置的参数,也就是说这些参数服务端都会以URL的形式给注册中心,然后消费端根据获取到的URL来根据SPI机制选择到底使用哪种策略。

服务端:

ExtensionLoader protocolLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
ExtensionLoader proxyLoader = ExtensionLoader.getExtensionLoader(ProxyFactory.class);

//注册中心服务–zk
final URL registryUrl = URL.valueOf(“registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?registry=zookeeper”);

//支持的协议:dubbo,http,hessian,rmi
URL serviceUrl = URL.valueOf(“dubbo://127.0.0.1:9001/com.nrsc.service.InvokerDemoService”);

@Test
public void serverRpc() throws IOException {
InvokerDemoService service = new InvokerDemoServiceImpl(“yoyo”);
//生成代理工厂
// — 由URL确定到底是动态代理工厂(JdkProxyFactory)还是静态代理工厂(JavassistProxyFactory)
// — 默认情况下为静态代理工厂
ProxyFactory proxy = proxyLoader.getAdaptiveExtension();

//下面这两句话完全可以放在外面 ---> 如果写在外面,这里的代码就和上文讲到的RPC完整链条的代码一致了
//这里为了测试消费端可以动态监测到服务端的发布/下线,所以写在了这里
serviceUrl = serviceUrl.setPort(9001);
//url中加入负载均衡和集群容错参数
//serviceUrl = serviceUrl.addParameter("loadbalance", "consistenthash");
//serviceUrl = serviceUrl.addParameter("cluster", "failfast");
//启动自己扩展的loadbalance和cluster组件
//serviceUrl = serviceUrl.addParameter("loadbalance", "first");
//serviceUrl = serviceUrl.addParameter("cluster", "failsms");
URL newRegistryUrl = registryUrl.addParameter(Constants.EXPORT_KEY, serviceUrl.toFullString());

Invoker<InvokerDemoService> serviceInvoker = proxy.getInvoker(service, InvokerDemoService.class, newRegistryUrl);

//获取具体的协议
Protocol protocol = protocolLoader.getAdaptiveExtension();
Exporter<InvokerDemoService> exporter = protocol.export(serviceInvoker);
System.out.println("server 启动协议:" + serviceUrl.getProtocol());
// 保证服务一直开着
System.in.read();
exporter.unexport();

}

/****

  • 除了接口外,其他和serverRpc1()一样,主要用来测试消费端可以动态监测到服务端的发布/下线

  • @throws IOException
    */
    @Test
    public void serverRpc2() throws IOException {
    InvokerDemoService service = new InvokerDemoServiceImpl(“nrsc”);
    //生成代理工厂
    // — 由URL确定到底是动态代理工厂(JdkProxyFactory)还是静态代理工厂(JavassistProxyFactory)
    // — 默认情况下为静态代理工厂
    ProxyFactory proxy = proxyLoader.getAdaptiveExtension();

    //下面这两句话完全可以放在外面 —> 如果写在外面,这里的代码就和上文讲到的RPC完整链条的代码一致了
    //这里为了测试消费端可以动态监测到服务端的发布/下线,所以写在了这里
    serviceUrl = serviceUrl.setPort(9002);
    //url中加入负载均衡和集群容错参数
    //serviceUrl = serviceUrl.addParameter(“loadbalance”, “consistenthash”);
    //serviceUrl = serviceUrl.addParameter(“cluster”, “failfast”);
    //启动自己扩展的loadbalance和cluster组件
    //serviceUrl = serviceUrl.addParameter(“loadbalance”, “first”);
    //serviceUrl = serviceUrl.addParameter(“cluster”, “failsms”);
    URL newRegistryUrl = registryUrl.addParameter(Constants.EXPORT_KEY, serviceUrl.toFullString());

    Invoker serviceInvoker = proxy.getInvoker(service, InvokerDemoService.class, newRegistryUrl);

    //获取具体的协议
    Protocol protocol = protocolLoader.getAdaptiveExtension();
    Exporter exporter = protocol.export(serviceInvoker);
    System.out.println(“server 启动协议:” + serviceUrl.getProtocol());
    // 保证服务一直开着
    System.in.read();
    exporter.unexport();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    消费端:

@Test
public void clientRpc() throws IOException {
Protocol protocol = protocolLoader.getAdaptiveExtension();
//生成代理工厂
ProxyFactory proxy = proxyLoader.getAdaptiveExtension();

//由代理工厂生成Invoker对象
Invoker<InvokerDemoService> referInvoker = protocol.refer(InvokerDemoService.class, registryUrl);

//生成DemoService的代理类
InvokerDemoService service = proxy.getProxy(referInvoker);

for (int i = 0; i < 6; i++) {
    String result = service.sayHello(registryUrl.getProtocol() + "调用");
    System.out.println(result);
}
// 保证服务一直开着 ,测试消费端可以动态监测到服务端的发布/下线
//System.in.read();

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
测试结果1:
分别启用两个服务端,然后启用消费端,在未配置集群容错+ 负载均衡的情况下调用结果和结论如下:
在这里插入图片描述

测试结果2:
放开服务端代码中关于集群容错+ 负载均衡的注释代码,重新启用两个服务端,然后启用消费端,调用结果和结论如下:
在这里插入图片描述

4 自己扩展一个dubbo集群容错组件和负载均衡组件
4.1 扩展一个集群容错组件(Cluster)
由dubbo源码可知要想扩展一个集群容错组件,需要做两件事

(1)实现Cluster接口,并重写其Join方法
(2)在Join方法里返回一个XXXClusterInvoker 用于包装含有【可以调用服务端的服务】的Invoker ,且集群容错的逻辑,就写在XXXClusterInvoker 的Invoker方法里
这里为了简便期间,我以 FailfastClusterInvoker作为XXXClusterInvoker ,则自定的Cluster代码如下:

/***

  • 当然如果想新扩展一个Cluster组件,肯定还要配套弄一个XXXClusterInvoker,这里就直接使用FailfastClusterInvoker代替了
    */
    public class FailSmsCluster implements Cluster {

    @Override
    public Invoker join(Directory directory) throws RpcException {
    sendSms();
    return new FailfastClusterInvoker<>(directory);
    }

    private void sendSms() {
    System.out.println(“send sms notify!”);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    4.2 扩展一个loadbalance组件
    扩展loadbalance组件,要比扩展Cluster组件简单的多,只需要实现LoadBalance接口,并重写其select方法就可以了,这里提供一个比较简单的loadbalance组件 — 只选择第一个注册的Invoker。

public class FirstLoadBalance implements LoadBalance {

@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
    System.out.println("FirstLoadBalance : Select the first invoker...");
    return invokers.get(0);
}

}
1
2
3
4
5
6
7
8
4.3 不要忘了在MATE-INF/dubbo文件夹下指定这些SPI扩展组件
在这里插入图片描述***

4.3 测试
打开服务端的如下代码,并重启两个服务端 —>然后再重启消费端

//启动自己扩展的loadbalance和cluster组件
serviceUrl = serviceUrl.addParameter(“loadbalance”, “first”);
serviceUrl = serviceUrl.addParameter(“cluster”, “failsms”);
1
2
3
调用结果和结论如下:
在这里插入图片描述
在这里插入图片描述

end!

原文链接:https://blog.csdn.net/nrsc272420199/article/details/107451817?utm_medium=distribute.pc_feed.none-task-blog-personrec_tag-3.nonecase&depth_1-utm_source=distribute.pc_feed.none-task-blog-personrec_tag-3.nonecase&request_id=5f1ff14df769fa6e5f5d208a

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值