关于java:我去这是出BUG了呀

27次阅读

共计 5851 个字符,预计需要花费 15 分钟才能阅读完成。

你好呀,我是 why。

前两天在 Git 上晃荡的时候又人不知; 鬼不觉逛到 Dubbo 那里去了。

看了一下最近一个月的数据,社区活跃度还是很高的:

而后看了一下最新的 issue,大家发问都很踊跃。

其中看到了这样的一个 issue,发现有点意思:

https://github.com/apache/dubbo/issues/8055

于是写下这篇文章给你分享一下这个 BUG 和 BUG 背地的故事。

释怀,就算你齐全不懂 Dubbo,也不影响你理解这个 BUG。

先说一下,下文中提到的 Dubbo 代码,没有特地阐明的中央,都是我从 git 上拉下来的 Master 分支上的代码

啥 BUG 啊?

先给你形容一个这个 BUG 是啥样的。

其实就是这个 issue 的作者写进去的:Dubbo 框架外面的 Filter 排序过程有问题,即便依照框架要求写好规定后,最终生成的 Filter 链并不是咱们想要的。

那么齐全不懂 Dubbo 的敌人可能就遇到了第一个问题:啥是 Filter 呢?

其实就是一个过滤器而已,和 web 服务外面过滤器在概念上没啥两样。而 Dubbo 有十分多的 Filter,这些 Filter 独特组成了一个 Filter 调用链。

援用官网上的一个调用链路图,在 Filter 的中央我框起来了:

能够看到 Filter 是 Dubbo 框架的一个十分外围的组成部分,很多很多的性能都是从 Filter 扩大进去的。

你要是还不明确也没关系,你只有晓得有这样的一个 Filter 调用链就行了,链上的 Filter 各司其职,各干各的事儿。

好的,那么当初需要来了:

我当初要求链上的 Filter 的执行程序是我能管制的,即我定义 Filter 的时候你得给我留个中央设置它的优先级。

听起来是很简略的一个需要,对吧?

我间接给你留个口子,让你输出 order 参数,不输出给个默认值,而后组装 Filter 链的时候依据 Order 排个序。

不是我吹牛,十分钟就能写完,两头还带着三分钟的摸鱼。

然而,就这么个需要出 BUG 了。

具体啥景象呢?

我这里把我的项目拉下来,基于官网的测试用例,改巴改巴,给你演示一下这个 BUG 的体现是啥。

在 Dubbo 外面有这样的一个注解:

org.apache.dubbo.common.extension.Activate

这里的 Order 就是做排序用的。简略演示一下,你看我当初有 5 个 Filter:

排序规定是 Order 越小的越先执行,那么这个 Filter 链的执行程序应该是这样的:

Filter4 -> Filter3 -> Filter2 -> Filter1 -> Filter5

搞个测试案例,咱们验证一下:

合乎预期,没有任何故障。

另外阐明一下,官网的对于 Filter 的测试用例在这里,你有趣味,源码拉下来就可以看:

org.apache.dubbo.common.extension.support.ActivateComparatorTest#testActivateComparator

不论是官网的案例,还是我本人写的案例,其中最要害的排序功能是这一行代码实现的:

Collections.sort(filters, ActivateComparator.COMPARATOR)

而这一行代码外面最要害的就是 ActivateComparator.COMPARATOR 这个货色。

这个货色就是 BUG 之源,不慌,等下再说。

那么为什么说它有 BUG 呢?

后面演示了失常的状况下,是合乎预期的。

然而你看 Activate 注解,外面还有这样的两个货色:

before、after,含意是指定 Filter A 在 Filter B 之前或者之后执行。

然而被打上了 @Deprecated 注解,字段阐明上也备注了:

Deprecated since 2.7.0

2.7.0 之后被破除。

那么就有点意思了,为啥被破除?

来,看个例子,还是刚刚的那个测试用例。

我就略微的这么一改:

@Activate(before = "_2")
public class Filter5 implements Filter0{}

改变点就是在 Filter5 上配置了:

@Activate(before = “_2”)

含意就是 Filter5 在“_2”之前执行。

“_2”是啥?

就是 Filter2 的一个映射而已:

那么问题就来了,作为一个失常的程序猿,自信的对 Filter5 进行了这个改变之后,他心田的想法肯定是想要把这样的 Filter 链:

Filter4 -> Filter3 -> Filter2 -> Filter1 -> Filter5

批改为这样:(Filter5 在 Filter2 之前执行):

Filter4 -> Filter3 -> Filter5 -> Filter2 -> Filter1

