Storm學習總結

友德發表於2018-07-11

分享的目的

讓大家更加深入瞭解Storm的架構以及運用JStorm之類的流式計算框架解決業務開發過程中遇到的問題能夠有所幫助

分享大綱

Storm介紹和系統架構
Storm核心類
Storm trident框架
Storm重要引數

Storm介紹和系統架構

1.

Twitter將Storm正式開源了,這是一個分散式的、容錯的實時計算系統

2.

Storm為分散式實時計算提供了一組通用原語,可被用於“流處理”之中,實時處理訊息並更新資料

3.

Storm可以方便地在一個計算機叢集中編寫與擴充套件複雜的實時計算,Storm用於實時處理。Storm保證每個訊息都會得到處理,而且它非常快,在一個小叢集中,每秒可以處理數以百萬計的訊息

4.

開發者可以使用任意程式語言來做開發

主要特點:

1. 簡單的程式設計模型

類似於MapReduce降低了並行批處理複雜性,Storm降低了進行實時處理的複雜性;

2. 容錯性

Storm會管理工作程式和節點的故障; 

3.水平擴充套件

計算是在多個執行緒、程式和伺服器之間並行進行的; 

4.可靠的訊息處理

Storm保證每個訊息至少能得到一次完整處理。任務失敗時,它會負責從訊息源重試訊息, 如何判斷一條訊息是否處理成功或失敗,通過異或來判斷;

5.快速

系統的設計保證了訊息能得到快速的處理,使用ØMQ作為其底層訊息佇列
image.png
storm系統架構圖
image.png

Storm 名詞

1.Nimbus

任務的中央排程器(相當於Yarn中 ResourceManager)

2.Supervisor

worker的代理, 負責管理Worker的生命週期(相當於Yarn中NodeManager, AppMaster)

3.Stream

被處理的資料

4.Spout

資料來源(Map或Reduce)

5.Bolt

處理資料(Map或Reduce)

6.Task

執行於Spout或Bolt中的執行緒(Container)

7.Executor

worker程式中的執行緒,預設情況下,一個executor對應一個task

8.Worker

執行Task執行緒的程式(容器)

9.Tuple

流資料

10.Stream Grouping

定義了Bolt接收什麼東西作為輸入資料

11.Topology

Stream Grouping連線起來的Spout和Bolt節點網路

Storm資料流—Tuple

1.

Tuple(元組)是Storm最主要的資料結構,一個元組就是一個命名的值列表,其中的每一個值可以是任何型別;

2.

Tuple(元組)都是動態型別——欄位的型別不需要提前被宣告;

3.

Tuple(元組)有一些方法像getInteger 和getString之類 得到欄位的值的方法,而不需要強制轉換結果型別(和JDBC的ResultSet非常類似);

Storm 的容錯

工作程式worker 失效:

如果一個節點的工作程式worker死掉,supervisor 程式會嘗試重啟該worker。如果連續重啟worker 失敗或者worker 不能定期向Nimbus 報告心跳,Nimbus 會分配該任務到叢集其他的節點上執行;

叢集節點失效:

如果叢集中某個節點失效,分配給該節點的所有任務會因超時而失敗,Nimbus 會將分配給該節點的所有任務重新分配給叢集中的其他節點;

Nimbus 或者supervisor 守護程式失敗:

Nimbus 和supervisor 都被設計成快速失敗(遇到未知錯誤時迅速自我失敗)和無狀態的(所有的狀態資訊都儲存在Zookeeper 上或者是磁碟上)。Nimbus 和supervisor 守護程式必須在一些監控工具(例如,daemontools 或者monitor)的輔助下執行,一旦Nimbus 或者supervisor 失敗,可以立刻重啟它們,整個叢集就好像什麼事情也沒發生。最重要的是,沒有工作程式worker 會因為Nimbus 或supervisor 的失敗而受到影響,Storm 的這個特性和Hadoop 形成了鮮明的對比,在Hadoop中如果JobTracker 失效,所有的任務都會失敗;

Nimbus 所在的節點失效:

如果Nimbus 守護程式駐留的節點失敗,工作節點上的工作程式worker 會繼續執行計算任務,如果worker 程式失敗,supervisor 程式會在該節點上重啟失敗的worker 任務。但是,沒有Nimbus的影響時,所有worker 任務不會分配到其他的工作節點機器上,即使該worker所在的機器失效;

storm非同步併發執行模式

