java大數(shù)據(jù)之spark

一、Spark簡介

1.1 Spark是什么

Spark是一個通用的并行計算框架,由UCBerkeley的AMP實驗室開發(fā)。Spark基于map reduce 算法模式實現(xiàn)的分布式計算,擁有Hadoop MapReduce所具有的優(yōu)點;但不同于Hadoop MapReduce的是Job中間輸出和結(jié)果可以保存在內(nèi)存中,從而不再需要讀寫HDFS,節(jié)省了磁盤IO耗時,號稱性能比Hadoop快100倍。

1.2 Spark官網(wǎng)

http://spark.apache.org/

1.3 Spark架構(gòu)及生態(tài)

(1)Spark Core:包含Spark的基本功能;尤其是定義RDD的API、操作以及這兩者上的動作。其他Spark的庫都是構(gòu)建在RDD和Spark Core之上的

(2)Spark SQL:提供通過Apache Hive的SQL變體Hive查詢語言(HiveQL)與Spark進行交互的API。每個數(shù)據(jù)庫表被當(dāng)做一個RDD,Spark SQL查詢被轉(zhuǎn)換為Spark操作。

(3)Spark Streaming:對實時數(shù)據(jù)流進行處理和控制。Spark Streaming允許程序能夠像普通RDD一樣處理實時數(shù)據(jù)

(4)MLlib:一個常用機器學(xué)習(xí)算法庫,算法被實現(xiàn)為對RDD的Spark操作。這個庫包含可擴展的學(xué)習(xí)算法,比如分類、回歸等需要對大量數(shù)據(jù)集進行迭代的操作。

(5)GraphX:控制圖、并行圖操作和計算的一組算法和工具的集合。GraphX擴展了RDD API,包含控制圖、創(chuàng)建子圖、訪問路徑上所有頂點的操作


1.4 Spark架構(gòu)的組成圖

(1)Cluster Manager:在standalone模式中即為Master主節(jié)點,控制整個集群,監(jiān)控worker。在YARN模式中為資源管理器

(2)Worker節(jié)點:從節(jié)點,負(fù)責(zé)控制計算節(jié)點,啟動Executor或者Driver。

(3)Driver: 運行Application 的main()函數(shù)

(4)Executor:執(zhí)行器,是為某個Application運行在worker node上的一個進程


1.5 Spark三種集群模式

1.Standalone獨立集群

2.Mesos, apache mesos

3.Yarn, hadoop yarn

1.6 Spark與hadoop比較

(1)Hadoop有兩個核心模塊,分布式存儲模塊HDFS和分布式計算模塊Mapreduce

(2)spark本身并沒有提供分布式文件系統(tǒng),因此spark的分析大多依賴于Hadoop的分布式文件系統(tǒng)HDFS

(3)Hadoop的Mapreduce與spark都可以進行數(shù)據(jù)計算,而相比于Mapreduce,spark的速度更快并且提供的功能更加豐富

1.7 Spark運行流程

spark運行流程圖如下:


(1)構(gòu)建Spark Application的運行環(huán)境,啟動SparkContext

(2)SparkContext向資源管理器(可以是Standalone,Mesos,Yarn)申請運行Executor資源,并啟動StandaloneExecutorbackend,

(3)Executor向SparkContext申請Task

(4)SparkContext將應(yīng)用程序分發(fā)給Executor

(5)SparkContext構(gòu)建成DAG圖,將DAG圖分解成Stage、將Taskset發(fā)送給Task Scheduler,最后由Task Scheduler將Task發(fā)送給Executor運行

(6)Task在Executor上運行,運行完釋放所有資源

1.8 Spark運行特點

(1)每個Application獲取專屬的executor進程,該進程在Application期間一直駐留,并以多線程方式運行Task。這種Application隔離機制是有優(yōu)勢的,無論是從調(diào)度角度看(每個Driver調(diào)度他自己的任務(wù)),還是從運行角度看(來自不同Application的Task運行在不同JVM中),當(dāng)然這樣意味著Spark Application不能跨應(yīng)用程序共享數(shù)據(jù),除非將數(shù)據(jù)寫入外部存儲系統(tǒng)

(2)Spark與資源管理器無關(guān),只要能夠獲取executor進程,并能保持相互通信就可以了

(3)提交SparkContext的Client應(yīng)該靠近Worker節(jié)點(運行Executor的節(jié)點),最好是在同一個Rack里,因為Spark Application運行過程中SparkContext和Executor之間有大量的信息交換

(4)Task采用了數(shù)據(jù)本地性和推測執(zhí)行的優(yōu)化機制

1.9 Spark運行模式

(1)Spark的運行模式多種多樣,靈活多變,部署在單機上時,既可以用本地模式運行,也可以用偽分布模式運行,而當(dāng)以分布式集群的方式部署時,也有眾多的運行模式可供選擇,這取決于集群的實際情況,底層的資源調(diào)度即可以依賴外部資源調(diào)度框架,也可以使用Spark內(nèi)建的Standalone模式。

(2)對于外部資源調(diào)度框架的支持,目前的實現(xiàn)包括相對穩(wěn)定的Mesos模式,以及hadoop YARN模式

(3)本地模式:常用于本地開發(fā)測試,本地還分別 local 和 local cluster

1.10 Spark特點

(1)運行速度快 => Spark擁有DAG執(zhí)行引擎,支持在內(nèi)存中對數(shù)據(jù)進行迭代計算。官方提供的數(shù)據(jù)表明,如果數(shù)據(jù)由磁盤讀取,速度是Hadoop MapReduce的10倍以上,如果數(shù)據(jù)從內(nèi)存中讀取,速度可以高達(dá)100多倍。

(2)適用場景廣泛 => 大數(shù)據(jù)分析統(tǒng)計,實時數(shù)據(jù)處理,圖計算及機器學(xué)習(xí)

(3)易用性 => 編寫簡單,支持80種以上的高級算子,支持多種語言,數(shù)據(jù)源豐富,可部署在多種集群中

