AxonFramework是我們交易系統(tǒng)選擇的架構(gòu)基礎, 使用CQRS/EventSource 不拘泥于框架使用言秸,其實不套用任何的框架筋搏,自己構(gòu)建可能有更多的調(diào)整和細化的余地, 選用一個框架驶社, 可以加快開發(fā)的速度-至少是前期, 但是也有很多框架上面的掣肘, 得失在于自己使用中的權衡测萎,此篇主要講講CQRS中基礎的domain 中event storage.
Axonframework, 現(xiàn)在到3.0 版本亡电, 由 Allard Buijze, 作為主要貢獻者, 后面有一個商業(yè)化咨詢團隊支持:AxonIQ 的開源項目硅瞧, 在JAVA 中是歷史比較長的開源CQRS項目, 其實不久前有一個 reveno, 在一些存儲落地方面采用更激進的方案份乒, 但是現(xiàn)在好像不怎么維護, 既然要上production腕唧, 還是選擇比較成熟冒嫡, 社區(qū)比較活躍的開源項目。 Allard Buijze 本人也比較熱心四苇, 在社區(qū)問的問題基本在3天內(nèi)能夠回答孝凌, Axon對分布式支持不是太友好, 包括Event storage 的瓶頸月腋, 有次專門寫信給他們團隊蟀架, 他們還是特意安排了個gotomeeting, 講解了半個小時瓣赂,非常熱心, 對次Event source AxonIQ 提出了一個 Beta Programmer 試圖使用 AKKA persistent Actors 解決, 最終這個項目沒有太多的更新片拍, 但是項目時間不等人煌集, 所以我們改造了比較關鍵的幾個模塊, 能夠更友好的對緩存和分布式支持捌省。
Event Store 的瓶頸
在我們使用Event source 設計方案的時候苫纤,不能不討論后面的落地方案 Event store; Event source 的本質(zhì)不是只保存一個對象最新的狀態(tài),而是到達這個最新狀態(tài)所有的歷程和經(jīng)過纲缓。這一系列的過程和經(jīng)歷就是一個事件鏈卷拘,當然大家還習慣只在乎現(xiàn)在的狀態(tài),現(xiàn)在的狀態(tài)可以從這系列的事件中得到祝高, 在Event source 習慣稱呼這個為projection.
Event source 不是一個新的概念栗弟, 由Martin Fowler 十幾年前就提出來了。但是這個當時只是一個小眾的技術工闺, 現(xiàn)如今變成了主流乍赫,大概因為這樣的理念更適合當下的幾大應用:
- 大數(shù)據(jù), 機器學習陆蟆,人工智能等告訴我們歷史數(shù)據(jù)非常重要雷厂, 里面可能隱藏著寶貴的礦藏,有效的分析利用他們叠殷,可以揭示很有商業(yè)后面的秘密心肪,這些都基于你積累了這些歷史數(shù)據(jù)饥瓷, 在傳統(tǒng)的應用中状勤,我們可能很少或者沒有記錄這里歷史的數(shù)據(jù)和轉(zhuǎn)變過程安券。而Event source 架構(gòu)下敦间, 需要記錄所有歷史狀態(tài)桶良,這樣可以恢復到過去的任何一個狀態(tài)缎浇。
- 在高分布式系統(tǒng)中检号, 一事件驅(qū)動的微服務設計架構(gòu)已近變得流行措左, 如果你需要設計一個一事件驅(qū)動的微服務系統(tǒng)依痊,他需要一個落地方案,Event source 是個不錯的選擇怎披。
- 現(xiàn)在我們的系統(tǒng)需要面臨越來越多的監(jiān)管壓力胸嘁,有些業(yè)務需求我們展示系統(tǒng)發(fā)生的任何細節(jié),Event source 可以滿足這樣的需求凉逛。
某種意義上來說性宏, event 和其他數(shù)據(jù)類型沒有什么差異,但是作為event source 中的event又有自身的特征状飞。
寫
映入我腦海的第一映象毫胜, event store應該是一個append-only 的數(shù)據(jù)庫书斜, event 代表過去已經(jīng)發(fā)生的事情,于是他不可以刪除和更改酵使,比起CRUD,這樣的特征讓數(shù)據(jù)庫設計更簡單荐吉, 這樣其實一個復雜成熟的關系數(shù)據(jù)庫有點大材小用,在我們寫入event 的時候還是要注意事務口渔, 特別在一系列事件需要在一個事務中處理样屠, 需要保證原子性操作, 還有同一個對象上面的事物缺脉, 需要有對象的sequence 序列痪欲,保證唯一性; 其實也就是相當于版本號枪向。特別在并發(fā)訪問的情況下需要保證一致性勤揩。
讀
讀事件同時有他的特點, 事件有時序性秘蛔, 也就是每個有一個sequence number, 一般會批量的讀取陨亡, 或者整個讀取,比如在做replay的時候深员, 最近的數(shù)據(jù)趨向于更高概率被讀到负蠕, 比如在從snapshot 恢復狀態(tài)的時候, 這里時序性非常重要不能打破倦畅, 也就是某個聚合跟上面發(fā)生的事件遮糖, 一定是有先后關系的。
如何界定歷史事件和當前事件會有點模糊叠赐, 事件更像是一個事件流欲账,當一個聚合跟被創(chuàng)建的時候, 從某個點開始的事件被讀出來芭概,加上新進來的事件赛不, 形成一個stream,以供消費,這些事件是連貫的罢洲,對于下面的聚合跟踢故, 不會區(qū)分是歷史的還是剛剛發(fā)生的。
擴展性和彈性
比較我們傳統(tǒng)的僅僅保存一個對象的最后一個狀態(tài)惹苗, event source需要保存更多的信息:他是保存導致狀態(tài)變化的所有事件殿较,這些數(shù)據(jù)將會不斷的增長,單純落地到磁盤上不是一個很大的問題桩蓉, 挑戰(zhàn)是要保證很高的讀寫效率淋纲, 在你的業(yè)務量增長后, 保證底層的db 的可擴展性非常重要院究, 多個節(jié)點冗余備份數(shù)據(jù)是個必要的選擇帚戳。
Event Source 特有需求
一般Event source都要滿足這些需求: 對象狀態(tài)要做snapshot 玷或,也就是特定的時間點對象狀態(tài)的保存, 這個點的狀態(tài)由之前發(fā)生所有事件得到最后狀態(tài)片任, 做snapshot 不是一個必須的選擇偏友, 但是有他可以大大節(jié)約你恢復狀態(tài)的時間,你不需要從頭第一個事件來得到最后的狀態(tài)对供。 同時在一個大型的系統(tǒng)中位他, 事件也是不同模塊之間交互的橋梁, 這些事件有自己的上下文产场,所以在設計這些事件的時候要注意粒度鹅髓, 可能需要在發(fā)送前進行,切分京景,聚合或者轉(zhuǎn)換窿冯。
事件存儲Event storage選擇
更具這些特質(zhì)我們可以對照現(xiàn)下我們的選擇, 確實有很多的選擇方案确徙,一個傳統(tǒng)的關系數(shù)據(jù)庫醒串, 一個簡單的event table,可供選擇的, 比如Mysql, Oracle, SQL server 等等鄙皇。
很多公司開始時候趨向于選擇他們芜赌, 一個是公司本來就在用, 帶入成本低伴逸,經(jīng)驗比較豐富缠沈, 當擴展性漸漸變成瓶頸后,可能大家發(fā)現(xiàn)關系數(shù)據(jù)庫和event source 不是最佳搭配错蝴,現(xiàn)在的數(shù)據(jù)庫主要為隨機的CRUD, 還有很多高級的sql 語法上面的功能洲愤, 但是對于event source這些不是必要的,而去有點多余顷锰, 他僅僅是為快速的批量讀寫事件信息柬赐,當今的關系數(shù)據(jù)庫集群的代價還是比較大;而比起后起之秀的 MongoDB, 和ElasticSearch等等天然的集群和易擴展性就遜色不少馍惹。
MongoDB 是文檔型DB, 可以以集群方式工作躺率,很方便橫向擴展玛界,很適合做Event source, 每個事件可以做為一個文檔万矾, 但是基本弊端: 事務一致性, MongoDB事務是單個文檔范圍慎框, 對于多個并發(fā)的事件不能保證事務一致性良狈, 需要做些工作,而且對于事件源笨枯,需要保證讀事件的順序一致性薪丁, 而要做到這一點遇西, MongoDB, 需要添加額外的索引來保證這一切,這些都需要額外的開支严嗜,在事件增加的情況下粱檀, 會帶來更多的性能開銷。
Kafka, Apache Kafka 是一個高性能漫玄、高擴展性在事件驅(qū)動領域是比較流行的解決方案茄蚯, 他既提供消息的pipline, stream, 可提供存儲; 但是做為event storage, 還是有點弊端睦优, 一個是消息的存儲事件有限制渗常, 默認是168小時, 雖然這個可以該汗盘,但是這個不是kafka的本意皱碘, 如果濫用這一特征,可能會帶來不必要的性能問題隐孽, 主要的問題還在癌椿, kafka 更是一個消息中間件, 存儲消息不是他的本質(zhì)工作缓醋, 而event 需要不定時的讀取如失, 而且這些事件需要關聯(lián)到某個聚合根(對象),kafka 里面的消息是按照topic 來分送粱, 客戶端是按照某個offset來讀取褪贵,很難按照某個對象來過濾獲取這些消息,所以雖然kafka 在存儲的量抗俄,和事件的吞吐量上面有很多優(yōu)勢脆丁,但是還只適合做一個消息的broker, 不能做為一個event source.
Cassandra 同樣是Apache下項目, 是一個分布式高可用的DB, 通過在集群中保存多分復制來保證高可用性动雹, 不需要指定特定的master, 這里Cassandra 可以更具用戶的配置槽卫,來保證讀寫的一致性程度,一般使用quorum-based一致性來保證讀寫一致性胰蝠, Cassandra, 在某種意義上可以滿足我們Event source 中的高可用性歼培, 一致性,事件發(fā)生的序列一致性茸塞, 需要依賴LWT(light weight transaction), LWT 的實現(xiàn)需要4(!) 4x3x2x1次在集群中協(xié)調(diào)實現(xiàn)寫入操作(具體操作步驟筆者也不是了解太多)躲庄,參考上面的問題,對事務的支持钾虐, 特別對多個管理事件的事務支持也有局限性噪窘。
可以看到現(xiàn)在市面上的種種解決方案都不能很好的滿足我們的event source需求。現(xiàn)如今的這些通用的方式效扫, 需要做適當?shù)牟眉舨拍転閑vent source 所用倔监,鑒于上面我們描述的event source 的特質(zhì)直砂, 順序的讀寫(append only),并發(fā)量大浩习,基于這些特征静暂, AxonIQ 推出來自己的event store 解決方案以保證高效的 store、 snapshot,谱秽、讀籍嘹、寫、序列保證弯院,AxonIQ event store, 只不過這個是商業(yè)的辱士,做為框架外的獨立模塊。
方案
上面描述主要是AxonIQ, 選擇解決方案的理由和過程听绳, 不過我們沒有使用他的產(chǎn)品AxonIQ Event store; 但是他們選擇的標準和過程有不錯的的參考價值颂碘。
對于一個交易系統(tǒng),性能椅挣,高可用性头岔,穩(wěn)定性都是非常核心的元素, 實現(xiàn)性能也就是讀寫效率鼠证, 最好在內(nèi)存操作; 高可用性需要做parittion 和replication峡竣。
內(nèi)存中讀寫需要最終還是要落地,這里我們選擇了apache 的ignite(下面會有更多的一篇獨立講解)量九。 過來事件保持一定的replicate(bakcup 2 ) 到cluster幾點适掰, 落地由ignite 批量輸入到mysql. snapshot 也是以同樣的方式。
ignite 有自己的落地方案荠列, 不一定需要一個關系數(shù)據(jù)庫类浪, 直接以自己的格式序列化到磁盤中,但是這部分功能缺乏基本的purge,archive,migrate 功能肌似, 需要在商業(yè)版中才有, 所以現(xiàn)在只能放到傳統(tǒng)的mysql db中费就。
ignite 天生的良好集群解決方案,可以很好的保證高可用性川队, 一般設置2~3 replicate指標力细、可以定制 replicate 策略:比如避免在Neighbors之間保持backup,能很好的保證高可用性; 落地端ignite 可以做update 的collapse固额, 當然對于event source眠蚂,這個功能省掉了。
在具體的事件過程中有個比較麻煩的地方对雪, axonframwork, 對于聚合根的狀態(tài)河狐, 做了個本地的緩存米绕, 這對于性能的提升很有幫助瑟捣, 但是對于集群節(jié)點馋艺,一旦集群節(jié)點的拓撲圖變化掉后, 聚合根可能被shift 到另外一個節(jié)點迈套,或者再回來捐祠, 這個時候本地的緩存是不可用的, 這個時候其實需要從桑李, 上次snapshot 處踱蛀,在根據(jù)后續(xù)事件來恢復, 所以一旦某個聚合根從某個節(jié)點shift出去贵白, 必須刪除本地的緩存率拒,相當于本地的cache invalidate 掉。
為什么這個聚合根不保存到分布式緩存中呢禁荒? 對于更新頻繁的聚合根猬膨, 比如book, 在每秒有5個左右的報價情況下(mass order 可能50個左右,對應撤單重下單+市場下撤單+上百個產(chǎn)品)呛伴, 把龐大的聚合根每次更新都寫入到分布式緩沖中勃痴,不太現(xiàn)實,我們做法是每過1024個版本热康, 才snapshot 一次(記得LMAX其實是一天做一次),所以僅僅在本地緩存沛申, 是一個很好的權衡,這種情況下姐军,一個聚合根從某個節(jié)點 shift出去铁材, 再回來,本地的緩存不一致是比較嚴重的問題奕锌, 那么需要在集群reblance 時候衫贬, 根據(jù)自身節(jié)點partition變動, 清除下本地的緩存歇攻, 保證下次回來從event store 中恢復狀態(tài)固惯。
條條大道通羅馬, 任何的技術框架缴守、語言葬毫、解決方案等都不會只有一種,他們本質(zhì)上沒有太多的優(yōu)劣之分屡穗, 適合自己的業(yè)務場景需要才是最好的贴捡, 這里需要權衡團隊的接受能力, 學習維護的成本村砂,最重要的還有ROI烂斋。
GoXTX 下一代交易平臺技術供應商
GoXTX one-stop solution for neXT generation eXchange