心跳游戏源码-如何学习开源项目,我总结的套路

我在上一篇文章中提到了参与开源社区的各种好处。 本文分享了在使用或学习开源项目源码过程中的一些经验和方法。

因为最近在研究Apache Pulsar消息队列,所以就以这个项目为例。 不过,本文介绍了可以在其他小型开源项目中使用的通用方法。 如果你也对消息队列相关技术感兴趣,可以参考之前的文章和。

下面我们将详细介绍一些方法,主要分为两部分:

第一部分是文档,在这里你可以获得有效的信息来解决问题;

第二部分是实践部分,即如何有效地断点或者使用工具来理解源码

1.文献检索方法

如果我们想了解一个开源项目,文档可以帮助我们解决大部分问题。 当然,我这里所说的不仅仅指官网文档,还包括Issue、PR、源码中的注释、单元测试等。 从这些地方可以得到很多有用的信息,所以我把它们统称为文档。 我们先从最简单的Start开始。

1.官网文档,重点是快速入门和概念部分。

官网文档无疑是最权威的信息来源。 但官网文档的问题是内容太多、太全面。 它们适合在遇到问题或需求时作为功能指南进行查阅。

因此,官网的内容需要有选择地研究。 我建议优先考虑两个部分:

第一个是快速入门部分,教你如何快速部署一个demo服务; 第二个是概念部分,是术语解释、核心功能介绍等。

不用说,快速部署demo服务是我们学习新技术的第一步。 通常放在文档的第一章; 而功能/术语的解释是为了我们能够顺利学习进阶资料或者参与社区讨论。 重要伏笔。

对于Pulsar这样的消息队列来说,发送和接收消息其实才是核心功能,所以官网Concepts and Architecture部分的Messaging章节显得非常重要。 详细介绍了Pulsar中的订阅模式、死信队列等关键功能:

我在上一篇文章中介绍过Pulsar采用存储计算分离架构,存储层依赖于Apache Bookkeeper。 所以如果你的目标是学习Pulsar,那么Bookkeeper的官网文档也需要阅读,因为Pulsar中的很多功能都会与Bookkeeper进行交互。

你可以在本地启动一个 Bookkeeper 集群并在客户端上玩。 阅读并理解Bookkeeper中的专业术语将有助于你理解Pulsar中的一些设计。

2.阅读文档并查看单元测试用例,以帮助我们准确理解每个函数的预期行为。

一般来说,成熟的开源项目的测试用例都比较完整,会覆盖所有关键功能的预期行为。 因此,虽然单个测试用例也是很好的学习材料,但最好将其与文档结合使用。

例如,有时文档中某个功能的文字描述可能冗长且混乱,或者文档可能没有介绍一些技术设计细节。

当遇到这些情况时,我们大概率可以在单个测试文件中找到对应的功能测试代码。 根据测试代码,很容易实现功能逆向,俗话说“说话便宜,给我看代码”。

例如,有一次我看到一个消费者写了一篇关于纪元的日志。 我在分布式领导人选举的背景下听说过这个术语,但事实上,消费消息与分布式领导人选举无关,那么这个纪元到底是做什么的呢? 哪个?

我在文档中找不到答案。 这应该是具体实现中的一个术语,于是我在源码中搜索了包含testEpoch和epochTest这两个关键字的函数名,找到了几个测试用例:

PS:测试函数名的test关键字可能在开头,也可能在结尾,所以需要两者都搜索。

浏览完这些测试用例的内容,我大概明白了。 原来这个epoch是消息重新传递函数(redelivery)中的一个术语,主要是用来避免消息的重复消费。

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

首先,问题列表就不言而喻了。 如果您在使用软件时遇到问题,首先要考虑的是搜索问题列表。

虽然有时候并不是直接的答案,但是如果用不同的关键词多搜索几次,很有可能就能找到一些解决问题的思路。

此外,PR 信息可以帮助我们了解各个代码片段的上下文。

例如,如果你在阅读某段代码时有疑问,不明白这段代码的用途,你可以在IDEA中右键单击该代码两侧,打开“Annotate with Git Blame”,可以看到这段代码是谁添加的以及何时添加的:

然后将键盘悬停在作者昵称上两秒,就会弹出代码合并到master分支时的PR标题和链接:

18260是该PR的编号,点击跳转到对应的PR页面:

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

有了这些上下文信息,我们就可以在阅读源代码时防止出现障碍。

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

以Pulsar为例,如果需要进行更重要的改变,则需要提出PIP提案(Pulsar Improvement Proposal),这是专门解释背景信息和设计思想的文档。

wiki 页面上收集了此类 PIP 文档:

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

例如,在Pulsar的事务实现中,有一个专门的PIP,详细介绍了设计思想。 结合PIP思想的指导学习源码就容易多了:

我个人认为一个好的PIP结合源码会带我们从讨论设计到实现一个功能。 这是一本非常好的教科书。 如果你花更多的精力去研究,你一定会有所收获。

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

当然,熟练掌握信息检索工具进行高效检索也是一项重要技能,比如IDEA的各种搜索、GitHub Issue/PR搜索句型,这些方法网上都可以轻松找到,就不赘述了。

2.源码阅读方法

如果想要真正了解一个项目,看源码绝对是不可避免的一部分。 阅读源码的好处不用多说,但是阅读源码肯定会花费大量的时间,而且过程也不会轻松。

想想看,成熟的开源项目已经发展了很多年,其功能也在不断演变。 很多人都在上面写过代码,恐怕没有人能保证完全理解系统的每一个细节。 当我们阅读源代码时,就像探索一座巨大的城市。 在角落里很容易迷路。

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

提示1:不建议看“死代码”。 建议在调试实际问题的过程中去理解代码。

也就是说,不要苦读代码,最好用动态调试的方式来研究每个函数中到底做了什么。

以 Pulsar 为例,我们可以在命令行以独立模式启动 Pulsar Broker:

$ bin/pulsar standalone

然后使用Java客户端创建一个生产者来发送消息:

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

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

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

第一个问题是我们自己的项目通过Maven导入客户端包。 如果你踏入这个包,你听到的就是反编译后的class文件,并不能直接看到源代码。 即使IDEA可以直接帮我们下载源码,但是如果我们从事客户端开发,我们需要最新版本的master分支代码,这和上传到Maven的源码还是不一样的。

这个问题比较容易解决。 我们直接从GitHub下载源码,在客户端包上创建测试文件并编写逻辑,这样我们就可以调试最新的客户端代码。

第二个问题更加困难。 我们要调整整个Pulsar消息发送流程,那么这就必须涉及到Pulsar客户端和Pulsar 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 文件,将该参数添加到独立模式的启动参数中:

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 时就会挂起:

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

此时,您可以在IDE中随意断掉代码,只有单击调试按钮后,代理才会启动,到达断点时就会暂停。 我们可以在IDE中查看变量、堆栈等信息。

这样我们就可以在IDE中同时调试客户端和Broker端的代码了。

但需要注意的是,远程调试的源代码必须与命令行启动的broker保持一致,否则会导致调试时行数不匹配的问题。

如果源码不匹配,可以在pulsar项目根目录下用maven重新编译当前源码:

$ mvn package -DskipTests -Dlicense.skip=true

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

技巧二:多猜、多搜,可以通过在底层库(标准库、网络框架等)设置条件断点来过滤掉关键流程。

这句话似乎是高效调试的关键。 “猜测”是第一次看源代码时重要且有效的方法。 结合IDE的搜索功能,可以帮助我们快速定位关键代码。

为什么底层库适合断点? 因为大型项目的代码细节很难搞清楚,而且加上各种异步、多线程操作,很容易“丢失”代码。 如果在底层库的socket/方法上设置断点,就可以根据调用栈来分析调用过程。

当然,底层库被调用的次数很多,可能会有很多不相关的调用,所以需要结合条件断点来过滤掉不相关的调用。

仍然以 Pulsar 为例,我现在想探究一下 Producer 发送消息的过程,所以 Producer 和 Broker 之间的网络通信过程是一个重要的切入点。

首先,我发现Pulsar的网络合约使用了protobuf,并注意到文件PulsarApi.proto中有一个BaseCommand的定义:

message BaseCommand {
    enum Type {
        CONNECT     = 2;
        SUBSCRIBE   = 4;

        PRODUCER    = 5;

        SEND        = 6;
        SEND_RECEIPT= 7;

        MESSAGE     = 9;
        ACK         = 10;

        PING = 18;
        PONG = 19;
        ...
    }
    
    required Type type = 1;

    
    optional CommandConnect connect          = 2;
    optional CommandConnected connected      = 3;
    ...
}

我们还发现Pulsar底层依赖于netty框架来实现网络通信,因此我们可以大胆推测源码中一定有一大段switch语句,根据命令中的类型分类来处理相应的命令。

所以我们可以全局搜索SEND_RECEIPT案例,找到PulsarDecoder文件:

这里会根据不同的命令类型调用不同的句柄函数,所以你可以认为这是Pulsar关键函数的入口点。

并且注意,这是一个公共包,也就是说客户端和broker都依赖这个包,所以在交换机上设置断点时可以看到客户端和broker之间的网络交互。 每次跳转的情况是网络命令的交互顺序:

PS:因为 ping/pong 心跳消息在调试时很麻烦心跳游戏源码,所以我们可以通过条件断点跳过脉冲消息。 另外,我们需要增加客户端中的各种超时,以避免调试时出现超时错误。

这样我们就可以开始我们的测试用例,通过这个断点来了解Pulsar发送消息的过程:

当然,如果你想探究每一步做了什么,直接跳到具体的handle函数中一步步调试即可。

技巧三、使用各种可视化工具。

比如上面提到的网络通信过程中,我们知道了消息的产生过程,但是每个protobuf数据包中都存储了哪些信息呢?

关于这个问题,社区里有大佬写了一个lua脚本,可以使用wireshark解析Pulsar协议格式。 详细信息在这里:

按照说明配置并启动wireshark后,可以使用以下过滤命令过滤掉不相关的数据包:

tcp.port eq 6650 and pulsar and protobuf.field.name ne "ping" and protobuf.field.name ne "pong"

接下来启动standalone,通过Java客户端发送消息,在wireshark中可以抓到10个数据包,和刚才调试得到的流程是一样的:

同时我们还可以查看每个包的具体数据。 例如PARTITITONED_METADATA命令是查询该主题对应的分区数量。 由于这是一个非分区主题,PARTITITONED_METADATA_RESPONSE 返回 0:

再比如,LOOKUP命令用于查询broker的URL,因为我们启动的standalone只有一个broker,所以LOOKUP_RESPONSE只返回一个URL:

实际使用场景中肯定有多个broker,所以这个LOOKUP_RESPONSE应该返回多个broker URL。

最后我们看一下实际发送消息的SEND命令上的具体数据:

可以看到上面有producer_name、sequence_id等数据。 每条消息的sequence_id单调递增,以避免网络重传导致消息重复心跳游戏源码,类似于tcp中的seq。

另外可以看到,真正的报文数据放在数据包的末尾,通过数组记录数据的粗细。

具体的玩法有很多,这里就不一一列举了。 事实上,不仅wireshark可以分析Pulsar的网络通信,你还可以使用zookeeper的可视化工具来查看Pulsar的元数据。

例如,prettyZoo是一个用于可视化zookeeper的开源工具,那么我可以在Pulsar单机启动后将prettyZoo连接到zookeeper的端口(它会手动启动zookeeper),并直观地查看zookeeper中的节点数据:

这方面的很多资料可能不太容易理解,但是我们手头有源代码,而且这些路径很可能是以字符串常量的形式表达的,所以全局搜索一下就可以了。

比如生产者名称的路径,我们通过查找就可以定位到:

简单浏览一下源码,原来是用zookeeper来生成全局唯一的生产者名的。

终于

这篇文章够长了。 主要介绍一些阅读开源项目源码的实用方法。 概括起来就是:善于寻找资源、善于利用工具。

虽然本文以 Pulsar 为例,但这种方法是通用的,可以应用于任何成熟的开源项目。

如果你也有一些经验想要分享,可以留言告诉我,掌握技巧只是万里长征的第一步,让我们在开源社区共同成长、进步。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 游戏源码 心跳游戏源码-如何学习开源项目,我总结的套路 https://www.wkzy.net/game/185927.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务