(4)容錯性高。Spark引進了彈性分布式數(shù)據(jù)集RDD (Resilient Distributed Dataset) 的抽象,它是分布在一組節(jié)點中的只讀對象集合,這些集合是彈性的,如果數(shù)據(jù)集一部分丟失,則可以根據(jù)“血統(tǒng)”(即充許基于數(shù)據(jù)衍生過程)對它們進行重建。另外在RDD計算時可以通過CheckPoint來實現(xiàn)容錯,而CheckPoint有兩種方式:CheckPoint Data,和Logging The Updates,用戶可以控制采用哪種方式來實現(xiàn)容錯。

1.11 Spark的適用場景

(1)目前大數(shù)據(jù)處理場景有以下幾個類型:

(2)復(fù)雜的批量處理(Batch Data Processing),偏重點在于處理海量數(shù)據(jù)的能力,至于處理速度可忍受,通常的時間可能是在數(shù)十分鐘到數(shù)小時;

(3)基于歷史數(shù)據(jù)的交互式查詢(Interactive Query),通常的時間在數(shù)十秒到數(shù)十分鐘之間

(4)基于實時數(shù)據(jù)流的數(shù)據(jù)處理(Streaming Data Processing),通常在數(shù)百毫秒到數(shù)秒之間

1.12 Spark基本概念

Application =>Spark的應(yīng)用程序,包含一個Driver program和若干Executor

SparkContext => Spark應(yīng)用程序的入口,負(fù)責(zé)調(diào)度各個運算資源,協(xié)調(diào)各個Worker Node上的Executor

Driver Program =>運行Application的main()函數(shù)并且創(chuàng)建SparkContext

Executor =>是為Application運行在Worker node上的一個進程,該進程負(fù)責(zé)運行Task,并且負(fù)責(zé)將數(shù)據(jù)存在內(nèi)存或者磁盤上。每個Application都會申請各自的Executor來處理任務(wù)

Cluster Manager =>在集群上獲取資源的外部服務(wù) (例如:Standalone、Mesos、Yarn)

Worker Node =>集群中任何可以運行Application代碼的節(jié)點,運行一個或多個Executor進程

Task =>運行在Executor上的工作單元

Job => SparkContext提交的具體Action操作,常和Action對應(yīng)

Stage =>每個Job會被拆分很多組task,每組任務(wù)被稱為Stage,也稱TaskSet

RDD =>是Resilient distributed datasets的簡稱,中文為彈性分布式數(shù)據(jù)集;是Spark最核心的模塊和類

DAGScheduler =>根據(jù)Job構(gòu)建基于Stage的DAG,并提交Stage給TaskScheduler

TaskScheduler =>將Taskset提交給Worker node集群運行并返回結(jié)果

Transformations =>是Spark API的一種類型,Transformation返回值還是一個RDD,所有的Transformation采用的都是懶策略,如果只是將Transformation提交是不會執(zhí)行計算的

Action =>是Spark API的一種類型,Action返回值不是一個RDD,而是一個scala集合;計算只有在Action被提交的時候計算才被觸發(fā)。



二、Spark Core簡介

2.1 Spark Core之RDD

2.1.1 RDD是什么

RDD(Resilient Distributed Dataset)叫做彈性分布式數(shù)據(jù)集,是Spark中最基本的數(shù)據(jù)抽象,它代表一個不可變、可分區(qū)、里面的元素可并行計算的集合。RDD具有數(shù)據(jù)流模型的特點:自動容錯、位置感知性調(diào)度和可伸縮性。RDD允許用戶在執(zhí)行多個查詢時顯式地將工作集緩存在內(nèi)存中,后續(xù)的查詢能夠重用工作集,這極大地提升了查詢速度。

2.1.2 RDD的屬性

(1)partitions(分區(qū)):一組分片(Partition),即數(shù)據(jù)集的基本組成單位。對于RDD來說,每個分片都會被一個計算任務(wù)處理,并決定并行計算的粒度。用戶可以在創(chuàng)建RDD時指定RDD的分片個數(shù),如果沒有指定,那么就會采用默認(rèn)值。默認(rèn)值就是程序所分配到的CPU Core的數(shù)目。

(2)partitioner(分區(qū)方法):一個計算每個分區(qū)的函數(shù)。Spark中RDD的計算是以分片為單位的,每個RDD都會實現(xiàn)compute函數(shù)以達(dá)到這個目的。compute函數(shù)會對迭代器進行復(fù)合,不需要保存每次計算的結(jié)果。

(3)dependencies(依賴關(guān)系):RDD之間的依賴關(guān)系。RDD的每次轉(zhuǎn)換都會生成一個新的RDD,所以RDD之間就會形成類似于流水線一樣的前后依賴關(guān)系。在部分分區(qū)數(shù)據(jù)丟失時,Spark可以通過這個依賴關(guān)系重新計算丟失的分區(qū)數(shù)據(jù),而不是對RDD的所有分區(qū)進行重新計算。

(4)compute(獲取分區(qū)迭代列表):一個Partitioner,即RDD的分片函數(shù)。當(dāng)前Spark中實現(xiàn)了兩種類型的分片函數(shù),一個是基于哈希的HashPartitioner,另外一個是基于范圍的RangePartitioner。只有對于于key-value的RDD,才會有Partitioner,非key-value的RDD的Parititioner的值是None。Partitioner函數(shù)不但決定了RDD本身的分片數(shù)量,也決定了parent RDD Shuffle輸出時的分片數(shù)量。

(5)preferedLocations(優(yōu)先分配節(jié)點列表) :一個列表,存儲存取每個Partition的優(yōu)先位置(preferred location)。對于一個HDFS文件來說,這個列表保存的就是每個Partition所在的塊的位置。按照“移動數(shù)據(jù)不如移動計算”的理念,Spark在進行任務(wù)調(diào)度的時候,會盡可能地將計算任務(wù)分配到其所要處理數(shù)據(jù)塊的存儲位置。

2.1.3 RDD的創(chuàng)建方式

(1)通過讀取文件生成的

