Dubbo集群模块的目的是将集群Invokers构造一个透明的Invoker对象,其中包含了容错机制、负载均衡、目录服务(服务地址集合)、路由机制等,为RPC层提供高可用、高并发、自动发现、可治理的SOA特性。

本文我们主要讨论以下八个问题:

一、集群模块的需求功能点有哪些?

二、集群模块的总体设计框架是什么样的?

三、Dubbo提供了哪些容错机制?如何实现的?

四、Dubbo提供了哪些负载均衡机制?如何实现的?

五、Dubbo目录服务是干什么的?提供了哪几种类型的目录服务?

六、Dubbo提供了哪些路由机制?如何实现的?

七、总结集群模块如何带来高可用、自动发现、可治理的特性?

        一、集群模块的需求功能点有哪些?

基于文章开头讨论的目标内容,我认为集群模块的需求功能点主要有以下几点:

1、将集群Invokers构造一个透明的Invoker对象提供给Rpc模块调用;

2、提供容错机制,包括Failover(失败自动切换,尝试其他服务器)、Failfast(失败立即返回并抛出异常)、Failsafe(失败忽略异常)、Failback(失败自动恢复,记录日志并定时重试)、Forking(并行调用多个服务,一个成功立即返回)、Broadcast(广播调用所有提供者,任意一个报错则报错);

3、提供负载均衡机制,包括Random(带权重的随机访问)、RoundRobin(带权重的轮训访问)、LeastActive(选择最少活跃者)、ConsistentHash(一致性哈希,相同参数的请求总是发到同一提供者);

4、提供目录服务,包括静态目录服务(将调用地址存到本地)、注册中心注册目录服务两种方式;

5、提供路由机制,包括条件路由(在URL配置条件表达式)和脚本路由(使用脚本语言编写脚本,返回路由结果);

        二、集群模块的总体设计框架是什么样的?

调用关系如下图所示:

接口声明如下图所示:

        

Cluster接口:声明join()方法,从Directory实例中的Invoker列表中返回一个Invoker;

Directory接口:list()方法,可查询Invoker列表;

LoadBalance接口:select()方法,可从一个Invoker集合中结合负载均衡策略选择一个Invoker返回;

Router接口:route()方法,从给定的Invoker集合路由选择一个Invoker返回;

Merger接口:merge(T)方法,将多个调用返回的结果合并起来返回;

Configurator接口:configure(URL)方法,配置加工URL参数并返回;

三、Dubbo提供了哪些集群容错机制?如何实现的?

        由于篇幅过程,我将其内容单独写了一篇博客,详见 《集群容错机制》

        

四、Dubbo提供了哪些负载均衡机制?如何实现的?

        由于篇幅过长,我又将内容拆分到另一片博客,见《集群负载均衡》

五、Dubbo目录服务是干什么的?提供了哪几种类型的目录服务?

        dubbo目录服务提供了获取提供者服务地址列表的功能,目录服务的调用者是com.alibaba.dubbo.rpc.cluster.Cluster的join()方法。目前dubbo提供了静态目录服务和注册中心目录服务。静态目录服务实现了一个静态的地址列表本地内存缓存。注册中心目录服务提供了分布式注册检索服务地址的功能。在提供目录服务返回服务地址的时候,调用dubbo的路由服务,实现了请求服务的路由功能。接下来,我们来讨论目录服务的实现细节。

1、Directory接口

Directory接口中,

Class<T> getInterface()方法返回服务的接口类;

list(Invocation invocation)就是获取服务地址列表的方法;

父接口Node中,

getUrl()方法返回了服务声明的URL信息,

isAvailable()方法判断目录服务是否可用,或者是否存在可用的服务提供者;

destroy()销毁目录服务及所有提供者服务。

2、AbstractDirectory,目录服务的默认实现抽象类;

(1)将路由对象引入目录服务,方法setRouters()设置了路由对象,实现见如下代码:

     protected void setRouters(List<Router> routers) {
// copy list
routers = routers == null ? new ArrayList<Router>() : new ArrayList<Router>(routers);
// append url router
String routerkey = url.getParameter(Constants.ROUTER_KEY);
if (routerkey != null && routerkey.length() > 0) {
//通过SPI实例化配置的路由对象工厂对象
RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(routerkey);
routers.add(routerFactory.getRouter(url));
}
// append mock invoker selector
routers.add(new MockInvokersSelector());
Collections.sort(routers);
this.routers = routers;
}

