一文讀懂大數(shù)據(jù)開源生態(tài)圈

從Google的大數(shù)據(jù)三駕馬車談起

Google在2003年到2004年先后發(fā)布了被稱為大數(shù)據(jù)三駕馬車的三篇重要論文,分別是分布式數(shù)據(jù)處理MapReduce、分布式數(shù)據(jù)存儲GFS以及列式存儲 數(shù)據(jù)庫BigTable永丝。正是谷歌的這三駕馬車掀開了大數(shù)據(jù)時代的序幕煌集,谷歌也毋庸置疑的成為了當代大數(shù)據(jù)技術(shù)的始祖,可以說現(xiàn)今幾乎所有的大數(shù)據(jù)技術(shù)都是由這三種技術(shù)發(fā)展而來的街州。

谷歌的這三駕馬車之所以能夠奠定大數(shù)據(jù)技術(shù)的基礎(chǔ),是因為這三種技術(shù)涵蓋了大數(shù)據(jù)技術(shù)所需要的海量存儲、海量數(shù)據(jù)庫隶校、分布式計算這三個最基本的需求。
大數(shù)據(jù)之所以能被稱為大數(shù)據(jù)蛹锰,就是因為要處理的數(shù)據(jù)量比一般情況下大得多深胳,大到單獨一臺機器遠遠無法承擔。
為了處理更大量的數(shù)據(jù)铜犬,傳統(tǒng)的解決辦法是升級機器舞终,配上更大的磁盤容量轻庆,更多核數(shù)的CPU,更大的內(nèi)存敛劝,來存儲和處理更多的數(shù)據(jù)余爆,這種做法叫做縱向擴展。這種做法簡單直接夸盟,但是成本高昂蛾方。更麻煩的是,單臺機器的性能是有極限的满俗,對于現(xiàn)在動不動就要上PB的數(shù)據(jù)規(guī)模來說转捕,再高的配置也遠遠不夠。更不用說唆垃,單臺機器還存在機器故障后數(shù)據(jù)丟失的風(fēng)險五芝,數(shù)據(jù)的可靠性難以保證。
谷歌的三駕馬車則為大數(shù)據(jù)問題提供了更優(yōu)的解決思路辕万,那就是增多機器數(shù)而非提升機器性能枢步,即橫向擴展。按照這種思路渐尿,可以使用大量的廉價通用服務(wù)器構(gòu)建一個巨大的集群醉途,對海量的數(shù)據(jù)進行分布式的存儲和處理。俗話說砖茸,三個臭皮匠頂上諸葛亮隘擎,100臺廉價服務(wù)器加在一起的性能是要遠高于單獨一臺頂配服務(wù)器的。因此利用谷歌的這種思路凉夯,你就能通過堆機器這種方法以相對低的成本獲得以往無法想象的數(shù)據(jù)處理性能货葬。

谷歌的這三駕馬車雖然牛逼,但是一直以來都只作為谷歌的內(nèi)部技術(shù)被使用劲够,并沒有向業(yè)界開源震桶,因此真正熟知并理解這三種技術(shù)的人其實并不多。真正被世人熟知的大數(shù)據(jù)技術(shù)始祖其實是Hadoop征绎,這個后人借助谷歌三駕馬車思想而構(gòu)建的開源大數(shù)據(jù)套件蹲姐。

大數(shù)據(jù)存儲之Hadoop HDFS

大數(shù)據(jù)處理的第一步自然是要先找到一個存放數(shù)據(jù)的地方。HDFS提供的便是海量文件的存儲技術(shù)人柿。
HDFS是谷歌GFS技術(shù)的開源實現(xiàn)版本柴墩。HDFS將文件分成塊分布到不同的機器上,從而借助成千上萬臺廉價PC實現(xiàn)海量數(shù)據(jù)的高可靠性存儲凫岖。
在HDFS中拐邪,一份文件會擁有多份分布在不同機器上的復(fù)制,因此即使某臺機器壞了數(shù)據(jù)也不會丟失隘截,具備極高的可靠性扎阶。
盡管HDFS使用了很多復(fù)雜的分布式存儲技術(shù),但是對用戶來說婶芭,使用HDFS和使用以往的文件系統(tǒng)一樣簡單东臀。用戶不用關(guān)心文件是如何分塊的或者文件存儲在集群的哪個節(jié)點上這種問題,只需要連上HDFS犀农,之后像使用Linux本地文件系統(tǒng)一樣使用HDFS即可惰赋。

大數(shù)據(jù)計算之Hadoop MapReduce

