當數據庫扼住系統性能咽喉?
大部分的軟件架構、組件或解決方案,都是在解決一些問題的同時,會帶來另外的問題。
數據庫的分庫分表,又可以分為垂直拆分和水平拆分(可能大家常說的分庫分表主要指的是后者):
垂直拆分:這是一種比較常見的數據庫設計方法,就是把一個字段比較多的大表,拆分成多個小表,特別是在現在分布式、微服務的架構下,可以把各個小表按照業務模型,劃分到不同的數據庫中,這樣就可以利用多臺數據庫服務器的性能;但當被拆分出來小表的數據量不斷增長,到了一個極限的時候,還是需要考慮水平拆分。
水平拆分:將表中的數據,按照一定的規則分布到不同的數據庫中,比如對主鍵進行Hash和取模操作后,按照結果把數據路由到對應的數據庫上;水平分庫分表,可以降低每張表的數據量,這也是現在大部分公司所使用的方法。
數據庫擴容問題上文中說到,水平拆分常用的手段是對主鍵進行Hash和取模操作后,按照結果把數據路由到對應的數據庫上;但如果被拆分的子表,數據量也達到極限值以后,就要面對數據庫擴容的問題,比如開始規劃分成8個庫,現在要擴到16個庫;
路由規則發生變化:hash(id)%8 變成了 hash(id)%16,那么歷史數據也就要面臨遷移的問題;這種情況,要么做數據遷移,要么增加分表算法的復雜性,讓算法可以兼容增加分表前后的數據路由。
復雜查詢、關聯查詢、order by、group by等的問題在單庫時代,復雜的關聯查詢是很容易實現的,但是數據庫被拆分后,數據被保存在了不同的數據庫服務器上,那么夸庫的join就成了很大的問題。通常解決方案有:
如果是垂直拆分,那么可以考慮做一定程度的字段冗余,避免跨表關聯;或者可以做數據同步,把需要的表同步到同一個庫中,進行表關聯;
代碼層面組裝,也就是把兩邊的數據都拿出來,然后在代碼里面關聯組裝;或者先獲取主表數據,再把其余字段補齊;但是從實際情況來看,這個方案在大多數場景下,實現起來都比較困難;
現在一個比較主流的做法,是引入ES或ES+HBase或solr+HBase,把部分字段的全量數據保存在同一個地方。
ID問題在水平拆分的場景下,一單一張表被拆分成多張表部署在多個數據庫中,那么就不能使用數據庫自身的主鍵生成機制了;這時候就需要由我們自己來考慮主鍵生成策略:
主鍵生成中心:可以利用數據庫、Redis、MongoDB、zookeeper等組件實現,需要生產主鍵的時候,調用主鍵生成中心的接口;缺點也很明顯,增加了網絡開銷,并且主鍵生成中心如果發生問題,后果會很嚴重。
UUID:本地生成,不需要第三方組件,生成比較簡單,性能好;不過缺點也不少,長度長,不利于存儲,并且沒有排序,是個字符串,不利于查詢。
一些唯一性ID的生成算法:比如Snowflake、UidGenerator、Leaf等等。
事務問題單庫的時候,解決事務問題很簡單,但是現在要保證跨庫的事務問題,需要額外的成本;
這種場景下(性能要求高,一致性要求不是那么的高),大部分公司會放棄事務的【實時】一致性,只要在一定的時間內,事務【最終】一致即可。
我將持續分享Java開發、架構設計、程序員職業發展等方面的見解,希望能得到你的關注。