換個(gè)姿勢(shì)入門(mén)大數(shù)據(jù)
這篇文章是我近期準(zhǔn)備在公司做大數(shù)據(jù)分享的內(nèi)容。因?yàn)榱?xí)慣了全英文的 keynote,所以本來(lái)標(biāo)題叫《Introduction to bigdata》,但微信的英文標(biāo)題字體總覺(jué)得有些別扭,所以還是取了這么個(gè)中文名。
這篇文章的目的是帶那些對(duì)大數(shù)據(jù)不了解又有興趣的人入門(mén)。如果你是老手可以忽略,或者想看看有沒(méi)有不一樣的東西也行。
我們學(xué)習(xí)一個(gè)新知識(shí),***步應(yīng)該是給它個(gè)明確的定義。這樣才能知道你學(xué)的是什么,哪些該學(xué),哪些又可以先不用管。
然而,大數(shù)據(jù)雖然很火,但其實(shí)是個(gè)概念沒(méi)那么清晰的東西,不同的人可能有不同的理解。
這次我們不去糾結(jié)具體的定義,也忽略那些 4 個(gè) V、6 個(gè) C 之類(lèi)傳統(tǒng)說(shuō)教的東西,甚至不想聊架構(gòu)演進(jìn)以及各種調(diào)優(yōu)的方法,這些東西講了大家也不一定懂,懂了也記不住,記住了也用不起來(lái)。
我們也不去關(guān)注 AI、Machine Learning 那些炫酷的應(yīng)用層面的東西,而是去看看大數(shù)據(jù)這棟房子的地基是什么模樣。限于篇幅,很多技術(shù)細(xì)節(jié)點(diǎn)到即止,有興趣的同學(xué)可以再按需了解,這也正是入門(mén)的含義所在。
一
首先***個(gè)問(wèn)題,大數(shù)據(jù),大數(shù)據(jù),多大叫大?或者換一個(gè)角度,什么時(shí)候需要用到大數(shù)據(jù)相關(guān)的技術(shù)?
這依然是個(gè)沒(méi)有標(biāo)準(zhǔn)答案的問(wèn)題,有些人可能覺(jué)得幾十 G 就夠大了,也有人覺(jué)得幾十 T 也還好。當(dāng)你不知道多大叫大,或者當(dāng)你不知道該不該用大數(shù)據(jù)技術(shù)的時(shí)候,通常你就還不需要它。
而當(dāng)你的數(shù)據(jù)多到單機(jī)或者幾臺(tái)機(jī)器存不下,即使存得下也不好管理和使用的時(shí)候;當(dāng)你發(fā)現(xiàn)用傳統(tǒng)的編程方式,哪怕多進(jìn)程多線程協(xié)程全用上,數(shù)據(jù)處理速度依然很不理想的時(shí)候;當(dāng)你碰到其他由于數(shù)據(jù)量太大導(dǎo)致的實(shí)際問(wèn)題的時(shí)候,可能你需要考慮下是不是該嘗試下大數(shù)據(jù)相關(guān)的技術(shù)。
二
從剛才的例子很容易能抽象出大數(shù)據(jù)的兩類(lèi)典型應(yīng)用場(chǎng)景:
- 大量數(shù)據(jù)的存儲(chǔ),解決裝不下的問(wèn)題。
- 大量數(shù)據(jù)的計(jì)算,解決算得慢的問(wèn)題。
因此,大數(shù)據(jù)的地基也就由存儲(chǔ)和計(jì)算兩部分組成。
三
我們?cè)趩螜C(jī),或者說(shuō)數(shù)據(jù)量沒(méi)那么大的時(shí)候,對(duì)于存儲(chǔ)有兩種需求:
- 文件形式的存儲(chǔ)
- 數(shù)據(jù)庫(kù)形式的存儲(chǔ)
文件形式的存儲(chǔ)是最基本的需求,比如各個(gè)服務(wù)產(chǎn)生的日志、爬蟲(chóng)爬來(lái)的數(shù)據(jù)、圖片音頻等多媒體文件等等。對(duì)應(yīng)的是最原始的數(shù)據(jù)。
數(shù)據(jù)庫(kù)形式的存儲(chǔ)則通常是處理之后可以直接供業(yè)務(wù)程序化使用的數(shù)據(jù),比如從訪問(wèn)日志文件里處理得到訪問(wèn)者 ip、ua 等信息保存到關(guān)系數(shù)據(jù)庫(kù),這樣就能直接由一個(gè) web 程序展示在頁(yè)面上。對(duì)應(yīng)的是處理后方便使用的數(shù)據(jù)。
大數(shù)據(jù)也只是數(shù)據(jù)量大而已,這兩種需求也一樣。雖然不一定嚴(yán)謹(jǐn),但前者我們可以叫做離線(offline)存儲(chǔ),后者可以叫做在線(online)存儲(chǔ)。
四
離線存儲(chǔ)這塊 HDFS(Hadoop Distributed File System) 基本上是事實(shí)上的標(biāo)準(zhǔn)。從名字可以看出,這是個(gè)分布式的文件系統(tǒng)。實(shí)際上,「分布式」也是解決大數(shù)據(jù)問(wèn)題的通用方法,只有支持***橫向擴(kuò)展的分布式系統(tǒng)才能在理論上有解決***增長(zhǎng)的數(shù)據(jù)量帶來(lái)的問(wèn)題的可能性。當(dāng)然這里的***要打個(gè)引號(hào)。

