【dubbo-go 源码解析】流量管理:如何优化路由策略

547 阅读5分钟

在前面的文章中,介绍过 dubbo-go 如何实现路由策略功能。路由策略功能是 dubbo-go 中的重要功能之一,可在控制面对服务的路由进行精细控制。同时,在实现之初,路由策略配置与其他配置是串行加载,在获得服务端的实例列表后,与处理好的路由规则进行匹配,从而过滤出可使用的候选实例。因为处理其中匹配逻辑较为复杂,所以导致启动服务的时间增加。网络请求通过健康实例优先路由规则与路由实例匹配时,通过双循环匹配(可能大家会有个小疑问,为什么是双循环呢?后面会慢慢解释)列表,导致查找时间增加。那针对以上问题,是否会有解决方法呢?答案是肯定的,所以本文中会介绍在路由策略的基础上,如何进行路由策略优化的两种手段展开分析,其分别是:异步解析路由规则、路由规则快速匹配。

图 1 将本次修改点具象化,在改造前,路由配置通过读取、解析、过滤、匹配四大步骤,将用户配置的路由规则,转换成可匹配请求的处理逻辑,最终得出请求可请求的实例。而本次改造最主要的目的是:网络请求通过健康实例优先路由规则与路由实例匹配时,需要将两个列表相交,分别是【健康的实例列表】与【符合路由规则的实例列表】,从而得出可访问的实例列表。如果使用双循环,算法复杂度随着两个列表的数据量增加而增加。所以本次改造时,选择了 压缩位图 作为底层存储,让匹配更高效。

在改造时,发现主要在存储方式变更后,会影响到 过滤与匹配 两大部分。在启动过滤时,改造成压缩位图,需要执行比较多构造逻辑。如果启动时同步执行,需时较长。所以在启动时改造成异步执行。而在匹配时,需要将入参的实例列表改造成压缩位图,以便后续进行匹配。

图片

异步解析路由规则

接下来,先分析如何将串行解析路由规则改造成异步解析,从而降低启动服务的时间。

在使用路由策略时,为了提高程序匹配路由规则的效率,路由规则的读取和解析皆在应用启动时完成。而随着需要读取的路由配置增加,启动时间则会越来越长。将解析路由规则中的过滤实例列表操作修改成了异步,从而减少启动需要处理的信息,达到降低启动时间的效果。到这里大家可能会有个小疑问:究竟改造优化了什么呢?

在构造路由链的构造方法中,将同步构建过滤器并过滤实例列表,修改成异步构建。在构造方法中,会调用一个异步定时器与监听修改方法:chain.loop。

// 构造路由链func NewRouterChain(url *common.URL) (*RouterChain, error) {    ......    go chain.Loop() <---- 异步定时器与监听修改方法    return chain, nil}

深入 chain.loop 内部,能看到其中包含定时与通知刷新缓存,两步的操作。

  • 定时刷新:用于初始化缓存。

  • 通知刷新:用于接收更新通知,更新路由的缓存,其结构是 router -> invokers。通过配置中心动态增加路由规则、实例列表更新后,都会触发该动作。

func (c *RouterChain) Loop() {    ticker := time.NewTicker(timeInterval)    for {        select {        case <-ticker.C:            // 定时刷新缓存            if protocol.GetAndRefreshState() {                c.buildCache()            }        case <-c.notify:            // 收到通知之后,构建缓存            c.buildCache()        }    }}

经过优化之后,有效降低服务的启动时间。

存储方式

本次的修改是将存储的数组,改成位图,而使用的位图为:RoaringBitmap。

主要的思路是:将32位无符号整数按照高 16 位分桶,最多可能有 216 个桶,每一个桶中有一个 container 用于存储数据。存储数据时,按照数据的高 16 位(k % 2^16)找到需要存储的桶,如果不存在则新建一个新的桶,再将低 16 位(k mod 2^16)放入桶的 container 中。换句话说:一个位图就是很多桶的集合。

图片

图 2

图 2 中,分别代表 3 个数据集:

  • 第一个桶,存储有前1000个62的倍数。

  • 第二个桶,存储有[216, 216+100)区间的数。

  • 第三个桶,存储有[2×216, 3×216)区间内的所有偶数。

如有兴趣,可访问:roaringbitmap.org/ 获取更多信息。

匹配实例列表

在路由的具体实现中,包含条件路由(condition)、连接健康路由(conncheck)、实例健康优先路由(healthcheck)、标签路由(tag) 四个主要实现。其中本次优化只适用于连接健康路由(conncheck)、实例健康优先路由(healthcheck)、标签路由(tag)三个实现,而不适用于条件路由(condition)。

以条件路由(condition)为例,需要将路由配置的内容与元数据(包含 Provider 与 Consumer)的内容相匹配(如图 2 所示),且路由配置里的内容为表达式形式,所以并不能进行位图匹配。

图片

图 2

以实例健康优先路由(healthcheck)为例,通过判断【健康的实例列表】与【符合路由规则的实例列表】的交点,从而快速的得出可访问的实例列表(如图 3 所示)。以便降低双循环带来的算法复杂度,提升程序的性能。在【健康的实例列表】或者【符合路由规则的实例列表】数据量增长时,性能不会带来明显的下降。

图片

图 3

连接健康路由(conncheck)与标签路由(tag)的实现,在实现思想层面上是一致的,我就不一一累赘了。

总结

相信你已经知道了在优化路由策略时, Dubbo-go 是通过哪些方式进行优化。而关于路由策略,还有很多深层的知识值得我们一一探索和学习,比如:

  • Dubbo-go 条件路由是如何实现?

  • Dubbo-go 标签路由是如何实现,如何通过池化优化其逻辑?

这一系列问题的答案,大家可以从代码中,慢慢寻找答案。

欢迎加入社区

在公众号【部长技术之路】后台回复关键字【dubbogo】加入社区。