事件溯源|日志记录-一个基础的微服务模式
導語:?微服務架構目前是各互聯網系統架構的首選,在使用微服務的過程中,調試一個分布式系統是一項具有挑戰的任務, 事件溯源是一種非常好的方式來解決微服務可見性的一種手段。且看大名鼎鼎的couchbase如何使用事件溯源解決微服務的可見性問題。
正如我在之前的文章中提到那樣,微服務是怎樣失敗的,調試一個分布式系統是一項具有挑戰的任務。 許多東西可能是錯的并且是不可控的, 例如網絡的不穩定性,臨時不可用或者是一些外部的BUG。
用一些工具監控網絡能被快速解決(像Service Mesh?),你也可以使用一些額外的工具像OpenTracing 來做分布式的日記記錄. 但是當我們談到理解我們的實體狀態時,并沒有快速的即插即用的框架。
你的數據可能比你代碼存活的時間還要長, 然而我們卻忽略了我們的數據隨著時間推移而產生的變化。 在大多數系統中,即使是簡單的問題, 例如“該實體如何達到這種狀態?” 或 “一個月前我的狀態是怎樣的?” 都無法回答,因為沒有保存任何變更的歷史記錄。 持續追蹤這些變更狀態對一個系統的健康是極其重要的, 不僅是為了安全或者是調試目的, 而是出于巨大的商業價值(你的產品負責人會很高興)
?
解決方案
通過事件溯源|事件記錄為服務行為增加可見性是一種很好的方式。
一個比較好的辦法是。這個有10年之久的基本概念是讓一個應用的每一次變更都應該被記錄在一個事件對象并且被有序存儲。?
如果這聽起來很熟悉,那可能是因為任何版本的控制系統或數據庫事務日志都是這種模式的重度用戶。
讓我們來深入理解它是怎么工作的。 假設我們正在為一個電商網站構建一個訂單服務(Order Service) , 讓我們看一下我們的應用狀態和事件看起來是怎樣的:
許多作者對于事件的溯源和記錄都定義了三個主要的規則:?
-
事件總是不可變的;?
-
事件總是那些過去已經發生過的事。 一些開發者錯誤指令(例如: PlaceOrder) 事件(ex: OrderPlaced)
-
理論上, 在任意時間點,你可以刪除你當前的狀態并且通過重新處理所接收到全部消息來重新構建你的整個系統。
?
事件溯源| 事件記錄流
-
消息接器:?負責將傳入的請求轉換為事件并且校驗他們;?
-
事件存儲:?負責有序存儲這些事件并且通知監聽器;?
-
事件監聽器:正如你可能猜到的, 它負責根據每一個事件類型來執行對應的業務邏輯
這種模式有很多種實現方式, 在Couchbase5.5中使用的“事件服務”就是這種模式的實現之一。 總而言之, 它允許你編寫函數, 在一個文檔被插入/更新/刪除時來觸發這些函數。 這個事件機制也能讓你生成curl 請求,因此無論何時將給定的文檔存儲在數據庫中,都可以在應用程序中觸發一個endpoint來處理它。 讓我們看一下它是如何使用事件的:?
如果你想了解更多關于它(Event Service) 資料, 閱讀 couchbase eventing 官方文檔。
Couchbase Eventing 是異步的, 所以上述套件實現僅適用于你的應用只接收異步調用的情況。 它也可以用來充當額外的安全層以觸發通知, 例如,如果有人試圖手動更新一個事件。
在某些系統中, 事件的字段和結構可能有很大的不同, 將這些事件存儲在一個固定結構的RDBM 中是很難建模的, 出于這個原因, 開發人員通常將它他們用一個varchar類型的字段將這些事件存儲為一個json字符串。這種辦法存在一個主要的問題: 它使得事件查找變得困難,使你的大部分查詢變慢, 變復雜并且充拆著大量的類似'likes'操作。 其中一種可有的解決方案是使用文檔數據庫, 因為它們大多數將文檔存儲為json并且具有用于查詢它的類似SQL的語言, 如N1QL[1]。
?
快照-對你的狀態進行版本控制
事件溯源世界中添加版本控制/歷史記錄被稱為快照。 當你想要想要知道N天以前的狀態是什么樣的, 可以避免你重新處理所有的事件。 當你需要快速識別在某時間點時應用的狀態與處理一個事件之后的所預期的狀態之間的差異。?
快照具功能十分好用,成本低,易實現并且非常適合實時的上報。 如果你決定實現一個Event Sourcing, 可以在實現快照中多投入一點努力。?
修復不一致的問題
這部份是你的所有的努力得到回報的地方。 一旦你在這個地方有了事件溯源/記錄并且具有快照功能, 你就可以使用Retroactive Event 模式的來修復不一致的情況。?
我總結一下, 如果你修復了一個BUG并且現在需要調整被影響實體的狀態, 而不是手動更新他, 你可以將你的實體的狀態設置為BUG之前的狀態,并且從那個時刻重放所以與之關聯的事件。 無須手動就會自動修改你的狀態。?
-
回滾狀態: 回滾一個實體的到這個BUG之前的狀態。 你能避免第一步和第二步重放所有的事件。??然而在這種情況下,我們正恢復以前的狀態,因為我們希望避免重新處理整個過程。
-
忽略快照:?所有恢復后的快照都應該標記為忽略,以避免將來恢復不一致的快照。
-
重新構建事件: 從目標之后重建所有事件。
但是,如果事件中有錯誤數據或者從來沒有被觸發過,該怎么辦?我們可以更新或刪除事件并重新處理整個事件嗎?
如果你還記得,事件溯源的第一條規則是“事件永遠是不變的”,這是一個很好的理由;你需要相信你所看到的日志。但它不能回答我們的問題;只需略微修改一下:我們如何在不更改事件的情況下更改事件日志?
那么,解決這個問題的一個簡單方法就是將事件標記為可忽略的,以便在重建過程中我們可以忽略它們:
如果事件是由錯誤數據或錯誤順序觸發的呢?使用這種方法,我們不得不做的就是將所有事件標記為可忽略的,并添加一個具有正確值或位置正確的新事件,如下所示:
很酷,不是嗎?但是這里有一個額外的棘手任務:我們如何構建一系列事件,而事件本身也可以在其中間插入其他事件?
一個笨辦法是為每個實體添加一個浮點計數器。它會讓你根據超任務(supertask)的理論在中間無限增加項(實際上,你受到float/double 最大的長度限制),這通常足以容納所有必要的事件來修復你的狀態:
當然,上面的方法有一些瑕疵, 但是它是一種簡單的實現,易查詢并且能大多數場上運行的很好。 如果你需構建一個更健壯結構,考慮在一個鏈表結構中存儲你的事件:
關于外部系統|其他微服務?
微服務不是孤島,重放事件的副作用之一是你的服務向外部發送消息是合理的。這些消息可能會在其他系統中引發不一致或傳播錯誤,這可能會使情況比以前更糟糕。
不幸的事,由于可能的情況多種多樣, 這里沒有銀彈來解決這個問題, 并且每一個案例不得不單獨處理。 下面給出一些普遍的解決方案:
-
臨時修改配置禁止發送任務外部消息或者添加一個攔截器允許你配置哪些消息需要發送;?
-
重新路由指定的請求到一個假的服務(如果你正在使用的服務網絡模式就是一個典型的場景)
-
使其他服務能夠識別出一個給定的操作已經在過去以相同的參數執行了,而不是拋出一個錯誤,服務需要像以前一樣返回相同的成功消息。
當然,有相當多的情況下,您無法自動修復外部不一致情況,在這種情況下,預計其他系統會輸出人為可讀的錯誤和/或觸發人工干預的通知。
?
事件溯源的優點
?盡管它是一個簡單的模式,但使用它有很多優點:
-
事件日志具有很高的商業價值;
-
它在DDD和事件驅動架構下運行得非常好。
-
調試用應用程序狀態中所有變更的來源;
-
它允許您重放失敗的事件;
-
易于調試,您可以將目標實體的所有事件復制到您的機器并調試每個事件,以了解應用程序如何達到特定狀態(忽略從生產環境復制數據的安全隱患);
-
允許您使用追溯事件模式重建/修復您的狀態。
許多作者還將優先級作為時間查詢的能力,但我認為查詢多個后續事件不是一項簡單的任務。因此,我通常認為時間查詢是快照模式的一個優點。
事件溯源的缺點
-
在同步調用中不太直觀,因為需要首先將請求轉換為事件。
-
無論何時部署重大更新,如果您想要向后兼容(也稱為“事件升級”),你將被迫遷移事件歷史記錄。
-
某些實現可能需要額外的工作來檢查最新事件的狀態,以確保所有事件都已被處理。
-
事件可能包含私有數據,所以不要忘記確保事件日志得到適當保護。
?
結論
我已經展示了稍微修改過的事件溯源/事件記錄模式,這在過去幾年一直很適合我。我第一次聽說這種方法是近10年前在Marting Fowler博客文章(必讀)中。從那以后,它為我提供了很多幫助,使我的微服務的狀態幾乎牢不可破。
然而,這種方法也不該在你的所有服務中不分青紅皂白地使用。我個人認為只有核心價值才是真正值得的。例如,您可能不需要保留用戶在系統中更改自己的姓名的所有時間的歷史記錄。
如果您有任何問題,請隨時在@deniswsrosa推特給我
?
參考鏈接
[1]?https://query-tutorial.couchbase.com/tutorial/#1
總結
以上是生活随笔為你收集整理的事件溯源|日志记录-一个基础的微服务模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入了解 gRPC:协议
- 下一篇: 使用Go语言从零编写PoS区块链