這是 HDFS 的簡(jiǎn)易架構(gòu)圖,看起來(lái)仍然不太直觀,其實(shí)要點(diǎn)只有幾句話:
- 文件被以 block 為單位拆分后存放在不同的服務(wù)器上,每個(gè) block 都在不同機(jī)器上做了多份冗余。
- 有 NameNode 和 DataNode 兩種角色,前者存放元數(shù)據(jù)也就是每個(gè) block 保存在哪里,后者負(fù)責(zé)存放實(shí)際數(shù)據(jù)。
- 讀和寫(xiě)數(shù)據(jù)都要先向 NameNode 拿到對(duì)應(yīng)文件的元數(shù)據(jù),然后再找對(duì)應(yīng)的 DataNode 拿實(shí)際的數(shù)據(jù)。
可以看到,HDFS 通過(guò)集中記錄元數(shù)據(jù)的方式實(shí)現(xiàn)了分布式的效果,數(shù)據(jù)量增長(zhǎng)只需要添加一些新的 DataNode 就可以了,單機(jī)容量不再是限制。
而為了保證數(shù)據(jù)的高可用,比如某臺(tái)服務(wù)器突然壞了再也起不來(lái)了,HDFS 通過(guò)冗余的方式(通常是 3 副本)來(lái)解決這個(gè)問(wèn)題。這也是分布式系統(tǒng)里最常用的高可用方式,雖然成本可能很高。
系統(tǒng)級(jí)別的高可用才有意義,所以除了數(shù)據(jù)的高可用,元數(shù)據(jù)的高可用也至關(guān)重要。思路一樣 -- 備份。HDFS 提供了 Secondary NameNode 來(lái)提供元數(shù)據(jù)的冗余。當(dāng)然更好的方式是使用 NameNode HA 的方式,通過(guò) active/standby 一組 NameNode 來(lái)保證不間斷的元數(shù)據(jù)讀寫(xiě)服務(wù)。
同樣,擴(kuò)展性剛才也只考慮到數(shù)據(jù)的橫向擴(kuò)展,元數(shù)據(jù)呢?當(dāng)數(shù)據(jù)量大到一定程度,元數(shù)據(jù)也會(huì)非常大,類(lèi)似我們?cè)趥鹘y(tǒng)關(guān)系數(shù)據(jù)庫(kù)里碰到的索引膨脹的問(wèn)題。解決的思路是 NameNode Federation。簡(jiǎn)單講就是把原來(lái)的一組 active/standy NameNode 拆分成多組,每組只管理一部分元數(shù)據(jù)。拆分后以類(lèi)似我們?cè)?Linux 系統(tǒng)里掛載(mount)硬盤(pán)的方法對(duì)外作為整體提供服務(wù)。這些 NameNode 組之間相互獨(dú)立,2.x 版本的 HDFS 通過(guò) ViewFS 這個(gè)抽象在客戶端通過(guò)配置的方式實(shí)現(xiàn)對(duì)多組 NameNode 的透明訪問(wèn),3.x 版本的 HDFS 則實(shí)現(xiàn)了全新的 Router Federation 來(lái)在服務(wù)端保證對(duì)多組 NameNode 的透明訪問(wèn)。
可以看到,元數(shù)據(jù)的橫向擴(kuò)展和實(shí)際數(shù)據(jù)的橫向擴(kuò)展思路完全一樣,都是拆分然后做成分布式。
五
和離線存儲(chǔ)對(duì)應(yīng)的是在線存儲(chǔ),可以參照傳統(tǒng)的 MySQL、Oracle 等數(shù)據(jù)庫(kù)來(lái)理解。在大數(shù)據(jù)領(lǐng)域最常用的是 HBase。
數(shù)據(jù)分類(lèi)的標(biāo)準(zhǔn)很多,HBase 可能被歸類(lèi)為 NoSQL 數(shù)據(jù)庫(kù)、列式數(shù)據(jù)庫(kù)、分布式數(shù)據(jù)庫(kù)等等。
說(shuō)它是 NoSQL 數(shù)據(jù)庫(kù),是因?yàn)?HBase 沒(méi)有提供傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)的很多特性。比如不支持通過(guò) SQL 的形式讀寫(xiě)數(shù)據(jù),雖然可以集成 Apache Phoenix 等第三方方案,但畢竟原生不支持;不支持二級(jí)索引(Secondary Index),只有順序排列的 rowkey 作為主鍵,雖然通過(guò)內(nèi)置的 Coprocessor 能實(shí)現(xiàn),第三方的 Apache Phoenix 也提供了 SQL 語(yǔ)句創(chuàng)建二級(jí)索引的功能,但畢竟原生不支持;Schema 不那么結(jié)構(gòu)化和確定,只提供了列族來(lái)對(duì)列分類(lèi)管理,每個(gè)列族內(nèi)的列的數(shù)量、類(lèi)型都沒(méi)有限制,完全在數(shù)據(jù)寫(xiě)入時(shí)確定,甚至只能通過(guò)全表掃描確定一共有哪些列。從這些角度看,HBase 甚至都不像是個(gè) DataBase,而更像是個(gè) DataStore。
說(shuō)它是列式數(shù)據(jù)庫(kù),是因?yàn)榈讓哟鎯?chǔ)以列族為單位組織。不同行的相同列族放在一起,同一行的不同列族反而不在一起。這也就使得基于列(族)的過(guò)濾等變得更加容易。
說(shuō)它是分布式數(shù)據(jù)庫(kù),是因?yàn)樗峁┝藦?qiáng)大的橫向擴(kuò)展的能力。這也是 HBase 能成為大數(shù)據(jù)在線存儲(chǔ)領(lǐng)域主流方案的主要原因。
HBase 能提供超大數(shù)據(jù)量存儲(chǔ)和訪問(wèn)的根本在于,它是基于 HDFS 的,所有的數(shù)據(jù)都以文件的形式保存在 HDFS 上。這樣就天然擁有了橫向擴(kuò)展的能力,以及高可用等特性。
解決了數(shù)據(jù)存儲(chǔ)的問(wèn)題,剩下就需要在這個(gè)基礎(chǔ)上提供一套類(lèi)似數(shù)據(jù)庫(kù)的 API 服務(wù),并保證這套 API 服務(wù)也是可以很容易橫向擴(kuò)展的。