(2)得到服务对象列表,主要通过doList()实现得到服务列表,此方法由子类实现,然后依次调用router列表做路由筛选,实现如下:

     public List<Invoker<T>> list(Invocation invocation) throws RpcException {
if (destroyed) {
throw new RpcException("Directory already destroyed .url: " + getUrl());
}
List<Invoker<T>> invokers = doList(invocation);
List<Router> localRouters = this.routers; // local reference
if (localRouters != null && localRouters.size() > 0) {
for (Router router : localRouters) {
try {
if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
invokers = router.route(invokers, getConsumerUrl(), invocation);
}
} catch (Throwable t) {
logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
}
}
}
return invokers;
}

3、StaticDirectory静态目录服务,doList()将构造方法中传入的invoker列表原样返回。

4、RegistryDirectory 类整合了注册中心和目录服务,具体实现实在dubbo-register模块,我将另外写一篇博客讨论。

        六、Dubbo提供了哪些路由机制?如何实现的?

路由机制为用户提供了灵活可配置的服务筛选功能,通过用户自定义配置路由规则,决定一次 dubbo 服务调用的目标服务器。使用场景例如:提供差异化服务,给重要的请求提供性能好的服务器,反之给次要的服务提供性能差的服务器;读写分离,根据方法名字add,update,delete等分为一组,查询方法分为一组,分别映射到不同的服务器上;对客户端设置白名单、黑名单;更多场景及具体的应用说明详见dubbo用户手册路由规则篇

dubbo提供了两种路由机制:条件路由和脚本路由。

1、向注册中心写入路由规则,通常由监控中心或服务治理页面完成,代码实现主要在dubb-register模块:

 RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("condition://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=" + URL.encode("host = 10.20.153.10 => host = 10.20.153.11") + "));

其中:

  • condition:// 表示路由规则的类型,支持条件路由规则和脚本路由规则,可扩展,必填。
  • 0.0.0.0 表示对所有 IP 地址生效,如果只想对某个 IP 的生效,请填入具体 IP,必填。
  • com.foo.BarService 表示只对指定服务生效,必填。
  • category=routers 表示该数据为动态配置类型,必填。
  • dynamic=false 表示该数据为持久数据,当注册方退出时,数据依然保存在注册中心,必填。
  • enabled=true 覆盖规则是否生效,可不填,缺省生效。
  • force=false 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 flase
  • runtime=false 是否在每次调用时执行路由规则,否则只在提供者地址列表变更时预先执行并缓存结果,调用时直接从缓存中获取路由结果。如果用了参数路由,必须设为 true,需要注意设置会影响调用的性能,可不填,缺省为 flase
  • priority=1 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0
  • rule=URL.encode("host = 10.20.153.10 => host = 10.20.153.11") 表示路由规则的内容,必填。

2、条件路由

基于条件表达式的路由规则,如:host = 10.20.153.10 => host = 10.20.153.11。=>左边的表达式表示消费端信息(matchWhen),右边的表达式表示服务提供者的服务信息(matchThen)。

代码实现中,解析=>左右的条件表达式,左侧消费端条件表达式解析成when,右侧服务端条件表达式解析成then,封装matchWhen()和matchThen()分别匹配消费端和服务端表达式。

我们看看源码如何实现路由逻辑的。

条件路由实现主要靠两个类,ConditionRouterFactory,实现了ConditionRouter实例化的过程;ConditionRouter,实现了具体的路由机制,其中route方法实现如下,实现主要靠解析和匹配条件表达式。

     public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
throws RpcException {
if (invokers == null || invokers.size() == 0) {
return invokers;
}
try {
//消费端条件不匹配规则,就全部返回
if (!matchWhen(url, invocation)) {
return invokers;
}
List<Invoker<T>> result = new ArrayList<Invoker<T>>();
if (thenCondition == null) {
logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
return result;
}
for (Invoker<T> invoker : invokers) {
//遍历invokers,找到匹配规则的invoker(服务端)加入result
if (matchThen(invoker.getUrl(), url)) {
result.add(invoker);
}
}
if (result.size() > 0) {
return result;
} else if (force) { //如果结果为空,force=true,则返回空的列表,否则返回所有的invokers(不执行route规则)
logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(Constants.RULE_KEY));
return result;
}
} catch (Throwable t) {
logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
}
return invokers;
}

