1. 程式人生 > >事件溯源|日誌記錄-一個基礎的微服務模式

事件溯源|日誌記錄-一個基礎的微服務模式

導語: 微服務架構目前是各網際網路系統架構的首選,在使用微服務的過程中,除錯一個分散式系統是一項具有挑戰的任務, 事件溯源是一種非常好的方式來解決微服務可見性的一種手段。且看大名鼎鼎的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