一个排序引发的BUG (下)

简介: 一个排序引发的BUG (下)

来个例子演示一下:


image.png


左边是测试用例,右边是排序规则,下面是输出结果。

从输出结果可以看到,最终的 Filter 链取决于 list 的添加顺序。

这也就是 7757 这个 issues 说的:

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


image.png


image.png

好,现在我们把排序顺序改回来,同样的测试用例再跑一次,就稳定了:

image.png

眼睛尖的朋友可能还发现了一个问题。

这个地方还有一次提交:

image.png

  • 第一种判断: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 了:

image.png

如果采用第二种判断,最后 TreeMap 里面会有两个 Filter :

image.png

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

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

好了,比较器我就说完了,但是你发现没有,我到现在都还没给你说排序过程不稳定这个 BUG 到底是啥,只是给你引申了一个其他的 BUG 出来。

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

这个过程其实比较复杂,涉及到 Timsort 排序方法,就这方法就得另起一篇文章才能说清楚。

所以,我换了一个思路,主要给你看比较的过程,至于这个过程背后的原因。

就是 Timsort 在搞鬼,欢迎你自己去探索一番。

那过程是啥呢?

我在比较方法的入口处加上这样的输出语句:

image.png

image.png

测试用例是这样的:

@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());
}

输出的日志是这样的:

image.png

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

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

然后,Filter5 进来后先和 Filter1 比,发现其 Order 为 0 比 Filter1 的 -1 大,于是比较结束,得到这样的Filter 链:

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

整个过程中,Filter5 与 Filter2 完全没有发生任何比较的操作,也就更不涉及到 Filter5 里面的 before 标签了:

image.png

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

神奇吧?

为啥呢?

去看看 Timsort 的原理吧。


image.png


追根溯源


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

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

因为这个机制我个人觉得出发点是挺好的,多一个配置的地方,把选择权留给用户。

但是在实际的使用中,却容易出现比较混乱的情况。

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

image.png

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

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

image.png

这三个方法使用起来甚至比 before/after 更加复杂了。

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

image.png

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

image.png

然后就一直留在 Dubbo 源码里面,直到 6 年后的 2018 年 8 月 7 日,打上了不建议使用的注解:

image.png

image.png

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

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

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

但是,有了另外的一个想法:

当年梁飞引入这两个方法后,他写的比较器,是否考虑到了这样的情况呢?

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

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

image.png

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


image.png

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

或许,当年就不应该引入这两个方法。

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


image.png

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

SofaRPC 和 Dubbo 和 HSF 之间有着千丝万缕的爱恨情仇,于是我去瞅了一眼 SofaRPC 对应的地方:

com.alipay.sofa.rpc.ext.Extension


image.png


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

这样比较器的代码就很简单了:

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


image.png

另外,我顺便对比了一下梁飞最早写的比较器和现在最新的比较器的代码,功能完全一样,但是代码却差异较大:

image.png

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

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

有兴趣的自己去翻一翻。

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

目录
相关文章
|
7天前
|
人工智能 API 内存技术
刚刚 DeepSeek V4.1 Flash 开启内测,1 分钟教你用上!
刚刚 DeepSeek 内测群发布了 DeepSeek V4.1 Flash 中间版本内测的消息,这次的模型采用了新的结构,原生支持多模态、能力更强、速度更快、且成本更低。
1786 10
|
11天前
|
人工智能 运维 BI
阿里云千问办公QwenWork深度解析:基于Qwen3.8,六大核心能力重构企业全自动化工作流与计费选型指南
传统AI办公工具大多停留在对话问答、文档摘要、简单文案生成层面,只能完成单点碎片化任务,无法自主拆解复杂业务流程,很难串联多工具、多文档、外部业务系统完成端到端完整工作交付。很多企业在落地AI办公的时候,需要组合多款不同工具,来回切换界面,手动复制粘贴中间结果,智能化改造落地门槛居高不下。千问办公QwenWork是整合多款智能体产品能力打造的一体化企业办公智能体平台,底层基座依托Qwen3.8大模型,打通桌面端Agent、云端Agent、企业协同Agent三种运行形态,不再局限简单问答,接收业务目标之后自主拆解任务步骤,调用各类工具,处理文档、表格、浏览器自动化、数据查询,直接输出可交付的办公
1643 3
|
12天前
|
网络协议 Linux iOS开发
【2026实测】Wireshark下载+安装+汉化+使用教程(图文版,巨详细)
Wireshark 是一款免费开源的网络协议分析工具,可实时捕获、解析并可视化数据包,助你诊断网络故障、分析通信协议(如HTTP、DNS、TCP等)。支持Windows/macOS/Linux,含中文界面,新手入门便捷。(239字)
|
8天前
|
SQL 人工智能 前端开发
QoderWake 1.0 正式发布:从桌面里的 Agent,到工作现场的数字员工
QoderWake v1.0正式发布:企业级数字员工团队平台。支持“一句话建岗”,预置10类特训岗位;Waker常驻钉钉/飞书群,@即响应、自动协作、跨任务记忆;具备定时/事件/API多触发方式与统一任务看板;已沉淀27.6万条记忆、12.3万项技能,助力组织实现人机协同增效。
779 2
|
6天前
|
缓存 测试技术 API
DeepSeek V4.1 Flash 内测接入:改个模型名即可调用(附代码)
DeepSeek V4.1 Flash 内测不用申请,base_url 不变、改个模型名就能调,9/10 到期。本文讲清接入、计费限流与多模态注意点。
803 0
DeepSeek V4.1 Flash 内测接入:改个模型名即可调用(附代码)
|
20天前
|
人工智能 自然语言处理 安全
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
本文聚焦阿里云2026年推出的三款自研AI办公产品,清晰拆解千问办公、Qoder Teams、Qoder CN的差异化定位与能力边界:千问办公主打职场全场景提效,支持自然语言指令一键完成PPT生成、数据分析等高频办公任务;Qoder Teams面向程序员团队,深度整合AI代码生成、团队协同与企业知识库能力;Qoder CN则专为金融、政务等强合规场景打造,实现数据不出境与VPC私有化部署。文章同步给出分场景选型指南与最新活动定价,帮助不同类型的企业按需组合产品,实现业务岗、研发岗与强合规场景的AI能力全覆盖。
3969 5
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
|
11天前
|
人工智能 自然语言处理 安全
阿里云AI数智鉴密:AI 生成内容如何拿到一张"防篡改的身份证"
隐形水印 + C2PA签名:让AI生成内容“持证上岗”。
1155 0
|
13天前
|
缓存 数据可视化 开发工具
DeepSeek Harness 怎么更新?dsh 更新完整指南:更新本体(npx、npm、源码)与更新插件两种方式
DeepSeek Harness 的更新分两层:本体更新(npx 自动最新、npm update -g、源码 git pull)与插件更新(插件市场点更新、命令行覆盖安装)。本文按「准备 → 更新本体 → 更新插件 → 更新后检查」四步走,覆盖新手常见疑问。
1519 1
DeepSeek Harness 怎么更新?dsh 更新完整指南:更新本体(npx、npm、源码)与更新插件两种方式
|
6天前
|
人工智能
千问办公官网入口:阿里AI办公QwenWork产品页和免费网页端链接
千问办公官网含两大入口:一是网页端(qwenwork.cn),即开即用,支持浏览器直接访问;二是阿里云产品页 https://t.aliyun.com/U/JNKJuO 提供免费/付费版详情、功能介绍及使用指南。

热门文章

最新文章