我C,一個庫里Curry幾百個表,這誰受得了?
隨著業(yè)務越來越復雜,數據量越來越大,并發(fā)量越來越大,數據庫的性能越來越低。好不容易找運維申請了兩臺機器,讓DBA部署了幾個實例,想把一些業(yè)務庫拆分出來,卻發(fā)現一個庫里幾百個表,拆不出來,擴不了容,尷尬!
因為數據庫強關聯在一起,無法通過增加數據庫實例擴容,就是一個耦合的典型案例。
什么樣的場景會出現這類耦合?
舉個栗子。
有一個公共用戶數據庫DB_USER,里面table_user存放了通用的用戶數據:
- table_user (uid, name, passwd, …)
在數據量比較小,并發(fā)量比較小,業(yè)務還沒有這么復雜的時候,為了提高資源利用率(程序員才沒有考慮什么資源利用率,更多的是圖方便),業(yè)務A把用戶個性化的數據也放在這個庫里:
- table_A(uid, A業(yè)務的個性化屬性)
業(yè)務A有一個需求,即要展現用戶公共屬性,又要展現業(yè)務A個性化屬性,程序員經常這么實現的:
- select * from table_user, table_A
- where table_user.uid = table_A.uid
- and table_user.uid = $uid
初期關聯查詢沒有任何問題,單條記錄訪問,命中索引,一次查詢所有數據,簡單高效。
如何產生各業(yè)務數據耦合?
通過join實現業(yè)務,導致通用表table_user和業(yè)務表table_A必須存在于一個數據庫實例里。
如果業(yè)務B也這么做,業(yè)務C也這么做,會導致公用業(yè)務,業(yè)務A,業(yè)務B,業(yè)務C都必須存在于一個數據庫實例里。
會產生什么潛在問題呢?
假如A業(yè)務線上線了一個新功能,不小心進行了全表掃描,導致數據庫CPU100%,數據庫實例性能下降,由于實例共用,通用業(yè)務,業(yè)務B和業(yè)務C都會受影響。
即某個業(yè)務線的數據庫性能急劇下降導致所有業(yè)務都受影響,這種耦合,歷史總是驚人的相似:
- 業(yè)務B的大boss在群里首先發(fā)飆:“技術都干啥了,怎么系統(tǒng)掛了”
- 業(yè)務B的rd一臉無辜:“業(yè)務A上線了,所以我們掛了”
額,然而,這個理由,好像在大boss那解釋不通…
- 業(yè)務B的大boss:“趕緊加幾臺機器,拆分開”
- 業(yè)務B的rd一臉無奈:“加機器加實例也擴容不了”
- 業(yè)務B的大boss對業(yè)務2的rd吼道“還想甩鍋,拖出去祭天”
- ...
唉,加了幾臺機器,加了幾個實例,然而并沒有什么卵用,都耦合在一個實例里,完全擴不了容。
那,如何解除公共數據庫與業(yè)務數據庫的耦合?
第一步:公共數據訪問下沉服務化。
還是上面的例子,當公共的user數據訪問服務化之后,依據服務化的原則:
- 業(yè)務層只能通過服務RPC接口訪問數據;
- 底層user庫屬于user服務私有;
- 任何上游不允許跨過服務訪問底層的user庫;
第二步:垂直拆分,個性化數據訪問上浮。
原來業(yè)務方:通過join一次性獲取通用的數據和個性化的業(yè)務數據數據。
服務化+垂直拆分后,變成兩次訪問:
- 一次取得業(yè)務數據(業(yè)務可以直接調用自己的數據庫,也可以自己做業(yè)務服務調用RPC接口);
- 一次取得共性數據(調用通用的RPC接口);
兩種方式相比:
- 之前的方式其實業(yè)務代碼可能會更簡單一些,因為它是將這個業(yè)務邏輯放在了SQL語句中,但是導致數據庫耦合在了一起;
- 后面這種方式就是業(yè)務的代碼會更復雜,會變成多次訪問,將原來在SQL中進行的邏輯計算變成業(yè)務代碼中的邏輯計算,但是數據庫解耦了;
業(yè)務復雜,數據量大,并發(fā)老大,對擴展性要求更高的架構,一定是后者。
此時各業(yè)務有自己的庫,公共有公共的庫:
- 早期:可以放在一個數據庫實例里;
- 后期:可以很容易地通過新增數據庫實例,把user庫或者業(yè)務A/B/C的庫拆分出來,實現增加機器增加實例就實現擴容;
個性業(yè)務數據訪問垂直拆分,共性數據訪問服務化下沉,只是一個很小的優(yōu)化點,但對于數據庫解耦卻是非常的有效。
希望大家每天收獲一點點,這樣架構就能美好一點點。
【本文為51CTO專欄作者“58沈劍”原創(chuàng)稿件,轉載請聯系原作者】