數(shù)據(jù)有地方存了,接下來要做的當然是對數(shù)據(jù)進行分析了呵哨。
Hadoop MapReduce和Google的MapReduce一樣赁濒,提供海量數(shù)據(jù)的分布式處理能力。通過MapReuduce孟害,成百上千臺機器可以共同協(xié)作去計算同一個問題拒炎,從而依靠大量機器的共同力量去解決海量數(shù)據(jù)的計算問題。
MapReduce通過Map和Reduce兩個主要階段挨务。在Map階段击你,MapReuce把數(shù)據(jù)劃分成很多個部分讀入,然后將數(shù)據(jù)解析成方便計算和key-value形式谎柄。在Reduce階段丁侄,數(shù)據(jù)按照key的不同被再次劃分到不同的機器,然后每臺機器格子對數(shù)據(jù)進行聚合計算朝巫,最終形成用戶期望的計算結(jié)果鸿摇。下邊一張圖可以讓你對MapReduce的過程有一個更形象的認識:


MapReduce過程

MapReduce實際的計算流程要比上邊描述的復(fù)雜的多,而你只要記住劈猿,MapReduce解決的本質(zhì)問題就是如何將數(shù)據(jù)合理的分布到很多臺機器上進行計算拙吉,以及如何合理的合并多臺機器上的計算結(jié)果。

Pig:

通過編寫MapReduce腳本已經(jīng)可以借助大數(shù)據(jù)手段解決幾乎所有的海量數(shù)據(jù)的計算和分析問題糙臼。但是庐镐,MapReduce存在一個嚴重的問題,那就是MapReduce腳本編寫起來實在是太費勁了变逃!想編寫MapReuce程序必逆,你首先需要弄懂MapReduce的原理,合理的把計算過程拆分成Map和Reduce兩步揽乱,然后你還需要正確的配置一大堆MapReduce的執(zhí)行參數(shù)名眉,之后提交任務(wù),反復(fù)檢查運行狀態(tài)凰棉,檢查運行結(jié)果损拢,這時候如果你的MapReduce腳本存在問題,那么你還需要去翻log分析問題出在哪里撒犀,然后修改你的腳本再來一遍福压。因此掏秩,想寫出一個能用的MapReduce程序不但有較高的難度,還需要耗費大量的時間和精力荆姆。

那么蒙幻,如果我很懶,實在不想去寫復(fù)雜的MapReduce程序胆筒,那么有沒有什么辦法能夠簡化寫MapReduce的這個繁雜的過程呢邮破?當然有,那就是Pig了(此時你一定明白Pig這個名稱的由來了仆救,就是為懶人服務(wù)的工具)抒和。Pig的意義就是替你編寫復(fù)雜的MapReduce腳本,從而簡化MapReduce的開發(fā)流程彤蔽,大大縮短開發(fā)周期摧莽。
Pig實現(xiàn)了一種叫做Pig Latin的語言,你只需要編寫簡單的幾行Latin代碼铆惑,就可以實現(xiàn)在MapReduce需要大量代碼才能實現(xiàn)的功能范嘱。Pig幫你預(yù)設(shè)了多種數(shù)據(jù)結(jié)構(gòu),從而幫助你方便的將輸入文本中的內(nèi)容轉(zhuǎn)換為Pig中結(jié)構(gòu)化數(shù)據(jù)员魏。Pig還提供了諸如filter丑蛤、group by、max撕阎、min等常用的計算方法受裹,幫助你快速實現(xiàn)一些常規(guī)的數(shù)據(jù)計算。執(zhí)行和查看結(jié)果在Pig中也非常的簡單虏束,你不再需要配置MapReduce中的一大堆復(fù)雜的參數(shù)棉饶,也不再需要手動到HDFS上下載運行結(jié)果并對下載結(jié)果進行排版,Pig會直接把運行結(jié)果用良好的排版展示給你看镇匀,就像在SQL數(shù)據(jù)庫中一樣方便照藻。

有了Pig,不用寫MapReduce了汗侵,數(shù)據(jù)開發(fā)也快多了幸缕。但是Pig仍然存在一些局限,因為使用Pig從本質(zhì)上來說還相當于用MapReduce晰韵,只是腳本的編寫比以前快了发乔,但是你仍然要一行一行的去寫Pig腳本,從而一步一步的實現(xiàn)你的數(shù)據(jù)分析過程雪猪。此時如果有工程師說自己已經(jīng)懶得無可救藥了栏尚,連Pig腳本也不想寫;或者有數(shù)據(jù)分析師說自己不懂技術(shù)只恨,壓根不會寫腳本译仗,只會寫幾句簡單的SQL抬虽,那么有沒有什么比Pig還簡單的辦法呢?那么下邊就該Hive出場了古劲!

