引言
這是一篇講解微服務系統在擴展性伸縮性方面的演進文章,Jonas Boner認為目前普通的微服務最終將演進為事件驅動的響應式微系統架構。
今天系統架構大概有三種:單體Monolith、微單體Micoliths和微服務Microsystems,所謂微單體就是介于單體和微服務之間的一種,有很多服務,數據庫也進行了分庫分表,不像單體那樣只有一個大的WAR應用和一個數據庫,但是又不像微服務那樣,每個服務都有自己獨立的數據庫,互不干涉。
大部分微單體系統是切分了單體系統而形成的,每個Microlith由一個臺服務器運行,有自己的REST/Servlet,有自己的服務和JPA數據庫,數據庫訪問是一種同步堵塞式的數據庫線程連接池方式訪問,微單之間式通過同步的堵塞的RPC訪問(比如Dubbo和gRPC),這樣的系統沒有彈性,不具有伸縮。
Carl Hewitt說: 一個Actor模型不是真正的Actor,一個系統中應該有多個Actors。
邁向可擴展的微服務系統是引入Actor模型,有三種有用的方式:
1. 事件第一的DDD Events-first DDD
2. 響應式設計 Reactive Design
3. 基于事件的持久 Event-Based Persistence
實踐的事件第一的領域驅動設計
這種方式有別于傳統的DDD,傳統DDD最后可能形成一個臃腫的肥胖的聚合根,大家都把東西塞在里面,這種方式的問題在于我們過于關注聚焦名詞:領域對象,就像我們過于關注數據表一樣。
相反,我們應該首先聚焦關注發生了什么事情,也就是動詞,是事件。
Greg Young說:當你開始對事件建模時,將會強迫你思考系統的行為,而不是思考系統的結構。
具體怎么做呢?在傳統DDD中,分析建模第一步是找出情景場景,也就是有界的上下文,但是如何定義有界的上下文卻很難定義,有可能來自于用例需求的分解等等,但是這些邊界劃分不一定是面向領域的,可能是面向功能的,所以,事件建模就是通過事件來定義有界上下文。
比如訂單發生的地方是一個上下文,而進出庫事件發生的地方又是一個上下文。這些上下文都是因為發生不同事件而自然形成邊界。
事件重要特征是它代表發生的事實,這在很多跟蹤系統中有非常重要的應用,比如貨物跟蹤,財務和人事跟蹤記賬系統等等。
尋找發生的事實,如同大偵探波羅探案一樣,他在東方快車謀殺案中自詡和上帝一樣能看清真相,當然這也可能來自于他的強迫癥,當他發現犯罪事實竟然是來自每個人的人性之惡時,他再也不敢自詡上帝了。
我們使用DDD分析的系統真相,當然不會涉及到道德和人性幽暗,所以,在這樣的邏輯系統我們是可以
扮演上帝的,那么如何發現需求中的事實真相呢?通過事件風暴,Event Storming。
事實是遵循因果一致性的,理解事實是如何進行因果相關,Peter Alvaro說:因果性通往時空的途徑。哲學大師康德認為時空是我們觀察世界的方式,而時空蘊含的因果性正是我們科學追尋發現的目標。
因果關系代表了一種因果一致性,有因必有果,這是一致的,不會前后矛盾,是前后順序的,如同老子說:道生一,一生二,二生三,三生萬物,這也是表達了一種因果一致性。因果一致性對立面就是矛盾,如果發現矛盾,實際就是否定了因果一致性,有時我們會從矛盾方面去證偽因果一致性。因果一致性是形式邏輯的重要特征。
既然存在因果一致性,就會存在一致性自然形成的邊界,比如從第一因一直到最后一個果,這就自然形成了一條因果鏈,這條因果鏈自然也就形成自己的邊界,如果兩條因果鏈放在一起,我們能夠區分它們也是因為邊界不同。但是在佛教或現代藝術中,經常把最后一個果和第一個因再鏈接起來,形成了因果循環,也就是輪回,這實際破壞了一致性,是非時空理性思維方式。
在軟件需求中,我們如何發現因果鏈形成的邊界?一般這樣邊界中需要包含可變狀態和已經發生的事實,這些事實其實是造成狀態可變的原因,只不過表現形式不同而已。
所以,DDD中的尋找聚合,實際就是尋找因果一致性單元,也是尋找會發生failure的單元。