上線這個(gè)架構(gòu)圖已經(jīng)足夠簡(jiǎn)單,我們羅列幾個(gè)關(guān)鍵點(diǎn):
- 每個(gè)節(jié)點(diǎn)上都有一個(gè)叫 RegionServer 的程序來(lái)提供對(duì)數(shù)據(jù)的讀寫(xiě)服務(wù)
- 每個(gè)表被拆分成很多個(gè) Region,然后均衡地分散在各個(gè) RegionServer 上
另外有個(gè) HMaster 的服務(wù),負(fù)責(zé)對(duì)表的創(chuàng)建、修改、刪除已經(jīng) Region 相關(guān)的各種管理操作。
很容易看出,HBase 的分布式和 HDFS 非常像,RegionServer 類(lèi)似于 DataNode,HMaster 類(lèi)似于 NameNode,Region 類(lèi)似于 Block。
只要在 HDFS 層擴(kuò)容 DataNode,在 HBase 層擴(kuò)容 RegionServer,就很容易實(shí)現(xiàn) HBase 的橫向擴(kuò)展,來(lái)滿足更多數(shù)據(jù)的讀寫(xiě)需求。
細(xì)心的人應(yīng)該發(fā)現(xiàn)了,圖里沒(méi)有體現(xiàn)元數(shù)據(jù)。在 HDFS 里元數(shù)據(jù)是由 NameNode 掌控的,類(lèi)似的,HBase 里的元數(shù)據(jù)由 HMaster 來(lái)掌控。HBase 里的元數(shù)據(jù)保存的是一張表有哪些 Region,又各由哪個(gè) RegionServer 提供服務(wù)。
這些元數(shù)據(jù),HBase 采用了很巧妙的方法保存 -- 像應(yīng)用數(shù)據(jù)那些以 HBase table 的形式保存,這樣就能像操作一張普通表一樣操作元數(shù)據(jù)了,實(shí)現(xiàn)上無(wú)疑簡(jiǎn)單了很多。
而由于 HBase 的元數(shù)據(jù)以 Region 為粒度,遠(yuǎn)遠(yuǎn)比 HDFS 里的 block 粒度大多了,因此元數(shù)據(jù)的數(shù)據(jù)量一般也就不會(huì)成為性能瓶頸,也就不太需要再考慮元數(shù)據(jù)的橫向擴(kuò)展了。
至于高可用,存儲(chǔ)層面已經(jīng)有 HDFS 保障,服務(wù)層面只要提供多個(gè) HMaster 做主備就行了。
六
存儲(chǔ)的話題聊到這里。下面來(lái)看看計(jì)算這塊。
和存儲(chǔ)類(lèi)似,無(wú)論數(shù)據(jù)大小,我們可以把計(jì)算分為兩種類(lèi)型:
- 離線(offline)計(jì)算,或者叫批量(batch)計(jì)算/處理
- 在線(online)計(jì)算,或者叫實(shí)時(shí)(realtime)計(jì)算、流(stream)計(jì)算/處理
區(qū)分批處理和流處理的另一個(gè)角度是處理數(shù)據(jù)的邊界。批處理對(duì)應(yīng) bounded 數(shù)據(jù),數(shù)據(jù)量的大小有限且確定,而流處理的數(shù)據(jù)是 unbounded 的,數(shù)據(jù)量沒(méi)有邊界,程序永遠(yuǎn)執(zhí)行下去不會(huì)停止。
在大數(shù)據(jù)領(lǐng)域,批量計(jì)算一般用于用于對(duì)響應(yīng)時(shí)間和延遲不敏感的場(chǎng)景。有些是業(yè)務(wù)邏輯本身就不敏感,比如天級(jí)別的報(bào)表,有些則是由于數(shù)據(jù)量特別大等原因而被迫犧牲了響應(yīng)時(shí)間和延遲。而相反,流計(jì)算則用于對(duì)響應(yīng)時(shí)間和延遲敏感的場(chǎng)景,如實(shí)時(shí)的 PV 計(jì)算等。
批量計(jì)算的延遲一般較大,在分鐘、小時(shí)甚至天級(jí)。流處理則一般要求在毫秒或秒級(jí)完成數(shù)據(jù)處理。值得一提的是,介于兩者之間,還有準(zhǔn)實(shí)時(shí)計(jì)算的說(shuō)法,延遲通常在數(shù)秒到數(shù)十秒。準(zhǔn)實(shí)時(shí)計(jì)算很自然能想到是為了在延遲和處理數(shù)據(jù)量之間達(dá)到一個(gè)可以接受的平衡。
七
批量計(jì)算在大數(shù)據(jù)領(lǐng)域最老資歷的就是 MapReduce。MapReduce 和前面提到的 HDFS 合在一起就組成了 Hadoop。而 Hadoop 多年來(lái)一直是大數(shù)據(jù)領(lǐng)域事實(shí)上的標(biāo)準(zhǔn)基礎(chǔ)設(shè)施。從這一點(diǎn)也可以看出,我們按存儲(chǔ)和計(jì)算來(lái)對(duì)大數(shù)據(jù)技術(shù)做分類(lèi)也是最基本的一種辦法。
MapReduce 作為一個(gè)分布式的計(jì)算框架(回想下前面說(shuō)的分布式是解決大數(shù)據(jù)問(wèn)題的默認(rèn)思路),編程模型起源于函數(shù)式編程語(yǔ)言里的 map 和 reduce 函數(shù)。
得益于 HDFS 已經(jīng)將數(shù)據(jù)以 block 為單位切割,MapReduce 框架也就可以很輕松地將數(shù)據(jù)的初步處理以多個(gè) map 函數(shù)的形式分發(fā)到不同的服務(wù)器上并行執(zhí)行。map 階段處理的結(jié)果又以同樣的思路分布到不同的服務(wù)器上并行做第二階段的 reduce 操作,以得到想要的結(jié)果。
以大數(shù)據(jù)領(lǐng)域的「Hello World」-- 「Word Count」為例,要計(jì)算 100 個(gè)文件共 10 T 數(shù)據(jù)里每個(gè)單詞出現(xiàn)的次數(shù),在 map 階段可能就會(huì)有 100 個(gè) mapper 并行去對(duì)自己分配到的數(shù)據(jù)做分詞,然后把同樣的單詞「shuffle」到同樣的 reducer 做聚合求和的操作,這些 reducer 同樣也是并行執(zhí)行,***獨(dú)立輸出各自的執(zhí)行結(jié)果,合在一起就是最終完整的結(jié)果。