由外部存儲系統(tǒng)的數(shù)據(jù)集創(chuàng)建,包括本地的文件系統(tǒng),還有所有Hadoop支持的數(shù)據(jù)集,比如HDFS、Cassandra、HBase等

scala> val file = sc.textFile("/spark/hello.txt")

(2)通過并行化的方式創(chuàng)建RDD

由一個已經(jīng)存在的Scala集合創(chuàng)建。

scala> val array = Array(1,2,3,4,5)

array: Array[Int] = Array(1, 2, 3, 4, 5)

scala> val rdd = sc.parallelize(array)

rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[27] at parallelize at <console>:26

(3)其他方式

讀取數(shù)據(jù)庫等等其他的操作。也可以生成RDD。RDD可以通過其他的RDD轉(zhuǎn)換而來的

2.1.4 RDD算子操作之Transformation

主要做的是就是將一個已有的RDD生成另外一個RDD。Transformation具有l(wèi)azy特性(延遲加載)。Transformation算子的代碼不會真正被執(zhí)行。只有當(dāng)我們的程序里面遇到一個action算子的時候,代碼才會真正的被執(zhí)行。這種設(shè)計讓Spark更加有效率地運行。

常用的Transformation:


2.1.4 RDD算子操作之Action

觸發(fā)代碼的運行,我們一段spark代碼里面至少需要有一個action操作。

常用的Action:


2.1.5 RDD依賴關(guān)系的本質(zhì)內(nèi)幕

由于RDD是粗粒度的操作數(shù)據(jù)集,每個Transformation操作都會生成一個新的RDD,所以RDD之間就會形成類似流水線的前后依賴關(guān)系;RDD和它依賴的父RDD(s)的關(guān)系有兩種不同的類型,即窄依賴(narrow dependency)和寬依賴(wide dependency)。如圖所示顯示了RDD之間的依賴關(guān)系。


從圖中可知:

窄依賴:是指每個父RDD的一個Partition最多被子RDD的一個Partition所使用,例如map、filter、union等操作都會產(chǎn)生窄依賴;(獨生子女)

寬依賴:是指一個父RDD的Partition會被多個子RDD的Partition所使用,例如groupByKey、reduceByKey、sortByKey等操作都會產(chǎn)生寬依賴;(超生)

需要特別說明的是對join操作有兩種情況:

(1)圖中左半部分join:如果兩個RDD在進行join操作時,一個RDD的partition僅僅和另一個RDD中已知個數(shù)的Partition進行join,那么這種類型的join操作就是窄依賴,例如圖1中左半部分的join操作(join with inputs co-partitioned);

(2)圖中右半部分join:其它情況的join操作就是寬依賴,例如圖1中右半部分的join操作(join with inputs not co-partitioned),由于是需要父RDD的所有partition進行join的轉(zhuǎn)換,這就涉及到了shuffle,因此這種類型的join操作也是寬依賴。

總結(jié):

在這里我們是從父RDD的partition被使用的個數(shù)來定義窄依賴和寬依賴,因此可以用一句話概括下:如果父RDD的一個Partition被子RDD的一個Partition所使用就是窄依賴,否則的話就是寬依賴。因為是確定的partition數(shù)量的依賴關(guān)系,所以RDD之間的依賴關(guān)系就是窄依賴;由此我們可以得出一個推論:即窄依賴不僅包含一對一的窄依賴,還包含一對固定個數(shù)的窄依賴。

一對固定個數(shù)的窄依賴的理解:即子RDD的partition對父RDD依賴的Partition的數(shù)量不會隨著RDD數(shù)據(jù)規(guī)模的改變而改變;換句話說,無論是有100T的數(shù)據(jù)量還是1P的數(shù)據(jù)量,在窄依賴中,子RDD所依賴的父RDD的partition的個數(shù)是確定的,而寬依賴是shuffle級別的,數(shù)據(jù)量越大,那么子RDD所依賴的父RDD的個數(shù)就越多,從而子RDD所依賴的父RDD的partition的個數(shù)也會變得越來越多。

2.1.6 RDD依賴關(guān)系下的數(shù)據(jù)流視圖

在spark中,會根據(jù)RDD之間的依賴關(guān)系將DAG圖(有向無環(huán)圖)劃分為不同的階段,對于窄依賴,由于partition依賴關(guān)系的確定性,partition的轉(zhuǎn)換處理就可以在同一個線程里完成,窄依賴就被spark劃分到同一個stage中,而對于寬依賴,只能等父RDD shuffle處理完成后,下一個stage才能開始接下來的計算。


因此spark劃分stage的整體思路是:從后往前推,遇到寬依賴就斷開,劃分為一個stage;遇到窄依賴就將這個RDD加入該stage中。因此在圖2中RDD C,RDD D,RDD E,RDDF被構(gòu)建在一個stage中,RDD A被構(gòu)建在一個單獨的Stage中,而RDD B和RDD G又被構(gòu)建在同一個stage中。


在spark中,Task的類型分為2種:ShuffleMapTask和ResultTask;


簡單來說,DAG的最后一個階段會為每個結(jié)果的partition生成一個ResultTask,即每個Stage里面的Task的數(shù)量是由該Stage中最后一個RDD的Partition的數(shù)量所決定的!而其余所有階段都會生成ShuffleMapTask;之所以稱之為ShuffleMapTask是因為它需要將自己的計算結(jié)果通過shuffle到下一個stage中;也就是說上圖中的stage1和stage2相當(dāng)于mapreduce中的Mapper,而ResultTask所代表的stage3就相當(dāng)于mapreduce中的reducer。


在之前動手操作了一個wordcount程序,因此可知,Hadoop中MapReduce操作中的Mapper和Reducer在spark中的基本等量算子是map和reduceByKey;不過區(qū)別在于:Hadoop中的MapReduce天生就是排序的;而reduceByKey只是根據(jù)Key進行reduce,但spark除了這兩個算子還有其他的算子;因此從這個意義上來說,Spark比Hadoop的計算算子更為豐富。

2.2 Spark Core之Job

