去年年底,ThoughtWorks內部開展了一個研討會,討論“事件驅動”應用程序的性質。在過去的幾年里,他們建立了許多基于事件的系統,有被稱贊,也有被吐槽。ThoughtWorks的北美辦公室組織了一次峰會,來自世界各地的ThoughtWorks高級開發人員在會上分享了他們的想法。 峰會的最大結果是得出這樣的一個結論:當人們談論“事件”時,他們實際上意味著一些完全不同的事情。 所以Martin Fowler花了很多時間試圖從中挑出一些有用的模式, 本文就是對這些內容的簡要總結。
事件通知(Event Notification)
事件通知是最基本也是最簡單的模型。當一個系統發生了變更,它會通過發送事件消息的形式通知其他系統。發送消息的系統不要求接收消息的系統返回任何響應,即使有響應返回,它也不對其進行任何處理。這也就是所謂的“fire and forget”模式。
事件通知的好處在于它的簡單性,并且有助于降低系統間的耦合性。不過,如果在一個復雜的生態系統里使用了太多的事件通知,可能會帶來一些問題。太多的事件難以跟蹤,發生問題難以調試,除非借助完善的實時監控系統。消息流錯綜復雜,當其規模開始膨脹開來,就會造成隱患。
通知事件不會包含太多的數據,一般只包含了一些ID或者鏈接之類的信息。對于接收消息的系統來說,如果它們想得到進一步的信息,或者要基于當前事件做出一些變更,那么它們就需要向源系統發起請求,以便獲取更多的數據。那么問題來了,額外的請求不僅會造成延遲,而且一旦源系統宕機,后續的流程就無法繼續進行。
事件傳遞狀態轉移(Event-Carried State Transfer)
事件傳遞狀態轉移模型比事件通知更進一步,可以看作是對事件通知的改進。這個模型最大的特點是,事件里包含了發生變更的數據。對于接收事件的系統來說,如果想要采取進一步措施,可以直接使用事件里的數據,而無需再次向源系統發起請求,從而降低了延遲。而且就算源系統宕機,也不會影響到后續的流程。
不過,既然把變更數據放在事件里進行傳輸,那么占用更多的帶寬是不可避免的了。而且,如果有多個系統接收事件,那么這些數據就會有多個拷貝。
除此之外,接收事件的系統需要維護事件的狀態,從而將原本存在于源系統的復雜性轉移到了接收事件的系統上。
事件溯源(Event-Sourcing)
事件溯源的核心理念是說,在對系統的狀態做出變更時,把每次變更記錄為一個事件,在未來的任何時刻,都可以通過重新處理這些事件來重建系統的狀態。事件存儲是主要的事件來源,可以從事件存儲中重建系統的狀態。對于程序員來說,版本控制系統是一個最好的例子。提交日志就是事件存儲,而代碼工作副本就是系統狀態。在某個指定的工作副本上重播提交日志就可以創建另一個工作副本,也就是重建了某個時刻的系統狀態。
使用事件溯源的系統有哪些好處?首先,事件存儲結構簡單,易于存儲,它們可以被存儲在數據庫里、文件系統或者其他任意的存儲引擎里。因為記錄事件是插入操作,沒有修改也沒有刪除,就不需要用到事務控制,這也意味著可以避免使用鎖。所以,使用事件溯源可以提升系統的性能。其次,事件本身可以充當審計日志的作用。如果不使用事件溯源,那么就需要為系統維護單獨的審計日志。使用單獨的審計日志就意味著有存在兩個“真相源”,如果審計日志發生丟失,那么通過審計日志重建的狀態與真實的系統狀態會不一致。
不過,事件溯源也存在一些不足。如果事件很多,重放事件是一個耗時的過程,而且在重放過程中可能會涉及與第三方外部系統發生交互,所以需要做一些額外的操作。查詢某個時刻的狀態會變得很麻煩,因為需要通過重播事件來重建當時的狀態。解決辦法是使用快照。不過,系統沒有必要為每次變更都創建快照,而是階段性地創建快照。在查詢狀態時,通過在臨近的快照上重放少量的事件就可以獲得想要查詢的狀態。
CQRS
CQRS是Command Query Resposibility Segregation(命令查詢職責分離)的縮寫,它將讀操作(查詢)和寫操作(增、刪、改命令)進行分離,不僅讓邏輯更清晰,而且可以各自進行優化。對于讀多寫少的系統來說,就特別適合使用CQRS,因為可以針對讀性能和寫性能進行優化,而且可以進行橫向擴展。
不過CQRS的概念雖然簡單,但是實現起來相對復雜,而且涉及到很多領域驅動設計的(DDD)概念,最好結合事件溯源一起使用。
更多資料
早些年Martin Fowler的另一篇關于事件溯源的文章。Martin Fowler對CQRS更詳細的介紹。Martin Fowler關于事件通知和事件傳遞狀態轉移的詳細介紹。