那么理论状况是怎么的呢?

来跑一把:

咋回事?这不是我预期的执行链啊?

是的,这就是 BUG 的体现。

咋回事啊?

到底是咋回事呢?

且听我给你剖析一波。

上一大节我说了,问题出在排序算法上。

org.apache.dubbo.common.extension.support.ActivateComparator

来,一起看一下:

首先标号为 ① 的中央就是把 before、after、order 封装了一下,而后提供了几个比拟的办法。你晓得 ActivateInfo 这个实体外面有这些货色就行了,前面的代码会用到。

而后说说标号为 ② 的中央。

这个中央你别看挺长的,然而其实逻辑特地简略,以后比照的两个 filter 中的任何一个配置了 before、after 就会进入到标号为 ② 的局部的逻辑。

而后这外面的一坨逻辑是的这样的:

具体逻辑不细说了,等会给你来个直观的演示。

最初标号为 ③ 这个中央,有点意思,略微多说几句。

能走到标号为 ③ 的中央,阐明以后比照的两个 filter 都没有配置 after、before 这两个属性。

间接比照 Order 就行了。

这个中央对 Order 相等的状况还做了一个非凡解决:

o1.getSimpleName().compareTo(o2.getSimpleName()) > 0 ? 1 : -1

如果 Order 相等,再比拟类名。

这样做的起因也是保障排序的稳定性。

举个例子,比方这两 Filter,都没有指定 Order:

那如果咱们去掉这个判断:

代码就变成了这样:

if (a1.order > a2.order) {return 1} else {return -1}

简化一下就是这样:

return a1.order > a2.order ? 1:-1

那么这一块的代码,整体就会变成这样:

这样认真一看:咦,如同还能再优化一下。78 行和 80 行是一样的,所以能够去掉 78 行。

好的,通过这样的一番革新。

祝贺你,取得了一个老版本的代码:

右边是之前版本的代码,左边是当初 Master 分支的代码:

为什么会发生变化,必然是有起因的。

看一眼提交记录:

这次提交指向了编号为 7778 的提交:

https://github.com/apache/dubbo/pull/7778

而这次提交指向了编号为 7757 的 issue:

https://github.com/apache/dubbo/issues/7757

而这个 issue 在后面提到的编号为 8055 的 issue 里也提到了:

这个 issue 次要就是两张图。

第一张图是这样的:

在没有任何自定义 Filter,仅有官网原有的 Filter 的状况下,构建进去的 Filter 链,ExecuteLimitFilter 在 MonitorFilter 之前。

第二张图是这样的:

在退出了一系列自定义的 Filter(没有指定 Order)之后,ExecuteLimitFilter 就排在了 MonitorFilter 之后了。

至于这两个 Filter 排前排后的影响是什么,和文本关系不大,就不扩大了,你有趣味的能够去看看对应的链接。

总之,只有这样的判断逻辑是不稳当的:

return a1.order > a2.order ? 1:-1

来个例子演示一下:

右边是测试用例,左边是排序规定,上面是输入后果。

从输入后果能够看到,最终的 Filter 链取决于 list 的增加程序。

这也就是 7757 这个 issues 说的:

list 的遍历程序会影响到排序的程序。

因而,才会有了这样的一次提交:

好,当初咱们把排序程序改回来,同样的测试用例再跑一次,就稳固了:

眼睛尖的敌人可能还发现了一个问题。

这个中央还有一次提交:

  • 第一种判断:return o1.getSimpleName().compareTo(o2.getSimpleName())
  • 第二种判断:return o1.getSimpleName().compareTo(o2.getSimpleName()) > 0 ? 1 : -1;

你说这是在干啥?

第一种判断还忽略了这样的一种状况,包名不同然而类名雷同的状况:

  • com.why.a.whyFilter
  • com.why.b.whyFilter

这个时候 o1.getSimpleName().compareTo(o2.getSimpleName()) 返回的是 0。

返回 0 会产生啥?

间接吞掉一个 Filter 你信不信?

比方你的汇合是 HashSet,或者是 TreeMap。

这就巧了,Dubbo 用的就是 TreeMap。

来个测试用例演示一下。

如果采纳第一种判断,最初 TreeMap 外面只有一个 Filter 了:

如果采纳第二种判断,最初 TreeMap 外面会有两个 Filter:

细节,魔鬼都在细节外面。

哎呀,真的是防不胜防啊。

好了,比拟器我就说完了,然而你发现没有,我到当初都还没给你说排序过程不稳固这个 BUG 到底是啥,只是给你引申了一个其余的 BUG 进去。