在前面提到task、executor、worker、node,這四者的關係如下:

  1. 一個node上可以啟動多個worker程式
  2. 一個work程式上可以執行多個executor執行緒
    3.一個executor上可以執行1個或多個task, 即bolt或spout例項,預設情況下一個executor對應一個task

這就是Storm併發執行的模式,可以在建立topology的時候設定node, work 、executor和task的併發度模型, 下面拿單詞統計程式來詳細說明。

預設併發度

image.png
執行的併發模型如下:
image.png

調整併發度

image.png

Tuple訊息分組路由機制

Storm定義了7種內建資料流分組方式,分別是:

1. Shuffle Grouping(隨機分組):

隨機分發tuple給bolt的各個task,每個bolt接收到相同數量的tuple,程式碼中是用shuffleGrouping這個方法;

2. Fields Grouping(按欄位分組):

根據給定的欄位進行分組。比如說一個資料流根據word欄位進行分組,所有具有相同的word欄位值的tuple會路由到同一個bolt中(用得最多),程式碼中是用fieldsGrouping這個方法;

3. All Grouping(廣播傳送):

將所有tuples複製後分發給所有bolt task,這樣每個訂閱資料流的task都會收到tuple的一個copy,程式碼中是用allGrouping這個方法;

4. Global Grouping(全域性分組):

將所有tuples路由到唯一的一個task上,Storm按照最小的task ID來選取接收資料的task,程式碼中是用globalGrouping這個方法;

5. Non Grouping(不分組):

Shuffle Grouping 是一樣的效果,有一點不同的是Storm 會把這個Bolt 放到此Bolt 的訂閱者同一個執行緒裡面去執行,程式碼中是用noneGrouping這個方法;

6. Direct Grouping(指向型分組):

這種分組意味著訊息的傳送者指定由訊息接收者的哪個Task 處理這個訊息。只有被宣告為Direct Stream 的訊息流可以宣告這種分組方法。而且這種訊息tuple 必須使用emitDirect 方法來傳送,程式碼中是用directGrouping這個方法;

7.Local or shuffle grouping(本地或隨機分組):

和隨機分組類似,但是會將tuple分發給同一個worker內的bolt task。其它情況下,採用隨機分組的方式,這取決於topology的併發度,本地或隨機分組可以減少網路人傳輸,從而且提高topology的效能,程式碼中是用localOrShuffleGrouping這個方法;

自定義分組路由

可以通過實現CustomStreamGrouping的介面來自定義分組方式:
image.png
prepare()會在執行時間呼叫, 用來初始化分組資訊,分組的個體實現會在這些資訊決定如何向接收task分發tuple.
chooseTasks()方法返回一個tuple傳送目標的task的識別符號列表,它的兩個引數是傳送tuple的元件的id和tuple的值.

Storm核心類

1.BaseRichSpout

重寫nextTuple、declareOutputFields、 open、ack、 fail方法
open : 一般是初始化SpoutOutputCollector物件
nextTuple : 從資料來源接受資料,並向下遊的bolt發射資料流
declareOutputFields : 宣告發射資料的輸出欄位
ack : 成功的處理
fail : 失敗的處理

2.BaseRichBolt

重寫execute 、declareOutputFields、 prepare、 cleanup方法
prepare:一般是初始化SpoutOutputCollector物件
declareOutputFields :宣告發射資料的輸出欄位
execute : 處理資料,並向下遊傳送tuple
cleanup : 清除工作

3.TopologyBuilder

通過setSpout、setBolt構建topology

Storm trident框架

Storm Trident的資料模型

Storm Trident的核心資料模型是小批量去處理流資料(相當於Spark Stream元件),流分割槽在叢集的節點上,對流的操作也是並行的在每個分割槽上進行;
官方的描述是: Trident is an alternative interface to Storm. Tuples are processed as small batches(小批量). It provides exactly-once processing (剛好一次的處理), “transactional” datastore persistence(事物的持久化), and a set of common stream analytics operations.)。
Trident對事物處理方案如下:

 1. 一批資料被分配指定一個唯的id 稱作為“transaction id”. 如果一批資料被重複處理,將會得到是同
   一個transaction id(唯一、冪等);
 2.   在各批資料之中,狀態更新是保序的,例如:批次3的狀態更新不會在批次2狀態更新成功之前(保序);

Storm trident資料流

Trident資料流是分批次的,如下圖所示,而普通bolt或spout資料流是一個接一個連續的, trident資料每一批的大小,取決輸入資料量的大小
image.png

Trident API

Trident有五種對流的操作的API:

1.不需要網路傳輸的本地批次運算,每個分割槽(partion)的運算是互相獨立的

  1. Functions
  2. Filters
  3. partitionAggregate
  4. stateQuery and partitionPersist
  5. projection

2.需要網路傳輸的重分佈操作,不改變資料的內容;

  1. shuffle
  2. broadcast
  3. partitionBy
  4. global
  5. batchGlobal
  6. partition

3. 聚合操作,網路傳輸是該操作的一部分

  1. aggregate

4. 流分組(groupby)操作

  1. groupby

5. 合併和關聯操作

  1. merge
  2. join 

一個Trident topology完整的示例

image.png
示例中包含:
filter
function
groupby
persistentAggregate

filter

image.png

function

image.png

projection

投影操作是對資料上進行列裁剪
用法:如果你有一個流有【“a”,“b”,“c”,“d”】四個欄位,執行下面的程式碼:
mystream.project(new Fields(“b”,”d”))
輸出流將只有【“b”,“d”】兩個欄位

Repartition(重分割槽)

重分割槽操作是通過一個函式改變元組(tuple)在task之間的分佈。也可以調整分割槽數量(比如,如果併發的hint在repartition之後變大)重分割槽(repatition)需要網路傳輸,以下是重分割槽函式:

1. shuffle:

使用隨機演算法在目標分割槽中選一個分割槽傳送資料;
用法: inputStream.shuffle();

2. broadcast:

每個元組重複的傳送到所有的目標分割槽。這個在DRPC中很有用。如果你想做在每個分割槽上做一個statequery;
用法: inputStream.broadcast();

3. paritionBy:

根據一系列分發欄位(fields)做一個語義的分割槽。通過對這些欄位取hash值並對目標分割槽數取模獲取目標分割槽。paritionBy保證相同的分發欄位(fields)分發到相同的目標分割槽;
用法: inputStream.partitionBy(new Fields(“event”, “city”));

4.  global:

所有的tuple分發到相同的分割槽。這個分割槽所有的批次相同;
用法: inputStream.global();

5. batchGobal:

本批次的所有tuple傳送到相同的分割槽,不通批次可以在不通的分割槽;
用法: inputStream.batchGlobal();

6. patition:

這個函式接受使用者自定義的分割槽函式,很少用。使用者自定義函式事項 

backtype.storm.grouping.CustomStreamGrouping介面;

用法: inputStream.partition(grouping);

Aggregation(聚合)

1.

Trident有aggregate和persistentAggregate函式對流做聚合, aggregate在每個批次上獨立執行;persistentAggregate聚合流的所有的批次並將結果儲存下來,然後現在一個流上做全域性的聚合

2.

如果使用reducerAggregator或者aggretator,這個流先被分成一個分割槽,然後聚合函式在這個分割槽上執行。

3.

如果使用CombinerAggreator,Trident會在每個分割槽上做一個區域性的彙總,然後重分割槽聚合到一個分割槽,在網路傳輸結束後完成聚合。CombinerAggreator非常有效,在儘可能的情況下多使用.下面是一個做批次內聚合的例子:
mystream.aggregate(new Count(), new Fields(“count”))
和partitionAggregate一樣,聚合的aggregate也可以串聯。如果將CombinerAggreator和非CombinerAggreator串聯,trident就不能做區域性彙總的優化。

Trident流分組操作

1.

groupBy操作根據特殊的欄位對流進行重分割槽,分組欄位相同的元組(tuple)被分到同一個分割槽,如果對分組的流進行聚合,聚合會對每個組聚合而不是這個批次聚合。(和關係型資料庫的groupby相同).

2

persistentAggregate也可以在分組的流上執行,這種情況下結果將會儲存在MapState裡面,key是分組欄位,和普通聚合一樣,分組流的聚合也可以串聯。
image.png

Trident—merge和join

合併:

將不同的流合併,最簡單的方式就是合併(meger)多個流成為一個流, 用法如下:
topology.merge(stream1, stream2, stream3);
Trident合併的流欄位會以第一個流的欄位命名

關聯:

類似SQL的join都是對固定輸入的。而流的輸入是不固定的,所以不能按照sql的方法做join。Trident中的join只會在spout發出的每個批次見進行,下面是個join的例子:

一個流包含欄位【“key”,“val1” ,“val2”】,另一個流包含欄位【“x”,“val1”】:
topology.join(stream1, new Fields(“key”), stream2, new Fields(“x”), new Fields(“key”,”a”,”b”,”c”));
Stream1的“key”和stream2的“x”關聯。Trident要求所有的欄位要被名字,因為原來的名字將會會覆蓋。Join的輸入會包含:
1.  首先是join欄位。例子中stream1中的“key”對應stream2中的“x”。
2.  接下來,會吧非join欄位依次列出來,排列順序按照傳給join的順序。例子中” a”,” b”對應stream1中

