本文探討:
- 什么是管道過濾器風格(Pipe-and-filter Style)
- 管道過濾器風格的約束
- 管道過濾器風格的適用場景
- 什么是批量順序處理風格(Batch-sequential Style)
- 批量順序處理風格的約束
- 批量順序處理風格的適用場景
- 批量順序處理與管道過濾器的差異是什么
- 什么是過程控制風格(Process Control Style)
- 過程控制處理風格的約束
- 過程控制處理風格的適用場景
不論是出國還是看電影,不知道你有沒有發現,很多國家的自來水是可以直接喝的,而中國的就不行。你有沒有想過,為什么我們國家的自來水不能直接喝呢?而且好像就只有中國人特別喜歡喝熱水!感冒、咳嗽、不舒服,發燒、流感、大姨媽,只要身體有點什么不舒服了,不管三七二十一,先喝點熱水!
這主要和以前的瘟疫、寄生蟲、腸胃炎有關系。以前的水基本沒有過濾,寄生蟲病菌比較多,經常引起各種疾病,所以很多國家都對水進行了凈化處理,能達到直接喝的標准。而以前我們國家還沒有能力做這種凈化處理,但是發現水燒開了也可以有效的殺死這些細菌,所以喝熱水的傳統就這么延續下來了。
對水的凈化處理其實原理很簡單,就是「過濾」!如果你家里安裝過凈化水系統,應該會發現凈化器一般都會有幾個過濾器,一般是三個。作用就是自來水經過一個個的過濾器,去除了水中的各種雜質,能達到直接飲用的程度。其中,每一個過濾器都是獨立的,可以獨立的替換。
數據流風格就是類似的處理方式,將數據看做水,一個個的處理單元看過一層層的過濾,數據經過一個個的處理單元,最終達到我們需要的結果。
說到過濾器,我們的第一印象應該就是Web開發中遇到的「過濾器/攔截器」了,繼而想到的應該就是設計模式中的職責鏈模式。
所以我們就從我們熟悉的職責鏈模式開始,來聊聊「數據流風格」。
職責鏈模式
職責鏈的類圖如下:
從結構上看,職責鏈模式有兩個對象:Client和Handler(包括具體的Handler實現)
- Client發送消息給Handler
- Handler將消息委托給具體的Handler鏈條處理
- 處理完成后,返回給Client
Handler的具體實現就是一個個的過濾器,獨立的處理數據,也可以獨立的替換,這就解耦了請求發送者和接收者。請求沿着Handler鏈條依次傳遞,每個Handler判斷是否需要處理該請求,直到所有Handler都處理完成為止。
職責鏈主要適用於如下場景:
- 有多個對象可以處理一個請求,但是哪個對象處理該請求需要運行時自動確定
- 需要在不明確指定接收者的情況下,向多個對象中的一個提交一個請求
- 可處理一個請求的對象集合是動態指定的
下面是一個簡單的clojure例子:
(defn handler-request1 [condition]
(if (= "ConcreteHandler1" condition)
(println "ConcreteHandler1 handled ")
(println "ConcreteHandler1 passed ")))
(defn handler-request2 [condition]
(if (= "ConcreteHandler2" condition)
(println "ConcreteHandler2 handled ")
(println "ConcreteHandler2 passed ")))
(defn handler-requestn [condition]
(println "ConcreteHandlern handled "))
(->> "ConcreteHandler2"
handler-request1
handler-request2
handler-requestn)
;handler-request2和handler-requestn會處理此字符串
管道過濾器風格
管道過濾器風格和職責鏈模式在結構上比較類似。管道過濾器風格包含了四個組件:
- 讀端口:用於讀取數據,如標准輸入流、文本文件或傳感器采集的數據等。類似職責鏈的Client發送消息。
- 寫端口:用於寫出數據,如文本文件、數據庫、標准輸出等。類似職責鏈的Client接收處理完的消息。
- 管道:用於連接各個過濾器,使得消息能在各個過濾器之間進行流轉,也包含數據緩沖作用。類似職責鏈里的調用(消息發送)
- 過濾器:處理消息組件,過濾器可以通過pull(后續過濾器從當前過濾器中拉取數據),push(前面的過濾器向當前過濾器推送數據)或主動方式(不斷從前面的過濾器中拉取數據,並向后面的過濾器推送數據)來獲取消息。類似職責鏈模式里的Handler。
管道過濾器風格的完整流程為:「讀端口」獲取需要處理的信息,通過管道傳遞給過濾器鏈,每個過濾器自行判斷是否需要對信息進行處理,一個過濾器處理完后通過管道將消息傳遞給下一個或多個過濾器,直到所有的過濾器全部處理完畢,通過寫端口,將處理完成的信息寫出到目標位置。
管道過濾器風格的典型應用有:
- Linux的Shell
- JavaEE Servlet Filter
- 傳統的編譯器:一個階段(包括詞法分析、語法分析、語義分析和代碼生成)的輸出是另一個階段的輸入。
以Linux的Shell為例:
cat "app.log" | grep "^error" | cut -f 2
- cat指令獲取app.log的內容,將結果通過管道傳遞給grep
- grep指令過濾出所有以error開頭的行,將結果通過管道傳遞給cut
- cut取出第二列的內容
在這里cat,grep,cut就是一個個的過濾器;而 | 就是管道;輸入輸出則是標准輸入輸出。
管道過濾器風格將處理邏輯封裝到獨立的過濾器中:
- 可以通過新增過濾器的方式增加對信息的處理邏輯,相反可以通過移除過濾器的方式減少對信息的處理邏輯。提高了系統的擴展性
- 過濾器之間沒有邏輯上的關聯關系,提高了復用性
- 同時易於增加過濾器和移除過濾器,提高了系統的可維護性
- 但是每個過濾器需要對信息進行解析,降低了系統性能,以及增加了過濾器自身的復雜度
- 不過過濾器可以並行執行,這可以提高系統性能
管道過濾器風格主要用於處理數據的系統,不適用於處理交互的應用。
批量順序處理風格
同樣的「批量順序處理風格」也是主要用於處理數據的架構風格。一般它的處理組件稱為階段(stages)或者步驟(steps)。
它的處理流程如下:
- 讀取一批數據,進行處理
- 信息不是通過所謂的「管道」在各個階段之間進行流轉,而是通過類似臨時中間文件來進行階段之間的流轉,而文件可以被刪除
- 下一階段需要在前一階段處理完后才能執行
- 下一階段會從前一階段處理后的文件里,選擇自己需要的文件進行處理,處理后再寫到文件中
批量順序處理風格的每一步處理都是獨立的,並且每一步是順序執行的。只有當前一步處理完,后一步處理才能開始。數據傳達在每一步處理之間作為一個整體。比較適用於需要順序執行的某些固定操作的場景,並且這種流程不經常進行變化。Windows下的BAT程序就是這種應用的典型實例。
批量順序處理風格對架構屬性的影響與管道過濾器基本一致,這里不再贅述。
管道過濾器與批量順序處理差異
兩種風格都包含一系列的計算組件,通過將數據交給這一系列的計算組件來處理,來完成任務。
兩種風格的不同之處在:
管道過濾器風格 | 批量順序處理風格 |
---|---|
細粒度 | 粗粒度 |
結果驅動處理 | 高延遲 |
內部輸入 | 外部訪問輸入 |
可以並發 | 不支持並發 |
交互不友好 | 不支持交互 |
內部輸入:過濾器之間直接傳遞數據
外部訪問輸入:批量順序處理一般都會先輸出到一個臨時文件,下一個階段再從臨時文件中獲取
過程控制風格
過程控制風格和上面的兩個風格處理方式差異較大,一般應用在嵌入式開發中,包含了四個組件:
- 傳感器(Sensor):監聽某些重要信息
- 控制器(Controller):邏輯控制
- 執行器(Actuator):操作過程的物理方法
- 過程(Process):你想要控制的內容
空調使用的就是過程控制風格,如下圖:
- 我們打開空調,設置了21度
- 空調的溫度傳感器(Sensor)檢測到室內溫度(Process)為15度,溫差為6度
- 控制器(Controller)控制執行器(Actuator)吹熱風
- 當室內溫度達到21度后,傳感器就通知控制器控制執行器停止工作
過程控制風格將系統拆分為一個個的子系統或模塊:
- 各個子系統或模塊相互獨立,提高了模塊的復用性,以及系統的進化性和可維護性
- 子系統或模塊間的交互可能會影響性能