Hive:

Hive是一種數(shù)據(jù)倉庫軟件斥赋,可以在分布式存儲系統(tǒng)的基礎(chǔ)之上(例如HDFS)構(gòu)建支持SQL的海量數(shù)據(jù)存儲數(shù)據(jù)庫。
簡單的說产艾,Hive的作用就是把SQL轉(zhuǎn)換為MapReduce任務(wù)進行執(zhí)行,拿到結(jié)果后展示給用戶滑绒。如此一來闷堡,你就可以使用普通的SQL去查詢分布在分布式系統(tǒng)上的海量數(shù)據(jù)。
盡管Hive能提供和普通SQL數(shù)據(jù)庫一樣好用的SQL語句疑故,但是Hive的查詢時延是要遠高于普通數(shù)據(jù)庫的杠览,畢竟查詢時間和數(shù)據(jù)規(guī)模二者還是不能兼得的啊纵势!由于Hive并且每次查詢都需要運行一個復(fù)雜的MapReduce任務(wù)踱阿,因此Hive SQL的查詢延時是遠高于普通SQL的。與此同時钦铁,對于傳統(tǒng)數(shù)據(jù)庫必備的行更新软舌、事務(wù)、索引這一類“精細化”操作牛曹,大條的Hive自然也都不支持佛点。畢竟Hive的誕生就是為了處理海量數(shù)據(jù),用Hive處理小數(shù)據(jù)無異于殺雞用牛刀黎比,自然是無法得到理想的效果超营,因此,Hive只適合海量數(shù)據(jù)的SQL查詢阅虫。

Spark

有了Hive演闭,不會編程的你也能用SQL分析大數(shù)據(jù)了,世界似乎已經(jīng)美好了很多颓帝∶着觯可惜好戲不長,慢慢的躲履,你還發(fā)現(xiàn)Hive依舊有一堆的問題见间,最典型的問題就是查詢時延太長(這里特指MapReduce Hive,而非Spark Hive)工猜。受限于MapReduce任務(wù)的執(zhí)行時間米诉,查一次Hive快則幾十分鐘,慢則幾小時都是有可能的篷帅。試想領(lǐng)導(dǎo)著急的問你還要報表史侣,而你只能無奈的等待緩慢的Hive查詢運行完拴泌,此時的你一定急的想砸顯示屏了。那么有沒有什么既好用惊橱,又執(zhí)行迅速的大數(shù)據(jù)工具呢蚪腐?下邊就該我們的新星級產(chǎn)品Spark登場了。

與MapReduce類似税朴,Spark同樣是分布式計算引擎回季,只是Spark的誕生要比MapReduce晚一些,但是Spark后來者居上正林,如今在很多領(lǐng)域都大有取代MapReduce的趨勢泡一。

Spark相較于MapReduce最大的特點就是內(nèi)存計算和對DAG模型的良好支持,借助這些特點觅廓,對于計算任務(wù)鼻忠,尤其是需要分很多個階段進行的復(fù)雜計算任務(wù),Spark的執(zhí)行速度要遠遠快于MapReeduce杈绸。
在MapReduce執(zhí)行過程中帖蔓,需要將中間數(shù)據(jù)先寫入到磁盤中,然后再加載到各個節(jié)點上進行計算瞳脓,受限于巨大的磁盤IO開銷塑娇,MapReduce的執(zhí)行經(jīng)常要很長時間。而Spark則是將中間數(shù)據(jù)加載在內(nèi)存中篡殷,因此能取得遠高于MapReduce的執(zhí)行速度钝吮。

Spark的優(yōu)點還遠不止此。相較MapReduce板辽,Spark提供了很多高層次封裝的功能奇瘦,在易用性上和功能豐富程度上都要遠遠高于MapReduce。用Spark你甚至只需要一行代碼就能實現(xiàn)group劲弦、sort等多種計算過程耳标。這點上Spark可以說是同時融合了Pig和Hive的特點,能夠用簡單幾行代碼實現(xiàn)以往MapReduce需要大量代碼才能實現(xiàn)的功能邑跪。下邊來一行Spark代碼讓大家感受下:

val wordCounts = textFile.flatMap(line => line.split(" ")).groupByKey(identity).count()

