大咖分享 | 通過(guò)制作一個(gè)迷你版Flink來(lái)學(xué)習(xí)Flink源碼
轉(zhuǎn)自公眾號(hào):大數(shù)據(jù)那些事
摘要:將Flink從100多萬(wàn)行代碼刪改到10萬(wàn)行,且沒(méi)有損失Flink基本功能的操作分享。
作者簡(jiǎn)介:
左元,尚硅谷高級(jí)講師,中科院電子所碩士,精通C、Python、Javascript、Golang等開發(fā)語(yǔ)言,多年云計(jì)算、大數(shù)據(jù)、數(shù)據(jù)分析、分布式爬蟲、前端和后端開發(fā)經(jīng)驗(yàn),對(duì)比特幣、以太坊以及超級(jí)賬本等區(qū)塊鏈技術(shù)有源碼級(jí)別的深入研究。熱愛(ài)技術(shù),尤其喜歡研究各種算法,在Web開發(fā)、分布式系統(tǒng)、區(qū)塊鏈技術(shù)以及機(jī)器學(xué)習(xí)等方面有深厚的積累。

緣起
在尚硅谷教授Flink框架期間,我們計(jì)劃寫一本有關(guān)Flink實(shí)踐的書籍,將我們?cè)谥v課過(guò)程中碰到的難點(diǎn)以及碰撞出的火花沉淀下來(lái),供業(yè)界參考,方便大家快速上手Flink,以及能夠?qū)崿F(xiàn)一些復(fù)雜的需求,編寫出高性能的Flink流處理程序。
在教學(xué)與教研的過(guò)程中,其實(shí)我們很少去深入研究源碼,而是著重于熟練掌握Flink提供的API,以及如何在應(yīng)用程序?qū)用鎸懗龈咝阅艿腇link程序。
為什么不去深入研究源碼?
盲目去閱讀源碼是學(xué)習(xí)編程最低效的一種方式。
Flink的源碼非常龐大,有一百多萬(wàn)行Java代碼以及幾十萬(wàn)行Scala代碼。不帶任何目的去讀,非常容易迷失在源碼里。
想要去理解一個(gè)框架的原理,最好的方式是自己去實(shí)現(xiàn)一個(gè),也就是廣為人知的“造輪子”。想學(xué)習(xí)操作系統(tǒng),就自己實(shí)現(xiàn)一個(gè)小的內(nèi)核;想學(xué)習(xí)編譯原理,就自己實(shí)現(xiàn)一個(gè)小的編譯器;想學(xué)習(xí)Web框,就自己實(shí)現(xiàn)一個(gè)小的Web框架;想研究編輯器,可以自己動(dòng)手實(shí)現(xiàn)一個(gè)簡(jiǎn)單的文本編輯器;想學(xué)習(xí)Hadoop,就自己寫一個(gè)小的MapReduce計(jì)算引擎……(以上這些輪子,在下都造過(guò),有的還造過(guò)不止一個(gè))
學(xué)習(xí)編程最重要的方法依然是不斷去寫代碼!
不是看代碼。碰到問(wèn)題,第一時(shí)間應(yīng)該是去查看官方文檔,使用搜索引擎搜索,以及去StackOverflow這樣的網(wǎng)站去提問(wèn),如果碰到實(shí)在解決不了的問(wèn)題,才去閱讀源碼,而這個(gè)時(shí)候,也必然對(duì)Flink框架的使用已經(jīng)非常熟練了,閱讀源碼也就有的放矢了。Torvalds Linus所說(shuō)的“read the fucking source code”說(shuō)的也就是這種情況。
Flink源碼很龐大,迭代了很多年,貢獻(xiàn)人數(shù)接近1000人,重構(gòu)過(guò)很多次。所以源碼里面使用了很多編程中比較高級(jí)的技術(shù),例如:
無(wú)處不在的依賴注入,這樣的好處是解耦,但不好的地方就是閱讀起來(lái)很困難,因?yàn)楦鞣N類的實(shí)現(xiàn)非常分散。當(dāng)然解耦是大型項(xiàng)目必須要做的事情。
大量使用了設(shè)計(jì)模式:工廠模式,觀察者模式,還有例如訪問(wèn)者模式這樣很少用到的設(shè)計(jì)模式(如果沒(méi)有使用Java寫過(guò)編譯器或者解釋器,基本不可能對(duì)訪問(wèn)者模式有比較深刻的理解,因?yàn)樵L問(wèn)者模式最常用的場(chǎng)景就是遍歷抽象語(yǔ)法樹)。
大量使用了一些Java的高級(jí)特性,例如Java的異步編程:Future特性、Java用來(lái)實(shí)現(xiàn)線程池的Executor接口、海量的匿名函數(shù)等等。
底層通信大量使用了Scala編寫的Akka以及Java的Netty。所以涉及到了Scala和Java的混合編程,這也是一個(gè)挑戰(zhàn)。諸如此類就不一一列舉了,如果對(duì)相關(guān)知識(shí)沒(méi)有深入的理解,那么看源碼本身既很難讀懂,也沒(méi)有多少收獲。
為什么現(xiàn)在又要開始讀Flink的源碼呢?因?yàn)閷憰脑?,必須?duì)Flink的底層有一個(gè)深入的理解,所以就涉及到了讀源碼的問(wèn)題。那么,接下來(lái)要解決的問(wèn)題就是如何閱讀源碼?
如何閱讀源碼
這就說(shuō)到了本文的重點(diǎn)——
通過(guò)制作一個(gè)迷你版的Flink來(lái)熟悉Flink的源碼。
閱讀源碼最重要的一點(diǎn)就是:一定要去調(diào)試和修改源碼!
這樣才能真正理解源碼。我的方法就是通過(guò)刪除和修改Flink的源碼,使得刪改以后的Flink源碼可以運(yùn)行基本的Flink程序,例如:word count程序。
經(jīng)過(guò)刪改以后,我將Flink原本的100多萬(wàn)行代碼刪改到了10萬(wàn)行,而沒(méi)有損失Flink的基本功能,也就是說(shuō)核心的計(jì)算引擎并沒(méi)有受到破壞。還順便發(fā)現(xiàn)了一處變量的命名錯(cuò)誤,并提交了pull request,成為了Flink的源碼貢獻(xiàn)者 :)

