讀寫分離水太深,你把握不住,讓CQRS來

多年之前,那時我正年輕,作技術如魚得水,甚至一度但願本身能當一生的一線程序員。程序員

可是我又有兩個小願望想要達成:一個是想多掙點錢;另外一個就是對項目的技術棧和架構選型能多有點主動權。數據庫

多掙點錢是由於當時我剛結婚不久,有本身的家庭規劃,因此掙錢的慾望也蠻強。緩存

而想有多點技術主動權的緣由則是當時領導很賞識我,有些東西逐漸的放權讓我作,我嚐到了甜頭,因此,也有了本身的一些小野心。服務器

而正巧就在那時候,領導給我了一個如今看來職業生涯中還挺重要的機會。架構

當時,廣告聯盟正是發展的如火如荼的時候,公司也想參與進去分杯羹,因而決定從零開始搞一套廣告平臺。併發

而我正好也有些相似的開發經驗,且作事還算靠譜,因而,領導便想着讓我去當這套系統的技術負責人。高併發

若是我能把系統作好,對我來講絕對是個證實本身的機會,對之後達成個人兩個小願望有好處。對我誘惑很大。性能

只是,老天給你開了一扇門,就總要給你關一扇窗。這個機會不只僅是我領導看上了,當時,還有另一個部門的老大也瞄上了。學習

不得已,上了高層會議討論。討論來討論去的結果就是學習當時別的公司的作法,內部競爭。測試

兩個部門作各作一套平臺,而後各放到線上運營一陣子,誰作得好誰就能獲得公司全力投入的機會。

好吧,機會變成了冒險。只是到此時,我也並不能退縮。一旦我退縮會連累賞識個人領導,並且未來在公司的發展也會嚴重受阻,只能衝了。

爲了贏得這場競爭,我和這套系統的產品負責人也溝通了許久。最後定下來了兩個必須實現的目標:

1. 這套系統功能必定要儘可能多,尤爲是提供給相關業務人員的功能要多。

之因此要這樣,是由於如今是內部競爭。而對於內部競爭,使用咱們這套系統的業務人員話語權其實很是大,他們的滿意度極可能是最終評估的勝負手。

同時,咱們也計劃爲投放在咱們這套系統的廣告主們多準備一些體驗度很是好的數據追蹤和分析功能,這樣能最大的增長咱們產品的吸引力。

2. 這套系統的穩定性和可靠性要求很是高,有時候哪怕爲此作一些過分設計和實現也是值得的。

這裏要解釋下穩定性和可靠性在咱們當時那個場景裏的含義。穩定性就是要保證性能是穩定的,也就是說咱們的系統響應時間應該盡全力保證在一個很短的時間內響應。

而可靠性則是咱們的系統應該盡全力保證不出錯,由於出錯極可能就會形成用戶流失,致使咱們的產品失敗。

定完目標以及產品給完需求後,我就和團隊進入了異常艱苦的開發工做。那時候,我真的是付出了我全身心的心血。

其實,我原本是個享受生活賽過埋頭苦幹的人。雖然此前工做也很忙碌,可是空閒日子也是過得很愜意的。聽聽歌,看看電影,有時和老婆找家餐廳享用美食,時不時的也會踢一場酣暢淋漓的足球。

但是,自從開始投入了這套廣告系統的開發之後,清閒的日子就一去不復返了。

我記得那時候我下班是踉踉蹌蹌的走,上班又是踉踉蹌蹌的來。當時最大的心願就是有張牀,躺下去永遠別有人叫醒我。

但是即便這樣辛苦,我依然遇到了數不清楚的難題,這些橫亙在開發路上的硬骨頭,致使個人開發目標一再被調整。

其中最麻煩的,就是高併發的性能問題。

當時個人經驗尚淺,Java 說實話周邊的生態也並不完善。能用來承載訪問的也就是緩存和數據庫。同時,因爲版權等問題,我還只能選擇 MySQL 數據庫。

爲了解決這些性能問題,我還特地把官方的 MySQL 手冊打印了出來,每天鑽研。

開始的時候,爲了抗住預想中的超高併發量,我採用的是當時很流行的讀寫分離模式。

可是,實際測試下來,老是有各類不滿意的地方。其中最麻煩的就是各類複雜查詢的性能。

我說過爲了得到內部競爭的勝利,這套系統咱們儘量想去往高併發、多功能這兩個目標上靠。因此,爲了這兩個目標,這套系統其實多了不少方便業務人員使用的功能,而且這個功能設想的目標是:

在高併發下,也依然保持穩定和流暢。

其中,最典型的一個業務就是能夠實時更新的廣告投放排行功能。

這個廣告投放排行需求是這樣的:

  • 首先,咱們的用戶要能在管理後臺看到他們本身的投放廣告排行,排名是根據消費的金額和點擊次數等指標來排次序。
  • 其次,在咱們的後臺,也給業務人員也搞了個這麼個排名,不一樣的是它是個全局的,是咱們全部客戶投放的廣告的一個總排行。
  • 而後,這個排名要能實時的根據消費金額和點擊次數的變化而變化。固然,這個實時能夠搞成準實時,只要別延遲太過也能夠。

