阅读开源项目源码的实用技巧(上)

电子说

1.3w人已加入

描述

本文分享一下在使用或者学习开源项目源码的过程中的一些经验技巧。

因为我最近在研究 Apache Pulsar 这款消息队列,所以就以这个项目为例, 不过本文介绍的都是通用的技巧,完全可以用在其他大型开源项目中

下面就来具体介绍一些技巧,主要分两部分:

第一部分是文档篇,即能够哪里能够获取有效的信息解决问题;

第二部分是实操篇,即如何高效打断点或借助工具理解源码。

一、文档检索技巧

想学习了解一个开源项目,文档可以帮我们解决大部分问题。当然我这里所说的不单单指官网文档,还包括 issue、PR、源码中的注释和单元测试,这些地方都可以获得大量有用的信息,所以我把它们统称为文档,下面我们从最简单的开始。

1、官网文档,着重 quickstart 和 concept 部分

官网文档无疑是最权威的资料来源,不过官网文档的问题是内容太多太全面,适合遇到问题或需求时当做功能手册去查阅。

所以官网的内容需要选择性地学习,我建议优先着重两个部分:

一是 quickstart 部分,也就是教你如何快速部署一个 demo 服务;二是 concept 部分,也就是名词解释、核心功能介绍等内容。

快速部署 demo 服务不用说了,是我们学习新技术的第一步,一般会被放在文档的第一章;而功能/名词的解释是我们接下来顺畅地学习进阶资料或参与社区讨论的重要铺垫。

对 Pulsar 这样一个消息队列来说,收发消息显然是核心功能,所以官网 Concepts and Architecture 部分中的 Messaging 章节显然是很重要的,详细介绍了 Pulsar 中诸如订阅模式、死信队列等关键功能:

源码

我在前文 Apache Pulsar 架构设计 介绍到 Pulsar 采用存算分离的架构,存储层依靠 Apache Bookkeeper。所以如果你的目标是学习 Pulsar,那么 Bookkeeper 的官网文档也是需要阅读的,因为 Pulsar 中的很多功能都会和 Bookkeeper 交互。

可以在本地启一个 Bookkeeper 集群用 client 玩一玩,阅读了解一下 Bookkeeper 中的专业术语,有助于理解 Pulsar 中的一些设计。

2、看完文档看单元测试用例,辅助我们准确理解每个功能的预期行为

一般成熟开源项目的测试用例比较完备,会覆盖所有关键功能的预期行为,所以单测用例其实也是很好的学习资料,和文档搭配食用效果最佳。

比方说,有时候文档用文字描述某个功能可能会比较繁琐,让人看的云里雾里,又或者文档中并没有介绍一些技术设计的细节。

遇到这种情况,我们大概率可以在单测文件中找到对应的功能测试代码,根据测试代码很容易反推功能,正所谓「talk is cheap, show me the code」。

举个例子,有一次我看到 consumer 打出一条关于epoch的日志,我在分布式选主的场景倒是听说过这个名词,不过显然消费消息和分布式选主没什么关系,所以这个epoch到底是干什么的?

文档里没找到答案,这应该是一个具体实现中的术语,所以我就在源码中搜索包含testEpochepochTest这两个关键词的函数名,发现了几个测试用例:

源码

PS:测试函数名的 test 关键字可能在开头也可能在最后,所以需要都搜一下。

浏览了一下这几个测试用例的内容就大致理解了,原来这个epoch是消息重投递功能(redelivery)中的一个术语,主要用于防止重复消费消息。

3、善用 GitHub,从项目的 issue/PR/wiki 列表获取有效信息

首先,issue 列表不用多说了,如果你在使用软件的过程中遇到了问题,首先考虑的就是去 issue 列表搜索。

虽然有时候搜出来的并不是直接的答案,但多换关键词搜几次,大概率就能找到一些思路解决问题了。

另外, PR 信息可以帮助我们了解某些代码片段的上下文背景

举个例子,比如你阅读某段代码时有疑惑,不明白这个代码的目的是什么,那么可以在 IDEA 中的代码左侧单击右键,打开「Annotate with Git Blame」就可看到这段代码是谁在什么时候添加上去的:

源码

然后把鼠标悬停在作者昵称上两秒,就会弹出这个代码被合进 master 分支时的 PR 标题和链接:

源码

18260就是这个 PR 的编号,点击即可跳转到对应的 PR 页面:

源码

可以看到这个 PR 是用来修复18241号 issue 的,在18241号 issue 中详细描述了 bug 信息及复现方法:

源码

有了这些上下文信息,就可以避免我们阅读源码时的障碍了。

最后, wiki 页面可以帮我们了解一些重要的功能设计或改动

拿 Pulsar 来说,如果需要做比较重要的改动,需要提出一个 PIP 提案(Pulsar Improvement Proposal),也就是一个专门讲解背景信息、设计思路的文档。

而这些 PIP 文档就收集在 wiki 页面:

源码

所以在了解某个功能模块的设计思路时,可以先去 wiki 页面看看是否有相关的 PIP 可供参考。

比如 Pulsar 的事务实现,就有一个专门的 PIP 详细介绍了设计思路,结合 PIP 的思路指引去学习源码就会容易很多:

源码