以上代碼實現(xiàn)Word Count次坡。
除此之外,Spark還支持Python画畅、Scala砸琅、Java三種開發(fā)語言,對于Python和Scala甚至還提供了交互式操作功能轴踱,對于非Java開發(fā)者以及科研工作者真是友好到爆症脂,事實上Spark確實也廣受科研工作者的歡迎。
新版本的Spark還提供了Spark SQL、Spark streaming(后邊會介紹)等高層次的功能诱篷,前者提供類似Hive的SQL查詢壶唤,后者提供強大的實時計算功能(后邊會詳細介紹),大有一統(tǒng)大數(shù)據(jù)分析領(lǐng)域的趨勢棕所。因此Spark絕對是當今發(fā)展勢頭最好的大數(shù)據(jù)組件之一闸盔。

不過Spark也并非真的就無敵了,內(nèi)存計算的特點還是會對Spark能夠應(yīng)對的數(shù)據(jù)規(guī)模產(chǎn)生影響琳省。另外迎吵,對于計算過程的控制和調(diào)優(yōu),Spark也不如MapReduce靈活针贬。

Storm

有了前邊講的這一系列工具钓觉,我們已經(jīng)能夠?qū)A繑?shù)據(jù)進行方便的計算分析了。但是前邊的這些工具坚踩,從基礎(chǔ)的MapReduce到簡單易用的Hive,都依然存在一個問題瓤狐,那就是計算過程需要較長的時間瞬铸。也就是說,從你開始執(zhí)行數(shù)據(jù)分析任務(wù)础锐,到MapReduce生成你要的結(jié)果嗓节,常常需要若干小時的時間。由于這個延時的存在皆警,MapReduce得到的數(shù)據(jù)分析結(jié)果是要比線上業(yè)務(wù)慢半拍的拦宣,例如今天得到昨天的日志分析結(jié)果,也因此信姓,MapReduce又被稱作離線數(shù)據(jù)處理或者批處理鸵隧。
但是,如果你希望能夠立刻得到數(shù)據(jù)的分析結(jié)果意推,例如像天貓雙十一實時大屏那樣實時的顯示最新的成交額豆瘫,那么你就需要一些實時數(shù)據(jù)處理工具了。

最新火起來的實時數(shù)據(jù)處理工具要當屬Apache Storm了菊值。我們直到在MapReduce這類離線處理工具中外驱,數(shù)據(jù)是要一批一批的被處理的,并且每批數(shù)據(jù)都需要一定的處理時延腻窒。而在Storm中昵宇,是沒有批這個概念的,在Storm中數(shù)據(jù)就如同水龍頭中的水一樣源源不斷地流出儿子,并被實時的進行處理瓦哎。因此,在Storm中,只要你搭建好了數(shù)據(jù)處理流程杭煎,數(shù)據(jù)就會源源不斷的恩够,永不停止的被接受并處理,并且你可以隨時看到數(shù)據(jù)的最新處理結(jié)果羡铲。


Storm

盡管Storm和MapReduce的處理流程差異很大蜂桶,但是它們的基本思路是一致的,都是把數(shù)據(jù)按照key分散到不同的機器上也切,之后由各個機器對數(shù)據(jù)進行處理扑媚,最終將各個機器的計算結(jié)果匯總在一起。
不同的是雷恃,Storm中的數(shù)據(jù)處理是一條一條實時進行的疆股,因此結(jié)果會處于一種不斷的刷新過程中;而MapReduce是一次性處理完所有輸入數(shù)據(jù)并得到最終結(jié)果倒槐,因此你將會直接看到最終結(jié)果旬痹。
例如,假設(shè)有大量的文章需要統(tǒng)計單詞的出現(xiàn)次數(shù)讨越,對于MapReduce两残,你將直接看到最終結(jié)果:hello: 3, world: 2, you: 6;而對于Storm把跨,你將會看到hello:1, world:1, you: 1, world: 2, you:2……這樣的處于不斷刷新中的統(tǒng)計結(jié)果人弓。

另外值得一提的是,Storm和MapReduce也并不是一個互相取代的關(guān)系着逐,而是一個互補的關(guān)系崔赌。Storm可是讓你實時的得到數(shù)據(jù)的最新統(tǒng)計狀態(tài),但是在數(shù)據(jù)吞吐量方面是要低于MapReduce的耸别,并且對于相同的數(shù)據(jù)量健芭,如果只關(guān)注最終結(jié)果,MapReuce得到最終結(jié)果所需的時間和資源肯定是要小于Storm的太雨。因此吟榴,如果你需要實時的查看數(shù)據(jù)的最新統(tǒng)計狀態(tài),用Storm囊扳;如果你只關(guān)注數(shù)據(jù)的最終統(tǒng)計結(jié)果吩翻,用MapReduce。