自己呢,作排行榜因爲用的指標比較多,就須要寫很複雜的 SQL 去數據庫中查詢。再加上個須要實時變化,那就得不停的去數據庫中查詢。

而對於這種狀況,我不管如何優化老是得不到滿意的結果。若是我緩存這個排行呢,因爲這個排行須要各類統計加排序,因此從數據庫中查詢出來後,還須要各類模型轉換,若是併發量上來,查詢再轉換,性能真的掉的飛快。

那時候,個人壓力很是大,腦子一直在想着性能問題,手上的 MySQL 手冊翻得都快爛的掉了頁。就連回到家睡覺時,眼睛閉上腦海裏老是想着如何解決這些問題。

最終上線的時間不斷地逼近,手上的項目卻死死卡在這些性能難題上難以進展,競爭對手卻時不時聽到內部競爭對手順利進行到某某程度的消息。

這一切的一切我快扛不住了,心裏勸本身放棄的聲音也愈來愈大。

我曾經一度認爲本身是一個韌性很是強的人,可是如今看來,其實也就是個再普通不過的打工仔而已。

我要逃避了,我想去和產品商量就這樣上線吧,我不想管了,是死是活看老天爺吧,賭對方也遇到我這種問題,甚至還不如我。

只是就在我準備拉上產品最終肯定就這樣上線的時候,我心裏強烈的不甘阻止了我。我想在我放棄以前,不管如何要知道競爭對手怎麼樣了,對方有什麼方案和思路可供我參考的。

我找遍了我全部公司的熟人,去不停的打探競爭對手的消息。可是,結果並很差,由於對方比我作的更絕,他們進行了封閉式的開發,並且警戒性很是高。

最終,我只獲得了一個關鍵詞:CQRS。對方用 CQRS 來解決性能問題!!!

我年少讀書,那時尚未手機,老是能一心一意的作好讀書這件事,讀書效率極高。可是現在有了手機,如今我再讀書,老是時不時會分心去看看手機裏的信息,有時候爲了好好把書讀進去,還不得不把手機特地丟在遠處,防止分心。

而 CQRS 就是這種思路。這個模式與其說是一種架構模式還不如說是一種思想。

CQRS 認爲一套系統裏的操做,總共就分爲讀和寫兩大類。若是一套系統不專門把讀和寫專門分開優化,那麼系統就像我讀書帶着手機那樣,會一心兩用,從而由於彼此影響,致使各自的性能沒法達到最優。

因此,讀寫應該專門的分開,並分別優化。

在 CQRS 裏,寫這種行爲被稱爲命令,而讀行爲被稱爲查詢。由於想讓他們分開,因此 CQRS 模式中文翻譯過來就被稱爲命令查詢權責分離模式

我知道這套思路以後,原本並不在乎,由於乍一看,這套東西其實和我採用的數據庫的讀寫分離是同樣的,就是把讀寫給分開。

可是,個人技術直覺告訴我,這些並無那麼簡單。

在計算機的世界裏,一個名詞不會平白無故出現,也不會平白無故的開始流行。若是真的和數據庫的讀寫分離同樣,那直接叫數據庫讀寫分離就行了。必定有什麼不同了。

我沒再知足於中文的搜索結果了,我直接去了 Martin Flower 的網站看原始版本去了。而後,我發現了這樣一幅架構圖。

再結合他的原文我一會兒明白了,是模型,模型的不一樣!

原來的數據庫讀寫分離確實把讀寫的這兩個行爲分開了,可是它依然有一個重要的事情沒有作,那就是職責的分開。

什麼叫職責的分開呢?就是讀寫雙方不要搞同一套模型。而數據庫讀寫分離的問題就在這裏,它使用了同一個模型。

使用同一個模型在這裏形成的問題是,這個模型因爲既要考慮讀取數據不能太困難,也要考慮寫入數據不能太困難。

而這個偏偏就是違背了 CQRS 中的核心思想:讀寫完全自由

若是咱們使用 CQRS 思想的話,假設寫入不須要關心讀取的問題,讀取數據也不用關心寫入的問題,那麼雙方是否是能夠完全放飛自我了?

好比,寫入數據因爲不須要考慮讀取,那我大可使用 Json 格式,使用 XML 格式之類的非標準格式,甚至直接寫個日誌均可以。而讀取數據則根本不須要考慮寫入的問題,我甚至能夠弄成一個容易搜索的索引格式來。

而 CQRS 在我看來,正是解決卡死個人性能問題的靈丹妙藥。

以廣告排行這個問題爲例,廣告排行麻煩就麻煩在,每次加載排行榜須要有很複雜的查詢,去數據庫中讀取數據。

若是能完全地把排行榜的讀取排行榜依賴的那些點擊、消費指標的更新分開,那我苦惱的排行榜性能問題就能迎刃而解。

