[翻譯]akka in action之akka-stream ( 3 使用Graph DSL實現扇入扇出 )

weixin_34107955發表於2017-10-18

3 使用Graph DSL實現扇入扇出

到目前為止,我們只研究了一個輸入和一個輸出的線性處理。Akka-stream提供了用於描述扇入和扇出的圖DSL(領域專用語言),能夠定義有大量輸入和輸出的圖。圖形 DSL 差不多是一種圖解 ASCII-許多情況下, 你可以將圖形的白板圖轉換為 DSL。

有許多扇入和扇出GraphStages,可用於建立各種圖形,例如Source、FLow、Sink。也可以建立你自己的自定義GraphStages 。

你以使用圖形 DSL 建立任意Shape 的圖形。在akka-stream方面,Shape定義了圖有多少輸入和輸出(這些輸入和輸出稱作入口(Inlets) 和出口(Outlets))。在下面的例子中,我們將建立一個Flow-shaped 圖,所以它可用於像前面的POST路由。內部它使用了一個扇出形狀。

3.1 廣播到流

繼續上一章的例子,我們按照日誌事件的狀態將日誌事件進行拆分(一個Sink用於所有的錯誤,一個用於所有的警告,等等),以便每次對一個或多個狀態發出GET請求時,不必過濾這些事件。下圖展示了BroadcastGraphStage 如何將事件傳送到不同的流。

8252507-fb454ed8a8e4a033.png
使用BroadcastGraphStage拆分事件

圖DSL提供了GraphDSL.Builder 來建立圖中的節點,並且 ~> 方法用於將節點連線在一起,很像via方法。圖中的節點是型別圖, 在引用圖的某個部分時可能會混淆, 因此在某些情況下, 我們將使用術語 "node"。

下面展示了程式碼中上圖如何定義。也展示了從圖開放入口和出口中定義一個流。

8252507-8e3580d4df456b33.png
Broadcast

探究Graphs和Shapes

processStates返回型別可能不是你所期望的。不是Flow[Event, ByteString, NotUsed]型別,而是Graph[FlowShape[Event, ByteString], NotUsed]。事實上,Flow[-In, +Out, +Mat]繼承於Graph[FlowShape[In, Out], Mat]。這表示一個Flow僅僅是一個有預定義Shape的Graph。如果你稍微深入akka-stream原始碼,你會發現 FlowShape 是一個只有一個輸入和一個輸出的Shape。

所有預定義的元件定義的方法類似:所有都定義為帶有一個Shape的Graph。例如,Source和Sink分別繼承於 Graph[SourceShape[Out], Mat] 和Graph[SinkShape[In], Mat]。

引數builder是一個GraphDSL.Builder,它是可變的。它只打算在這裡使用匿名函式來建立一個圖。GraphDSL.Builder的add方法返回一個Shape,它描述了一個圖的入口和出口。

過濾流寫入到不同檔案,如logFileSink(logId, state) 方法呼叫所示。例如,對於檔案logId1的錯誤,將追加一個1-errors檔案。

在POST路由裡使用processStates:

src.via(processStates(logId))
  .toMat(logFileSink(logId))(Keep.right)
  .run()

用於返回錯誤日誌檔案的GET路由與普通的GET路由類似,除了一個命名約定(用於從[log-id]-error 檔案讀取)。

下一節,我們將看看合併源,以便可以返回合併在一起的所有日誌,或者僅是某日誌檔案所有狀態不是OK的日誌事件。

3.2 合併流

讓我們看看用於合併源的圖。在第一個例子中,我們合併了所有狀態不是OK的事件到一個檔案中。 對/logs/[log-id]/not-ok 進行GET將返回所有狀態不是OK的事件。下圖展示了MergeGraphStage 如何將三個Sources合併為一個。

8252507-5b1f667dea2e88ce.png
使用MergeGraphStage合併非OK狀態

下面的程式碼展示了MergeGraphStage 如何應用圖DSL。它定義了一個mergeNotOK方法,將特定 logId 的所有非OK日誌源合併到一個源中。

8252507-28fab39b98efb6fe.png
合併所有非OK狀態

注意,warning, error 和 critical 源,首先通過了一個JSON幀化流,否則, 您可以讀取任意 ByteStrings 並將它們合併在一起, 從而導致 JSON 輸出出現亂碼。

三個源由帶有三個入口的MergeGraphStage 合併。SourceShape 由merge.out 出口建立。Source有一個fromGraph 方便方法,將帶有SourceShape 的Graph轉化為Source。

MergePreferred GraphStage
MergeGraphStage 從其任何輸入中隨機取元素。Akka-stream 還提供了一個 MergePreferredGraphStage, 它有一個輸出埠、一個首選輸入埠, 以及零個或多個次級埠。當其中一個輸入有可用的元素時, MergePreferred 會發出, 如果多個輸入有可用的元素, 則傾向於首選的輸入。

mergeNotOk 方法稍後用於getLogNotOkRoute 去建立讀取的Source,如下所示

def getLogNotOkRoute =
  pathPrefix("logs" / Segment /"not-ok") { logId =>
    pathEndOrSingleSlash {
      get {
        extractRequest { req =>
          complete(Marshal(mergeNotOk(logId)).toResponseFor(req))
        }
      }
    }
  }

還有一個用於合併源的簡化 API, 我們將使用它來合併所有日誌。請求GET /logs 將返回所有合併在一起的日誌。下面程式碼展示瞭如何使用簡化的 API。

8252507-1535abf3def7b302.png
mergeSources 方法

Source.combine 方法從多個源中建立Source,類似於使用圖形DSL進行的操作。mergeSources 用於合併具有相同型別任意數量的源。例如該方法用於/logs路由中,如下所示。

8252507-845e78df8ed21f07.png
GET /logs

預定義和自定義GraphStages
在akka-stream 中有相當多的預定義 GraphStages, 這裡沒有展示負載平衡 (-Balance)、壓縮 (Zip、ZipWith) 和串聯流 (Concat) 等等。這些圖的DSL非常類似於已經展示的示例。在所有情況下, 都需要向生成器新增節點,連線形狀的入口和出口(由add方法返回),然後從函式返回某個形狀,並將其傳遞給Graph.create 方法。也可以編寫自己的自定義 GraphStage, 這超出了akka-stream的介紹性章節的範圍。

本節中展示的 BroadcastGraphStage 在任何輸出應用背壓時都應用背壓, 這意味著您只能以最慢的使用者可以讀取的速度進行廣播。下一節將討論如何使用緩衝來允許生產者和消費者以不同的速度執行, 以及如何在不同速度下執行的生產者和消費者之間進行調解。

相關文章