多年以前,那時我正年輕,做技術如魚得水,甚至一度希望自己能當一輩子的一執行緒式員,
但是我又有兩個小愿望想要達成:一個是想多掙點錢;另一個就是對專案的技術堆疊和架構選型能多有點主動權,
多掙點錢是因為當時我剛結婚不久,有自己的家庭規劃,所以掙錢的欲望也蠻強,
而想有多點技術主動權的原因則是當時領導很賞識我,有些東西逐漸的放權讓我做,我嘗到了甜頭,所以,也有了自己的一些小野心,
而正巧就在那時候,領導給我了一個現在看來職業生涯中還挺重要的機會,
當時,廣告聯盟正是發展的如火如荼的時候,公司也想參與進去分杯羹,于是決定從零開始搞一套廣告平臺,
而我正好也有些類似的開發經驗,且做事還算靠譜,于是,領導便想著讓我去當這套系統的技術負責人,
如果我能把系統做好,對我來說絕對是個證明自己的機會,對以后達成我的兩個小愿望有好處,對我傭訓很大,
只是,老天給你開了一扇門,就總要給你關一扇窗,這個機會不僅僅是我領導看上了,當時,還有另外一個部門的老大也瞄上了,
不得已,上了高層會議討論,討論來討論去的結果就是學習當時別的公司的做法,內部競爭,
兩個部門做各做一套平臺,然后各放到線上運營一陣子,誰做得好誰就能得到公司全力投入的機會,
好吧,機會變成了冒險,只是到此時,我也并不能退縮,一旦我退碩訓連累賞識我的領導,而且將來在公司的發展也會嚴重受阻,只能沖了,
為了贏得這場競爭,我和這套系統的產品負責人也溝通了許久,最后定下來了兩個必須實作的目標:
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 次了,
這類問題每天都在出現,而他們因為系統太復雜了,查問題、定位問題、解決問題的時間被大大拉長,最后,客戶們紛紛不干了,公司只好把客戶轉到了我這邊的平臺上,
競爭結束了,我勝利了,可是我真的無法高興地起來,因為今天他因為錯誤的引入新技術失敗了,那明天我又何嘗不會因為誤用新技術新思想而失敗呢?今日的他又何嘗不是明日的我?
愿天下程式員凡事深思熟悉,謹言慎行!
你好,我是四猿外,
一家上市公司的技術總監,管理的技術團隊一百余人,
我從一名非計算機專業的畢業生,轉行到程式員,一路打拼,一路成長,
我會把自己的成長故事寫成文章,把枯燥的技術文章寫成故事,
歡迎關注我的公眾號,關注之后還可以獲取演算法、高并發等干貨學習資料,

我建了一個讀者交流群,里面大部分是程式員,一起聊技術、作業、八卦,歡迎加我微信,拉你入群,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/286008.html
標籤:架構設計
下一篇:恕我直言,微服務挺好,但不適合你
