源碼:http://www.jinhusns.com/Products/Download/?type=xcjnode
關於CQRS(Command Query Responsibility Segration)架構,你們應該不會陌生了。簡單的說,就是一個系統,從架構上把它拆分爲兩部分:命令處理(寫請求)+查詢處理(讀請求)。而後讀寫 兩邊能夠用不一樣的架構實現,以實現CQ兩端(即Command Side,簡稱C端;Query Side,簡稱Q端)的分別優化。CQRS做爲一個讀寫分離思想的架構,在數據存儲方面,沒有作過多的約束。因此,我以爲CQRS能夠有不一樣層次的實現, 好比:git
我以爲要實現高性能,能夠談的東西還有不少。下面我想重點說說我想到的一些設計思路:github
我以爲這是很重要的一點。什麼是資源爭奪?我想就是多個線程同時修改同一個數據。就像阿里秒殺活動同樣,秒殺開搶時,不少人同時搶一個商品,致使商 品的庫存會被併發更新減庫存,這就是一個資源爭奪的例子。通常若是資源競爭不激烈,那無所謂,不會影響性能;可是若是像秒殺這種場景,那db就會抗不住 了。在秒殺這種場景下,大量線程須要同時更新同一條記錄,進而致使MySQL內部大量線程堆積,對服務性能、穩定性形成很大傷害。那怎麼辦呢?我記得阿里 的丁奇寫過一個分享,思路就是當MySQL的服務端多個線程同時修改一條記錄時,能夠對這些修改請求進行排隊,而後對於InnoDB引擎層,就是串行的。 這樣排隊後,無論上層應用發過來多少並行的修改同一行的請求,對於MySQL Server端來講,內部老是會聰明的對同一行的修改請求都排隊處理;這樣就能確保不會有併發產生,從而不會致使線程浪費堆積,致使數據庫性能降低。這個 方案能夠見下圖所示:數據庫
如上圖所示,當不少請求都要修改A記錄時,MySQL Server內部會對這些請求進行排隊,而後一個個將對A的修改請求提交到InnoDB引擎層。這樣看似在排隊,實際上會確保MySQL Server不會死掉,能夠保證對外提供穩定的TPS。編程
可是,對於商品秒殺這個場景,還有優化的空間,就是Group Commit技術。Group Commit就是對多個請求合併爲一次操做進行處理。秒殺時,你們都在購買這個商品,A買2件,B買3件,C買1件;其實咱們能夠把A,B,C的這三個請 求合併爲一次減庫存操做,就是一次性減6件。這樣,對於A,B,C的這三個請求,在InnoDB層咱們只須要作一次減庫存操做便可。假設咱們Group Commit的每一批的size是50,那就是能夠將50個減操做合併爲一次減操做,而後提交到InnoDB。這樣,將大大提升秒殺場景下,商品減庫存的 TPS。可是這個Group Commit的每批大小不是越大越好,而是要根據併發量以及服務器的實際狀況作測試來獲得一個最優的值。經過Group Commit技術,根據丁奇的PPT,商品減庫存的TPS性能從原來的1.5W提升到了8.5W。性能優化
從上面這個例子,咱們能夠看到阿里是如何在實際場景中,經過優化MySQL Server來實現高併發的商品減庫存的。可是,這個技術通常人還真的不會!由於沒多少人有能力去優化MySQL的服務端,排隊也不行,更別說Group Commit了。這個功能並非MySQL Server自帶的,而是須要本身實現的。可是,這個思路我想咱們均可以借鑑。服務器
那麼對於CQRS架構,如何按照這個思路來設計呢?我想重點說一下我上面提到的第二種CQRS架構。對於C端,咱們的目標是儘量的在1s內處理更 多的Command,也就是數據寫請求。在經典DDD的四層架構中,咱們會有一個模式叫工做單元模式,即Unit of Work(簡稱UoW)模式。經過該模式,咱們能在應用層,一次性以事務的方式將當前請求所涉及的多個對象的修改提交到DB。微軟的EF實體框架的 DbContext就是一個UoW模式的實現。這種作法的好處是,一個請求對多個聚合根的修改,能作到強一致性,由於是事務的。可是這種作法,實際上,沒 有很好的遵照避開資源競爭的原則。試想,事務A要修改a1,a2,a3三個聚合根;事務B要修改a2,a3,a4;事務C要修改a3,a4,a5三個聚合 根。那這樣,咱們很容易理解,這三個事務只能串行執行,由於它們要修改相同的資源。好比事務A和事務B都要修改a2,a3這兩個聚合根,那同一時刻,只能 由一個事務能被執行。同理,事務B和事務C也是同樣。若是A,B,C這種事務執行的併發很高,那數據庫就會出現嚴重的併發衝突,甚至死鎖。那要如何避免這 種資源競爭呢?我以爲咱們能夠採起三個措施:網絡
這個作法其實就是縮小事務的範圍,確保一個事務一次只涉及一條記錄的修改。也就是作到,只有單個聚合根的修改纔是事務的,讓聚合根成爲數據強一致性 的最小單位。這樣咱們就能最大化的實現並行修改。可是你會問,可是我一個請求就是會涉及多個聚合根的修改的,這種狀況怎麼辦呢?在CQRS架構中,有一個 東西叫Saga。Saga是一種基於事件驅動的思想來實現業務流程的技術,經過Saga,咱們能夠用最終一致性的方式最終實現對多個聚合根的修改。對於一 次涉及多個聚合根修改的業務場景,通常老是能夠設計爲一個業務流程,也就是能夠定義出要先作什麼後作什麼。好比以銀行轉帳的場景爲例子,若是是按照傳統事 務的作法,那多是先開啓一個事務,而後讓A帳號扣減餘額,再讓B帳號加上餘額,最後提交事務;若是A帳號餘額不足,則直接拋出異常,同理B帳號若是加上 餘額也遇到異常,那也拋出異常便可,事務會保證原子性以及自動回滾。也就是說,數據一致性已經由DB幫咱們作掉了。架構
可是,若是是Saga的設計,那就不是這樣了。咱們會把整個轉帳過程定義爲一個業務流程。而後,流程中會包括多個參與該流程的聚合根以及一個用於協 調聚合根交互的流程管理器(ProcessManager,無狀態),流程管理器負責響應流程中的每一個聚合根產生的領域事件,而後根據事件發送相應的 Command,從而繼續驅動其餘的聚合根進行操做。併發
轉帳的例子,涉及到的聚合根有:兩個銀行帳號聚合根,一個交易(Transaction)聚合根,它用於負責存儲流程的當前狀態,它還會維護流程狀 態變動時的規則約束;而後固然還有一個流程管理器。轉帳開始時,咱們會先建立一個Transaction聚合根,而後它產生一個 TransactionStarted的事件,而後流程管理器響應事件,而後發送一個Command讓A帳號聚合根作減餘額的操做;A帳號操做完成後,產 生領域事件;而後流程管理器響應事件,而後發送一個Command通知Transaction聚合根確認A帳號的操做;確認完成後也會產生事件,而後流程 管理器再響應,而後發送一個Command通知B帳號作加上餘額的操做;後續的步驟就不詳細講了。大概意思我想已經表達了。總之,經過這樣的設計,咱們可 以經過事件驅動的方式,來完成整個業務流程。若是流程中的任何一步出現了異常,那咱們能夠在流程中定義補償機制實現回退操做。或者不回退也不要緊,由於 Transaction聚合根記錄了流程的當前狀態,這樣咱們能夠很方便的後續排查有狀態沒有正常結束的轉帳交易。具體的設計和代碼,有興趣的能夠去看一 下ENode源代碼中的銀行轉帳的例子,裏面有完整的實現。
和上面秒殺的設計同樣,咱們能夠對要同時修改同一個聚合根的Command進行排隊。只不過這裏的排隊不是在MySQL Server端,而是在咱們本身程序裏作這個排隊。若是咱們是單臺服務器處理全部的Command,那排隊很容易作。就是隻要在內存中,當要處理某個 Command時,判斷當前Command要修改的聚合根是否前面已經有Command在處理,若是有,則排隊;若是沒有,則直接執行。而後當這個聚合根 的前一個Command執行完後,咱們就能處理該聚合根的下一個Command了;可是若是是集羣的狀況下呢,也就是你不止有一臺服務器在處理 Command,而是有十臺,那要怎麼辦呢?由於同一時刻,徹底有可能有兩個不一樣的Command在修改同一個聚合根。這個問題也簡單,就是咱們能夠對要 修改聚合根的Command根據聚合根的ID進行路由,根據聚合根的ID的hashcode,而後和當前處理Command的服務器數目取模,就能肯定當 前Command要被路由到哪一個服務器上處理了。這樣咱們能確保在服務器數目不變的狀況下,針對同一個聚合根實例修改的全部Command都是被路由到同 一臺服務器處理。而後加上咱們前面在單個服務器裏面內部作的排隊設計,就能最終保證,對同一個聚合根的修改,同一時刻只有一個線程在進行。
經過上面這兩個設計,咱們能夠確保C端全部的Command,都不會出現併發衝突。可是也要付出代價,那就是要接受最終一致性。好比Saga的思 想,就是在最終一致性的基礎上而實現的一種設計。而後,基於以上兩點的這種架構的設計,我以爲最關鍵的是要作到:1)分佈式消息隊列的可靠,不能丟消息, 不然Saga流程就斷了;2)消息隊列要高性能,支持高吞吐量;這樣才能在高併發時,實現整個系統的總體的高性能。我開發的EQueue就是爲了這個目標而設計的一個分佈式消息隊列,有興趣的朋友能夠去了解下哦。
CQRS架構是基於消息驅動的,因此咱們要儘可能避免消息的重複消費。不然,可能會致使某個消息被重複消費而致使最終數據沒法一致。對於CQRS架構,我以爲主要考慮三個環節的消息冪等處理。
這一點,我想不難理解。好比轉帳的例子中,假如A帳號扣減餘額的命令被重複執行了,那會致使A帳號扣了兩次錢。那最後就數據沒法一致了。因此,咱們 要保證Command不能被重複執行。那怎麼保證呢?想一想咱們平時一些判斷重複的操做怎麼作的?通常有兩個作法:1)db對某一列建惟一索引,這樣能夠嚴 格保證某一列數據的值不會重複;2)經過程序保證,好比插入前先經過select查詢判斷是否存在,若是不存在,則insert,不然就認爲重複;顯然通 過第二種設計,在併發的狀況下,是不能保證絕對的惟一性的。而後CQRS架構,我認爲咱們能夠經過持久化Command的方式,而後把CommandId 做爲主鍵,確保Command不會重複。那咱們是否要每次執行Command前線判斷該Command是否存在呢?不用。由於出現Command重複的概 率很低,通常只有是在咱們服務器機器數量變更時纔會出現。好比增長了一臺服務器後,會影響到Command的路由,從而最終會致使某個Command會被 重複處理,關於這裏的細節,我這裏不想多展開了,呵呵。有問題到回覆裏討論吧。這個問題,咱們也能夠最大程度上避免,好比咱們能夠在某一天系統最空的時候 預先增長好服務器,這樣能夠把出現重複消費消息的狀況降至最低。天然也就最大化的避免了Command的重複執行。因此,基於這個緣由,咱們沒有必要在每 次執行一個Command時先判斷該Command是否已執行。而是隻要在Command執行完以後,直接持久化該Command便可,而後由於db中以 CommandId爲主鍵,因此若是出現重複,會主鍵重複的異常。咱們只要捕獲該異常,而後就知道了該Command已經存在,這就說明該Command 以前已經被處理過了,那咱們只要忽略該Command便可(固然實際上不能直接忽略,這裏我因爲篇幅問題,我就不詳細展開了,具體咱們能夠再討論)。然 後,若是持久化沒有問題,說明該Command以前沒有被執行過,那就OK了。這裏,還有個問題也不能忽視,就是某個Command第一次執行完成了,也 持久化成功了,可是它因爲某種緣由沒有從消息隊列中刪除。因此,當它下次再被執行時,Command Handler裏可能會報異常,因此,健壯的作法時,咱們要捕獲這個異常。當出現異常時,咱們要檢查該Command是否以前已執行過,若是有,就要認爲 當前Command執行正確,而後要把以前Command產生的事件拿出來作後續的處理。這個問題有點深刻了,我暫時不細化了。有興趣的能夠找我私聊。
而後,由於咱們的架構是基於ES的,因此,針對新增或修改聚合根的Command,老是會產生相應的領域事件(Domain Event)。咱們接下來的要作的事情就是要先持久化事件,再分發這些事件給全部的外部事件訂閱者。你們知道,聚合根有生命週期,在它的生命週期裏,會經 歷各類事件,而事件的發生總有肯定的時間順序。因此,爲了明確哪一個事件先發生,哪一個事件後發生,咱們能夠對每一個事件設置一個版本號,即version。聚 合根第一個產生的事件的version爲1,第二個爲2,以此類推。而後聚合根自己也有一個版本號,用於記錄當前本身的版本是什麼,它每次產生下一個事件 時,也能根據本身的版本號推導出下一個要產生的事件的版本號是什麼。好比聚合根當前的版本號爲5,那下一個事件的版本號則爲6。經過爲每一個事件設計一個版 本號,咱們就能很方便的實現聚合根產生事件時的併發控制了,由於一個聚合根不可能產生兩個版本號同樣的事件,若是出現這種狀況,那說明必定是出現併發衝突 了。也就是必定是出現了同一個聚合根同時被兩個Command修改的狀況了。因此,要實現事件持久化的冪等處理,也很好作了,就是db中的事件表,對聚合 根ID+聚合根當前的version建惟一索引。這樣就能在db層面,確保Event持久化的冪等處理。另外,對於事件的持久化,咱們也能夠像秒殺那樣, 實現Group Commit。就是Command產生的事件不用立馬持久化,而是能夠先積累到必定的量,好比50個,而後再一次性Group Commit全部的事件。而後事件持久化完成後,再修改每一個聚合根的狀態便可。若是Group Commit事件時遇到併發衝突(因爲某個聚合根的事件的版本號有重複),則退回爲單個一個個持久化事件便可。爲何能夠放心的這樣作?由於咱們已經基本 作到確保一個聚合根同一時刻只會被一個Command修改。這樣就能基本保證,這些Group Commit的事件也不會出現版本號衝突的狀況。因此,你們是否以爲,不少設計實際上是一環套一環的。Group Commit什麼時候出發?我以爲能夠只要知足兩個條件了就能夠觸發:1)某個定時的週期到了就能夠觸發,這個定時週期能夠根據本身的業務場景進行配置,好比 每隔50ms觸發一次;2)要Commit的事件到達某個最大值,即每批能夠持久化的事件個數的最大值,好比每50個事件爲一批,這個BatchSize 也須要根據實際業務場景和你的存儲db的性能綜合測試評估來獲得一個最適合的值;什麼時候可使用Group Commit?我以爲只有是在併發很是高,當單個持久化事件遇到性能瓶頸時,才須要使用。不然反而會下降事件持久化的實時性,Group Commit提升的是高併發下單位時間內持久化的事件數。目的是爲了下降應用和DB之間交互的次數,從而減小IO的次數。不知不覺就說到了最開始說的那3 點性能優化中的,儘可能減小IO了,呵呵。
CQRS架構圖中,事件持久化完成後,接下來就是會把這些事件發佈出去(發送到分佈式消息隊列),給消費者消費了,也就是給全部的Event Handler處理。這些Event Handler多是更新Q端的ReadDB,也多是發送郵件,也多是調用外部系統的接口。做爲框架,應該有職責儘可能保證一個事件儘可能不要被某個 Event Handler重複消費,不然,就須要Event Handler本身保證了。這裏的冪等處理,我能想到的辦法就是用一張表,存儲某個事件是否被某個Event Handler處理的信息。每次調用Event Handler以前,判斷該Event Handler是否已處理過,若是沒處理過,就處理,處理完後,插入一條記錄到這個表。這個方法相信你們也都很容易想到。若是框架不作這個事情,那 Event Handler內部就要本身作好冪等處理。這個思路就是select if not exist, then handle, and at last insert的過程。能夠看到這個過程不像前面那兩個過程那樣很嚴謹,由於在併發的狀況下,理論上仍是會出現重複執行Event Handler的狀況。或者即使不是併發時也可能會形成,那就是假如event handler執行成功了,可是last insert失敗了,那框架仍是會重試執行event handler。這裏,你會很容易想到,爲了作這個冪等支持,Event Handler的一次完整執行,須要增長很多時間,從而會最後致使Query Side的數據更新的延遲。不過CQRS架構的思想就是Q端的數據由C端經過事件同步過來,因此Q端的更新自己就是有必定的延遲的。這也是CQRS架構所 說的要接收最終一致性的緣由。
你們知道,整個CQRS架構中,Command,Event的產生以及處理是很是頻繁的,數據量也是很是大的。那如何保證這幾步冪等處理的高性能 呢?對於Command的冪等處理,若是對性能要求不是很高,那咱們能夠簡單使用關係型DB便可,好比Sql Server, MySQL均可以。要實現冪等處理,只須要把主鍵設計爲CommandId便可。其餘不須要額外的惟一索引。因此這裏的性能瓶頸至關因而對單表作大量 insert操做的最大TPS。通常MySQL數據庫,SSD硬盤,要達到2W TPS應該沒什麼問題。對於這個表,咱們基本只有寫入操做,不須要讀取操做。只有是在Command插入遇到主鍵衝突,而後纔可能須要偶爾根據主鍵讀取一 下已經存在的Command的信息。而後,若是單表數據量太大,那怎麼辦,就是分表分庫了。這就是最開始談到的,要避開海量數據這個原則了,我想就是經過 sharding避開大數據來實現繞過IO瓶頸的設計了。不過一旦涉及到分庫,分表,就又涉及到根據什麼分庫分表了,對於存儲Command的表,我以爲 比較簡單,咱們能夠先根據Command的類型(至關於根據業務作垂直拆分)作第一級路由,而後相同Command類型的Command,根據 CommandId的hashcode路由(水平拆分)便可。這樣就能解決Command經過關係型DB存儲的性能瓶頸問題。其實咱們還能夠經過流行的基 於key/value的NoSQL來存儲,好比能夠選擇本地運行的leveldb,或者支持分佈式的ssdb,或者其餘的,具體選擇哪一個,能夠結合本身的 業務場景來選擇。總之,Command的存儲能夠有不少選擇。
經過上面的分析,咱們知道Event的存儲惟一須要的是AggregateRootId+Version的惟一索引,其餘就無任何要求了。那這樣就 和CommandStore同樣好辦了。若是也是採用關係型DB,那隻要用AggregateRootId+Version這兩個做爲聯合主鍵便可。而後 若是要分庫分表,咱們能夠先根據AggregateRootType作第一級垂直拆分,即把不一樣的聚合根類型產生的事件分開存儲。而後和Command一 樣,相同聚合根產生的事件,能夠根據AggregateRootId的hashcode來拆分,同一個AggregateRootId的全部事件都放一 起。這樣既能保證AggregateRootId+Version的惟一性,又能保證數據的水平拆分。從而讓整個EventStore能夠無限制水平伸 縮。固然,咱們也徹底能夠採用基於key/value的NoSQL來存儲。另外,咱們查詢事件,也都是會肯定聚合根的類型以及聚合根的ID,因此,這和路 由機制一直,不會致使咱們沒法知道當前要查詢的聚合根的事件在哪一個分區上。
In-Memory模式也是一種減小網絡IO的一種設計,經過讓全部生命週期還沒結束的聚合根一直常駐在內存,從而實現當咱們要修改某個聚合根時, 沒必要再像傳統的方式那樣,先從db獲取聚合根,再更新,完成後再保存到db了。而是聚合根一直在內存,當Command Handler要修改某個聚合根時,直接從內存拿到該聚合根對象便可,不須要任何序列化反序列化或IO的操做。基於ES模式,咱們不須要直接保存聚合根, 而是隻要簡單的保存聚合根產生的事件便可。當服務器斷電要恢復聚合根時,則只要用事件溯源(Event Sourcing, ES)的方式恢復聚合根到最新狀態便可。
瞭解過actor的人應該也知道actor也是整個集羣中就一個實例,而後每一個actor本身都有一個mailbox,這個mailbox用於存放 當前actor要處理的全部的消息。只要服務器不斷電,那actor就一直存活在內存。因此,In-Memory模式也是actor的一個設計思想之一。 像以前很轟動的國外的一個LMAX架構,號稱每秒單機單核能夠處理600W訂單,也是徹底基於in-memory模式。不過LMAX架構我以爲只要做爲學 習便可,要大範圍使用,仍是有不少問題要解決,老外他們使用這種架構來處理訂單,也是基於特定場景的,而且對編程(代碼質量)和運維的要求都很是高。具體 有興趣的能夠去搜一下相關資料。
關於in-memory架構,想法是好的,經過將全部數據都放在內存,全部持久化都異步進行。也就是說,內存的數據纔是最新的,db的數據是異步持 久化的,也就是某個時刻,內存中有些數據可能尚未被持久化到db。固然,若是你說你的程序不須要持久化數據,那另當別論了。那若是是異步持久化,主要的 問題就是宕機恢復的問題了。咱們看一下akka框架是怎麼持久化akka的狀態的吧。
從上面的過程,咱們能夠看出,akka框架本質上也實現了避免資源競爭的原則,由於每一個actor是單線程處理它的mailbox中的每一個消息的, 從而就避免了併發衝突。而後咱們能夠看到akka框架也是先持久化事件以後,再更新actor的狀態的。這說明,akka採用的也叫保守的方式,即必須先 確保數據落地,再更新內存,再處理下一個消息。真正理想的in-memory架構,應該是能夠忽略持久化,當actor處理完一個消息後,當即修改本身的 狀態,而後當即處理下一個消息。而後actor產生的事件的持久化,徹底是異步的;也就是不用等待持久化事件完成後再更新actor的狀態,而後處理下一 個消息。
我認爲,是否是異步持久化不重要,由於既然你們都要面臨一個問題,就是要在宕機後,恢復actor的狀態,那持久化事件是不可避免的。因此,我也是 認爲,事件沒必要異步持久化,徹底能夠像akka框架那樣,產生的事件先同步持久化,完成後再更新actor的狀態便可。這樣作,在宕機恢復actor的狀 態到最新時,就只要簡單的從db獲取全部事件,而後經過ES獲得actor最新狀態便可。而後若是擔憂事件同步持久化有性能瓶頸,那這個老是不可避免,這 塊不作好,那整個系統的性能就上不去,因此咱們能夠採用SSD,sharding, Group Commit, NoSQL等方法,優化持久化的性能便可。固然,若是採用異步持久化事件的方式,確實能大大提升actor的處理性能。可是要作到這點,還須要有一些前提 的。好比要確保整個集羣中一個actor只有一個實例,不能有兩個同樣的actor在工做。由於若是出現這種狀況,那這兩個同樣的actor就會同時產生 事件,致使最後事件持久化的時候一定會出現併發衝突(事件版本號相同)的問題。但要保證急羣衆一個actor只有一個實例,是很困難的,由於咱們可能會動 態往集羣中增長服務器,此時一定會有一些actor要遷移到新服務器。這個遷移過程也很複雜,一個actor從原來的服務器遷移到新的服務器,意味着要先 中止原服務器的actor的工做。而後還要把actor再新服務器上啓動;而後原服務器上的actor的mailbox中的消息還要發給新的actor, 而後後續可能還在發給原actor的消息也要轉發到新的actor。而後新的actor重啓也很複雜,由於要確保啓動以後的actor的狀態必定是最新 的,而咱們知道這種純in-memory模式下,事件的持久化時異步的,因此可能還有一些事件還在消息隊列,還沒被持久化。因此重啓actor時還要檢查 消息隊列中是否還有未消費的事件。若是還有,就須要等待。不然,咱們恢復的actor的狀態就不是最新的,這樣就沒法保證內存數據是最新的這個目的,這樣 in-memory也就失去了意義。這些都是麻煩的技術問題。總之,要實現真正的in-memory架構,沒那麼容易。固然,若是你說你能夠用數據網格之 類的產品,無分佈式,那也許可行,不過這是另一種架構了。
上面說了,akka框架的核心工做原理,以及其餘一些方面,好比akka會確保一個actor實例在集羣中只有一個。這點其實也是和本文說的同樣, 也是爲了不資源競爭,包括它的mailbox也是同樣。以前我設計ENode時,沒了解過akka框架,後來我學習後,發現和ENode的思想是如此接 近,呵呵。好比:1)都是集羣中只有一個聚合根實例;2)都對單個聚合根的操做的Command作排隊處理;3)都採用ES的方式進行狀態持久化;4)都 是基於消息驅動的架構。雖然實現方式有所區別,但目的都是相同的。
源碼:http://www.jinhusns.com/Products/Download/?type=xcj
本文,從CQRS+Event Sourcing的架構出發,結合實現高性能的幾個要注意的點(避開網絡開銷(IO),避開海量數據,避開資源爭奪),分析了這種架構下,我所想到的一些 可能的設計。整個架構中,一個Command在被處理時,通常是須要作兩次IO,1)持久化Command;2)持久化事件;固然,這裏沒有算上消息的發 送和接收的IO。整個架構徹底基於消息驅動,因此擁有一個穩定可擴展高性能的分佈式消息隊列中間件是比不可少的,EQueue正是在向這個目標努力的一個 成果。目前EQueue的TCP通訊層,能夠作到發送100W消息,在一臺i7 CPU的普通機器上,只需3s;有興趣的同窗能夠看一下。