從微服務到工作流:Jet訂單系統演變過程分享
Jet的訂單管理系統(OMS)負責許多業務功能:
- 訂單初始化和驗證
- 收費/信貸/資金管理
- 訂單履行整合
- 訂單歷史
- 優惠(退款,退貨等)
OMS的上述功能已經基於微服務使用pub / sub、事件溯源、HTTP呼叫和一些其他技術進行了組合開發和執行。然而,隨著Jet的發展和業務需求的擴大,系統和架構的複雜性也在增加,以至於我們運行了數十種服務。隨著服務數量的增加,維護和改進系統變得更加困難。這導致更長的功能開發週期,因為幾乎所有功能的邏輯都分佈在多個服務上,這使得維護和除錯很難。
大多數基於微服務的架構都是這種情況,因為它們通常與輸入/輸出,SLA和個人服務職責緊密相關,所有這些都使其更難以適應不斷變化的需求。對於Jet的系統,每個“業務流程”都是通過一組處理該流程特定操作的微服務進行編排的,即(建立訂單,更新訂單歷史記錄,傳送交易電子郵件等)
在我們基於微服務的架構中,每個服務都使用相同的樣板實現:
輸入流incoming-streams |> 解碼decode (DomainEvent -> Input option) |> 處理handle (Input -> Async<Output>) |> 解釋interpret (Output -> Async<unit>)
解碼功能是消費使用輸入流中的領域事,把它變成了輸入型別,並送喂到處理handle功能; 在處理功能這塊將執行各種檢查或收集所需的任何其他資料,然後將其傳遞給執行副作用的解釋函式。
這個過程本質上很複雜,需要大量的樣板才能有效地建模和構建。雖然我們使用聊上述這種decode -> handle -> interpret的管道作為構建服務的模板,但仍然要求每個微服務具有可擴充套件性,效能調整,冪等性,錯誤處理和重試,日誌,指標,儀表板等納入設計。
隨著系統的發展,構建/維護基於微服務的體系結構的複雜性最終會對整個系統和團隊產生負面影響。
這就是為什麼在2017年1月,我們開始建模並建立一個新的系統/平臺,能夠代表我們目前在微服務中的所有複雜業務流程。設計/構建一套工作流系統,現在稱為OMS 2.0.
OMS 2.0系統是一個工作流系統,其設計和構建旨在幫助促進Jet訂單處理系統的改進開發,可擴充套件性和可擴充套件性。
工作流系統的核心設計基於我們在舊的基於微服務的架構中嘗試提供的一組保證:
- 冪等性 - 基於唯一識別符號去除重複事件(觸發器)的能力。
- 一致性 - 作為我們的狀態管理層為多個不同的後備儲存提供支援。這意味著我們還提供基於後備儲存的可配置的一致性模型。我們的狀態管理層的實現將總是模仿強一致性模型,因為我們需要能夠讀取我們自己的寫入。
與我們之前基於微服務的架構相比,這些保證最終促成了開發系統時提供了許多額外的功能,這些功能是我們基於微服務的架構與新工作流系統之間的一些關鍵差異。工作流新系統的主要功能是:
- 深度工作流 - 工作流表示為 ofollow,noindex" target="_blank">DAG (有向非迴圈圖),但可以按照您希望的方式巢狀。
- EventSourcing事件溯源 - 狀態更改都是完全事件源於日誌,而無需瞭解事件源語義。該系統的設計旨在鼓勵用“事件”進行思考。注意:OMS系統始終將事件源作為關鍵元件,但是使用了工作流新系統以後,它直接進入系統預設方式。
- 簡單實現 - 業務功能被工作流定義和相應的步驟驅動實現,這迫使系統實現模組化,並迫使開發人員在進入實現之前提前思考並思考業務流程。
- 可重用性 - 在設計時考慮了可重用性,但為開發人員留下了足夠的靈活性,以便他們可以根據自己的需要設計流程。當需要將新工作流引入現有流時,開發人員可以從現有步驟建立新流,也可以在工作流級重用。例如,為了處理Jet內的數字SKU(Apple Care),我們添加了新的工作流程來處理保修,同時允許訂單工作流程的其餘部分保持不變。這使我們能夠快速迭代並以極快的速度向系統提供新功能。
- 驗證/迴歸 - 通過工具驗證步驟行為可以快速執行迴歸測試。我們在Jet上充分利用了這一功能,我們的工作流程實現具有超過80%的程式碼覆蓋率,而無需編寫專用測試。只需單擊按鈕即可輕鬆生成新測試。
- 冪等 - 為工作流程提供冪等保證。通過從工作流DSL配置的唯一識別符號的組合來實現冪等性。
- 系統可擴充套件性 - 系統本質上是彈性的。可以輕鬆擴充套件系統以提高業務流的吞吐量。在我們過去系統中,系統的規模受到核心服務之間通訊的分割槽消費者通道的數量的限制。
- 工作流版本控制 - 系統在執行工作流程期間會維護工作流程的版本。也就是說,一旦例項啟動,工作流就不會更改。這使我們可以將更改部署到工作流程中,而無需擔心執行中的那些執行。由於工作流是業務流的表示,因此我們可以獨立地迭代業務流。
- 低級別問題只需處理一次 - 可伸縮性,效能,冪等性,重試次數,錯誤處理等都在平臺級而不是在每個微服務中處理。
- 度量標準/監控 - 系統級別度量標準易於控制,可輕鬆跟蹤/跟蹤和監控業務流程執行。
- 狀態管理 - 這是所有狀態變化的單一事實來源,稱為Journal。Journal充當為系統提供動力的標準日誌。它還充當除錯工具,以完全瞭解指定工作流的歷史記錄。與我們歷史上使用的事件源微服務相比,對狀態變化的可追溯性是一個很大的不同。
- 對延遲工作流的支援 - 能夠配置工作流定義以允許一次只執行一個工作流。在基於微服務的架構中實現這種能力非常具有挑戰性。
- 手動稽核 - 列入黑名單或有一些無根據失敗的工作流程是為了人工稽核而編寫的,可以通過虛擬化進行稽核和重新提交(工作流程系統的前端,後續帖子中有更多內容)
架構概述
新的工作流程系統很大程度上受到了 Pat Helland 的 Life Beyond Distributed Transactions的 啟發,並被設計為一個雙層架構:
- 基礎架構層 - 處理諸如可擴充套件性,冪等性和正確性,錯誤處理/重試,日誌記錄,度量等問題。目標是一次性解決這些問題,而不是針對每個服務或用例。
- 工作流層 - 與規模無關並處理實際的業務實現,這是工作流DSL和相應的步驟實現。
系統架構本質上是decode -> handle -> interpret我們在微服務中使用的管道的高度抽象版本。工作流系統也是採用此管道模板,並通過在每個操作之間繪製服務邊界來進一步分離關注點:
- 工作流觸發器(解碼)
- 工作流程執行器(控制代碼)
- 副作用執行者(解釋)
由於我們使用上述體系結構定義了非常明確的服務邊界,因此可以使工作流的執行高度並行化,每個服務的多個例項都可以獨立擴充套件。
工作流定義
使用F#DSL(域特定語言)定義工作流。但是,該系統並不嚴格限於F#DSL,此DSL已被證明可與其他語言(如Javascript)一起使用。工作流DSL定義了一系列需要執行的步驟。工作流定義的示例:
[<Workflow>] let sampleWorkflow = workflow <font>"SampleWorkflow"</font><font> [Trigger.Stream (</font><font>"kafka://jetkafka/mock-input"</font><font>, TaskIdType.FromPath(Path.JsonPath(</font><font>"$.orderId"</font><font>)), Path.JsonPath(</font><font>"$.orderId"</font><font>))] mockWorkflowMetadata (step(</font><font>"CreateOrder"</font><font>, </font><font>"CreateOrder"</font><font>) => step(</font><font>"ReserveInventory"</font><font>, </font><font>"ReserveInventory"</font><font>) => step(</font><font>"ShipOrder"</font><font>, </font><font>"ShipOrder"</font><font>) =>> [ step(</font><font>"ChargeCustomer"</font><font>, </font><font>"ChargeCustomer"</font><font>) =?> [ cond(</font><font>"WriteChargeSuccess"</font><font>, </font><font>"WriteChargeSuccess"</font><font>, Condition.Simple(Qualifier.State, </font><font>"$.transactionSuccess"</font><font>, </font><font>"true"</font><font>)) cond(</font><font>"WriteChargeFailure"</font><font>, </font><font>"WriteChargeFailure"</font><font>, Condition.Simple(Qualifier.State, </font><font>"$.transactionSuccess"</font><font>, </font><font>"false"</font><font>)) ] step(</font><font>"UpdateOrderHistory"</font><font>, </font><font>"UpdateOrderHistory"</font><font>) ] ) </font>
我將在本系列的第二篇文章中討論DSL如何工作的細節,現在,需要注意的重要部分是工作流必須包含三個方面:
- 觸發:我們應該如何觸發這個工作流程,即Kafka訊息,服務匯流排訊息,EventStore Steam,REST等。
- 元資料:這是控制工作流的元引數,如重試,併發鎖定,聚合等。
- 步驟:工作流程的關鍵行為是什麼或步驟應該是什麼?我們應該以什麼順序執行這些步驟,如果有條件步驟,我們可以並行執行多個步驟等。
Side-Effects副作用如何在這個DSL中代表?答案是它們是在步驟定義中實現的:
[<WorkflowStepsContainer>] module CheckApiHealth = open OMS.Common open OMS.Workflow.Core.Types [<WorkflowStep>] let CheckApiHealth (state : State) (aggregate : OrderAggregate) (input : TestInput) = let endpoint = sprintf <font>"http://fakeApi?omsservice-method=get"</font><font> let request = obj() let healthCheckEvt = { DomainEvent.empty with name = </font><font>"Health"</font><font> data = request |> Json.serializeToBytes } let healthCheckSideEffect = healthCheckEvt |> SideEffect.fromUri endpoint StepEvaluation.Result(state, </font><font>""</font><font>, [healthCheckSideEffect]) </font>
每個步驟都必須返回StepEvaluation.Result包含三個引數的型別:
- 狀態:步驟之間傳遞的當前狀態
- 輸入/輸出:步驟能夠傳遞輸出,該輸出在輸入時提供給前一步驟。
- 副作用:步驟需要執行的副作用列表。
這些引數用於協調工作流引擎中的步驟。詳細介紹了這些過程的工作原理和方式是後期文章的主題。
視覺化展現
工作流程系統還包括一個名為視覺化Visualizer工具的工具,可讓使用者獲得有關工作流程的所見即所得。通過在工作流日誌中顯示詳細資訊,Visualizer可以顯示正在進行的工作流程以及歷史工作流程的工作流程詳細資訊。下面顯示的是Visualizer如何支援檢查工作流的任何單次執行的示例。
面介紹了一些重要的亮點:
- 驗證和迴歸測試:能夠驗證先前執行工作流程的一個或多個步驟。
- 手動稽核:能夠檢查並重新提交失敗的工作流程或副作用
- 自我記錄:能夠在工作流程執行的任何時刻檢查每個工作流程和每個步驟的狀態,輸入和輸出。
一些統計資料
以下是我們一年多來執行的生產例項的一些統計資料。大多數這些工作流程都在標準訂單處理流程中使用:
Journal工作流歷史紀錄: 2千2百萬
工作流例項完成: 9千3百萬
唯一步驟:197
獨立工作流定義:18
未來擴充套件
以下功能是作為工作流引擎的邏輯擴充套件進行討論的內容。
- 支援Lambda(或類似)無伺服器功能。這些函式將充當工作流的步驟實現,Orchestrator將利用這些實現。
- 適用於OMS 2.0的PAAS模型,其中核心平臺已部署一次,但新工作流程可上載到現有執行部署中。使用.Net是支援這一點的限制因素,但是,最近新增的Javascript支援允許這樣做。
- 支援.Net Core和Linux容器
備擇方案
我們並不知道有工作流程編排和設計的替代方案,如:
- Netflix Conductor
- Amazon State Language + AWS Lambda
- Workflow Engine
- Workflow Foundation
- BizTalk
我們選擇自己構建OMS 2.0平臺有幾個原因,而不是試圖改進上述開箱即用的東西(banq注:關鍵時上述工作流引擎都不是基於事件溯源構建的):
- 能夠維護單獨的資料儲存以儲存工作流事件以從故障中恢復
- 狀態跟蹤和管理,能夠在執行的任何時刻重放或視覺化狀態
- 用於流和流程工作流視覺化的UI
- 與現有基礎架構整合,並與現有技術堆疊整合(Microsoft Azure + F#)
- 根據業務需求新增新功能或功能的可擴充套件性
- 可擴充套件性,在多個區域中跨多個VM擴充套件工作流程執行的能力。
一些替代方案具有一些此功能,但缺乏對可擴充套件性,狀態跟蹤/管理等內容的支援,這最終導致我們決定構建自己的系統。
結論
從基於分散式微服務的體系結構遷移到基於工作流的體系結構,對我們的開發,支援和設計開銷產生了巨大影響。作為DSL設計和合理化複雜業務流程然後實施單一責任步驟的能力對我們創新和構建複雜系統的能力產生了深遠的影響。工作流引擎提供的其他好處,如工具,故障排除,可擴充套件性。