Flink和Spark Streaming

談完Storm锥咸,就必須順帶也談一下另外兩種同樣火爆的實時數(shù)據(jù)處理工具狭瞎,那就是Flink和Spark Straming。這兩種技術(shù)要晚于storm誕生搏予,但是現(xiàn)在大有后來者居上的趨勢熊锭。

Flink與Storm非常類似,都能夠提供實時數(shù)據(jù)流處理功能。區(qū)別在于Flink還能夠支持一些更高層的功能碗殷,例如group by這種常用算法精绎。另外,F(xiàn)link還具備比Storm更高的吞吐量和更低的延時锌妻,這是因為Flink在處理完一條條數(shù)據(jù)的時候是分組批量確認的代乃,而Storm則是一條一條確認。Flink的這種特性帶來了很大的性能優(yōu)勢仿粹,但是也會對單條數(shù)據(jù)的處理時延帶來很大的不穩(wěn)定因素搁吓,因為任何相鄰數(shù)據(jù)的處理失敗都會導(dǎo)致整組數(shù)據(jù)被重新處理,從而嚴重影響一組數(shù)據(jù)的處理時延吭历。因此堕仔,如果你追求更高的吞吐量,可以選擇Flink晌区,如果你對每條數(shù)據(jù)的處理時延都有極高的要求摩骨,那么選Storm。

至于Spark Streaming朗若,其實并不能算得上是純正的實時數(shù)據(jù)處理仿吞,因為Spark Streaming在處理流數(shù)據(jù)時依然用的是批處理的模式,即湊齊一批數(shù)據(jù)后啟動一個Spark任務(wù)得到處理結(jié)果捡偏。你甚至可以把Spark Streaming簡單看成是帶流輸入的Spark。得益于Spark任務(wù)執(zhí)行快速的優(yōu)點峡迷,盡管Spark Streaming是一種偽實時處理系統(tǒng)银伟,但是依然能得到還不錯的實時性(秒級),當然要跟Storm比的話實時性還是差不少的绘搞,但是Spark在吞吐量方面要強于Storm彤避。

Spark Streaming和Flink除了吞吐量這個優(yōu)點外,還有另一個重要的優(yōu)點夯辖,那就是能夠同時支持批處理和實時數(shù)據(jù)處理琉预。也就是說,你只需要一套系統(tǒng)就能夠同時分析你的實時數(shù)據(jù)和離線數(shù)據(jù)蒿褂,這對于架構(gòu)精簡(tōu lǎn)來說是大有好處的圆米。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市啄栓,隨后出現(xiàn)的幾起案子娄帖,更是在濱河造成了極大的恐慌,老刑警劉巖昙楚,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件近速,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機削葱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門奖亚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人析砸,你說我怎么就攤上這事昔字。” “怎么了干厚?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵李滴,是天一觀的道長。 經(jīng)常有香客問我蛮瞄,道長所坯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任挂捅,我火速辦了婚禮芹助,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闲先。我一直安慰自己状土,他們只是感情好,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布伺糠。 她就那樣靜靜地躺著蒙谓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪训桶。 梳的紋絲不亂的頭發(fā)上累驮,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機與錄音舵揭,去河邊找鬼谤专。 笑死,一個胖子當著我的面吹牛午绳,可吹牛的內(nèi)容都是我干的置侍。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼拦焚,長吁一口氣:“原來是場噩夢啊……” “哼蜡坊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赎败,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤算色,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后螟够,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體灾梦,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡峡钓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了若河。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片能岩。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖萧福,靈堂內(nèi)的尸體忽然破棺而出拉鹃,到底是詐尸還是另有隱情,我是刑警寧澤鲫忍,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布膏燕,位于F島的核電站,受9級特大地震影響悟民,放射性物質(zhì)發(fā)生泄漏坝辫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一射亏、第九天 我趴在偏房一處隱蔽的房頂上張望近忙。 院中可真熱鬧,春花似錦智润、人聲如沸及舍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锯玛。三九已至,卻和暖如春兼蜈,著一層夾襖步出監(jiān)牢的瞬間更振,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工饭尝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人献宫。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓钥平,卻偏偏與公主長得像,于是被迫代替她去往敵國和親姊途。 傳聞我的和親對象是個殘疾皇子涉瘾,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容