3、脚本路由

通过配置脚本或脚本文件设置路由规则rule,程序编译执行脚本函数,得到脚本筛选后的invokers数组。具体逻辑很简单,就是加载脚本,加载对应脚本引擎,执行脚本,得到结果后转换输出的过程。

     public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
try {
List<Invoker<T>> invokersCopy = new ArrayList<Invoker<T>>(invokers);
Compilable compilable = (Compilable) engine;
Bindings bindings = engine.createBindings();
bindings.put("invokers", invokersCopy);
bindings.put("invocation", invocation);
bindings.put("context", RpcContext.getContext());
CompiledScript function = compilable.compile(rule);
Object obj = function.eval(bindings);
if (obj instanceof Invoker[]) {
invokersCopy = Arrays.asList((Invoker<T>[]) obj);
} else if (obj instanceof Object[]) {
invokersCopy = new ArrayList<Invoker<T>>();
for (Object inv : (Object[]) obj) {
invokersCopy.add((Invoker<T>) inv);
}
} else {
invokersCopy = (List<Invoker<T>>) obj;
}
return invokersCopy;
} catch (ScriptException e) {
//fail then ignore rule .invokers.
logger.error("route error , rule has been ignored. rule: " + rule + ", method:" + invocation.getMethodName() + ", url: " + RpcContext.getContext().getUrl(), e);
return invokers;
}
}

七、总结集群模块如何带来高可用、自动发现、可治理的特性?

集群模块在dubbo中非常重要,提供了基于RPC功能的高可用特性,此特性主要是通过集群容错和负载均衡策略支持的,在服务提供者出现异常时,可配置的容错特性确保服务能按照配置的策略返回,或切换到其他服务并重试,或立即抛出异常,满足了不同服务不同场景的异常后处理需要。可配置的负载均衡策略满足了不同场景、需求的服务实现负载均衡,在不同场景下均匀的分摊负载,在负载不够用的情况下灵活的增加机器节点承担多余的负载,保证了集群的高可用特性。并且可以灵活配置节点的权重,实现针对不同配置的服务节点承担的请求不同。

可配置的路由功能满足了很多场景下的灵活路由需求,如读写分离,消费端请求的白名单、黑名单,针对不同配置的服务提供给不同的消费者等等。由于可配置性强,可以针对不同的需求做不同的配置,可提供很灵活的功能特性。通过配置规则,实现服务降级,可以屏蔽有问题的服务,并定义服务响应。这些都体现了Dubbo可治理的特性。

目录服务简单的定义了对服务地址列表的查询功能,配合dubb-register注册中心模块功能可以实现服务注册、自动发现功能。详见注册中心模块的源码分析章节。

集群模块就讨论到这里了,如有不当之处,欢迎大家提出异议和宝贵的意见,这样有助于我技术上的提升。