從上圖可以看到,shuffle 操作處理 map 階段的輸出以作為 reduce 階段的輸入,是個(gè)承上啟下的關(guān)鍵過(guò)程。這個(gè)過(guò)程雖然是 MapReduce 框架自動(dòng)完成的,但由于涉及非常多的 IO 操作,而 IO 往往是數(shù)據(jù)處理流程中最消耗性能的部分,因此 shuffle 也就成了性能調(diào)優(yōu)的重點(diǎn)。
可以看到,正是采用了分布式計(jì)算的思想,利用了多臺(tái)服務(wù)器多核并行處理的方法,才使得我們能以足夠快的速度完成對(duì)海量數(shù)據(jù)的處理。
八
MapReduce 框架作為一個(gè)分布式計(jì)算框架,提供了基礎(chǔ)的大數(shù)據(jù)計(jì)算能力。但隨著使用場(chǎng)景越來(lái)越豐富,也慢慢暴露出一些問(wèn)題。
資源協(xié)調(diào)
前面我們講分布式存儲(chǔ)的時(shí)候提到了 NameNode 這么一個(gè)統(tǒng)一管理的角色,類(lèi)似的,分布式計(jì)算也需要有這么一個(gè)統(tǒng)一管理和協(xié)調(diào)的角色。更具體的說(shuō),這個(gè)角色需要承擔(dān)計(jì)算資源的協(xié)調(diào)、計(jì)算任務(wù)的分配、任務(wù)進(jìn)度和狀態(tài)的監(jiān)控、失敗任務(wù)的重跑等職責(zé)。
早期 MapReduce -- 即 MR1 -- 完整地實(shí)現(xiàn)了這些功能,居中統(tǒng)一協(xié)調(diào)的角色叫做 JobTracker,另外每個(gè)節(jié)點(diǎn)上會(huì)有一個(gè) TaskTracker 負(fù)責(zé)收集本機(jī)資源使用情況并匯報(bào)給 JobTracker 作為分配資源的依據(jù)。
這個(gè)架構(gòu)的主要問(wèn)題是 JobTracker 的職責(zé)太多了,在集群達(dá)到一定規(guī)模,任務(wù)多到一定地步后,很容易成為整個(gè)系統(tǒng)的瓶頸。
于是有了重構(gòu)之后的第二代 MapReduce -- MR2,并給它取了個(gè)新名字 YARN(Yet-Another-Resource-Negotiator)。

