一、為什么需要分庫分表


基於 Mycat 1.6.7.3 版本
1 為什么要分庫分表
  1.1 數據庫性能瓶頸的出現
    對於應用來說,如果數據庫性能出現問題,要么是無法獲取連接,是因為在高並發的情況下連接數不夠了。要么是操作數據變慢,數據庫處理數據的效率除了問題。要么
是存儲出現問題,比如單機存儲的數據量太大了,存儲的問題也可能會導致性能的問題。歸根結底都是受到了硬件的限制,比如 CPU,內存,磁盤,網絡等等。但是我們優
化肯定不可能直接從擴展硬件入手,因為帶來的收益和成本投入比例太比。所以我們先來分析一下,當我們處理數據出現無法連接,或者變慢的問題的時候,
我們可以從哪些層面入手。
 
 1.2 數據庫優化方案對比
   數據庫優化有很多層面。
   1.2.1 SQL 與索引
   因為 SQL 語句是在我們的應用端編寫的,所以第一步,我們可以在程序中對 SQL 語句進行優化,最終的目標是用到索引。這個是容易的也是最常用的優化手段。
     1.2.2 表與存儲引擎
    第二步,數據是存放在表里面的,表又是以不同的格式存放在存儲引擎中的,所以我們可以選用特定的存儲引擎,或者對表進行分區,對表結構進行拆分或者冗余處理,
    或者對表結構比如字段的定義進行優化。
     1.2.3 架構
    第三步,對於數據庫的服務,我們可以對它的架構進行優化。如果只有一台數據庫的服務器,我們可以運行多個實例,做集群的方案,做負載均衡。或者基於主從復制實現讀寫分離,讓寫的服務都訪問 master 服務器,讀的請求都訪
          問從服務器,slave 服務器自動 master 主服務器同步數據。或者在數據庫前面加一層緩存,達到減少數據庫的壓力,提升訪問速度的目的。為了分散數據庫服務的存儲壓力和訪問壓力,我們也可以把不同的數據分布到不同的服務節點,
    這個就是分庫分表(scale out)。注意主從(replicate)和分片(shard)的區別:主從通過數據冗余實現高可用,和實現讀寫分離。分片通過拆分數據分散存儲和訪問壓力。
         1.2.4 配置
              第四步,是數據庫配置的優化,比如連接數,緩沖區大小等等,優化配置的目的都是為了更高效地利用硬件。
        1.2.5 操作系統與硬件
             最后一步操作系統和硬件的優化。
從上往下,成本收益比慢慢地在增加。所以肯定不是查詢一慢就堆硬件,堆硬件叫做向上的擴展(scale up)。
 
什么時候才需要分庫分表呢?我們的評判標准是什么?如果是數據量的話,一張表存儲了多少數據的時候,才需要考慮分庫分表?
如果是數據增長速度的話,每天產生多少數據,才需要考慮做分庫分表?如果是應用的訪問情況的話,查詢超過了多少時間,有多少請求無法獲取連接,才需要分庫分表?
這是一個值得思考的問題。
 
  1.3 架構演進與分庫分表
     1.3.1 單應用單數據庫
      2013 年的時候,我們公司采購了一個消費金融核心系統,這個是一個典型的單體架構的應用。同學們應該也很熟悉,單體架構應用的特點就是所有的代碼都在一個工程里面,打成
    一個 war 包部署到 tomcat,最后運行在一個進程中。
    這套消費金融的核心系統,用的是 Oracle 的數據庫,初始化以后有幾百張表,比如客戶信息表、賬戶表、商戶表、產品表、放款表、還款表等等。
             

 

 

  為了適應業務的發展,我們這一套系統不停地在修改,代碼量越來越大,系統變得越來越臃腫。為了優化系統,我們搭集群,負載均衡,加緩存,優化數據庫,優化業務代碼系統,但是都應對不了系統的訪問壓力。
  所以這個時候系統拆分就勢在必行了。我們把以前這一套采購的核心系統拆分出來很多的子系統,比如提單系統、商戶管理系統、信審系統、合同系統、代扣系統、催收系統,所有的系統都依舊共用一套 Oracle 數據庫。
  1.3.2 多應用單數據庫
    對代碼進行了解耦,職責進行了拆分,生產環境出現問題的時候,可以快速地排查和解決
             

 

     

這種多個子系統共用一個 DB 的架構,會出現一些問題。第一個就是所有的業務系統都共用一個 DB,無論是從性能還是存儲的角度來說,都是滿足不了需求的。隨着我們的業務繼續膨脹,我們又會增加更多的系統來訪問核心數
據庫,但是一個物理數據庫能夠支撐的並發量是有限的,所有的業務系統之間還會產生競爭,最終會導致應用的性能下降,甚至拖垮業務系統。 
                            

 

 