莫慌,这不是我还没想好怎么给你形容嘛。

这个过程其实比较复杂,波及到 Timsort 排序办法,就这办法就得另起一篇文章能力说分明。

所以,我换了一个思路,次要给你看比拟的过程,至于这个过程背地的起因。

就是 Timsort 在搞鬼,欢送你本人去摸索一番。

那过程是啥呢?

我在比拟办法的入口处加上这样的输入语句:

五个 Filter 是这样的:

测试用例是这样的:

@Test
public void whyTest(){List<Class> filters = new ArrayList<>();
    filters.add(Filter4.class);
    filters.add(Filter3.class);
    filters.add(Filter2.class);
    filters.add(Filter1.class);
    filters.add(Filter5.class);
    Collections.sort(filters, ActivateComparator.COMPARATOR);
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < filters.size(); i++) {builder.append(filters.get(i).getSimpleName()).append("->");
    }
    System.out.println(builder.toString());
}

输入的日志是这样的:

发现问题了没?

首先我很神思的管制了一下 list 的增加程序:

这样前三次比拟就能构建这样的 Filter 链:

Filter4->Filter3->Filter2->Filter1->

而后,Filter5 进来后先和 Filter1 比,发现其 Order 为 0 比 Filter1 的 -1 大,于是比拟完结,失去这样的 Filter 链:

Filter4->Filter3->Filter2->Filter1->Filter5->

整个过程中,Filter5 与 Filter2 齐全没有产生任何比拟的操作,也就更不波及到 Filter5 外面的 before 标签了:

然而当我把 list 的增加程序批改一下:

咦,就正确了,你就说神不神奇?

神奇吧?

为啥呢?

去看看 Timsort 的原理吧。

追根溯源

其实写到这里的我产生了一个疑难:

是谁,什么时候,引入了 after/before 机制?

因为这个机制我集体感觉出发点是挺好的,多一个配置的中央,把选择权留给用户。

然而在理论的应用中,却容易呈现比拟凌乱的状况。

于是我看了一下提交记录:

这个注解最早是梁飞(就是 Dubbo 我的项目要的开创者之一)写进去的,而设计之初没有 before 和 after,然而有一个 match 和 mismatch。

而后在写出这个注解一天之后的凌晨 1 点 54 分,提交了一个办法级别的匹配:

这三个办法应用起来甚至比 before/after 更加简单了。

于是一觉睡醒之后的 12:34 分,梁飞又删除了这三个配置:

两个月之后的 2012 年 5 月 8 日,退出了 after 和 before 配置:

而后就始终留在 Dubbo 源码外面,直到 6 年后的 2018 年 8 月 7 日,打上了不倡议应用的注解:

并提到了这个 issue:

https://github.com/apache/dubbo/issues/2180

外面说:Dubbo 源码中没有应用 after 和 before,且排序是存在问题的。

于是这两个办法,在 2.7.0 版本之后,被标注为不倡议应用,宣告了该办法的死亡。

我不晓得 2012 年,梁飞为什么引入了这两个办法,我也曾想从他的代码提交记录上找到点蛛丝马迹,惋惜没有。

然而,有了另外的一个想法:

当年梁飞引入这两个办法后,他写的比拟器,是否思考到了这样的状况呢?

于是我马上又看了比拟器的代码提交记录:

org.apache.dubbo.common.extension.support.ActivateComparator

并且把他的代码拷贝了进去,用同样的测试用例跑了一下:

很遗憾,也有一样的问题。

或者,当年就不应该引入这两个办法。

大道至简,学 Spring 的 Order,就只有一个 Order:

而后我又忽然想了另外一个框架:SofaRPC。

SofaRPC 和 Dubbo 和 HSF 之间有着千头万绪的爱恨情仇,于是我去瞅了一眼 SofaRPC 对应的中央:

com.alipay.sofa.rpc.ext.Extension

用于排序的,也就只是保留了 order。

这样比拟器的代码就很简略了:

com.alipay.sofa.rpc.common.struct.OrderedComparator

另外,我顺便比照了一下梁飞最早写的比拟器和当初最新的比拟器的代码,性能齐全一样,然而代码却差别较大:

不得不说,通过几次重构之后,最新的比拟器的可读性高了很多。

我追踪了一下这个类的提交记录,也就看着这个类的一步步演变,其实算是一个比拟好的代码重构的例子。

有趣味的本人去翻一翻。

好了,就到这了,打完出工。

正文完
 0