spark中對RDD的操作類型分為transformation和action,其中transformation是一種延遲執(zhí)行的操作,并不會立即執(zhí)行而是返回一個含有依賴關(guān)系的RDD,例如map、filter、sortBy、flatMap等操作,當(dāng)調(diào)用action操作時,** spark會通過DAGScheduler構(gòu)建一個job的執(zhí)行拓?fù)?包括多個stage和task,所有的stage和task構(gòu)成了這個action觸發(fā)的job,最后提交到集群中執(zhí)行。

2.3 Spark Core之Stage

Job是由stage構(gòu)成的,spark中的stage只有兩種,shuffleMapStage和resultStage。Job劃分Stage的依據(jù)是shuffle與否(即依賴的類型),當(dāng)DagScheduler進行DAG執(zhí)行圖構(gòu)建時,遇到一個shuffle dependency會生成一個shuffle map stage,調(diào)用鏈最后一個shuffle reduce端將生成為result stage 也叫 final stage.


handleJobSubmitted->submitStage(finalStage)->遞歸調(diào)用submitStage,如果當(dāng)前提交的stage沒有parent stage則直接提交taskSet,否則將當(dāng)前stage加入waiting ?stage列表,每當(dāng)觸發(fā)某些事件時(MapStageSubmitted、TaskCompletion..)都會進行一次 waiting stages 的提交。

2.4 Spark Core之Task

task是邏輯的具體執(zhí)行單元,stage由task構(gòu)成,當(dāng)提交stage時,DAGScheduler會根據(jù)當(dāng)前stage的類型序列化出不同類型的task并進行broadcast,如果是shuffleMapStage則序列化出ShuffleMapTask,如果是resultStage則序列化出ResultTask,其中task的數(shù)量和當(dāng)前stage所依賴的RDD的partition的數(shù)量是一致的,Task作用的數(shù)據(jù)單元是partition,即每個task只處理一個partition的數(shù)據(jù)。

2.5 Spark Core之shuffle

shuffle是影響spark性能的核心所在,和mapreduce中的shuffle概念類似,在spark2.0中shuffleManager只有sortShuffleManager,并且在滿足一定條件下可以使用Serialized sorting 即在tungsten中對其進行的優(yōu)化

2.6 Spark Core之Tungsten

該項目主要是為了讓spark計算模型能更好的利用硬件性能,主要包含三部分:

1、內(nèi)存管理與二進制處理

2、cache-aware

3、代碼生成


三、Spark Streaming簡介

3.1 Spark Streaming概述

Spark Streaming是Spark核心API的一個擴展,可以實現(xiàn)高吞吐量的、具備容錯機制的實時流數(shù)據(jù)的處理。支持從多種數(shù)據(jù)源獲取數(shù)據(jù),包括Kafk、Flume、Twitter、ZeroMQ、Kinesis 以及TCP sockets,從數(shù)據(jù)源獲取數(shù)據(jù)之后,可以使用諸如map、reduce、join和window等高級函數(shù)進行復(fù)雜算法的處理。最后還可以將處理結(jié)果存儲到文件系統(tǒng),數(shù)據(jù)庫和現(xiàn)場儀表盤。在“One Stack rule them all”的基礎(chǔ)上,還可以使用Spark的其他子框架,如集群學(xué)習(xí)、圖計算等,對流數(shù)據(jù)進行處理。

Spark Streaming處理的數(shù)據(jù)流圖:

3.2 Spark Streaming架構(gòu)

SparkStreaming是一個對實時數(shù)據(jù)流進行高通量、容錯處理的流式處理系統(tǒng),可以對多種數(shù)據(jù)源(如Kdfka、Flume、Twitter、Zero和TCP?套接字)進行類似Map、Reduce和Join等復(fù)雜操作,并將結(jié)果保存到外部文件系統(tǒng)、數(shù)據(jù)庫或應(yīng)用到實時儀表盤。


l計算流程:Spark Streaming是將流式計算分解成一系列短小的批處理作業(yè)。這里的批處理引擎是Spark Core,也就是把Spark Streaming的輸入數(shù)據(jù)按照batch size(如1秒)分成一段一段的數(shù)據(jù)(Discretized Stream),每一段數(shù)據(jù)都轉(zhuǎn)換成Spark中的RDD(Resilient Distributed Dataset),然后將Spark Streaming中對DStream的Transformation操作變?yōu)獒槍park中對RDD的Transformation操作,將RDD經(jīng)過操作變成中間結(jié)果保存在內(nèi)存中。整個流式計算根據(jù)業(yè)務(wù)的需求可以對中間的結(jié)果進行疊加或者存儲到外部設(shè)備。下圖顯示了Spark Streaming的整個流程。


圖Spark Streaming構(gòu)架


l容錯性:對于流式計算來說,容錯性至關(guān)重要。首先我們要明確一下Spark中RDD的容錯機制。每一個RDD都是一個不可變的分布式可重算的數(shù)據(jù)集,其記錄著確定性的操作繼承關(guān)系(lineage),所以只要輸入數(shù)據(jù)是可容錯的,那么任意一個RDD的分區(qū)(Partition)出錯或不可用,都是可以利用原始輸入數(shù)據(jù)通過轉(zhuǎn)換操作而重新算出的。

對于Spark Streaming來說,其RDD的傳承關(guān)系如下圖所示,圖中的每一個橢圓形表示一個RDD,橢圓形中的每個圓形代表一個RDD中的一個Partition,圖中的每一列的多個RDD表示一個DStream(圖中有三個DStream),而每一行最后一個RDD則表示每一個Batch Size所產(chǎn)生的中間結(jié)果RDD。我們可以看到圖中的每一個RDD都是通過lineage相連接的,由于Spark Streaming輸入數(shù)據(jù)可以來自于磁盤,例如HDFS(多份拷貝)或是來自于網(wǎng)絡(luò)的數(shù)據(jù)流(Spark Streaming會將網(wǎng)絡(luò)輸入數(shù)據(jù)的每一個數(shù)據(jù)流拷貝兩份到其他的機器)都能保證容錯性,所以RDD中任意的Partition出錯,都可以并行地在其他機器上將缺失的Partition計算出來。這個容錯恢復(fù)方式比連續(xù)計算模型(如Storm)的效率更高。