所以,分庫其實是我們在解決系統性能問題的過程中,對系統進行拆分的時候帶來的一個必然的結果。現在的微服務架構也是一
樣的,只拆應用不拆分數據庫,不能解決根本的問題。
   1.3.4 什么時候分表
  
當我們對原來一個數據庫的表做了分庫以后,其中一些表的數據還在以一 個非常快這個時候查詢也已經出現了非常明顯的效率下降,
所以,在分庫之后,還需要進一步進行分表。當然,我們最開始想到的可能是在一個數據庫里面拆分數據,分區或者分表,到后面才是切分到多個數據庫中。
分表主要是為了減少單張表的大小,解決單表數據量帶來的性能問題
                           

 

 

我們需要清楚的是,分庫分表會提升系統的復雜度,如果在近期或者未來一段時間內必須要解決存儲和性能的問題,就不要去做超前設計和過度設計。就像我們搭建項目,從快速實現的角度來說,肯定是從單體項目起步的,在業務豐富完善之前,也用不到微服務架構。如果我們創建的表結構合理,字段不是太多,並且索引創建正確的情況下, 單張表存儲幾千萬的數據是完全沒有問題的,這個還是以應用的實際情況為准。當然我們也會對未來一段時間的業務發展做一個預判
2 分庫分表的類型和特點
從維度來說分成兩種,一種是垂直,一種是水平。
垂直切分:基於表或字段划分,表結構不同。我們有單庫的分表,也有多庫的分庫。
水平切分:基於數據划分,表結構相同,數據不同,也有同庫的水平切分和多庫的切分。 
    

 

 

2.1 垂直切分
垂直分表有兩種,一種是單庫的,一種是多庫的。
  2.1.1 單庫垂直分表
    單庫分表,比如:商戶信息表,拆分成基本信息表,聯系方式表,結算信息表,附件表等等。
   2.1.2 多庫垂直分表
    多庫垂直分表就是把原來存儲在一個庫的不同的表,拆分到不同的數據庫。
 
比如:消費金融核心系統數據庫,有很多客戶相關的表,這些客戶相關的表,全部單獨存放到客戶的數據庫里面。合同,放款,風控相關的業務表也是一樣的。
 
          

 

         

 

 

當我們對原來的一張表做了分庫的處理,如果某些業務系統的數據還是有一個非常 快的增長速度,比如說還款數據庫的還款歷史表,數據量達到了幾個億,這個時候硬件
限制導致的性能問題還是會出現,所以從這個角度來說垂直切分並沒有從根本上解決單庫單表數據量過大的問題。在這個時候,我們還需要對我們的數據做一個水平的切分。
 
2.2 水平切分
  當我們的客戶表數量已經到達數千萬甚至上億的時候,單表的存儲容量和查詢效率都會出現問題,我們需要進一步對單張表的數據進行水平切分。水平切分的每個數據庫的表結構都是一樣的,只是存儲的數據不一樣,比如每個庫存儲 1000 萬的數據。水平切分也可以分成兩種,一種是單庫的,一種是多庫的。
   2.2.1 單庫水平分表
  銀行的交易流水表,所有進出的交易都需要登記這張表,因為絕大部分時候客戶都是查詢當天的交易和一個月以內的交易數據,所以我們根據使用頻率把這張表拆分成三張表:
  當天表:只存儲當天的數據。
  當月表:在夜間運行一個定時任務,前一天的數據,全部遷移到當月表。用的是 insert into select,然后 delete。
  歷史表:同樣是通過定時任務,把登記時間超過 30 天的數據,遷移到 history
  歷史表(歷史表的數據非常大,我們按照月度,每個月建立分區)。
  費用表:
    消費金融公司跟線下商戶合作,給客戶辦理了貸款以后,消費金融公司要給商戶返
    費用,或者叫提成,每天都會產生很多的費用的數據。為了方便管理,我們每個月建立一張費用表,例如 fee_detail_201901……fee_detail_201912
    
  但是注意,跟分區一樣,這種方式雖然可以一定程度解決單表查詢性能的問題,但是並不能解決單機存儲瓶頸的問題
2.2.2 多庫水平分表
  另一種是多庫的水平分表。比如客戶表,我們拆分到多個庫存儲,表結構是完全一樣的。
  

 

 

一般我們說的分庫分表都是跨庫的分表。既然分庫分表能夠幫助我們解決性能的問題,那我們是不是馬上動手去做,甚至在項目設計的時候就先給它分幾個庫呢?先冷靜
一下,我們來看一下分庫分表會帶來哪些問題,也就是我們前面說的分庫分表之后帶來的復雜性。
 
