從讀寫分離到CQRS,張大胖是如何解決性能問題的?
1.不堪重負(fù)的數(shù)據(jù)庫
張大胖公司的數(shù)據(jù)庫已經(jīng)不堪重負(fù)了。
這個系統(tǒng)最早是兩個實習(xí)生寫的, 按照最初的設(shè)計,只是內(nèi)部用戶玩的, 大家可以把一些閑置不用的東西放在上面做交換, 僅此而已,后來為了在互聯(lián)網(wǎng)的大潮中賺點錢,又包裹上了一層Web的外衣, 讓外界也可以訪問。
大家沒有想到互聯(lián)網(wǎng)威力如此巨大, 用戶量會如此之多, 他們系統(tǒng)使用的Mysql數(shù)據(jù)庫很快就撐不住了。
作為技術(shù)負(fù)責(zé)人的張大胖早已經(jīng)向老大申請了一筆費用, 專門買了一個高性能的服務(wù)器來應(yīng)對, 但是洶涌而來的用戶很快就把高性能給吃得連渣都不剩。
張大胖憂心忡忡: “老大,怎么辦? ”
老大也是技術(shù)出身,反問道: “你分析過為什么數(shù)據(jù)庫壓力這么大嗎? ”
“無非就是讀寫量太大了,尤其是有一些非常復(fù)雜的查詢, 比如最近24小時最熱門的物品之類,需要寫很復(fù)雜的SQL, 運行起來實在太慢了。”
“我記得咱們倆聊過讀寫分離啊, 怎么不試一試?”
“老大啊, 你不知道,這實在是不好弄啊, 為了實現(xiàn)讀寫分離, 得把數(shù)據(jù)庫拆分成master庫和slave庫, 還比較簡單, 但是我們的系統(tǒng)代碼也得改啊, 寫數(shù)據(jù)的時候用master 庫, 讀數(shù)據(jù)的時候用slave庫, 你知道我們這是上個世紀(jì)開發(fā)的系統(tǒng),典型的遺留代碼, 改動起來太麻煩了。”
老大說:“那也得改啊, 你要知道現(xiàn)在這個系統(tǒng)可是咱們公司***的收入來源了。 你們要是不想改,就退下來,我只好去找李小瘋?cè)プ隽?rdquo;
張大胖向來瞧不起馬屁精李小瘋,技術(shù)不咋地,升的到挺快,一起進(jìn)公司的, 現(xiàn)在已經(jīng)比自己高一級了。
張大胖趕緊說:“ 別別, 還是我來”
張大胖帶著幾個弟兄和遺留代碼奮戰(zhàn)了幾個月, 工作量不亞于一次重寫。 張大胖深深地體會到,別看現(xiàn)有代碼很爛, 但是經(jīng)過無數(shù)人的修補(bǔ),勉強(qiáng)能工作。 現(xiàn)在自己從頭寫一遍,出的問題更多,很多小細(xì)節(jié)考慮不到,被測出了無數(shù)Bug。
不過好處也是巨大的,這次重寫,理清了業(yè)務(wù), 實現(xiàn)了讀寫分離,還把緩存也用上了, ***熬了兩天兩夜,新系統(tǒng)終于上線了。
張大胖想著好日子就要開始了,嶄新的代碼, 嶄新的系統(tǒng),應(yīng)該可以撐一段時間。
2.復(fù)雜的查詢
可是新系統(tǒng)上線了一周后,問題又出現(xiàn)了,這次的問題主要集中在一些復(fù)雜的SQL查詢上,這些SQL查詢最要命的得有幾十行! 嚴(yán)重地拖累了數(shù)據(jù)庫 !
張大胖找來DBA 小梁過來做優(yōu)化,小梁看了半天說: “沒轍, 你們的業(yè)務(wù)太復(fù)雜了, 你看看有這么多表在做Join,怎么可能快呢?”
張大胖說:“這沒辦法啊,數(shù)據(jù)庫就是這么設(shè)計的啊, 你懂的,無論如何也得滿足***范式吧。 要不這樣,你給我們創(chuàng)建一個視圖(View) 吧, 把這個復(fù)雜的查詢給封裝起來, 這樣我們使用起來就簡單了”
“那也是換湯不換藥啊, 實際的查詢還在, 沒有本質(zhì)的改變, 照樣還是慢。”
“唉,這可怎么辦, 我們有20多個復(fù)雜查詢,怎么才能提高速度呢?”
小梁說: “你看看這個超級復(fù)雜的查詢, 不就是為了獲得過去24小時的熱門產(chǎn)品嗎,要是有個表單獨存放就好了 hot_products(id, name, desc, total_sold) , 這樣以來一條簡單的SQL就搞定”
小梁的話啟發(fā)了張大胖: 實際上,一套單一的數(shù)據(jù)庫表 對于報表、搜索、事務(wù)等不同的行為是不適當(dāng)?shù)?!
現(xiàn)在復(fù)雜的數(shù)據(jù)查詢和簡單的數(shù)據(jù)修改利用的就是同一套領(lǐng)域模型和數(shù)據(jù)庫表, 現(xiàn)在的數(shù)據(jù)庫表主要是為了新增、修改數(shù)據(jù)而設(shè)計的, 對于復(fù)雜的查詢并不友好。 我們能不能單獨的建一套數(shù)據(jù)庫,專門應(yīng)對查詢呢?
有了這個專門的查詢庫, 用戶在界面上發(fā)起查詢的時候處理起來非常簡單, 一條SQL就搞定,甚至都不用通過業(yè)務(wù)領(lǐng)域?qū)?,換句話說數(shù)據(jù)庫模型和展示層是對應(yīng)的! 再也不用像原來那樣從原始數(shù)據(jù)庫表中得到數(shù)據(jù),轉(zhuǎn)化成領(lǐng)域?qū)ο螅?然后再轉(zhuǎn)化成展示層對象, 實在是太麻煩了 !
但是這個專門的查詢庫該如何更新呢? 更重要的是能不能忍受數(shù)據(jù)的延遲呢?
3.CQRS
張大胖把自己的想法和苦惱給老大講了下。
老大拍了拍他的肩膀: “看來你小子開竅了啊, 想得挺深入的, 從業(yè)務(wù)上看數(shù)據(jù)的延遲可以忍受,比如過去24小時的熱門產(chǎn)品,一點點過時的數(shù)據(jù)對用戶不會產(chǎn)生重大的影響。只要你能達(dá)到最終一致就可以了。”
“那我們該怎么更新這個專門的查詢庫呢?”
“我最近在看一個叫做CQRS的東西” 老大說 “ 你遇到的這個問題可以用同樣的思路來解決下”
“什么是CQRS ? ”
"Command Query Responsibility Segregation,就是命令(增刪改)和查詢的責(zé)任分離, 你看看這個圖"
“這和我剛才的圖差不多啊” 張大胖說
“所以說思路是一致的嘛, 在CQRS中, 強(qiáng)調(diào)的是讀(Query)和寫(Command) 的分離 , 它背后的理念是用戶讀到的數(shù)據(jù)通常是過時的,比如過去24小時最火的產(chǎn)品, 既然如此, 為什么還要從數(shù)據(jù)庫中讀取一遍,轉(zhuǎn)化為領(lǐng)域模型,DTO, VO, ***在UI層展示呢? 何不直接一點,干脆為‘讀’專門建立一個直接的數(shù)據(jù)源呢? 這新的數(shù)據(jù)源不一定是關(guān)系數(shù)據(jù)庫,可以是Cache ,可以直接存儲為xml/json數(shù)據(jù), 只要界面查詢起來方便即可。 ”
“是,最早我也是這么想的,那這個Event是怎么回事?”
“Event 就是事件嘍,例如有人下了一個訂單, 導(dǎo)致某個產(chǎn)品已經(jīng)賣出, 這個時候就可以發(fā)布一個產(chǎn)品已經(jīng)賣出(ProductSold)的事件 , 其中包含產(chǎn)品的ID, 價格,賣出時間等屬性, 這樣的事件被處理以后,可以變成任意的Read Model,例如過去24小時最火的產(chǎn)品 。”
“奧,原來是這么玩的啊, 通過事件機(jī)制把同步變成異步 ” 張大胖說 “ 還有一個問題,如果我們用CQRS, 難道我們的應(yīng)用需要把所有的Command 和Query完全分開嗎, 查詢都通過新的數(shù)據(jù)源? 可是很多查詢很簡單,直接使用關(guān)系數(shù)據(jù)庫就夠了啊。 ”
“不,不要把攤子鋪得太大, 引入一種新的技術(shù)也是需要付出代價的,我們把同步操作變成了異步的操作, 得有良好的事件處理機(jī)制才可以。 所以先用這種思路把你的當(dāng)前問題,也就是復(fù)雜查詢的問題解決掉吧!” 老大***拍了板。
【本文為51CTO專欄作者“劉欣”的原創(chuàng)稿件,轉(zhuǎn)載請通過作者微信公眾號coderising獲取授權(quán)】