缘起
在尚硅谷教授flink框架期间,我们决定写一本有关flink实践的书籍。将我们在讲课过程中碰到的难点以及碰撞出的火花沉淀下来,供业界参考,方便大家快速上手flink,以及能够实现一些复杂的需求和编写出高性能的flink流处理程序。
在授课以及研发课程的过程中,其实我们很少去深入研究源码,而是着重于熟练掌握flink提供的api,以及如何在应用程序层面写出高性能的flink程序。不去深入研究源码的原因有以下几点:
- 盲目的去阅读源码可以说是学习编程最低效的一种方式。flink的源码非常庞大,有一百多万行Java代码以及几十万行Scala代码。不带任何目的的去读,非常容易迷失在源码里。想要去理解一个框架的原理,最好的方式是自己去实现一个,也就是广为人知的造轮子。比如想学习操作系统,就自己实现一个小的内核;想学习编译原理,就自己实现一个小的编译器;想学习web框,就自己实现一个小的web框架;想研究编辑器,可以自己动手实现一个简单的文本编辑器;想学习Hadoop,就自己写一个小的MapReduce计算引擎;等等(以上这些轮子,在下都造过,有些还造过不止一个)。
- 学习编程最重要的方法依然是:不断的去写代码。而不是看代码。碰到问题,第一时间应该是去查看官方文档,使用搜索引擎搜索,以及去StackOverflow这样的网站去提问,如果碰到实在解决不了的问题,才去阅读源码,而这个时候,也必然对flink这个框架的使用已经非常熟练了,阅读源码也就有的放矢了。Torvalds Linus所说的read the fucking source code说的也就是这种情况。
- flink源码很庞大,迭代了很多年,贡献人数接近1000,重构过很多次。所以源码里面使用了很多编程中比较高级的技术,例如:
- 无处不在的依赖注入,这样的好处是解耦,但不好的地方就是阅读起来很困难,因为各种类的实现非常分散。当然解耦是大型项目必须要做的事情。
- 大量使用了设计模式:工厂模式,观察者模式,还有例如访问者模式这样很少用到的设计模式(如果没有使用Java写过编译器或者解释器,基本不可能对访问者模式有比较深刻的理解,因为访问者模式最常用的场景就是遍历抽象语法树)。
- 大量使用了一些Java的高级特性,例如Java的异步编程:Future特性;Java用来实现线程池的Executor接口;以及海量的匿名函数等等。
- 底层通信大量使用了Scala编写的Akka以及Java的Netty。所以涉及到了Scala和Java的混合编程,这也是一个挑战。 等等等等。就不一一列举了,如果对相关知识没有深入的理解,那么看源码本身既很难读懂,也没有多少收获。
那为什么现在要开始读flink的源码呢?因为写书的话,必须对flink的底层有一个深入的理解。所以就涉及到了读源码的问题。那么接下来要解决的问题就是如何阅读源码?
如何阅读源码
这就说到了本文的重点————通过制作一个迷你的flink来熟悉flink的源码。
阅读源码最重要的一点就是一定要去调试和修改源码,这样才能真正理解源码。
而我的方法就是通过删除和修改flink的源码,使得删改以后的flink源码可以运行基本的flink程序,例如:word count程序。
经过删改以后,我将flink原本的一百多万行代码删改到了10万行,而没有损失flink的基本功能,也就是说核心的计算引擎并没有受到破坏。还顺便发现了一处变量的命名错误,并提交了pull request,成为了flink的源码贡献者:)。
我制作的迷你flink的仓库地址是:https://www.github.com/confucianzuoyuan/mini-flink
针对flink源码,我大概做了以下修改:
- 删除了flink的一些库,例如flink table,flink cep等libraries。到最后删到只剩下这几个库:flink-core, flink-runtime, flink-java, flink-streaming-java, flink-metrics, flink-optimizer。
- 将flink每个库的测试代码全部删除,也就是每个lib的tests文件夹。进行到现在,大概剩下30万行java代码。接下来,真正的挑战开始了。因为这几个模块有互相依赖的关系。随便删除一个模块甚至一个文件,都会爆红一大片。
- 将flink核心代码库例如flink-core,flink-runtime等lib中的统计模块代码flink-metrics删除,由于metrics代码耦合在flink源码的很多文件中,所以删除起来很麻烦,因为需要修改很多函数的签名或者类的定义等等。
- 将flink-optimizer这个优化模块删除。
- 将文件系统相关的代码删除,因为word count程序并没有用到文件的读写。而且文件系统相关代码也不是flink计算引擎的核心部件。这部分工作量也很大,因为文件系统的代码也分散在了flink源码中很多的地方。
- 修改代码:将一些运行代码时用不到的接口实现、条件语句、异常处理等代码删去,因为这些代码在运行的时候用不到,而且在阅读源码时,使我们抓不到重点。如果将这些代码删去,在看源码时将会很清爽,也方便加注释。举个例子:由于我在运行程序时,使用的是Intellij IDEA本地运行,所以其实使用的是MiniCluster这个迷你集群。而flink的执行器接口是
PipelineExecutor
,共有好几个实现:LocalExecutor
,EmbeddedExecutor
,RemoteExecutor
,AbstractJobClusterExecutor
,AbstractSessionClusterExecutor
;而由于我们只使用了LocalExecutor
这一个实现,所以其余的都可以删除。这样读代码会方便很多。 - 由于flink的master节点会开启一个web ui,所以web ui也需要去掉,由于web ui中涉及到flink的metrics数据的展示,以及耦合在其他的一些代码里,在删除的时候颇费了一些周折。
- 逐一阅读各个模块,找出没有用到的代码,然后移除。
- 很重要的一点:使用git来管理项目,每删改一些代码,就进行commit操作,这样在改了代码以后,如果程序跑不通,可以回滚到之前可以跑通的代码!
经过以上一系列步骤,可以将flink源码删改至10万行左右,迷你flink就诞生了。
收获了什么
其实收获非常多,比如成了源码贡献者:),哈哈,当然这个并不重要。
这里要强调一点,提高写程序能力的唯一方法就是不断的写代码,不断的写没写过的代码,不断的写不熟悉的代码!
收获大概有以下一些:
- 在删改flink源码的过程中,由于需要保证word count程序能跑通,所以碰到的报错信息都必须要修复掉。在修复的过程中,逼迫我去认真阅读代码,从而搞懂了flink的整个执行流程。这就是我之前所说的为什么阅读源码要去运行它、修改它的原因,只有这样,才能把源码彻底搞清楚。
- 看到了Java高手是如何写代码的。其实我之前并没有阅读过大型的Java项目的代码,所以很多Java开发才使用到的技术并没有特别关注。之前读过大型的代码库大概是像C语言,Python,JavaScript,Golang,还有一些比较冷门的语言例如OCaml,Rust之类的。这次阅读flink代码,确实学到了不少Java开发相对高级一些的技术。例如,Java的设计模式具体是如何使用的。如何使用各种设计模式将大型项目解耦,可以说各种设计模式,对于Java而言是不得不用的技术,对于这一点,我有了更深的体会。
- 其他语言例如Python的协程,JavaScript的Promise这些异步编程方式在Java中是如何使用的,具体来讲就是Java的Future这一特性,在flink中得到了大量的使用,有关并发的操作基本都是由Future这一特性来实现的。
- flink源码中大量使用了泛型,虽然Java的泛型比之类型系统更加强大的语言(Scala, Haskell, Ocaml等)有所欠缺,但Java本着实用主义的态度,设计出来的泛型也能达到很好的使用效果。
- flink底层通信依赖Scala编写的Akka这一著名的Actor模式的并发库(来源于Erlang)。由于之前并没有Akka的使用经验,因此借此机会好好的学习了一下Actor模式的并发是如何实现的。以及在Java中如何去使用Scala编写的库,或者说如何进行Java和Scala的混合编程。
- 最重要的一点就是发现flink中蕴含了很多优秀的设计思想,可以说是集很多年来分布式系统领域发展的大成,基本是流处理框架的巅峰之作。
- 还有很多很多小的收获,例如学到使用Java如何实现元组,Either这样的在函数式编程语言(Scala,Haskell, OCaml)中才有的数据结构,如何正确的使用Executor管理线程池。
而在我发现一个微小的拼写错误并提交修复以后,代码很快就得到了合并(知道我怎么成为源码贡献者的了吧,就是个拼写错误,哈哈)。说明flink社区非常有活力,发展速度非常快。未来大有可期。非常值得认真学习并使用。
迷你flink的仓库地址:https://www.github.com/confucianzuoyuan/mini-flink
欢迎大家clone下来学习!
评论0