JobTracker 的兩個(gè)核心功能 -- 資源管理和任務(wù)的調(diào)度/監(jiān)控被拆分開(kāi),分別由 ResourceManager 和 ApplicationMaster 來(lái)承擔(dān)。ResouerceManager 主要負(fù)責(zé)分配計(jì)算資源(其實(shí)還包括初始化和監(jiān)控 ApplicationMaster),工作變的很簡(jiǎn)單,不再容易成為瓶頸,部署多個(gè)實(shí)例后也很容易實(shí)現(xiàn)高可用。而 ApplicationMaster 則是每個(gè) App 各分配一個(gè),所有 Job 的資源申請(qǐng)、調(diào)度執(zhí)行、狀態(tài)監(jiān)控、重跑等都由它來(lái)組織。這樣,負(fù)擔(dān)最重的工作分散到了各個(gè) AM 中去了,瓶頸也就不存在了。
開(kāi)發(fā)成本
為了使用 MapReduce 框架,你需要寫(xiě)一個(gè) Mapper 類(lèi),這個(gè)類(lèi)要繼承一些父類(lèi),然后再寫(xiě)一個(gè) map 方法來(lái)做具體的數(shù)據(jù)處理。reduce 也類(lèi)似??梢钥吹竭@個(gè)開(kāi)發(fā)和調(diào)試成本還是不低的,尤其對(duì)于數(shù)據(jù)分析師等編程能力不那么突出的職位來(lái)說(shuō)。
很自然的思路就是 SQL 化,要實(shí)現(xiàn)基本的數(shù)據(jù)處理,恐怕沒(méi)有比 SQL 更通用的語(yǔ)言了。
早期對(duì) MapReduce 的 SQL 化主要有兩個(gè)框架實(shí)現(xiàn)。一個(gè)是 Apache Pig,一個(gè)是 Apache Hive。前者相對(duì)小眾,后者是絕大部分公司的選擇。
Hive 實(shí)現(xiàn)的基本功能就是把你的 SQL 語(yǔ)句解釋成一個(gè)個(gè) MapReduce 任務(wù)去執(zhí)行。
比如我們現(xiàn)在創(chuàng)建這么一張測(cè)試表:
- create table test2018(id int, name string, province string);
然后通過(guò) explain 命令來(lái)查看下面這條 select 語(yǔ)句的執(zhí)行計(jì)劃:
- explain select province, count(*) from test2018 group by province;
可以看到,Hive 把剛才的 SQL 語(yǔ)句解析成了 MapReduce 任務(wù)。這條 SQL 很簡(jiǎn)單,如果是復(fù)雜的 SQL,可能會(huì)被解析成很多個(gè)連續(xù)依賴執(zhí)行的 MapReduce 任務(wù)。
另外,既然是 SQL,很自然的,Hive 還提供了庫(kù)、表這類(lèi)抽象,讓你來(lái)更好的組織你的數(shù)據(jù)。甚至傳統(tǒng)的數(shù)據(jù)倉(cāng)庫(kù)技術(shù)也能很好地以 Hive 為基礎(chǔ)開(kāi)展。這又是另一個(gè)很大的話題,這里不再展開(kāi)。
計(jì)算速度
MapReduce 每個(gè)階段的結(jié)果都需要落磁盤(pán),然后再讀出來(lái)給下一階段處理。由于是分布式系統(tǒng),所以也有很多需要網(wǎng)絡(luò)傳輸數(shù)據(jù)的情況。而磁盤(pán) IO 和網(wǎng)絡(luò) IO 都是非常消耗時(shí)間的操作。后者可以通過(guò)數(shù)據(jù)本地性(locality)解決 -- 把任務(wù)分配到數(shù)據(jù)所在的機(jī)器上執(zhí)行,前者就很大程度地拖慢了程序執(zhí)行的速度。
在這個(gè)問(wèn)題上解決的比較好的是 Apache Spark。 Spark 號(hào)稱基于內(nèi)存的分布式計(jì)算框架,所有計(jì)算都在內(nèi)存中進(jìn)行,只要當(dāng)內(nèi)存不夠時(shí)才會(huì) spill 到磁盤(pán)。因此能盡可能地減少磁盤(pán)操作。
同時(shí),Spark 基于 DAG(Directed Acyclic Graph)來(lái)組織執(zhí)行過(guò)程,知道了整個(gè)執(zhí)行步驟的先后依賴關(guān)系,也就有了優(yōu)化物理執(zhí)行計(jì)劃的可能,很多無(wú)謂和重復(fù)的 IO 操作也就被省略了。
另外一個(gè)不得不提的點(diǎn)是,MapReduce 編程模型太過(guò)簡(jiǎn)單,導(dǎo)致很多情況下一些并不復(fù)雜的運(yùn)算卻需要好幾個(gè) MapReduce 任務(wù)才能完成,這也嚴(yán)重拖累了性能。而 Spark 提供了類(lèi)型非常豐富的操作,也很大程度上提升了性能。
編程模型
上一段提到 Spark 類(lèi)型豐富的操作提升了性能,另一個(gè)好處就是開(kāi)發(fā)復(fù)雜度也變低了很多。相比較之下,MapReduce 編程模型的表達(dá)能力就顯得非常羸弱了,畢竟很多操作硬要去套用先 map 再 reduce 會(huì)非常麻煩。
以分組求平均值為例,在 MapReduce 框架下,需要像前面說(shuō)的那樣寫(xiě)兩個(gè)類(lèi),甚至有人會(huì)寫(xiě)成兩個(gè) MapReduce 任務(wù)。
而在 Spark 里面,只要一句 ds.groupby(key).avg() 就搞定了。 真的是沒(méi)有對(duì)比就沒(méi)有傷害。
九
毫無(wú)疑問(wèn),每個(gè)人都希望數(shù)據(jù)越早算出來(lái)越好,所以實(shí)時(shí)計(jì)算或者叫流計(jì)算一直是研究和使用的熱點(diǎn)。
前面提到,Hadoop 是大數(shù)據(jù)領(lǐng)域的標(biāo)準(zhǔn)基礎(chǔ)設(shè)施,提供了 HDFS 作為存儲(chǔ)系統(tǒng),以及 MapReduce 作為計(jì)算引擎,但卻并沒(méi)有提供對(duì)流處理的支持。因此,流處理這個(gè)領(lǐng)域也就出現(xiàn)了很多競(jìng)爭(zhēng)者,沒(méi)有形成早些年 MapReduce 那樣一統(tǒng)江湖的局面。
這里我們簡(jiǎn)單看下目前流行度***的三個(gè)流處理框架:Spark Streaming、Storm 和 Flink。
既然能各自鼎立天下,這些框架肯定也都各有優(yōu)缺點(diǎn)。篇幅有限,這里我們挑選幾個(gè)典型的維度來(lái)做對(duì)比。
編程范式
常規(guī)來(lái)說(shuō),可以把編程范式或者通俗點(diǎn)說(shuō)程序的寫(xiě)法分為兩類(lèi):命令式(Imperative)和聲明式(Declarative)。前者需要一步步寫(xiě)清楚「怎么做」,更接近機(jī)器,后者只用寫(xiě)需要「做什么」,更接近人。前文提到 WordCount 的例子,MapReduce 的版本就屬于命令式,Spark 的版本就屬于聲明式。
不難看出,命令式更繁瑣但也賦予了程序員更強(qiáng)的控制力,聲明式更簡(jiǎn)潔但也可能會(huì)失去一定的控制力。
延遲(latency)
延遲的概念很簡(jiǎn)單,就是一條數(shù)據(jù)從產(chǎn)生到被處理完經(jīng)歷的時(shí)間。注意這里的時(shí)間是實(shí)際經(jīng)歷的時(shí)間,而不一定是真正處理的時(shí)間。
吞吐量(throughput)
吞吐量就是單位時(shí)間內(nèi)處理數(shù)據(jù)的數(shù)量。和上面的延遲一起通常被認(rèn)為是流處理系統(tǒng)在性能上最為重要的兩個(gè)指標(biāo)。
下面我們就從這幾個(gè)維度來(lái)看看這三個(gè)流處理框架。
Spark Streaming
Spark Streaming 和我們前面提到的用于離線批處理的 Spark 基于同樣的計(jì)算引擎,本質(zhì)上是所謂的 mirco-batch,只是這個(gè) batch 可以設(shè)置的很小,也就有了近似實(shí)時(shí)的效果。
編程范式
和離線批處理的 Spark 一樣,屬于聲明式,提供了非常豐富的操作,代碼非常簡(jiǎn)潔。
延遲
由于是 micro-batch,延遲相對(duì)來(lái)一條處理一條的實(shí)時(shí)處理引擎會(huì)差一些,通常在秒級(jí)。當(dāng)然可以把 batch 設(shè)的更小以減小延遲,但代價(jià)是吞吐量會(huì)降低。
由于是基于批處理做的流處理,所以就決定了 Spark Streaming 延遲再怎么調(diào)優(yōu)也達(dá)不到有些場(chǎng)景的要求。為了解決這個(gè)問(wèn)題,目前尚未正式發(fā)布的 Spark 2.3 將會(huì)支持 continuous processing,提供不遜于 native streaming 的延遲。continuous processing 顧名思義摒棄了 mirco-batch 的偽流處理,使用和 native streaming 一樣的 long-running task 來(lái)處理數(shù)據(jù)。到時(shí)候?qū)⒁耘渲玫姆绞阶層脩糇约哼x擇 micro-batch 還是 continuous processing 來(lái)做流式處理。
吞吐量
由于是 micro-batch,吞吐量比嚴(yán)格意義上的實(shí)時(shí)處理引擎高不少。從這里也可以看到,micro-batch 是個(gè)有利有弊的選擇。
Storm
Storm 的編程模型某種程度上說(shuō)和 MapReduce 很像,定義了 Spout 用來(lái)處理輸入,Bolt 用來(lái)做處理邏輯。多個(gè) Spout 和 Bolt 互相連接、依賴組成 Topology。Spout 和 Bolt 都需要像 MR 那樣定義一些類(lèi)再實(shí)現(xiàn)一些具體的方法。
編程范式
很顯然屬于命令式。
延遲
Storm 是嚴(yán)格意義上的實(shí)時(shí)處理系統(tǒng)(native streaming),來(lái)一條數(shù)據(jù)處理一條,所以延遲很低,一般在毫秒級(jí)別。
吞吐量
同樣,由于來(lái)一條數(shù)據(jù)處理一條,為了保證容錯(cuò)(falt tolerance) 采用了逐條消息 ACK 的方式,吞吐量相對(duì) Spark Streaming 這樣的 mirco-batch 引擎就差了不少。
需要補(bǔ)充說(shuō)明的是,Storm 的升級(jí)版 Trident 做了非常大的改動(dòng),采用了類(lèi)似 Spark Streaming 的 mirco-batch 模式,因此延遲比 Storm 高(變差了),吞吐量比 Storm 高(變好了),而編程范式也變成了開(kāi)發(fā)成本更低的聲明式。
Flink
Flink 其實(shí)和 Spark 一樣都想做通用的計(jì)算引擎,這點(diǎn)上比 Storm 的野心要大。但 Flink 采用了和 Spark 完全相反的方式來(lái)支持自己本來(lái)不擅長(zhǎng)的領(lǐng)域。Flink 作為 native streaming 框架,把批處理看做流處理的特殊情況來(lái)達(dá)到邏輯抽象上的統(tǒng)一。
- 編程范式
典型的聲明式。
- 延遲
和 Storm 一樣,F(xiàn)link 也是來(lái)一條處理一條,保證了很低的延遲。
- 吞吐量
實(shí)時(shí)處理系統(tǒng)中,往往低延遲帶來(lái)的就是低吞吐量(Storm),高吞吐量又會(huì)導(dǎo)致高延遲(Spark Streaming)。這兩個(gè)性能指標(biāo)也是常見(jiàn)的 trade-off 了,通常都需要做取舍。
但 Flink 卻做到了低延遲和高吞吐兼得。關(guān)鍵就在于相比 Storm 每條消息都 ACK 的方式,F(xiàn)link 采取了 checkpoint 的方式來(lái)容錯(cuò),這樣也就盡可能地減小了對(duì)吞吐量的影響。
十
到此為止,批處理和流處理我們都有了大致的了解。不同的應(yīng)用場(chǎng)景總能二選一找到適合的方案。
然而,卻也有些情況使得我們不得不在同一個(gè)業(yè)務(wù)中同時(shí)實(shí)現(xiàn)批處理和流處理兩套方案:
- 流處理程序故障,恢復(fù)時(shí)間超過(guò)了流數(shù)據(jù)的保存時(shí)間,導(dǎo)致數(shù)據(jù)丟失。
- 類(lèi)似多維度月活計(jì)算,精確計(jì)算需要保存所有用戶 id 來(lái)做去重,由此帶來(lái)的存儲(chǔ)開(kāi)銷(xiāo)太大,因此只能使用 hyperloglog 等近似算法。
- ...
這些場(chǎng)景下,往往會(huì)采用流處理保證實(shí)時(shí)性,再加批處理校正來(lái)保證數(shù)據(jù)正確性。由此還專(zhuān)門(mén)產(chǎn)生了一個(gè)叫 Lambda Architecture 的架構(gòu)。

