資料庫瓶頸
不管是IO瓶頸還是CPU瓶頸,最終都會導致資料庫的活躍連線數增加,進而逼近甚至達到資料庫可承載的活躍連線數的閾值。在業務service來看, 就是可用資料庫連線少甚至無連線可用,接下來就可以想象了(併發量、吞吐量、崩潰)。
IO瓶頸
- 第一種:磁碟讀IO瓶頸,熱點資料太多,資料庫快取放不下,每次查詢會產生大量的IO,降低查詢速度->分庫和垂直分表
- 第二種:網路IO瓶頸,請求的資料太多,網路頻寬不夠 ->分庫
CPU瓶頸
- 第一種:SQl問題:如SQL中包含join,group by, order by,非索引欄位條件查詢等,增加CPU運算的操作->SQL優化,建立合適的索引,在業務Service層進行業務計算。
- 第二種:單表資料量太大,查詢時掃描的行太多,SQl效率低,增加CPU運算的操作。->水平分表。
分庫分表
水平分庫
1、概念:以欄位為依據,按照一定策略(hash、range等),將一個庫中的資料拆分到多個庫中。2、結果:
- 每個庫的結構都一樣
- 每個庫中的資料不一樣,沒有交集
- 所有庫的資料並集是全量資料
3、場景:系統絕對併發量上來了,分表難以根本上解決問題,並且還沒有明顯的業務歸屬來垂直分庫的情況下。
4、分析:庫多了,io和cpu的壓力自然可以成倍緩解
水平分表
1、概念:以欄位為依據,按照一定策略(hash、range等),講一個表中的資料拆分到多個表中。2、結果:
- 每個表的結構都一樣
- 每個表的資料不一樣,沒有交集,所有表的並集是全量資料。
3、場景:系統絕對併發量沒有上來,只是單表的資料量太多,影響了SQL效率,加重了CPU負擔,以至於成為瓶頸,可以考慮水平分表。
4、分析:單表的資料量少了,單次執行SQL執行效率高了,自然減輕了CPU的負擔。
垂直分庫
1、概念:以表為依據,按照業務歸屬不同,將不同的表拆分到不同的庫中。2、結果:
- 每個庫的結構都不一樣
- 每個庫的資料也不一樣,沒有交集
- 所有庫的並集是全量資料
3、場景:系統絕對併發量上來了,並且可以抽象出單獨的業務模組的情況下。
4、分析:到這一步,基本上就可以服務化了。例如:隨著業務的發展,一些公用的配置表、字典表等越來越多,這時可以將這些表拆到單獨的庫中,甚至可以服務化。再者,隨著業務的發展孵化出了一套業務模式,這時可以將相關的表拆到單獨的庫中,甚至可以服務化。
垂直分表
1、概念:以欄位為依據,按照欄位的活躍性,將表中欄位拆到不同的表中(主表和擴充套件表)。2、結果:
- 每個表的結構不一樣。
- 每個表的資料也不一樣,一般來說,每個表的欄位至少有一列交集,一般是主鍵,用於關聯資料。
- 所有表的並集是全量資料。
3、場景:系統絕對併發量並沒有上來,表的記錄並不多,但是欄位多,並且熱點資料和非熱點資料在一起,單行資料所需的儲存空間較大,以至於資料庫快取的資料行減少,查詢時回去讀磁碟資料產生大量隨機讀IO,產生IO瓶頸。
4、分析:可以用列表頁和詳情頁來幫助理解。垂直分表的拆分原則是將熱點資料(可能經常會查詢的資料)放在一起作為主表,非熱點資料放在一起作為擴充套件表,這樣更多的熱點資料就能被快取下來,進而減少了隨機讀IO。拆了之後,要想獲取全部資料就需要關聯兩個表來取資料。
但記住千萬別用join,因為Join不僅會增加CPU負擔並且會將兩個表耦合在一起(必須在一個資料庫例項上)。關聯資料應該在service層進行,分別獲取主表和擴充套件表的資料,然後用關聯欄位關聯得到全部資料。
分庫分表工具
- sharding-jdbc(噹噹)
- TSharding(蘑菇街)
- Atlas(奇虎360)
- Cobar(阿里巴巴)
- MyCAT(基於Cobar)
- Oceanus(58同城)
- Vitess(谷歌) 各種工具的利弊自查
分庫分錶帶來的問題
分庫分表能有效緩解單機和單錶帶來的效能瓶頸和壓力,突破網路IO、硬體資源、連線數的瓶頸,同時也帶來一些問題,下面將描述這些問題和解決思路。
事務一致性問題
分散式事務
當更新內容同時存在於不同庫找那個,不可避免會帶來跨庫事務問題。跨分片事務也是分散式事務,沒有簡單的方案,一般可使用“XA協議”和“兩階段提交”處理。
分散式事務能最大限度保證了資料庫操作的原子性。但在提交事務時需要協調多個節點,推後了提交事務的時間點,延長了事務的執行時間,導致事務在訪問共享資源時發生衝突或死鎖的概率增高。隨著資料庫節點的增多,這種趨勢會越來越嚴重,從而成為系統在資料庫層面上水平擴充套件的枷鎖。
最終一致性
對於那些效能要求很高,但對一致性要求不高的系統,往往不苛求系統的實時一致性,只要在允許的時間段內達到最終一致性即可,可採用事務補償的方式。與事務在執行中發生錯誤立刻回滾的方式不同,事務補償是一種事後檢查補救的措施,一些常見的實現方法有:對資料進行對賬檢查,基於日誌進行對比,定期同標準資料來源進行同步等。
跨節點關聯查詢join問題
切分之前,系統中很多列表和詳情表的資料可以通過join來完成,但是切分之後,資料可能分佈在不同的節點上,此時join帶來的問題就比較麻煩了,考慮到效能,儘量避免使用Join查詢。解決的一些方法:
全域性表
全域性表,也可看做“資料字典表”,就是系統中所有模組都可能依賴的一些表,為了避免庫join查詢,可以將這類表在每個資料庫中都儲存一份。這些資料通常很少修改,所以不必擔心一致性的問題。
欄位冗餘
一種典型的反正規化設計,利用空間換時間,為了效能而避免join查詢。例如,訂單表在儲存userId的時候,也將userName也冗餘的儲存一份,這樣查詢訂單詳情順表就可以查到使用者名稱userName,就不用查詢買家user表了。但這種方法適用場景也有限,比較適用依賴欄位比較少的情況,而冗餘欄位的一致性也較難保證。
資料組裝
在系統service業務層面,分兩次查詢,第一次查詢的結果集找出關聯的資料id,然後根據id發起器二次請求得到關聯資料,最後將獲得的結果進行欄位組裝。這是比較常用的方法。
ER分片
關係型資料庫中,如果已經確定了表之間的關聯關係(如訂單表和訂單詳情表),並且將那些存在關聯關係的表記錄存放在同一個分片上,那麼就能較好地避免跨分片join的問題,可以在一個分片內進行join。在1:1或1:n的情況下,通常按照主表的ID進行主鍵切分。
跨節點分頁、排序、函式問題
跨節點多庫進行查詢時,會出現limit分頁、order by 排序等問題。分頁需要按照指定欄位進行排序,當排序欄位就是分頁欄位時,通過分片規則就比較容易定位到指定的分片;當排序欄位非分片欄位時,就變得比較複雜.需要先在不同的分片節點中將資料進行排序並返回,然後將不同分片返回的結果集進行彙總和再次排序,最終返回給使用者如下圖:
上圖只是取第一頁的資料,對效能影響還不是很大。但是如果取得頁數很大,情況就變得複雜的多,因為各分片節點中的資料可能是隨機的,為了排序的準確性,需要將所有節點的前N頁資料都排序好做合併,最後再進行整體排序,這樣的操作很耗費CPU和記憶體資源,所以頁數越大,系統效能就會越差。在使用Max、Min、Sum、Count之類的函式進行計算的時候,也需要先在每個分片上執行相應的函式,然後將各個分片的結果集進行彙總再次計算。
全域性主鍵避重問題
在分庫分表環境中,由於表中資料同時存在不同資料庫中,主鍵值平時使用的自增長將無用武之地,某個分割槽資料庫自生成ID無法保證全域性唯一。因此需要單獨設計全域性主鍵,避免跨庫主鍵重複問題。這裡有一些策略:
UUID
UUID標準形式是32個16進位制數字,分為5段,形式是8-4-4-4-12的36個字元。 UUID是最簡單的方案,本地生成,效能高,沒有網路耗時,但是缺點明顯,佔用儲存空間多,另外作為主鍵建立索引和基於索引進行查詢都存在效能問題,尤其是InnoDb引擎下,UUID的無序性會導致索引位置頻繁變動,導致分頁。
結合資料庫維護主鍵ID表
在資料庫中建立sequence表:
CREATE TABLE `sequence` (
`id` bigint(20) unsigned NOT NULL auto_increment,
`stub` char(1) NOT NULL default '',
PRIMARY KEY (`id`),
UNIQUE KEY `stub` (`stub`)
) ENGINE=MyISAM;
複製程式碼
stub欄位設定為唯一索引,同一stub值在sequence表中只有一條記錄,可以同時為多張表生辰全域性ID。使用MyISAM引擎而不是InnoDb,已獲得更高的效能。MyISAM使用的是表鎖,對錶的讀寫是序列的,所以不用擔心併發時兩次讀取同一個ID。當需要全域性唯一的ID時,執行:
REPLACE INTO sequence (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
複製程式碼
此方案較為簡單,但缺點較為明顯:存在單點問題,強依賴DB,當DB異常時,整個系統不可用。配置主從可以增加可用性。另外效能瓶頸限制在單臺Mysql的讀寫效能。
另有一種主鍵生成策略,類似sequence表方案,更好的解決了單點和效能瓶頸問題。這一方案的整體思想是:建立2個以上的全域性ID生成的伺服器,每個伺服器上只部署一個資料庫,每個庫有一張sequence表用於記錄當前全域性ID。 表中增長的步長是庫的數量,起始值依次錯開,這樣就能將ID的生成雜湊到各個資料庫上
Snowflake分散式自增ID演算法
Twitter的snowfalke演算法解決了分散式系統生成全域性ID的需求,生成64位Long型數字,組成部分:- 第一位未使用
- 接下來的41位是毫秒級時間,41位的長度可以表示69年的時間
- 5位datacenterId,5位workerId。10位長度最多支援部署1024個節點
- 最後12位是毫秒內計數,12位的計數順序號支援每個節點每毫秒產生4096個ID序列。
資料遷移、擴容問題
當業務高速發展、面臨效能和儲存瓶頸時,才會考慮分片設計,此時就不可避免的需要考慮歷史資料的遷移問題。一般做法是先讀出歷史資料,然後按照指定的分片規則再將資料寫入到各分片節點中。此外還需要根據當前的資料量個QPS,以及業務發展速度,進行容量規劃,推算出大概需要多少分片(一般建議單個分片的單表資料量不超過1000W)
什麼時候考慮分庫分表
能不分就不分
並不是所有表都需要切分,主要還是看資料的增長速度。切分後在某種程度上提升了業務的複雜程度。不到萬不得已不要輕易使用分庫分表這個“大招”,避免“過度設計”和“過早優化”。分庫分表之前,先盡力做力所能及的優化:升級硬體、升級網路、讀寫分離、索引優化等。當資料量達到單表瓶頸後,在考慮分庫分表。
資料量過大,正常運維影響業務訪問
這裡的運維是指:
- 對資料庫備份,如果單表太大,備份時需要大量的磁碟IO和網路IO
- 對一個很大的表做DDL,MYSQL會鎖住整個表,這個時間會很長,這段時間業務不能訪問此表,影響很大。
- 大表經常訪問和更新,就更有可能出現鎖等待。
隨著業務發展,需要對某些欄位垂直拆分
這裡就不舉例了。在實際業務中都可能會碰到,有些不經常訪問或者更新頻率低的欄位應該從大表中分離出去。
資料量快速增長
隨著業務的快速發展,單表中的資料量會持續增長,當效能接近瓶頸時,就需要考慮水平切分,做分庫分表了。
參考連結:
www.cnblogs.com/butterfly10…
www.cnblogs.com/littlechara…