我个人觉得,好的 PIP 结合源码,带我们把一个功能从讨论设计做到落地实现,这就是很好的教科书呀,多花精力去研究,肯定会有所收获的。

以上就是最常用的有效信息的获取途径,如果你在学习使用开源项目时遇到问题,那么可以尝试上述的方法去寻找答案。

当然,熟练掌握进行信息检索的工具进行高效检索也是重要的技能,比如说 IDEA 的各种搜索、GitHub issue/PR 的搜索语法,这些技巧网上可以很容易搜到,我就不赘述了。

二、源码阅读技巧

想真正了解一个项目,看源码肯定是逃不掉的一环。阅读源码的好处不用多说了,但阅读源码肯定会花费大量时间,而且这个过程不会很轻松。

你想嘛,成熟的开源项目经过多年的发展,功能不断演进,很多人往里面写过代码,恐怕没人能保证自己完全了解系统的每个细节。我们阅读源码,就好比探索一座庞大的城市,很容易迷失在某个犄角旮旯。

对于这个问题,我可以分享一些小技巧。

技巧一、不建议看「死代码」,建议在调试实际问题的过程中理解代码

换句话说,不要拿着代码硬读,最好是通过动态调试来研究每个功能中做了什么。

拿 Pulsar 举例,我们可以在命令行启动 standalone 模式的 Pulsar broker:

$ bin/pulsar standalone

然后用 Java client 创建一个 producer 发送一条消息:

PulsarClient client = PulsarClient.builder()
        .serviceUrl("pulsar://localhost:6650")
        .build();

Producer<byte[]> producer = client.newProducer()
        .topic("testTopic")
        .create();

MessageId messageId1 = producer.send(("hello1").getBytes());

client.close();

我们就可以调试这个简单的场景,看看 producer 是如何创建的,消息是如何发送并存储在 Pulsar 中的。

但如果想跟踪调试这段代码,会遇到一些问题:

第一个问题是,我们自己的项目是通过 Maven 引入 client 包的,如果进入这些包看到的是反编译的 class 文件,无法直接看到源码。就算 IDEA 可以直接帮我们下载源码,但如果我们在从事 client 的开发,需要 master 分支的最新版代码,这和上传到 Maven 的源码还是不一样。

这个问题比较容易解决,我们直接从 GitHub 下载源码,在 client 包里面创建一个 test 文件写逻辑,这样就可以调试最新的 client 代码了。

第二个问题比较棘手,我们想调通整个 Pulsar 发送消息的流程,那么这里面肯定要涉及 Pulsar client 和 Pulsar broker 的交互,而 broker 是通过命令行启动的,我如何调试 broker 里面的代码呢?

我们可以观察一下,bin/pulsar这个文件其实就是个 shell 脚本,可以找到这样一段代码:

elif [ $COMMAND == "standalone" ]; then
    PULSAR_LOG_FILE=${PULSAR_LOG_FILE:-"pulsar-standalone.log"}
    exec $JAVA $LOG4J2_SHUTDOWN_HOOK_DISABLED $OPTS ${ZK_OPTS} -Dpulsar.log.file=$PULSAR_LOG_FILE -Dpulsar.config.file=$PULSAR_STANDALONE_CONF org.apache.pulsar.PulsarStandaloneStarter $@

standalone命令其实就是运行java命令,输入一大堆参数,加载了一堆 jar 包,最终启动了PulsarStandaloneStarter这个类,所以我们可以使用 JVM 远程调试功能

IDE 就给我们提供了 Remote JVM Debug 功能:

源码

我新建一个远程调试,参数填默认的就行,这里 IDE 给我们自动生成了一段 JVM 参数:

源码

我们把这段 JVM 参数复制,把其中的suspend=n改成suspend=y,然后修改bin/pulsar文件,把这段参数添加到standalone模式的启动参数中:

elif [ $COMMAND == "standalone" ]; then
    # 添加调试参数,注意 suspend=y
    OPTS="${OPTS} -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005"

    PULSAR_LOG_FILE=${PULSAR_LOG_FILE:-"pulsar-standalone.log"}
    exec $JAVA $LOG4J2_SHUTDOWN_HOOK_DISABLED $OPTS ${ZK_OPTS} -Dpulsar.log.file=$PULSAR_LOG_FILE -Dpulsar.config.file=$PULSAR_STANDALONE_CONF org.apache.pulsar.PulsarStandaloneStarter $@

这样,我们本地命令行再执行执行bin/pulsar standalone时就会挂起:

$ bin/pulsar standalone --num-bookies 3
Listening for transport dt_socket at address: 5005

此时,你在 IDE 里可以给代码随意打断点,点击 debug 按钮后 broker 才会启动,走到断点处将暂停,我们可以在 IDE 中查看变量、堆栈等信息。

这样我们就能在 IDE 中同时调试 client 端和 broker 端的代码了。

但是需要注意的是, 进行远程调试的源代码必须和命令行启动的 broker 一致 ,否则会导致调试时行数对不上的问题。

如果出现源码对不上的情况,可以在 pulsar 项目的根目录用 maven 重新编译当前的源码:

$ mvn package -DskipTests -Dlicense.skip=true

编译好的二进制包在distribution/server/target中,我们在新的包中的bin/pulsar脚本添加远程 debug 的参数,然后再次启动即可顺利调试。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分