我制作的迷你版Flink的倉(cāng)庫(kù)地址:
https://www.githug.com/confucianzuoyuan/mini-flink
針對(duì)Flink源碼,我大概做了以下修改:
刪除了Flink的一些庫(kù),例如flink table,flink cep等libraries。到最后刪到只剩下這幾個(gè)庫(kù):flink-core、 flink-runtime、flink-java、flink-streaming-java、flink-metrics、flink-optimizer。
將Flink每個(gè)庫(kù)的測(cè)試代碼全部刪除,也就是每個(gè)lib的tests文件夾。進(jìn)行到現(xiàn)在,大概剩下30萬(wàn)行Java代碼。接下來(lái),真正的挑戰(zhàn)開始了,因?yàn)檫@幾個(gè)模塊有互相依賴的關(guān)系,隨便刪除一個(gè)模塊甚至一個(gè)文件,都會(huì)爆紅一大片。
將Flink核心代碼庫(kù)例如flink-core、flink-runtime等lib中的統(tǒng)計(jì)模塊代碼flink-metrics刪除,由于metrics代碼耦合在Flink源碼的很多文件中,所以刪除起來(lái)很麻煩,因?yàn)樾枰薷暮芏嗪瘮?shù)的簽名或者類的定義等等。
將flink-optimizer這個(gè)優(yōu)化模塊刪除。
將文件系統(tǒng)相關(guān)的代碼刪除,因?yàn)閣ord count程序并沒(méi)有用到文件的讀寫。而且文件系統(tǒng)相關(guān)代碼也不是Flink計(jì)算引擎的核心部件。這部分工作量也很大,因?yàn)槲募到y(tǒng)的代碼也分散在了Flink源碼中很多的地方。
修改代碼:將一些運(yùn)行代碼時(shí)用不到的接口實(shí)現(xiàn)、條件語(yǔ)句、異常處理等代碼刪去,因?yàn)檫@些代碼在運(yùn)行的時(shí)候用不到,而且在閱讀源碼時(shí),使我們抓不到重點(diǎn)。如果將這些代碼刪去,在看源碼時(shí)將會(huì)很清爽,也方便加注釋。
舉個(gè)例子:由于我在運(yùn)行程序時(shí),使用的是Intellij IDEA本地運(yùn)行,所以其實(shí)使用的是MiniCluster這個(gè)迷你集群。而Flink的執(zhí)行器接口是PipelineExecutor,共有好幾個(gè)實(shí)現(xiàn):LocalExecutor、EmbeddedExecutor、RemoteExecutor、AbstractJobClusterExecutor、AbstractSessionClusterExecutor,而由于我們只使用了LocalExecutor這一個(gè)實(shí)現(xiàn),所以其余的都可以刪除,這樣讀代碼會(huì)方便很多。
由于Flink的master節(jié)點(diǎn)會(huì)開啟一個(gè)web ui,所以web ui也需要去掉,由于web ui中涉及到Flink的metrics數(shù)據(jù)的展示,以及耦合在其他的一些代碼里,在刪除的時(shí)候頗費(fèi)了一些周折。
逐一閱讀各個(gè)模塊,找出沒(méi)有用到的代碼,然后移除。
很重要的一點(diǎn):使用Git來(lái)管理項(xiàng)目,每刪改一些代碼,就進(jìn)行commit操作,這樣在改了代碼以后,如果程序跑不通,可以回滾到之前可以跑通的代碼!
經(jīng)過(guò)以上一系列步驟,可以將Flink源碼刪改至10萬(wàn)行左右,迷你Flink就誕生了。
?
收獲了什么
其實(shí)收獲非常多,比如成了源碼貢獻(xiàn)者,哈哈,當(dāng)然這個(gè)并不重要。
這里要強(qiáng)調(diào)一點(diǎn):提高寫程序能力的唯一方法就是不斷的寫代碼,不斷的寫沒(méi)寫過(guò)的代碼,不斷的寫不熟悉的代碼!
收獲大概有以下這些:
在刪改Flink源碼的過(guò)程中,由于需要保證word count程序能跑通,所以碰到的報(bào)錯(cuò)信息都必須要修復(fù)掉。在修復(fù)的過(guò)程中,逼迫我去認(rèn)真閱讀代碼,從而搞懂了Flink的整個(gè)執(zhí)行流程。這就是我之前所說(shuō)的為什么閱讀源碼要去運(yùn)行它、修改它的原因,只有這樣,才能把源碼徹底搞清楚。
看到了Java高手是如何寫代碼的。其實(shí)我之前并沒(méi)有閱讀過(guò)大型的Java項(xiàng)目代碼,所以很多Java開發(fā)才使用到的技術(shù)并沒(méi)有特別關(guān)注。之前讀過(guò)大型的代碼庫(kù)大概是像C語(yǔ)言、Python、JavaScript、Golang,還有一些比較冷門的語(yǔ)言如OCaml、Rust之類的。這次閱讀Flink代碼,確實(shí)學(xué)到了不少Java開發(fā)相對(duì)高級(jí)一些的技術(shù)。例如,Java的設(shè)計(jì)模式具體是如何使用的。如何使用各種設(shè)計(jì)模式將大型項(xiàng)目解耦,可以說(shuō)各種設(shè)計(jì)模式,對(duì)于Java而言是不得不用的技術(shù),對(duì)于這一點(diǎn),我有了更深的體會(huì)。
其他語(yǔ)言例如Python的協(xié)程,JavaScript的Promise這些異步編程方式在Java中是如何使用的,具體來(lái)講就是Java的Future這一特性,在Flink中得到了大量的使用,有關(guān)并發(fā)的操作基本都是由Future這一特性來(lái)實(shí)現(xiàn)的。
Flink源碼中大量使用了泛型,雖然Java的泛型比Scala、Haskell、Ocaml等有所欠缺,但Java本著實(shí)用主義的態(tài)度,設(shè)計(jì)出來(lái)的泛型也能達(dá)到很好的使用效果。
Flink底層通信依賴Scala編寫的Akka這一著名的Actor模式的并發(fā)庫(kù)(來(lái)源于Erlang),由于之前并沒(méi)有Akka的使用經(jīng)驗(yàn),因此借此機(jī)會(huì)好好的學(xué)習(xí)了一下Actor模式的并發(fā)是如何實(shí)現(xiàn)的,以及在Java中如何去使用Scala編寫的庫(kù),或者說(shuō)如何進(jìn)行Java和Scala的混合編程。
最重要的一點(diǎn)就是發(fā)現(xiàn)Flink中蘊(yùn)含了很多優(yōu)秀的設(shè)計(jì)思想,可以說(shuō)是集很多年來(lái)分布式系統(tǒng)領(lǐng)域發(fā)展的大成,基本是流處理框架的巔峰之作。
還有很多很多小的收獲,例如學(xué)到使用Java如何實(shí)現(xiàn)元組,Either這樣的在函數(shù)式編程語(yǔ)言(Scala、Haskell、OCaml)中才有的數(shù)據(jù)結(jié)構(gòu),如何正確的使用Executor管理線程池……
我在發(fā)現(xiàn)一個(gè)微小的拼寫錯(cuò)誤并提交修復(fù)以后,代碼很快就得到了合并。說(shuō)明Flink社區(qū)非常有活力,發(fā)展速度非??欤磥?lái)大有可期,非常值得認(rèn)真學(xué)習(xí)并使用。
迷你版Flink的倉(cāng)庫(kù)地址:https://www.github.com/confucianzuoyuan/mini-flink
歡迎大家clone下來(lái)學(xué)習(xí)!