流處理層和批處理層各自獨(dú)立運(yùn)行輸出結(jié)果,查詢層根據(jù)時(shí)間選擇用哪份結(jié)果展示給用戶。比如最近一天用流處理的實(shí)時(shí)但不一定精確的結(jié)果,超過(guò)一天就用批處理不實(shí)時(shí)但精確的結(jié)果。
Lambda Architecture 確實(shí)能解決問(wèn)題,但流處理和批處理兩套程序,再加上頂在前面的查詢服務(wù),帶來(lái)的開(kāi)發(fā)和維護(hù)成本也不小。
因此,近年來(lái)也有人提出了 Kappa Architecture,帶來(lái)了另一種統(tǒng)一的思路,但也有明顯的缺點(diǎn)。限于篇幅這里不再贅述。
十一
計(jì)算框架是大數(shù)據(jù)領(lǐng)域競(jìng)爭(zhēng)最激烈的方向之一,除了前面提到的方案,還有 Impla、Tez、 Presto、Samza 等等。很多公司都喜歡造輪子,并且都聲稱自己的輪子比別人的好。而從各個(gè)框架的發(fā)展歷程來(lái)看,又有很明顯的互相借鑒的意思。
在眾多選擇中,Spark 作為通用型分布式計(jì)算框架的野心和能力已經(jīng)得到充分的展示和證實(shí),并且仍然在快速地進(jìn)化: Spark SQL 支持了類(lèi) Hive 的純 SQL 數(shù)據(jù)處理,Spark Streaming 支持了實(shí)時(shí)計(jì)算,Spark MLlib 支持了機(jī)器學(xué)習(xí),Spark GraphX 支持了圖計(jì)算。而流處理和批處理底層執(zhí)行引擎的統(tǒng)一,也讓 Spark 在 Lambda Architecture 下的開(kāi)發(fā)和維護(hù)成本低到可以接受。
所以,出于技術(shù)風(fēng)險(xiǎn)、使用和維護(hù)成本的考慮,Spark 是我們做大數(shù)據(jù)計(jì)算的***。當(dāng)然,如果有些實(shí)際應(yīng)用場(chǎng)景 Spark 不能很好的滿足,也可以選擇其他計(jì)算框架作為補(bǔ)充。比如如果對(duì)延遲(latency) 非常敏感,那 Storm 和 Flink 就值得考慮了。
十二
如開(kāi)篇所說(shuō),大數(shù)據(jù)是個(gè)非常廣的領(lǐng)域,初學(xué)者很容易迷失。希望通過(guò)這篇文章,能讓大家對(duì)大數(shù)據(jù)的基礎(chǔ)有個(gè)大概的了解。限于篇幅,很多概念和技術(shù)都點(diǎn)到即止,有興趣的同學(xué)可以再擴(kuò)展去學(xué)習(xí)。相信,打好了這個(gè)基礎(chǔ),再去學(xué)大數(shù)據(jù)領(lǐng)域的其他技術(shù)也會(huì)輕松一些。