MySQL架構設計談:從開發規范、選型、拆分到減壓
隨著MySQL自身的發展與不斷完善,不知不覺中整個互聯網行業已離不開這個完善又小巧的關系型數據庫,整個生態鏈也已經變得非常成熟,即便是初創企業和傳統企業也可以放心大膽地把數據庫遷移到MySQL上來。在大家和MySQL數據庫愉快玩耍的同時,我來聊聊MySQL架構設計相關的一些話題。
本文大綱:
- MySQL數據庫開發規范
- MySQL高可用架構選型
- MySQL Sharding拆分
- 利用NoSQL為MySQL減壓
一、MySQL數據庫開發規范
數據庫規范到底有多重要?有過初創公司經歷的朋友應該都深有體會。規范是數據庫運維的一個基石,能有效地減少數據庫出問題的概率,保障數據庫schema的合理設計并方便后續自動化的管理。
曾經我們花了大半年時間來做數據庫規范化的工作,例如制定數據庫開發指南、給程序員做培訓等,推進的時候也會遇到一些阻力。但規范之后運維質量會有一個質的提升,也增進了DBA的工作效率。
在開發規范方面,我們劃分為開發規范和運維規范兩部分。
1、開發規范
表設計的規范:
- 字段數量建議不超過20-50個
- 做好數據評估,建議純INT不超過1500萬,含有CHAR的不要超過1000萬。字段類型在滿足需求條件下越小越好,盡量使用UNSIGNED存儲非負整數,因為實際使用時候存儲負數的場景不多。
- 將字符轉換成數字存儲。例如使用UNSIGNED INT存儲IPv4 地址而不是用CHAR(15) ,但這種方式只能存儲IPv4,存儲不了IPv6。另外可以考慮將日期轉化為數字,如:from_unixtime()、unix_timestamp()。
- 所有字段均定義為NOT NULL,除非你真的想存儲null。
索引設計的規范:
1)所有表必須有顯式主鍵
- InnoDB表是以主鍵排序存儲的IOT表
- 盡量使用短、自增的列做索引
- 復制結構使用row格式,如果表有主鍵可以加速復制
- UNSIGNED INT自增列,也可以考慮BIGINT
- TINYINT做主鍵可能導致MySQL Crash
- 類型轉換會導致查詢效率很低
- 可用uuid_short()代替uuid(),轉成BIGINT存儲
2)合理地建立索引
- 選擇區分度高的列作為索引
- 單個索引字段數不超過5,單表索引數量不超過5,避免冗余索引
- 建立的索引能覆蓋80%主要的查詢,不求全,解決問題的主要矛盾
- 復合索引排序問題,多用explain去確認
SQL編寫規范:
1)避免在數據庫中進行大量計算任務
- 大事務拆成多個事務,分批多次操作
- 慎用text、blob大型字段,如要用考慮好拆分方案
- 頻繁查詢的字典表考慮用Cache抗
2)優化join
- 避免大表與大表之間的join,考慮讓小表去驅動大表join
- 最多允許三表join,***控制成兩表
- 控制join后面where選擇的行數
3)注重where條件,多用EXPLAIN確認
- where條件的字段,盡量用區別度高的字段,這樣走索引的性能更好
- 出現子查詢的SQL,先確認MySQL版本,利用explain確認執行計劃
- 進行分頁優化;DML時候多個value合并
Schema Review:
1)字符集問題
表字符集選擇UTF8 ,如果需要存儲emoj表情,就改成UTF8mb4
2)Schema設計原則
- 核心表字段數量盡可能地少,有大字段要考慮拆分
- 適當考慮一些反范式的表設計,增加冗余字段,減少JOIN
- 資金字段考慮統一*100處理成整型,避免使用decimal浮點類型存儲
- 日志類型的表可以考慮按創建時間水平切割,定期歸檔歷史數據
3)Schema設計目標
- 快速實現功能為主,保證節省資源
- 平衡業務技術各個方面,做好取舍
- 不要在DB里進行大計算,減少復雜操作
- 整體來說,這部分規范還是很容易遵守的,實現起來也沒有什么難度,就能取得很好的效果。
2、運維規范
(1)SQL審核
SQL評審這部分工作相信讓很多的DBA同學都叫苦不迭,人肉審核不僅效率低下,容易出錯,對DBA的自身發展也非常不利,難道我們來上班就是為了審核SQL的嗎?在經過了一段痛苦的人肉審核之后,我們接入了去哪兒網開源的Inception,并根據自身的業務特點做了一些調整。當然現在開源的SQL評審軟件已經很多了,大家可以自由選擇,也可以自行開發。
在審核與執行上線DDL語句的時候,要注意MySQL官方原生Online DDL和Percona公司的pt-osc之間的一些差異,例如pt-osc在執行時每次都要copy全表,相對來說比較慢,好處是不鎖表,并且有完善的條件檢測和延時負載策略控制。官方Online DDL雖然官方也一直在改進,但生產環境使用還不是很***,尤其要注意執行過程中容易導致MDL鎖。官方Online DDL也有優于pt-osc的地方,比如增刪索引,重命名列等,如下圖所示。
(2)權限控制
MySQL從5.6開始,逐步完善了權限系統,比如MySQL5.6可以安裝檢查密碼強度的插件,5.7開始增加了密碼過期機制、賬戶鎖定等功能,對SSL這一塊也做了一些優化,8.0版本增加了角色的功能,權限系統已經逐步在向Oracle數據庫靠攏了。在日常運維中,也可以使用pt-show-grants工具提高權限審查的力度。應用程序賬號應只賦予SELECT、INSERT、UPDATE權限,DELETE的邏輯改用UPDATE實現,并啟用sql_safe_updates選項。
另一個有效控制權限的方法就是SQL堡壘機,早期我們通過改造MyWebSQL實現,在Web版客戶端的基礎上加入了一些資源控制策略、審計、語法校驗等功能。后續又使用Python開發了功能更完備的SQL堡壘機,同時支持MySQL、Oracle、Greenplum等數據庫。
SQL堡壘機不僅可控制公司內部人員的數據庫權限,追溯各類人員對數據庫的操作,也能避免大查詢或全表更新的情況發生,支持審計需求,整體運維質量提升了一個臺階。
(3)MySQL版本選擇
- MySQL社區版,用戶群體***
- MySQL企業版,收費
- Percona Server版,新特性多,和MySQL社區版最接近
- MariaDB版,國內用戶暫時不多
- 選擇優先級:MySQL社區版> Percona Server > MariaDB > MySQL 企業版
對于版本選擇這件事,建議大家還是跟進官方社區版比較好,目前比較穩定的版本是MySQL5.6,推薦大家使用。有特殊需求的話再選擇MySQL5.7、PXC、TiDB、TokuDB等數據庫。
二、MySQL高可用架構選型
MySQL高可用方面,目前業界主流依然是基于異步復制的技術,例如Keepalived、MHA、ZooKeeper等,要求數據強一致的場景逐步開始使用分布式協議,這方面的典型代表有PXC、Group Replication、TiDB。下面我們就重點來說說keepalived、MHA和PXC這幾種大家用得比較多的架構。
1、keepalived高可用架構
業內使用非常普遍,它部署容易、方便維護,還節省服務器資源。這種架構的一個好處就是在發生切換后,原Master只需重新拉起來即可恢復高可用,不需要過多干預。擴展起來也方便,可以任意掛載只讀庫和災備庫。但它存在的問題也很明顯,比如Keepalived的檢測機制不完善、有腦裂隱患、數據一致性較弱等等。
還需要注意主從拓撲的設計。如下圖,只讀庫掛到哪個Master比較合適?顯然是M2,其它兩種拓撲在發生切換后都會影響到只讀庫的訪問。
2、MHA
MHA自誕生以來,就得到了業內的廣泛關注,并迅速流行開來。與keepalived相比,MHA***的優點就是在發生故障切換之后,能自動補齊binlog,***程度保證數據一致性。從服務器能自動切換,無需人工干預,能非常好的工作在讀寫分離的環境下。基于Perl語言的腳本也非常方便進行二次開發。MHA非常適合讀寫壓力比較大的應用。
但由于MHA在工作時需要配置SSH互信,因此選擇這種架構時內網安全一定要做到位。另外也可以搭配Binlog Server使用。
3、PXC
PXC全稱是Percona XtraDB Cluster,是Percona公司基于Galera協議開發的一個產品。PXC犧牲了CAP里面的P(Partition Tolerance),保留了C(Consistency )和A(Availability )。這種結構非常適合電商、金融類業務,自PXC和Group Replication出現以后,MySQL徹底掃清了進入金融行業的障礙。
PXC的優勢:
- 同步復制,解決了傳統架構復制延遲和腦裂的問題
- 數據強一致
- 多主復制,每個節點都可以讀寫數據
- 并行復制,多個事務可以并行推送到其他節點
- 高可用,單點故障不影響集群可用性
- 新節點自動部署
- 與傳統MySQL幾乎完全兼容
使用PXC要注意的問題:
- 不要有大事務
- 木桶效應,集群性能取決于性能最差的那個節點
- 并發效率有損失
- 網絡要求較高,建議萬兆網絡
- 多點并發寫時鎖沖突、死鎖問題多
- 寫無法擴展,無法解決熱點更新問題
除此之外,還有一類采用DNS/ZooKeeper的高可用架構,這種架構通常都需要自行開發,無通用的方案,比較適合大規模集群的高可用,這里我們不過多贅述。
下面簡單回顧一下上述幾種高可用架構:
- 雙Master架構:非常成熟,使用很普遍,要注意延遲和數據的一致性。
- PXC: 分布式協議,數據強一致性,并發效率略低,可用性好
- MHA:各項指標介于M-M和PXC之間,性能無損失,適合讀寫分離架構。
總而言之,沒有最***的架構,只有最適合的架構。選擇適合自己業務的即可。
三、MySQL sharding拆分
接下來是第三個議題,MySQL拆分原則和分庫分表設計。
首先先提一個問題,為什么要拆,不拆不行嗎?按照我們的經驗來看,當數據和業務到了一定的規模,都不可避免的要面臨分庫分表的問題。這就好像汽車的發動機一樣,要達到更高的性能,4缸6缸明顯是不夠用的,V8、V12才是王道。
拆分能解決如下幾個問題:
- 單庫并發較大
- 單庫物理文件太大
- 單表過大,DDL無法接受
- 防止出現性能瓶頸,提升性能
- 防止出現抖動不穩定現象
確定要進行數據庫的拆分了,應該怎么拆呢?
垂直拆分
優點:
- 拆分簡單明了,拆分規則明確
- 應用程序模塊清晰,整合容易
- 數據維護方便易行,容易定位
缺點:
- 表關聯需要改到程序中完成
- 事務處理變的復雜
- 熱點表還有可能存在性能瓶頸
- 過度拆分會造成管理復雜
水平拆分
優點:
- 不會影響表關聯、事務操作
- 超大規模的表和高負載的表可以打散
- 應用程序端改動比較小
- 拆分能提升性能,也比較易擴展
缺點:
- 數據分散,影響聚集函數的使用
- 切分規則復雜,維護難度增加
- 后期遷移較復雜
要先分庫還是先分表?
- 分庫的優點:實現簡單,庫與庫之間界限分明,便于維護,缺點是不利于頻繁跨庫操作,單表數據量大的問題解決不了。
- 分表的優點:能解決分庫的不足點,但是缺點恰恰是分庫的優點,分表實現起來比較復雜,特別是分表規則的劃分,程序的編寫,以及后期的數據庫拆分移植維護。
一巴掌拍板直接選分庫或分表都是不可取的,主要是看需要達到什么樣的擴展方式,才能決定先分庫還是先分表,根據具體的場景決定。分庫分表的最終目的還是為了擴展,而且要看拆分的規劃設計是針對哪一層。
上述問題都解決了,該考慮如何實現了,到底是在應用程序中實現,還是使用中間件?個人建議如果是小規模的拆分,直接在程序邏輯中實現即可,大規模的拆分再考慮使用各種中間件。
目前業內已經開源了很多的MySQL中間件產品,例如Atlas、DBProxy、MyCAT、OneProxy、DRDS、Vitess等等,每個中間件都有自己的特點,個別不太成熟的可能會存在一些Bug,選用之前要做好相關的調研與測試工作,上線使用一定要保證自己能hold住。如果要完全貼合自身業務,并且掌控得較好的還是要自行開發。
下面說說我們的拆分經驗。
首先我們先在壓力比較大的數據庫上做垂直拆分,剝離出活動、后臺統計等業務。這一步也是最容易實現的。
接下來,如果是消息類的數據,就基于時間維度進行拆分,單表控制在5-10G,行數控制到500-1000w這個樣子。這個時候我們發現數據庫的性能是比較好的,而且比較好維護。如果是用戶類的數據,就按照Hash或Range進行拆分。這種情況下用這種方法拆分會拆的比較均勻一些。
并發仍然比較高怎么辦?可以在時間維度拆分的基礎上再按Range或Hash進行拆分。
***要注意的就是不要過度的拆分,會造成復雜度的上升。Schema設計合理的情況下,10億的數據量也能跑的好好的。個別不關鍵的應用,例如日志、監控數據等,使用分區表、TokuDB也能抗。拆分對應用層總是有損的。
要做個“懶”DBA。
四、利用NoSQL為MySQL減壓
***一個議題,我們聊一聊NoSQL。NoSQL現在遍地開花,應用也很廣泛了,業內用的比較多的主要集中在Redis、MongoDB、Cassandra等NoSQL數據庫上。今天我們主要來說說和MySQL關聯最為密切的Redis。
為什么要使用Redis?
- 數據存儲在內存中,訪問速度快
- 能支持大批量操作及爆發性負載
- 數據結構豐富,有效緩解MySQL壓力
- 協議簡單,支持各種語言的API
- 存儲大量數據無需擔心性能
Redis主要作用還是抗讀的壓力。讀操作先到Redis,Redis中取不到再從MySQL數據庫訪問,從MySQL讀取到數據后,還要回寫到Redis。
使用Redis要注意的幾點:
性能方面,由于Redis完全是基于內存的訪問,性能無需擔心。
在使用Redis時,要注意Cache 和Storage不要混合使用。不要依賴Redis的持久化,持久化這一塊Redis要努力的還很多。另外如果你把Redis拿來做Storage的話,一旦Redis的內存跑滿,那就慘了,所有的Redis連接都會卡著不響應。如果只是把Redis來做cache的話,那問題就不大。
還有諸如緩存穿透、緩存雪崩、熱點key重建時緩存失效這些問題也是重點關注的對象。
如何利用Redis給MySQL加速:
1)利用K/V結構,緩存結果,例如存儲用戶信息、全局排行、統計信息等。
2)利用其豐富的數據結構為MySQL減壓,例如計數器、排序、Hash(把表映射到Redis中)、消息隊列等。
總結
系統架構設計是一個長期總結與進化的過程,講究均衡與取舍。在進行大規模MySQL架構設計的過程中,除了要汲取別人的經驗之外,還要關注各種架構背后的業務場景與架構思想,與自己的實際業務場景相結合,才能設計出一個好的系統架構來。