我費勁心思後,仿照 CQRS 的原版思想搞了一個這樣的設計思路:

這裏,數據統計就是廣告排名須要的點擊、消費等數據。這些數據會被放到一個單獨的數據庫中,這個數據庫只用來寫入,不考慮讀。

而後,展現廣告排行的功能自己又會單獨從緩存中把廣告排行的模型直接讀取出來展現出去,而不用專門再作什麼轉換了。也不存在什麼複雜查詢的問題。

可是,咱們的需求是要準實時的讓廣告排行根據點擊、消費等數據自動更新,那麼若是寫入數據和讀取數據模型分開了,該怎麼辦呢?

多年之前,當我第一次在網上買東西的時候,內心有個疑問:我下了個訂單,賣我東西的商家是怎麼知道的?莫非要一直盯着?

這個問題到我親自開發電商系統的時候才知道,當咱們下單的時候,須要發一個通知給對應的商家,告訴商家哪一個客戶購買了哪一個商品。

因此,廣告排行自動更新的解決方案有了,和電商下單通知商家的道理同樣。當有數據寫入的時候,咱們把寫入的數據複製一份通知給讀取數據的模型就能夠了。

好,如今整套邏輯完整了。

可是,我並無急於立刻把 CQRS 這套模式去應用到實際的項目當中。由於,我發現我居然不知道 CQRS 這套模式的缺點是什麼。

要知道,世界上還不存在完美的解決方案,全都是既有優勢又有缺點的。而 CQRS 我居然以爲很完美的解決了個人問題,這說明我對這套模式的認知還存在問題。

當時,離約定的上線時間已經愈來愈近了,差很少還剩一週時間。我真的很想閉眼把方案實施下去。

可是,不行,我這我的作事向來喜歡把事情想得通透,把事物認知的十分清楚後再去作。

我決定冒險花兩天去實現兩個功能點,而後親自體驗一下引入 CQRS 的得與失。

當兩天後,我終於發現了問題:引入 CQRS 的模式後,最大的問題在於引入了過分的複雜性

因爲須要讀和寫分開,那麼咱們開發的工做量無形中被加大了一倍。又引入 CQRS,這變得更復雜了。

由於咱們發現,不一樣的功能,只有使用不一樣的讀取或者寫入模型才能充分用上 CQRS 的優勢。

好比,廣告排行可能使用了緩存中間件去存取現成的排名。根據關鍵字搜索各類合適的廣告,可能就得考慮開源的搜索引擎中間件。每引入一種都會增長開發成本、服務器成本,以及更多的複雜度。

最終,咱們的廣告系統按時上線了。

只不過,並無普遍的採用 CQRS 模式,我只是把最重要的功能點用上了 CQRS,其他的有關性能的問題,我決定暫時放下。

之因此這樣,是由於我以爲大部分的問題,實際上是咱們過分設計引起的。即便所以我失敗了,我也認了。

我並不想爲本身親手打造的系統埋下巨大的隱患,更不想給團隊帶來無謂的工做量,我不想捲成這樣。

上線後,我是如此忐忑,尤爲是在上線運營的頭兩個月。

我不知道本身的妥協是否會誘發巨大的問題,我也不知道本身的所做所爲是否是真的是對的。

兩個系統的競爭在上線兩個月後就有結果了。

這麼快的獲得結果,偏偏就是由於個人對手普遍的使用了 CQRS 模式。

他從一開始設計的時候,就想着一舉成名,他的系統裏引入了七八種中間件。把大量的功能拆分紅了讀寫兩部分,而這引起了巨大的災難,過分的複雜性,致使整個系統難以控制。

其中最頭痛的就是,因爲引入 CQRS,他們必須經過消息的傳遞去溝通讀寫兩套組件。

可是,當讀取組件收到消息後,卻發現寫入失敗了。致使用戶看到了對應的數據後,過一段時間,卻發現數據和之前看到的對不上了。

好比,點擊次數,開始看到的是 1000 次,結果兩個小時後,發現變成了 999 次了。

這類問題天天都在出現,而他們由於系統太複雜了,查問題、定位問題、解決問題的時間被大大拉長。最後,客戶們紛紛不幹了,公司只好把客戶轉到了我這邊的平臺上。

競爭結束了,我勝利了,但是我真的沒法高興地起來。由於今天他由於錯誤的引入新技術失敗了,那明天我又未嘗不會由於誤用新技術新思想而失敗呢?今日的他又未嘗不是明日的我?

願天下程序員凡事深思熟悉,謹言慎行!


你好,我是四猿外,一家上市公司的技術總監,管理的技術團隊一百餘人。

我從一名非計算機專業的畢業生,轉行到程序員,一路打拼,一路成長。

我會把本身的成長故事寫成文章,把枯燥的技術文章寫成故事。

歡迎關注個人公衆號,關注後回覆【666】可領取我整理的一些珍藏技術資料。

相關文章
相關標籤/搜索