Spark Streaming中RDD的lineage關(guān)系圖


l實時性:對于實時性的討論,會牽涉到流式處理框架的應(yīng)用場景。Spark Streaming將流式計算分解成多個Spark Job,對于每一段數(shù)據(jù)的處理都會經(jīng)過Spark DAG圖分解以及Spark的任務(wù)集的調(diào)度過程。對于目前版本的Spark Streaming而言,其最小的Batch Size的選取在0.5~2秒鐘之間(Storm目前最小的延遲是100ms左右),所以Spark Streaming能夠滿足除對實時性要求非常高(如高頻實時交易)之外的所有流式準(zhǔn)實時計算場景。

l擴展性與吞吐量:Spark目前在EC2上已能夠線性擴展到100個節(jié)點(每個節(jié)點4Core),可以以數(shù)秒的延遲處理6GB/s的數(shù)據(jù)量(60M records/s),其吞吐量也比流行的Storm高2~5倍,圖4是Berkeley利用WordCount和Grep兩個用例所做的測試,在Grep這個測試中,Spark Streaming中的每個節(jié)點的吞吐量是670k records/s,而Storm是115k records/s。


Spark Streaming與Storm吞吐量比較圖

3.3 Spark Streaming之StreamingContext

有兩種創(chuàng)建StreamingContext的方式:

appName,是用來在Spark UI上顯示的應(yīng)用名稱。master,是一個Spark、Mesos或者Yarn 集群的URL,或者是local[*]。

batch interval可以根據(jù)你的應(yīng)用程序的延遲要求以及可用的集群資源情況來設(shè)置。


一個StreamingContext定義之后,必須做以下幾件事情:

1、通過創(chuàng)建輸入DStream來創(chuàng)建輸入數(shù)據(jù)源。

2、通過對DStream定義transformation和output算子操作,來定義實時計算邏輯。

3、調(diào)用StreamingContext的start()方法,來開始實時處理數(shù)據(jù)。

4、調(diào)用StreamingContext的awaitTermination()方法,來等待應(yīng)用程序的終止。可以使用CTRL+C手動停止,或者就是讓它持續(xù)不斷的運行進行計算。

5、也可以通過調(diào)用StreamingContext的stop()方法,來停止應(yīng)用程序。


StreamingContext需要注意的要點:

1、只要一個StreamingContext啟動之后,就不能再往其中添加任何計算邏輯了。比如執(zhí)行start()方法之后,還給某個DStream執(zhí)行一個算子。

2、一個StreamingContext停止之后,是肯定不能夠重啟的。調(diào)用stop()之后,不能再調(diào)用start()

3、一個JVM同時只能有一個StreamingContext啟動。在你的應(yīng)用程序中,不能創(chuàng)建兩個StreamingContext。

4、調(diào)用stop()方法時,會同時停止內(nèi)部的SparkContext,如果不希望如此,還希望后面繼續(xù)使用SparkContext創(chuàng)建其他類型的Context,比如SQLContext,那么就用stop(false)。

5、一個SparkContext可以創(chuàng)建多個StreamingContext,只要上一個先用stop(false)停止,再創(chuàng)建下一個即可。

3.4 Spark Streaming之DStream

DStream(Discretized Stream)作為Spark Streaming的基礎(chǔ)抽象,它代表持續(xù)性的數(shù)據(jù)流。這些數(shù)據(jù)流既可以通過外部輸入源賴獲取,也可以通過現(xiàn)有的Dstream的transformation操作來獲得。在內(nèi)部實現(xiàn)上,DStream由一組時間序列上連續(xù)的RDD來表示。每個RDD都包含了自己特定時間間隔內(nèi)的數(shù)據(jù)流。如圖7-3所示。


圖7-3???DStream中在時間軸下生成離散的RDD序列


對DStream中數(shù)據(jù)的各種操作也是映射到內(nèi)部的RDD上來進行的,如圖7-4所示,對Dtream的操作可以通過RDD的transformation生成新的DStream。這里的執(zhí)行引擎是Spark。

3.5 Spark Streaming之Input DStream and Receivers

如同Storm的Spout,Spark Streaming需要指明數(shù)據(jù)源。如上例所示的socketTextStream,Spark Streaming以socket連接作為數(shù)據(jù)源讀取數(shù)據(jù)。當(dāng)然Spark Streaming支持多種不同的數(shù)據(jù)源,包括Kafka、 Flume、HDFS/S3、Kinesis和Twitter等數(shù)據(jù)源;

3.6 Spark Streaming之Transformations

https://www.cnblogs.com/yhl-yh/p/7651558.html

3.7 Spark Streaming之Operations

https://www.cnblogs.com/yhl-yh/p/7651558.html

3.8 Spark Streaming之Join Operations

(1)DStream對象之間的Join

  這種join一般應(yīng)用于窗口函數(shù)形成的DStream對象之間,具體可以參考第一部分中的join操作,除了簡單的join之外,還有l(wèi)eftOuterJoin, rightOuterJoin和fullOuterJoin。

(2)DStream和dataset之間的join

  這一種join,可以參考前面transform操作中的示例。

3.9 Spark Streaming之Output Operations

https://www.cnblogs.com/yhl-yh/p/7651558.html



四、Spark SQL簡介

4.1 Spark SQL是什么

是Spark用來處理結(jié)構(gòu)化數(shù)據(jù)的一個模塊,它提供了一個編程抽象叫做DataFrame,并且作為分布式SQL查詢引擎的作用

4.2 Spark SQL基礎(chǔ)數(shù)據(jù)模型

DataFrame是由“命名列”(類似關(guān)系表的字段定義)所組織起來的一個分布式數(shù)據(jù)集合。你可以把它看成是一個關(guān)系型數(shù)據(jù)庫的表。

DataFrame可以通過多種來源創(chuàng)建:結(jié)構(gòu)化數(shù)據(jù)文件,hive的表,外部數(shù)據(jù)庫,或者RDDs

4.3 Spark SQL運行流程

1、將SQL語句通過詞法和語法解析生成未綁定的邏輯執(zhí)行計劃(Unresolved LogicalPlan),包含Unresolved Relation、Unresolved Function和Unresolved Attribute,然后在后續(xù)步驟中使用不同的Rule應(yīng)用到該邏輯計劃上

2、Analyzer使用Analysis Rules,配合元數(shù)據(jù)(如SessionCatalog 或是 Hive Metastore等)完善未綁定的邏輯計劃的屬性而轉(zhuǎn)換成綁定的邏輯計劃。具體流程是縣實例化一個Simple Analyzer,然后遍歷預(yù)定義好的Batch,通過父類Rule Executor的執(zhí)行方法運行Batch里的Rules,每個Rule會對未綁定的邏輯計劃進行處理,有些可以通過一次解析處理,有些需要多次迭代,迭代直到達(dá)到FixedPoint次數(shù)或前后兩次的樹結(jié)構(gòu)沒變化才停止操作。

3、Optimizer使用Optimization Rules,將綁定的邏輯計劃進行合并、列裁剪和過濾器下推等優(yōu)化工作后生成優(yōu)化的邏輯計劃

4、Planner使用Planning Strategies,對優(yōu)化的邏輯計劃進行轉(zhuǎn)換(Transform)生成可以執(zhí)行的物理計劃。根據(jù)過去的性能統(tǒng)計數(shù)據(jù),選擇最佳的物理執(zhí)行計劃CostModel,最后生成可以執(zhí)行的物理執(zhí)行計劃樹,得到SparkPlan

5、在最終真正執(zhí)行物理執(zhí)行計劃之前,還要進行preparations規(guī)則處理,最后調(diào)用SparkPlan的execute執(zhí)行計算RDD。

4.4 Spark SQL的誕生

但是,隨著Spark的發(fā)展,對于野心勃勃的Spark團隊來說,Shark對于Hive的太多依賴(如采用Hive的語法解析器、查詢優(yōu)化器等等),制約了Spark的One Stack Rule Them All的既定方針,制約了Spark各個組件的相互集成,所以提出了SparkSQL項目。SparkSQL拋棄原有Shark的代碼,汲取了Shark的一些優(yōu)點,如內(nèi)存列存儲(In-Memory Columnar Storage)、Hive兼容性等,重新開發(fā)了SparkSQL代碼;由于擺脫了對Hive的依賴性,SparkSQL無論在數(shù)據(jù)兼容、性能優(yōu)化、組件擴展方面都得到了極大的方便,真可謂“退一步,海闊天空”。

4.5 Spark SQL和Hive On Spark的區(qū)別

很多人誤以為Spark SQL和 Hive On Spark是一個東西,其實不然,這里對二者做一個簡單的介紹:Spark SQL的前身是Shark, Shark 剛開始也是使用了hive里面一些東西的,但隨著后來的發(fā)展,Shark始終不能很好的支持Hive里面的一些功能,這是因為在版本的更迭和Hive的多年發(fā)展,有些東西并不能始終支持,Shark 終止后,SparkSQL作為Spark生態(tài)的一員繼續(xù)發(fā)展,而不再受限于Hive,只是兼容Hive;而Hive On Spark 是Hive 想要將底層的執(zhí)行引擎在Spark上做很好的支持,并且想要對Hive的底層執(zhí)行引擎做更多的支持.


4.6 Spark SQL架構(gòu)

SparkSQL架構(gòu)分為前,后,中,三個部分,其中中間部分的Catalyst optimizer是架構(gòu)的核心,也是SparkSQL 優(yōu)化的關(guān)鍵所在。


4.7 Spark SQL出現(xiàn)原因

(1)使用SQL來進行大數(shù)據(jù)處理,這本身就為傳統(tǒng)的RDBMS人員降低了要求,因為這不需要使用人員掌握像mapreduce的編程方法。

(2)SparkSQL 可以支持SQL API,DataFrame和Dataset API多種API,這意味著開發(fā)人員可以采用DataFrame和Dataset API 進行編程,這和采用Spark core 的RDD API 進行編程有很大的不同,首先,并不像使用RDD進行編程時,開發(fā)人員在采用不同的編程語言和不同的方式開發(fā)應(yīng)用程序時,其應(yīng)用程序的性能千差萬別,但如果使用DataFrame和Dataset進行開發(fā)時,資深開發(fā)人員和初級開發(fā)人員開發(fā)的程序性能差異很小,這是因為SparkSQL 使用Catalyst optimizer 對執(zhí)行計劃做了很好的優(yōu)化。

(3)其次,SparkSQL既然對底層使用了很好的優(yōu)化方式,這就讓開發(fā)人員在不熟悉底層優(yōu)化方式時也可以進行很好的程序開發(fā),降低了開發(fā)人員對程序開發(fā)的門檻。

(4)最后,SparkSQL 對外部數(shù)據(jù)源(External Datasource)做了很好的支持,像對關(guān)系型數(shù)據(jù)庫,如mysql,可以使用外部數(shù)據(jù)源的方式使用SparkSQL對數(shù)據(jù)庫里面的數(shù)據(jù)直接進行處理,而不需要像以前使用sqoop導(dǎo)入導(dǎo)出。

4.8 Spark SQL如何使用

首先,利用sqlContext從外部數(shù)據(jù)源加載數(shù)據(jù)為DataFrame

然后,利用DataFrame上豐富的api進行查詢、轉(zhuǎn)換

最后,將結(jié)果進行展現(xiàn)或存儲為各種外部數(shù)據(jù)形式


4.9 Spark SQL愿景

1.寫更少的代碼

2.讀更少的數(shù)據(jù)

3.把優(yōu)化的工作交由底層的優(yōu)化器運行

4.14 SparkContext運行原理分析