2.3 多案分庫分表帶來的問題
   2.3.1 跨庫關聯查詢
    比如查詢在合同信息的時候要關聯客戶數據,由於是合同數據和客戶數據是在不同的數據庫,那么我們肯定不能直接使用 join 的這種方式去做關聯查詢。
    我們有幾種主要的解決方案:
      
      1、字段冗余
        比如我們查詢合同庫的合同表的時候需要關聯客戶庫的客戶表,我們可以直接把一些經常關聯查詢的客戶字段放到合同表,通過這種方式避免跨庫關聯查詢的問題。
      2、數據同步:比如商戶系統要查詢產品系統的產品表,我們干脆在商戶系統創建一張產品表,通過 ETL 或者其他方式定時同步產品數據。
      3、全局表(廣播表) 比如行名行號信息被很多業務系統用到,如果我們放在核心系統,每個系統都要去關聯查詢,這個時候我們可以在所有的數據庫都存儲相同的基礎數據。
      4、ER 表(綁定表)
        我們有些表的數據是存在邏輯的主外鍵關系的,比如訂單表 order_info,存的是匯總的商品數,商品金額;訂單明細表 order_detail,是每個商品的價格,個數等等。或者
       叫做從屬關系,父表和子表的關系。他們之間會經常有關聯查詢的操作,如果父表的數據和子表的數據分別存儲在不同的數據庫,跨庫關聯查詢也比較麻煩。所以我們能不能
       把父表和數據和從屬於父表的數據落到一個節點上呢?
       比如 order_id=1001 的數據在 node1,它所有的明細數據也放到 node1;order_id=1002 的數據在 node2,它所有的明細數據都放到 node2,這樣在關聯查詢的時候依然是在
          一個數據庫。
    上面的思路都是通過合理的數據分布避免跨庫關聯查詢,實際上在我們的業務中,也是盡量不要用跨庫關聯查詢,如果出現了這種情況,就要分析一
    下業務或者數據拆分是不是合理。如果還是出現了需要跨庫關聯的情況,那我們就只能用最后一種辦法
    5、系統層組裝
      在不同的數據庫節點把符合條件數據的數據查詢出來,然后重新組裝,返回給客戶端
   2.3.2 分布式事務
    比如在一個貸款的流程里面,合同系統登記了數據,放款系統也必須生成放款記錄,如果兩個動作不是同時成功或者同時失敗,就會出現數據一致性的問題。如果在
  一個數據庫里面,我們可以用本地事務來控制,但是在不同的數據庫里面就不行了。所以分布式環境里面的事務,我們也需要通過一些方案來解決。復習一下。分布式系統的基礎是 CAP 理論。
     1.C (一致性) Consistency:對某個指定的客戶端來說,讀操作能返回最新的寫操作。對於數據分布在不同節點上的數據來說,如果在某個節點更新了數據,那么在其他節點
    如果都能讀取到這個最新的數據,那么就稱為強一致,如果有某個節點沒有讀取到,那就是分布式不一致。
     2.A (可用性) Availability:非故障的節點在合理的時間內返回合理的響應(不是錯誤和超時的響應)。可用性的兩個關鍵一
    個是合理的時間,一個是合理的響應。合理的時間指的是請求不能無限被阻塞,應該在合理的時間給出返回。合理的響應指的是系統應該明確返回結果並且結果是正確的
     3.P (分區容錯性) Partition tolerance:當出現網絡分區后,系統能夠繼續工作。打個比方,這里集群有多台機器,有台機器網絡出現了問題,但是這個集群仍然可以正工作。
 
    CAP 三者是不能共有的,只能同時滿足其中兩點。基於 AP,我們又有了 BASE 理論。基本可用(Basically Available):分布式系統在出現故障時,允許損失部分可用功能,保證核心功能可用。 
    軟狀態(Soft state):允許系統中存在中間狀態,這個狀態不影響系統可用性,這里指的是 CAP 中的不一致。
    最終一致(Eventually consistent):最終一致是指經過一段時間后,所有節點數據都將會達到一致
    分布式事務有幾種常見的解決方案:
     1、全局事務(比如 XA 兩階段提交;應用、事務管理器(TM)、資源管理器(DB)),
    例如 Atomikos 兩階段提交
    
     2、基於可靠消息服務的分布式事務
      

 

       

    3、柔性事務 TCC(Try-Confirm-Cancel)tcc-transaction
      

 

       

      4、最大努力通知,通過消息中間件向其他系統發送消息(重復投遞+定期校對)
   2.3.3 排序、翻頁、函數計算問題
    跨節點多庫進行查詢時,會出現 limit 分頁,order by 排序的問題。比如有兩個節點,節點 1 存的是奇數 id=1,3,5,7,9……;節點 2 存的是偶數 id=2,4,6,8,10……
  執行 select*from user_info order by id limit 0,10需要在兩個節點上各取出 10 條,然后合並數據,重新排序。max、min、sum、count 之類的函數在進行計算的時候,
  也需要先在每個分片上執行相應的函數,然后將各個分片的結果集進行匯總和再次計算,最終將結果返回
 
   2.3.4 全局主鍵避重問題
    MySQL 的數據庫里面字段有一個自增的屬性,Oracle 也有 Sequence 序列。如果是一個數據庫,那么可以保證 ID 是不重復的,但是水平分表以后,每個表都按照自己的規律自增,
  肯定會出現 ID 重復的問題,這個時候我們就不能用本地自增的方式了。
    我們有幾種常見的解決方案:
    1)UUID(Universally Unique Identifier 通用唯一識別碼)   
    UUID 標准形式包含 32 個 16 進制數字,分為 5 段,形式為 8-4-4-4-12 的 36 個字符,例如:c4e7956c-03e7-472c-8909-d733803e79a9
    xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
    M 表示 UUID 版本,目前只有五個版本,即只會出現 1,2,3,4,5,數字 N 的一至三個最高有效位表示 UUID 變體,目前只會出現 8,9,a,b 四種情況。
    1、基於時間和 MAC 地址的 UUID
    2、基於第一版卻更安全的 DCE UUID
    3、基於 MD5 散列算法的 UUID
     4、基於隨機數的 UUID——用的最多,JDK 里面是 4
    5、基於 SHA1 散列算法的 UUID
    UUID 是主鍵是最簡單的方案,本地生成,性能高,沒有網絡耗時。但缺點也很明顯,由於 UUID 非常長,會占用大量的存儲空間;另外,作為主鍵建立索引和基於索引進行
    查詢時都會存在性能問題,在 InnoDB 中,UUID 的無序性會引起數據位置頻繁變動,導致分頁。
    2) 數據庫 把序號維護在數據庫的一張表中。這張表記錄了全局主鍵的類型、位數、起始值,當前值。當其他應用需要獲得全局 ID 時,先 for update 鎖行,取到值+1 后並且更新后 返回。並發性比較差。
     3)Redis 基於 Redis 的 INT 自增的特性,使用批量的方式降低數據庫的寫壓力,每次獲取一 段區間的 ID 號段,用完之后再去數據庫獲取,可以大大減輕數據庫的壓力
    4)雪花算法 Snowflake(64bit)4)雪花算法 Snowflake(64bit)
      

 

     核心思想:

      a)使用 41bit 作為毫秒數,可以使用 69 年

      b)10bit 作為機器的 ID(5bit 是數據中心,5bit 的機器 ID),支持 1024 個 節點

      c)12bit 作為毫秒內的流水號(每個節點在每毫秒可以產生 4096 個 ID)

       d)最后還有一個符號位,永遠是 0。

      代碼:snowflake.SnowFlakeTest

      優點:毫秒數在高位,生成的 ID 整體上按時間趨勢遞增;不依賴第三方系統,穩定 性和效率較高,理論上 QPS 約為 409.6w/s(1000*2^12),並且整個分布式系統內不會 產生 ID 碰撞;可根據自身業務靈活分配 bit 位。

      不足就在於:強依賴機器時鍾,如果時鍾回撥,則可能導致生成 ID 重復。

    當我們對數據做了切分,分布在不同的節點上存儲的時候,是不是意味着會產生多 個數據源?既然有了多個數據源,那么在我們的項目里面就要配置多個數據源。
    現在問題就來了,我們在執行一條 SQL 語句的時候,比如插入,它應該是在哪個數 據節點上面執行呢?又比如查詢,如果只在其中的一個節點上面,我怎么知道在哪個節 點,是不是要在所有的數據庫節點里面都查詢一遍,才能拿到結果?
    那么,從客戶端到服務端,我們可以在哪些層面解決這些問題呢?

  2.4 多數據源/讀寫數據源的解決方案

    我們先要分析一下 SQL 執行經過的流程。 DAO——Mapper(ORM)——JDBC——代理——數據庫服務   

     2.4.1 客戶端 DAO 層
        第一個就是在我們的客戶端的代碼,比如 DAO 層,在我們連接到某一個數據源之前, 我們先根據配置的分片規則,判斷需要連接到哪些節點,再建立連接。
        Spring 中提供了一個抽象類 AbstractRoutingDataSource,可以實現數據源的動態 切換
     。。。   

     2.4.4 代理層

        前面三種都是在客戶端實現的,也就是說不同的項目都要做同樣的改動,不同的編 程語言也有不同的實現,
        所以我們能不能把這種選擇數據源和實現路由的邏輯提取出來, 做成一個公共的服務給所有的客戶端使用呢?
        這個就是第四層,代理層。比如 Mycat 和 Sharding-Proxy,都是屬於這一層

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM