乐趣区

关于后端:浅析-Dubbo-30-中接口级地址推送性能的优化

URL 简介

在论述地址推送性能的具体优化之前,咱们有必要先理解一下与之非亲非故的内容 — URL。

定义

在不谈及 dubbo 时,咱们大多数人对 URL 这个概念并不会感到生疏。对立资源定位器 (RFC1738――Uniform Resource Locators (URL))应该是最广为人知的一个 RFC 标准,它的定义也非常简单。

因特网上的可用资源能够用简略字符串来示意,该文档就是形容了这种字符串的语法和语 义。而这些字符串则被称为:“对立资源定位器”(URL)

一个规范的 URL 格局 至少能够蕴含如下的几个局部

protocol://username:password@host:port/path?key=value&key=value

一些典型 URL

http://www.facebook.com/friends?param1=value1&param2=value2
https://username:password@10.20.130.230:8080/list?version=1.0.0
ftp://username:password@192.168.1.7:21/1/read.txt

当然,也有一些 不太合乎惯例的 URL,也被归类到了 URL 之中

192.168.1.3:20880
url protocol = null, url host = 192.168.1.3, port = 20880, url path = null

file:///home/user1/router.js?type=script
url protocol = file, url host = null, url path = home/user1/router.js

file://home/user1/router.js?type=script<br>
url protocol = file, url host = home, url path = user1/router.js

file:///D:/1/router.js?type=script
url protocol = file, url host = null, url path = D:/1/router.js

file:/D:/1/router.js?type=script
同上 file:///D:/1/router.js?type=script

/home/user1/router.js?type=script
url protocol = null, url host = null, url path = home/user1/router.js

home/user1/router.js?type=script
url protocol = null, url host = home, url path = user1/router.js

Dubbo 中的 URL

在 dubbo 中,也应用了相似的 URL,次要用于在各个扩大点之间传递数据,组成此 URL 对象的具体参数如下:

  • protocol:个别是 dubbo 中的各种协定 如:dubbo thrift http zk
  • username/password:用户名 / 明码
  • host/port:主机 / 端口
  • path:接口名称
  • parameters:参数键值对

    一些典型的 Dubbo URL

    dubbo://192.168.1.6:20880/moe.cnkirito.sample.HelloService?timeout=3000
    形容一个 dubbo 协定的服务
    
    zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.2&interface=org.apache.dubbo.registry.RegistryService&pid=1214&qos.port=33333&timestamp=1545721981946
    形容一个 zookeeper 注册核心
    
    consumer://30.5.120.217/org.apache.dubbo.demo.DemoService?application=demo-consumer&category=consumers&check=false&dubbo=2.0.2&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=1209&qos.port=33333&side=consumer&timestamp=1545721827784
    形容一个消费者

    能够说,任意的一个畛域中的一个实现都能够认为是一类 URL,dubbo 应用 URL 来对立形容了元数据,配置信息,贯通在整个框架之中。

Dubbo 2.7

URL 构造

在 Dubbo 2.7 中,URL 的构造非常简单,一个类就涵盖了所有内容,如下图所示。

地址推送模型

接下来咱们再来看看 Dubbo 2.7 中的地址推送模型计划,次要性能问题由下列过程引起。

上图中次要的流程为
1、用户新增 / 删除 DemoService 的某个具体 Provider 实例(常见于扩容缩容、网络稳定等起因)
2、ZooKeeper 将 DemoService 下所有实例推送给 Consumer 端
3、Consumer 端依据 Zookeeper 推送的数据从新全量生成 URL
依据该计划能够看出在 Provider 实例数量较小时,Consumer 端的影响比拟小,但当某个接口有大量 Provider 实例时,便会有大量不必要的 URL 创立过程。
而 Dubbo 3.0 中则次要针对上述推送流程进行了一系列的优化,接下来咱们便对其进行具体的解说。

Dubbo 3.0

URL 构造

当然,地址推送模型的优化仍然离不开 URL 的优化,下图是 Dubbo 3.0 中优化地址推送模型的过程中应用的新的 URL 构造。

依据上图咱们能够看出,在 Dubbo 2.7 的 URL 中的几个重要属性在 Dubbo 3.0 中曾经不存在了,取而代之的是 URLAddress 和 URLParam 两个类。原来的 parameters 属性被挪动到了 URLParam 中的 params,其余的属性则挪动到了 URLAddress 及其子类中。
再来介绍 URL 新增的 3 个子类,其中 InstanceAddressURL 属于利用级接口地址,本篇章中不做介绍。
而 ServiceConfigURL 及 ServiceAddressURL 次要的差异就是,ServiceConfigURL 是程序读取配置文件时生成的 URL。而 ServiceAddressURL 则是注册核心推送一些信息(如 providers)过去时生成的 URL。
在这里咱们顺便提一下为什么会有 DubboServiceAddressURL 这个子类,依照目前的构造来看,ServiceAddressURL 只有这一个子类,所以齐全能够将他们两个的属性全都放到 ServiceAddressURL 中,那么为什么还要有这个子类呢?其实是 Dubbo 3.0 为了兼容 HSF 框架所设计的,形象出了一个 ServiceAddressURL,而 HSF 框架则能够继承这个类,应用 HSFServiceAddressURL,当然,这个类目前没有体现进去,所以此处咱们简略一提,不过多解说。
那么,咱们接下来就讨论一下 Dubbo 3.0 为什么要改为此种数据结构,并且该构造和地址推送模型的优化有何关联性吧!

地址推送模型的优化

URL 构造上的优化

咱们在上大节中的类图里看到尽管原来的属性都被移到了 URLAddress 和 URLParam 里,然而 URL 的子类仍然多了几个属性,这几个属性天然也是为了优化而新增的,那么这里就讲讲这几个属性的作用。
ServiceConfigURL:这个子类中新增了 attribute 这个属性,这个属性次要是针对 URLParam 的 params 做了冗余,仅仅只是将 value 的类型从 String 改为了 Object,缩小了代码中每次获取 parameters 的格局转换耗费。
ServiceAddressURL:这个子类及其对应的其余子类中则新增了 overrideURL 和 consumerURL 属性。其中 consumerURL 是针对 consumer 端的配置信息,overrideURL 则是在 Dubbo Admin 上进行动静配置时写入的值,当咱们调用 URL 的 getParameter() 办法时,优先级为 overrideURL > consumerURL > urlParam。在 Dubbo 2.7 时,动静配置属性会替换 URL 中的属性,及当你有大量 URL 时耗费也是不可漠视的,而此处的 overrideURL 则防止了这种耗费,因为所有 URL 都会独特应用同一个对象。

多级缓存

缓存是 Dubbo 3.0 在 URL 上做的优化的重点,同时这部分也是间接针对地址推送模型所做的优化,那么接下来咱们就开始来介绍一下多级缓存的具体实现。
首先,多级缓存次要体现在 CacheableFailbackRegistry 这个类之中,它间接继承于 FailbackRegistry,以 Zookeeper 为例,咱们看看 Dubbo 2.7 和 Dubbo 3.0 继承构造的区别。

能够看到在 CacheableFailbackRegistry 缓存中,咱们新增了 3 个缓存属性 stringAddressstringParamstringUrls。咱们通过下图来形容这 3 个缓存的具体应用场景。

在该计划下,咱们应用了 3 个纬度的缓存数据(URL 字符串缓存、URL 地址缓存、URL 参数缓存),这样一来,在大部分状况下都能无效利用到缓存中的数据,缩小了 Zookeeper 反复告诉的耗费。

提早告诉

除了下面提到的优化之外,其实另外还有两个小小的优化。
第一个是解析 URL 时能够间接应用编码后的 URL 字符串字节进行解析,而在 Dubbo 2.7 中,所有编码后的 URL 字符串都须要通过解码才能够失常解析为 URL 对象。该形式也间接缩小了 URL 解码过程的开销。
第二个则是 URL 变更后的告诉机制减少了提早,下图以 Zookeeper 为例解说了实现细节。

在该计划中,当 Consumer 接管 Zookeeper 的变更告诉后会被动休眠一段时间,而这段时间内的变更在休眠完结后只会保留最初一次变更,Consumer 便会应用最初一次变更来进行监听实例的更新,以此办法来缩小大量 URL 的创立开销。

字符串重用

在旧版本实现中,不同的 URL 中属性雷同的字符串会存储在堆内不同的地址中,如 protocol、path 等,当有大量 provider 的状况下,Consumer 端的堆内会存在大量的反复字符串,导致内存利用率低下,所以此处提供了另一个优化形式,即字符串重用。
而它的实现形式也十分的简略,让咱们来看看对应的代码片段。

public class URLItemCache {private static final Map<String, String> PATH_CACHE = new LRUCache<>(10000);
    private static final Map<String, String> PROTOCOL_CACHE = new ConcurrentHashMap<>();

    // 省略无关代码片段

    public static String checkProtocol(String _protocol) {if (_protocol == null) {return _protocol;}
        String cachedProtocol = PROTOCOL_CACHE.putIfAbsent(_protocol, _protocol);
        if (cachedProtocol != null) {return cachedProtocol;}
        return _protocol;
    }

    public static String checkPath(String _path) {if (_path == null) {return _path;}
        String cachedPath = PATH_CACHE.putIfAbsent(_path, _path);
        if (cachedPath != null) {return cachedPath;}
        return _path;
    }
}

由如上代码片段能够得悉,字符串重用即为简略地应用了 Map 来存储对应的缓存值,当你应用了雷同的字符串时,便会从 Map 中获取早已存在的对象返回给调用方,由此便能够缩小堆内存中反复的字符串数以达到优化的成果。

优化后果

这里优化后果我援用了《Dubbo 3.0 前瞻:服务发现反对百万集群,带来可伸缩微服务架构》这篇文章中的两副图来阐明,下图模仿了在 220 万 个 Provider 接口的状况下,接口数据一直变更导致的 Consumer 端的耗费,咱们看到整个 Consumer 端简直被 Full GC 占满了,重大影响了性能。

那么咱们再来看看 Dubbo 3.0 中对 URL 进行优化后同一个环境下的压测后果,如下图所示。

咱们显著能够看到 Full GC 的频率缩小到了只有 3 次,大大晋升了性能。当然,该文章中还有其余方面的比照,此处便不一一援用了,感兴趣的读者能够自行去浏览该文章。

欢送在 https://github.com/apache/dubbo 给 Dubbo Star。
搜寻关注官网微信公众号:Apache Dubbo,理解更多业界最新动静,把握大厂面试必备 Dubbo 技能

退出移动版