前面我們隊Spark SQL運行架構(gòu)進行分析,知道從輸入SQL語句到生成Dataset分為5個步驟,但實際運行過程中在輸入SQL語句之前,Spark還有加載SessionCatalog步驟。

4.14.1 使用SessionCatalog保存元數(shù)據(jù)

在解析SQL語句之前需要初始化SQLContext,它定義了Spark SQL執(zhí)行的上下文,并把元數(shù)據(jù)保存在SessionCatalog中,這些元數(shù)據(jù)包括表名稱、表字段名稱和字段類型等。

SessionCatalog中保存的是表名和邏輯執(zhí)行計劃對應(yīng)的哈希列表,這些數(shù)據(jù)將在解析未綁定的邏輯計劃上使用。(SessionCatalog中的表名對應(yīng)的邏輯執(zhí)行計劃是什么?是這個Dataset對應(yīng)的邏輯執(zhí)行計劃)

4.14.2 使用Antlr生成未綁定的邏輯計劃

Spark 2.0版本起使用Antlr進行詞法和語法解析。使用Antlr生成未綁定的邏輯計劃分為兩個階段:第一階段的過程為詞法分析(Lexical Analysis),負(fù)責(zé)將符號(Token)分組成符號類(Token class or Token type),第二階段就是真正的Parser,默認(rèn)Antlr會構(gòu)建出一顆分析樹(Parser Tree)或者叫語法樹(Syntax

Tree)。

SQLContext類中定義了SQL的解析方法parseSql。具體的SQL解析在AbastrctSqlParser抽象類中的parse方法進行,解析完畢后生成語法樹,語法樹會根據(jù)系統(tǒng)初始化的AstBuilder解析生成表達(dá)式、邏輯計劃或表標(biāo)識對象。

在AbstractSqlParse的parse方法中,先實例化詞法解析器SqlBaseLexer和語法解析器SqlBaseParser,然后嘗試用Antlr較快的解析模式SLL,如果解析失敗,則會再嘗試使用普通解析模型LL,解析完畢后返回解析結(jié)果。

4.14.3 使用Analyzer綁定邏輯執(zhí)行計劃

該階段Analyzer使用Analysis Rules,結(jié)合SessionCatalog元數(shù)據(jù),對未綁定的邏輯計劃進行解析,生成了已綁定的邏輯計劃。在該處理過程中,先實例化一個Analyzer,在Analyzer中定義了FiexedPoint和Seq[Batch]兩個變量,其中FixedPoint為迭代次數(shù)的上線,而Seq[Batch]為所定義需要執(zhí)行批處理的序列,每個批處理由一系列Rule和策略所組成,策略一般分為Once和FixedPoint(可理解為迭代次數(shù))

4.14.4 使用Optimizer優(yōu)化邏輯計劃

Optimizer的實現(xiàn)和處理方式同Analyzer類似,在該類中定義一系列Rule并同樣繼承于RuleExecutor。利用這些Rule對邏輯計劃和Expression進行迭代處理,從而達(dá)到對樹的節(jié)點進行合并和優(yōu)化。其中主要的優(yōu)化策略總結(jié)起來是合并、列裁剪和過濾器下推等幾大類。

Optimizer的優(yōu)化策略不僅對已綁定的邏輯計劃進行優(yōu)化,而且對邏輯計劃中的Expression也進行了優(yōu)化,其原理就是遍歷樹,然后應(yīng)用優(yōu)化Rule,但是注意一點,對邏輯計劃處理時先序遍歷,而對Expression的處理是后續(xù)遍歷(根據(jù)樹節(jié)點類型來判斷是邏輯計劃還是Expression嗎?)

4.14.5 使用SparkPlanner生成可執(zhí)行物理計劃

SparkPlanner使用Planning Strategies對優(yōu)化的邏輯計劃進行轉(zhuǎn)換(Transform),生成可以執(zhí)行的物理計劃。在QueryExecution類代碼中,調(diào)用SparkPlanner.plan方法對優(yōu)化的邏輯計劃進行處理,而SparkPlanner并未定義plan方法,實際是調(diào)用SparkPlanner的祖父類QueyrPlanner的plan方法,然后會返回一個Iterator[Physicalplan]。

SparkPlanner繼承于SparkStrategies,而SparkStrategies繼承了QueryPlanner。其中SparkStrategies包含了一系列特定的Strategies,這些Strategies是繼承自QueryPlanner中定義的GenericStrategy。在SparkPlanner通過改寫祖父類QueryPlanner中strategies策略變量,在該變量中定義了轉(zhuǎn)換成物理計劃所執(zhí)行的策略。

4.14.6 使用QueryExecution執(zhí)行物理計劃

在該步驟中先調(diào)用SparkPlanner.preparations對物理計劃進行準(zhǔn)備工作,規(guī)范返回數(shù)據(jù)行的格式等,然后調(diào)用SparkPlan.execute執(zhí)行物理計劃,從數(shù)據(jù)庫中查詢數(shù)據(jù)并生成RDD。

SparkPlan的preparations其實是一個RuleExecutor[SparkPlan],它會調(diào)用RuleExecutor的execut方法對前面生成的物理計劃應(yīng)用Rule進行匹配,最終生成一個SparkPlan。

SparkPlan繼承于QueryPlan,SparkPlan中定義了SQL語句執(zhí)行的execute方法,執(zhí)行完execute方法返回的是一個RDD,之后可以運行Spark作業(yè)對該RDD進行操作。

4.15 Spark SQL小結(jié)

1)Spark SQL的應(yīng)用并不局限于SQL;

2)訪問hive、json、parquet等文件的數(shù)據(jù);

3)SQL只是Spark SQL的一個功能而已;

4)Spark SQL提供了SQL的api、DataFrame和Dataset的API;


五、SparkMLlib簡介

5.1 SparkMLlib是什么

MLlib是Spark的機器學(xué)習(xí)(Machine Learning)庫,旨在簡化機器學(xué)習(xí)的工程實踐工作,并方便擴展到更大規(guī)模。MLlib由一些通用的學(xué)習(xí)算法和工具組成,包括分類、回歸、聚類、協(xié)同過濾、降維等,同時還包括底層的優(yōu)化原語和高層的管道API。具體來說,其主要包括以下幾方面的內(nèi)容:

(1)算法工具:常用的學(xué)習(xí)算法,如分類、回歸、聚類和協(xié)同過濾;

(2)特征化公交:特征提取、轉(zhuǎn)化、降維,和選擇公交;

(3)管道(Pipeline):用于構(gòu)建、評估和調(diào)整機器學(xué)習(xí)管道的工具;

(4)持久性:保存和加載算法,模型和管道;

(5)實用工具:線性代數(shù),統(tǒng)計,數(shù)據(jù)處理等工具。

5.2 SparkMLlib調(diào)優(yōu)

MLlib是spark的可以擴展的機器學(xué)習(xí)庫,由以下部分組成:通用的學(xué)習(xí)算法和工具類,包括分類,回歸,聚類,協(xié)同過濾,降維,當(dāng)然也包括調(diào)優(yōu)的部分

Data

?typesBasic

statistics(基本統(tǒng)計)

summary statistics概括統(tǒng)計correlations 相關(guān)性stratified sampling 分層取樣hypothesis testing 假設(shè)檢驗random data generation 隨機數(shù)生成Classification

and regression(分類一般針對離散型數(shù)據(jù)而言的,回歸是針對連續(xù)型數(shù)據(jù)的。本質(zhì)上是一樣的)

linear

models (SVMs, logistic regression, linear regression)線性模型(支持向量機,邏輯回歸,線性回歸)naive

Bayes貝葉斯算法decision

trees決策樹ensembles

of trees(Random Forests and Gradient-Boosted Trees)多種樹(隨機森林和梯度增強樹)Collaborative

filtering協(xié)同過濾

alternating least squares (ALS)(交替最小二乘法(ALS) )Clustering

聚類

k-means k均值算法Dimensionality

reduction (降維)

singular value decomposition (SVD)奇異值分解principal component analysis (PCA)主成分分析Feature

extraction and transformation特征提取和轉(zhuǎn)化Optimization (developer)優(yōu)化部分

stochastic gradient descent隨機梯度下降limited-memory BFGS (L-BFGS) 短時記憶的BFGS (擬牛頓法中的一種,解決非線性問題)

六、SparkGraphX簡介

6.1 Spark GraphX什么

Spark GraphX是一個分布式圖處理框架,它是基于Spark平臺提供對圖計算和圖挖掘簡潔易用的而豐富的接口,極大的方便了對分布式圖處理的需求。

6.2 Spark GraphX的框架

設(shè)計GraphX時,點分割和GAS都已成熟,在設(shè)計和編碼中針對它們進行了優(yōu)化,并在功能和性能之間尋找最佳的平衡點。如同Spark本身,每個子模塊都有一個核心抽象。GraphX的核心抽象是Resilient Distributed Property Graph,一種點和邊都帶屬性的有向多重圖。它擴展了Spark RDD的抽象,有Table和Graph兩種視圖,而只需要一份物理存儲。兩種視圖都有自己獨有的操作符,從而獲得了靈活操作和執(zhí)行效率。


如同Spark,GraphX的代碼非常簡潔。GraphX的核心代碼只有3千多行,而在此之上實現(xiàn)的Pregel模式,只要短短的20多行。GraphX的代碼結(jié)構(gòu)整體下圖所示,其中大部分的實現(xiàn),

都是圍繞Partition的優(yōu)化進行的。這在某種程度上說明了點分割的存儲和相應(yīng)的計算優(yōu)化,的確是圖計算框架的重點和難點。

6.3 Spark GraphX實現(xiàn)分析

如同Spark本身,每個子模塊都有一個核心抽象。GraphX的核心抽象是Resilient Distributed Property Graph,一種點和邊都帶屬性的有向多重圖。它擴展了Spark RDD的抽象,有Table和Graph兩種視圖,而只需要一份物理存儲。兩種視圖都有自己獨有的操作符,從而獲得了靈活操作和執(zhí)行效率。


GraphX的底層設(shè)計有以下幾個關(guān)鍵點。

對Graph視圖的所有操作,最終都會轉(zhuǎn)換成其關(guān)聯(lián)的Table視圖的RDD操作來完成。這樣對一個圖的計算,最終在邏輯上,等價于一系列RDD的轉(zhuǎn)換過程。因此,Graph最終具備了RDD的3個關(guān)鍵特性:Immutable、Distributed和Fault-Tolerant,其中最關(guān)鍵的是Immutable(不變性)。邏輯上,所有圖的轉(zhuǎn)換和操作都產(chǎn)生了一個新圖;物理上,GraphX會有一定程度的不變頂點和邊的復(fù)用優(yōu)化,對用戶透明。

兩種視圖底層共用的物理數(shù)據(jù),由RDD[Vertex-Partition]和RDD[EdgePartition]這兩個RDD組成。點和邊實際都不是以表Collection[tuple]的形式存儲的,而是由VertexPartition/EdgePartition在內(nèi)部存儲一個帶索引結(jié)構(gòu)的分片數(shù)據(jù)塊,以加速不同視圖下的遍歷速度。不變的索引結(jié)構(gòu)在RDD轉(zhuǎn)換過程中是共用的,降低了計算和存儲開銷。


圖的分布式存儲采用點分割模式,而且使用partitionBy方法,由用戶指定不同的劃分策略(PartitionStrategy)。劃分策略會將邊分配到各個EdgePartition,頂點Master分配到各個VertexPartition,EdgePartition也會緩存本地邊關(guān)聯(lián)點的Ghost副本。劃分策略的不同會影響到所需要緩存的Ghost副本數(shù)量,以及每個EdgePartition分配的邊的均衡程度,需要根據(jù)圖的結(jié)構(gòu)特征選取最佳策略。目前有EdgePartition2d、EdgePartition1d、RandomVertexCut和CanonicalRandomVertexCut這四種策略。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,003評論 2 374

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