的“val1”和“val2”,“c”對應stream2中的“val1”。

注意:當join的流分別來自不同的spout,這些spout會同步發射的批次,也就是說,批次處理會包含每

       個spout發射的tuple。

partitionAggregate

image.png
有三種不同的聚合介面:CombinerAggreator,ReduceAggregator和Aggregator

Aggregator(聚合器)

CombinerAggregator

CombinerAggregator用來將一個集合的tuple組合到一個單獨的欄位中,Storm 對每一個tuple呼叫init方法,然後重複呼叫combine()方法直到一個分片的資料處理完成為止。傳遞給combine方法的兩個引數是區域性聚合的結果,以及呼叫了init()返回的值。Storm將tuple生成的值進行組合之後,Storm傳送組合結
果作為一個新的欄位,如果分片是空的,Storm會傳送zero()方法執行的返回。 

ReducerAggregator

Storm呼叫init()方法來獲取原始值。然後為分片中每一個tuple呼叫reduce()方法,直到分片的資料處理完成。第一個引數是區域性聚合的結果,這個方法需要將第二個引數tuple合併到區域性聚合結果中並返回。

Aggregator

Aggregators能發射任何型別和任何欄位的tuple, 在執行期間,他能發射任何tuple,執行的方式如下:
在處理一批資料之前,就呼叫init(), init()方法的返回值物件代表aggregation的state, 並且會傳遞到aggregate和complete方法中去;針對一批資料中的每一個tuple,都會呼叫aggregator方法,這個方法能夠更新state和選擇性的發射tuple流;當一批資料處理完成的時候,就會呼叫complete方法;

程式設計中三種聚合器的選擇

  1. 共同點:都可以實現一些簡單、複雜的計數、統計功能、在partitionAggregate操作方法上效能、功能都差不多;
  2. 區別:

    1. CombinerAggregator: 比較靈活,每一個tuple的初始值都可以根據實際情況靈活設定,適合流的

      aggregate 操作,此時trident會有自動優化機制(在網路傳輸之前就會做局轉換合併) 
    2. ReducerAggregator: 最容易使用, 初始值可以根據實際情況靈活設定,適合比較簡單處理的場景
    3. Aggregator: 最靈活, 批次的初始值可以靈活設定, 常用在多次統計處理,一次發射的場景

要求:學會用三種Aggregator實現各自的計數功能,

Trident State

Trident State有如下三個元件:

StateFactory :

該介面定義了Trident用來建立持久state物件的方法;

State :

該介面定義了beginCommit()和commit()方法,分別在Trident一批分割槽資料寫到後端儲存之前和之後呼叫。如果寫入成功,意味著,所有的處理都沒有報錯,Trident會呼叫commit()方法;

StateUpdater :

該介面定義了updateState()方法用來呼叫更新state,假定處理了一批tuple,Trident將三個引數傳遞給這個方法,需要更新的State物件,一個批次分割槽資料中TridentTuple物件的列表和TridentCollector例項用來視需要傳送額外的tuple作為state更新的結果;

Trident State事物

在Storm中,有三種型別的狀態,每個型別的描述如下:
image.png
在寫Storm指令碼的時候,這三種事物對應的MapState介面的實現,分別是:

  1. 重複事物型:TransactionalMap
  2. 不透明事物型:OpaqueMap
  3. 非事物型:NonTransactionalMap
    重複事物和不透明事物詳細看附件 Trident事物和冪等性

StateUpdater

  1. 從資料流中的tuple更新狀態(state)資訊, updateState()呼叫的時機是在state物件的beginCommit()和commit()方法呼叫之間進行,介面定義如下:
    image.png
  2. 使用的場景:主要是在Stream類的partitionPersist的各種方法中,主要的作用是允許topology從資料流中的tuple更新狀態(state)資訊,部分過載方法如下:
    image.png

StateQuery

stateQuery方法從State實現類物件生成一個輸入資料流, 操作示例如下:

TridentTopology topology = new TridentTopology();
topology.newDRPCStream("words") 
    .each(new Fields("args"), new Split(), new Fields("word")) 
    .groupBy(new Fields("word")) 
    .stateQuery(wordCounts,  new Fields("word"),  new MapGet(), new Fields("count")) 
    .each(new Fields("count"), new FilterNull()) ;