Dubbo源码学习总结系列三 dubbo-cluster集群模块的更多相关文章

  1. Dubbo源码学习总结系列一 总体认识

    本文写作时,dubbo最高版本是V2.6.0.  写这篇文章主要想回答以下4个问题: 一.dubbo是什么?完成了哪些主要需求? 二.dubbo适用于什么场景? 三.dubbo的总体架构是什么样的? ...

  2. dubbo源码学习(二)dubbo容器启动流程简略分析

    dubbo版本2.6.3 继续之前的dubbo源码阅读,从com.alibaba.dubbo.container.Main.main(String[] args)作为入口 简单的数据一下启动的流程 1 ...

  3. Dubbo源码学习总结系列二 dubbo-rpc远程调用模块

    dubbo本质是一个RPC框架,我们首先讨论这个骨干中的骨干,dubbo-rpc模块. 主要讨论一下几部分内容: 一.此模块在dubbo整体框架中的作用: 二.此模块需要完成的需求功能点及接口定义: ...

  4. Dubbo源码学习总结系列七---注册中心

    Dubbo注册中心是框架的核心模块,提供了服务注册发现(包括服务提供者.消费者.路由策略.覆盖规则)的功能,该功能集中体现了服务治理的特性.该模块结合Cluster模块实现了集群服务.Dubbo管理控 ...

  5. dubbo源码学习(一)dubbo容器启动流程简略分析

    最近在学习dubbo,dubbo的使用感觉非常的简单,方便,基于Spring的容器加载配置文件就能直接搭建起dubbo,之前学习中没有养成记笔记的习惯,时间一久就容易忘记,后期的复习又需要话费较长的时 ...

  6. Dubbo源码学习--集群负载均衡算法的实现

    相关文章: Dubbo源码学习文章目录 前言 Dubbo 的定位是分布式服务框架,为了避免单点压力过大,服务的提供者通常部署多台,如何从服务提供者集群中选取一个进行调用, 就依赖Dubbo的负载均衡策 ...

  7. Dubbo源码学习文章目录

    目录 Dubbo源码学习--服务是如何发布的 Dubbo源码学习--服务是如何引用的 Dubbo源码学习--注册中心分析 Dubbo源码学习--集群负载均衡算法的实现

  8. Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题

    Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题 相关文章: Dubbo源码学习文章目录 前言 主要是前一阵子换了工作,第一个任务就是解决目前团队在 Dubbo 停机时产生的问题 ...

  9. Dubbo源码学习--服务是如何引用的

    ReferenceBean 跟服务引用一样,Dubbo的reference配置会被转成ReferenceBean类,ReferenceBean实现了InitializingBean接口,直接看afte ...

随机推荐

  1. SPOJ 2798 QTREE3 - Query on a tree again!

    原oj题面 Time limit 2000 ms Memory limit 1572864 kB Code length Limit 50000 B OS Linux Language limit A ...

  2. 170905-MyBatis中的关系映射

    ===关系映射=== 参考文档复习:1对1,1对多,多对多 1.映射(多)对一.(一)对一的关联关系 1).使用列的别名 ①.若不关联数据表,则可以得到关联对象的id属性 ②.若还希望得到关联对象的其 ...

  3. 用CSS代码编写简易轮播图

    废话不多说,直接上代码 <!doctype html> <html> <head> <title></title> <meta cha ...

  4. 10.1 ‘The server's host key is not cached in the registry’

    10.1 ‘The server's host key is not cached in the registry’ This error message occurs when PuTTY conn ...

  5. 将String转化成Stream,将Stream转换成String, C# Stream 和 byte[] 之间的转换(文件流的应用)

    static void Main( string[] args ) { string str = "Testing 1-2-3"; //convert string 2 strea ...

  6. 剑指 Offer——最小的 K 个数

    1. 题目 2. 解答 2.1. 方法一--大顶堆 参考 堆和堆排序 以及 堆的应用,我们将数组的前 K 个位置当作一个大顶堆. 首先建堆,也即对堆中 [0, (K-2)/2] 的节点从上往下进行堆化 ...

  7. delphi如何按照控件的左右顺序来遍历窗体中的每个控件 [问题点数:20 http://bbs.csdn.net/topics/380216822

    delphi如何按照控件的左右顺序来遍历窗体中的每个控件delphi默认是按照控件添加进窗体的顺序来遍历的,有没有哪个属性能控制这个/?? 更多0分享到:   对我有用[0] 丢个板砖[0] 引用 | ...

  8. Linux 服务器安全优化

    最小的权限+最少的服务=最大的安全 所以,无论是配置任何服务器,我们都必须把不用的服务关闭.把系统权限设置到最小,这样才能保证服务器最大的安全.下面是CentOS服务器安全设置,供大家参考. 一.注释 ...

  9. json 格式

    Json格式规则:(Douglas Crockford提出的) 1) 并列的数据之间用逗号(“,”)分隔. 2) 映射用冒号(“:”)表示. 3) 并列数据的集合(数组)用方括号("[]&q ...

  10. 【Python】Visual Studio Code 安装&&使用 hello python~~~~

    1.安装Python 官网下载: https://www.python.org/downloads/   选择版本下载 2.下载完毕后,点击安装. 3.看到页面,直接下一步,全部默认选项. 4.安装即 ...