響應式設計
響應式設計分為兩個階段:初期的響應式編程和分布式的響應式系統。
響應式編程能夠幫助我們讓獨立的實例系統(一個VM 一個JVM或一臺服務器)內部運行更高效。通向響應式第一步是引入異步,也就是引入非堵塞,能夠更有效地利用資源,防止數據庫連接池的暴障拖垮數據庫,或一個很輕的小動作就能讓巍峨的Oracle當機趴下,這些都是同步堵塞造成的罪行。
異步和非堵塞能夠降低共享資源比如數據庫表等的競爭,避免大量使用事務機制造成的死鎖和數據庫連接池耗盡。
使用異步和非堵塞,會降低快速訪問沖擊緩慢的后端系統,比如大量的并行峰值訪問會立即打趴巍峨的ORacle數據庫。在一個大型系統在,存在各種不同處理效率的子系統,如何在快與慢之間協調,異步系統往往扮演重要的協調身份。
引入了響應式編程可以幫助處理好一臺機器內多線程問題,而響應式系統則是帶給我們多臺機器之間的分布式協調高效處理,實現彈性和伸縮性。
響應式系統是基于異步消息傳遞,能夠在空間和時間兩個維度上解耦。能夠實現從CPU核到Socket 到容器到服務器到數據中心的透明化處理方式。
真正微服務系統應該是響應式編程+響應式系統,每個微服務都需要被設計為一個分布式的微系統,
首先,將無狀態行為從有狀態的實體中分離出來,形成命令或事件,事件代表行為,實體代表結構,這樣行為和結構才能分別各自擴展伸縮。因為擴展無狀態行為比擴展有狀態的實體要容易得多。
基于事件的持久化
JIM Gray說:覆蓋式更新(update-in-place)是設計師是原罪,它違反了傳統幾百年來的會計記賬實踐。
Pat Helland說:真相是日志Log,數據庫只是日志子集的緩存。
對于建立一種可伸縮的持久存儲系統來說,事件日志才是正道。
CRUD增刪改查已經死了,只能留下增讀兩個操作CR,更新和刪除UD應該放入墳墓。
事件溯源Event Sourcing是一種CR,通過事件建模,能夠讓你注重系統中的來龍去脈,時間成為系統關鍵因素。
通過事件日志記錄事件按時間發生的順序,一旦發生事件就不斷新增追加到事件日志,其他使用者只要根據時間順序不斷讀取這些事件,再執行這些事件代表的動作,事件代表因,事件執行后自然會產生結果。
同時,事件日志避免了面向對象和關系數據庫的不匹配阻抗問題。讓對象歸對象,讓數據庫歸數據庫,
對象用于命令執行寫操作,而數據庫用于查詢讀操作,通過CQRS分離讀寫操作。使用事件日志同步讀寫兩個系統的數據。
如何協調不同聚合之間的交互呢?通過事件驅動的工作流,比如有三個聚合:訂單、支付和庫存,下訂單時,需要支付系統支付鈔票,然后從庫存出貨, 其中交互流程是這樣:
1. 開始訂單處理
2. 訂單系統發出保留訂購商品的命令給庫存系統
3. 庫存系統接受命令后,檢查是否有庫存,如果有執行保留該商品命令,發出商品被保留的事件
4. 訂單系統因為事先訂閱了商品被保留的事件,因為通過Kafka等事件流總線會接受到商品被保留的事件,
5. 訂單系統然后向支付系統發出扣款支付的命令
6. 支付系統接受到扣款命令后,如果能夠扣款,執行扣款,發出已經支付的事件。
7. 訂單系統因為也事先訂閱了支付系統的事件,將接受到已經支付的事件。
8. 訂單系統向庫存系統發出發貨命令。
9. 庫存系統接受發貨命令,將之前預先保留的商品進行真正出庫,發出商品裝運事件。
10. 訂單系統因為也事先訂閱了庫存系統的事件,將接受到商品已經出庫裝貨的事件。
11. 訂單系統確認整個訂單完成。
分布式事務怎么處理?
Pat Helland說,兩段提交的分布式事務其實是一種反高可用性的協議。
使用最終一致性的事務協議是一種猜測、抱歉試錯和補償的協議。比如Saga模式,Saga協調器能夠知道長事務運行中所有事務步驟,如果任何一個步驟,比如上面庫存系統出錯,Saga協調器將會采取補償回退之前所有步驟。
總結:不要建立微單系統,微服務其實是分布式微系統,積極擁抱響應式設計原則,擁抱事件第一的DDD和事件持久化方案。