1.为何要阅读源码
浪尖以自己的经验讲一下为何需要阅读源码吧!
a.解决企业中bug。比如flink早期bug,就很多,如json序列化工具,在开启flink仅一次处理,json格式不符合要求,就会抛异常而挂掉,然后重试,挂掉。这明显不科学,要解决这个bug就要会读源码,改源码。
bug代码如下:
修改后代码如下:
b.丰富原有框架的需求。比如spark streaming与kafka早期版本结合,是不支持动态监听新增的topic或者分区的,而企业中kafka的topic新增分区又很常见,这时候为了丰富功能需要改源码,是的spark streaming能感知到kafka的topic和分区的变化。
在浪尖知识星球分享过了。
c.二次开发做平台。比如,浪尖最近想基于tinkerpop做知识图谱存储的框架,但是想新增一些策略,新增一个自研存储层,新增一些gremlin语法算子,那就需要你读懂源码,合理规划修改源码。
d.学习。阅读源码,小了说可以学习编程语言,大了说可以学习架构设计,比如spark的rpc架构,hbase的存储层架构,sparkshuffle的分治思想及演进远离。
2.应该从何处阅读源码
阅读源码,到底该如何阅读呢?一个工程给你,几千,几万,甚至几十万代码;一个工程几个十几个模块,一下子摆到你面前,确实束手无策,无从下手。而且Scala语法这么随性,Java后期lambda表达也很狂野,这样也让很多基础不牢或者根本没怎么做过这类编程的人,很头痛,看不懂,举步维艰。
1).算子源码阅读。
一般的计算框架,为了方便使用,都会提供多种计算算子,而且算子又分为lazy形和action形。
对于spark lazy形算子,要理解为啥是懒执行,action算子是如何触发计算的?这两个疑问都是可以通过看对应算子的源码找到答案的。
比如spark sql的逻辑组织依赖的类,以下几个:
- dataset代表调用链。
- dataset的函数是算子
- 然后SparkStrategy是策略优化。
这些类,之间如何相互作用的,弄清楚之后源码也算是阅读懂了。
对于最近做的知识图谱存储,tinkerpop也是支持这两种类型的算子。只不过调用关系比spark 更乱,源码更难阅读,因为注释也比较差,后面有机会给大家品评。
tinkerpop自身为了表述逻辑,有四层类:
- traversal,类似于spark sql的dataset,代表迭代的顺序,会形成一个调用链。
- step,就是traversal算子里传入的函数对象,代表计算的步骤和逻辑。
- TraversalStrategy,类似于spark sql的SparkStrategy,对step算子进行优化。
- traverser,该对象主要是保存tinkerpop的计算状态,及统计信息等。spark则没有。
单个算子看完,其实不能解决大家的疑惑,因为spark还有血缘关系,血缘关系其实就是spark 算子的调用链,如下面的:
代码语言:javascript复制rdd1.map(w=>(w,1)).reducebykey(_ _).take(10)
rdd之间依赖的关系分类-窄依赖和宽依赖。如何划分的,类名称叫什么?
stage划分,task划分,task包装,task调度,task执行。应该带着这些疑问去读,一个疑问一个疑问的阅读源码,揭示迷底,这样你才能透彻理解。
2).rpc源码阅读。
对于spark 的内部通讯机制,浪尖之前也发过文章,大概有三种:
a.EventLoop 。如:DAGSchedulerEventProcessLoop,生产消费的阻塞队列机制。
b.sparkListener 。这是一种总线机制,spark ui的mertics都是通过这种机制获取展示的,当然task信息要通过nerry的rpc由executor端返回到driver端。
c.基于netty的RPC,task调度,任务返回等都是通过它的。
对于rpc阅读,可以进行简单了解吧,比如worker与master之间的rpc通信消息种类,executor与driver之间的rpc通信消息类型。
这样便于你了解,executor,driver及task的调度。
3).任务调度源码阅读。
任务调度源码阅读,主要是涵盖stage划分,task包装,driver到executor的task调度,task这行。
这里阅读的主要入口就是action算子,一步步查看调用链,然后利用eventloop rpc sparklistener,完成了整个task调度及结果信息返回的过程。
你读源码的时候,可以按照这几个步骤,一步步注释写文章梳理。
4).数据交互与存储源码阅读。
对于spark来说,他的存储管理层主要是blockmanager,无论是cache,shuffle,广播变量都是考它管理的。
- rpc架构,主要是mapoutputtracer及其子类。
- shuffle write,就是shufflemaptask的writer操作,包括排序,聚合,文件生成,溢写磁盘。
- shufflerdd,完成shuffle 读取操作,包括并发读取,批量读取限制,总量限制,溢写磁盘文件条件,及合并实际。
5).高深的就是底层运行环境源码阅读。
这里你可以带的疑问:
- driver和executor如何启动的。
- --jars等配置是如何配置生效的。
- driver和excutor的jvm参数如何配置生效的。
这里面可以学到很多东西的,我也录制过视频:java知识点,包括:classloader,processbuilder等。
3.应该如何使用工具方便的阅读源码
idea是一个阅读源码并调试的好工具。
社区版本idea查看继承关系的骚操作
经验篇:Intellij IDEA 看源码必备的技能!
如何在IntelliJ IDEA优雅的查看类继承关系...
高效开发:IntelliJ IDEA天天用,这些Debug技巧你都知道?
除了好好使用idea的工具外,也可以参考浪尖源码阅读视频和文档提供的思路,然后根据自己需要阅读源码。
4.需要debug源码么
直接说,好的源码不需要debug。比如,spark源码阅读,你完全没必要debug,因为spark架构设计,代码风格清晰明了,而且注释清晰易懂,按照注释、调用连及引用关系就可以流畅阅读源码。
有些必须要debug源码,才能更好的理解。如,tinkerpop,flink源码,注释很挫,而且很多地方引用关系,隐藏太深,还有各种优化策略,还有查询及执行逻辑不慎明了,命名规范问题等使得直接阅读比较困难,需要debug,梳理调用关系链,然后才能更好阅读成长。
调试flink源码
一般情况,可以先自己顺利源码,然后debug验证自己的梳理是否正确,反之依然,二者结合,相得益彰。
最后要做到,自己关注的细节明了,运行机制明了,这样你才不会被调优困扰。
阅读源码达到最佳的境界是:深入理解了框架的设计理念和设计细节,并且能对设计细节解决的案例场景和不适应的场景有深刻的认识和理解。