StateQueryProcessor核心原始碼如下:
image.png
由上面的程式碼可以看出,在流在執行stateQuery方法的時候,針對每一批資料中的每一個tuple,都會執行QueryFunction實現類的execute方法

persistentAggregate

persistentAggregate: 持久化聚合的的操作過程,它是GroupedStream類的 一個方法,它一般是用來在Stream物件通過groupby分組聚合操作之後生成的GroupedStream方法之後,在每個分片資料的每個分組上執行這方法,方法的各個引數,以及返回值如下:

1.

方法第一個引數傳入的StateFactory物件生成的State物件,結合前面介紹的State三種事物中某一種事物來進行持久化操作操作處理,一般持久化操作需要實現IBackingMap這個介面,比例持久化到本地檔案、Memcache、redis、HBase、LevelDB、HDFS、mysql等,通常在開發環境 下為了方便寫入到一個Map中即可;

2.

方法的第二引數一般是一個Aggregator物件,在分片上執持persistentAggregate的時候,會去執行這個Aggregator物件的初始化方法、多次迭代方法、完成方法,比例ReducerAggregator物件,在這個分片上先執行ReducerAggregator的init方法,然後針對每一個tuple,執行ReducerAggregator的 reduce方法,直到完成;

3.

第三引數是由第二個引數聚合操作指定產生的新欄位, 如Count聚合,一般會設定成count欄位;

4.

返回值的型別是一個TridentState物件,一般情況下返回的TridentState物件會再生成一個新的Stream物件,繼續往下游處理;

partitionPersist

partitionPersist : 是在一個在分割槽上進行持久化的操作方法,不需要網路通訊, 它是Stream類的一個方法,在每個分片資料的每個分組上執行這方法,方法的各個引數,以及返回值如下:

1.

第一個引數是一個StateFactory型別,和persistentAggregate的第一個引數一樣,功能也一樣;

2.

第二個引數是輸入的欄位集合;

3.

第三個引數是一個StateUpdater例項, storm在一個資料分片上呼叫這個方法時,會在state物件的beginCommit和commit這兩個方法之間會呼叫這個StateUpdater例項的updateState去更新狀態 ,資料批次的狀態是方法的第一個引數,該批次資料的集合是第二個引數, 第三個引數是一個TridentController控制器物件,方法呼叫的部分核心原始碼如下:
image.png

4.

第四個引數是一個方法操作的欄位;

Storm重要引數

worker.childopts:

預設情況下,Storm啟動worker程式時,JVM的最大記憶體是768M,但是線上上環境中,由於會在Bolt中載入大量資料,768M記憶體無法滿足需求,會導致記憶體溢位程式崩潰,可以通過在Strom的配置檔案storm.yaml中設定worker的啟動引數, 例如可以這樣設定:”-Xmn1024m -Xms2048m -Xmx2048m
-XX:+UseConcMarkSweepGC -XX: +UseCMSInitiatingOccupancyOnly “

Supervisor.slots.ports:

Storm的Slot,最好設定成OS核數的整數倍;同時由於Storm是基於記憶體的實時計算,Slot數不要大於每臺物理機可執行Slot個數;

storm.messaging.netty.buffer_size:

傳輸的buffer大小,預設1 MB,當Spout發射的訊息較大時,此處需要對應調整###storm.messaging.netty.max_retries:
建議設定為3

storm.messaging.netty.max_wait_ms:

建議設定為10000;

storm.messaging.netty.min_wait_ms:

建議設定為3000;

Topology.acker.executors:

由於Acker基本消耗的資源較小,強烈建議將此引數設定在較低的水平, 建議設定為1;

Topology.max.spout.pending:

一個Spout Task中處於pending狀態的最大的Tuple數量。該配置應用於單個Task,而不是整個Spout或Topology,可在Topology中進行覆蓋, 通常情況下,建議設定為1024;

Storm應用

1. 日誌分析

2. 訊息轉化器

3. 實時統計分析

1. 阿里雙11交易大屏

2. 騰訊網2015年 9.3閱兵線上人數實時統計(lamba架構)

3. 搜尋引擎中熱點詞頻

4.人工智慧

5. NLP(語音合成處理,高德導航上林志玲的導航語言提示)

6. 股票實事趨勢分析

7. 網際網路風控

8. 廣告推薦

推薦資料

  1. Storm 官方文件 : http://storm.apache.org/
  2. Storm分散式實時計算模式
  3. Storm原始碼分析

問題

Storm是如何確定一條訊息是否處理成功?
原值和返回值進行異或計算,如下:
image.png