數據庫水平切分及問題


    前面一篇文章說到,當遇到數據存儲層的高並發的時候,會首先想到讀寫分離,同時高並發有可能意味着數據量大,大量的查詢或更新操作集中在一張大表中,鎖的頻繁使用,會導致訪問速度的下降,而且數據量可能超過了單機的容量,所以我們想到了分庫分表。
    但是在分庫分表之前,我還是想多說幾句,除非使用那些透明的分庫分表方案,否則分庫分表是一個大工程。 所以在分庫分表前,我建議盡可能先升級數據庫的硬件,SSD/NVMe硬盤 + 大容量內存基本可以滿足一個小型互聯網公司大部分的應用, 對於中型互聯網公司需要使用到分庫分表的場景也不會太多,用戶,訂單,交易可能會用到的。但是即使是這些場景,現在也有了不同的解決方案,在以前,大部分的應用還是基於web的,如果一些操作需要用到用戶校驗,就要進行登錄,對數據庫操作比較頻繁,隨着移動互聯網的興起以及Nosql的出現,如今大部分的應用都遷移到了App端,基本都是通過accessToken+NoSql來進行用戶校驗的,大家可以想想你有多久沒有進行過登錄操作了。對於訂單交易,通過冷熱數據分離,也可以解決大部分場景。
數據庫切分分為水平切分,垂直切分, 垂直切分一般在拆分系統的時候使用,這里不再贅述,下面主要說數據的水平切分。 
水平切分方式
    1. 只分表:在一個數據庫下面,分成10張表,表名 user_0 ,user_1, user_2.....
    數據集中在一台服務器上,當單機性能瓶頸的時候,后續擴展困難。
    2. 只分庫,分成10個庫,每個庫一張表, 表名都是一樣的。 db0-user  db1-user db2-user......
    出現跨庫,沒有辦法使用事務。不過在分布式系統中,一般不會使用事務,保證數據最終一致性即可
    3. 分表分庫,10X10這種方式。 先分10個庫, 每個庫10張表。
確定了表切分的方式,接下來就是要根據一定的規則把數據落入到指定表中,這里介紹兩種形式,
取模:即找到某個字段,這個字段通過取模后的值盡可能平均,根據字段取模的方式決定數據落在哪張表,比如,我們訂單根據用戶Id來決定落在哪張表。
日期:這種方法在最開始的時候,並沒有決定要分多少張表,只是指定日期的數據落到指定的表中,比如用戶注冊,在2017-5-30日注冊,則可以將數據落入user_201705,這張表中。 
這兩種分表方法各有優劣,按照取模分,每張表的數據可以盡可能的平均,但是如果后面表擴展則比較麻煩,按照日期來分,雖然可以解決表擴展問題,但是數據有可能不平均,比如在5月,舉辦了促銷,結果那個月注冊的人很多。 
當數據開始寫數據庫,Id就是要考慮的,這里有幾個解決方案:
    1. Id自增,步進=分表數。比如分了3張表,每張表的起始Id不一樣,並且自增的幅度=表數量,這樣Id就是1,4,7  2,5,8  3,6,9 ,但是如果以后分表數增加,Id的步進還要變。 
    2. 單獨的表,用於記錄自增,可以單獨一張表,里面有一列,記錄的是當前的id, 可以獲取下一個Id,  只是這樣每次都要訪問這張表,會有性能瓶頸。 
    3. Guid
    4. snowflake算法
關於Id有幾個注意的地方,如果你的Id有可能出現在頁面上, 比如訂單Id, 用戶Id ,盡量不要使用自增的,因為別人可以根據這些Id,看出你現在的用戶規模,訂單量。通過在兩天中相同時間下單,可以看出你的交易量,如果權限沒有控制好,通過遍歷可以查出所有的信息。 
我們這里以用戶表為例,看把用戶信息寫入數據庫的一些方法,以及這些方法的一些問題。 
    當一個新用戶注冊,並沒有Id,可以通過分布式Id獲取到一個Id ,並對這個Id取模,得到具體的表,然后將Id連同用戶信息寫入表。 后面如果要根據Id得到用戶信息,只要對Id采用相同的方法,即可查到對應的信息。 
    但是上面的方法沒有覆蓋到一個場景,用戶登錄都是通過用戶名登錄的,怎么查呢,這個時候,一般的方法就是並行查所有的表,好一點的方法就是,在注冊的時候,額外建立一個映射,是username->表名的映射,當用戶登錄的時候,可以根據這個映射找到對應的表名,然后根據表名,再插到具體的用戶,這個映射可以放到數據庫中,也可以放到redis/mongodb中。 
    上面的方法可以解決問題,但是需要引入映射,如果不想要映射, 這個時候可以采用復合Id , 我們使用用戶名進行取模,算出具體的表索引,然后通過分布式Id+表索引作為新的Id(NewId)插入到表中,這樣我們對用戶名進行計算可以得到表索引,通過對NewId進行計算也可以得到表索引(NewId包含了分布式Id和表索引,通過分離,可以得到表索引)。 
    我們上面沒有考慮到擴展,比如現在我們有10張表,過段時間,發現10張表的存儲也快滿了, 這個時候就要再擴展10張表,變成20張表,那取模算法也要改,這個時候再對以前的NewId進行計算可能就得不到准備的表了,所以在NewId中,我們還要包含一個算法版本號。 
當所有的操作都在一個數據庫的時候, 可以很方便的使用事務,比如Java下spring的聲明式事務,但是當出現跨庫操作,事務的使用就不怎么方便了,對於互聯網公司,大多數系統都是分布式的,會選擇柔性事物。
隨着業務的發展,數據量增多,我們需要再次對表進行擴展,擴展方式如下: 
1. 需要遷移數據 
     1. 首次擴展:假如我們要開始分庫分表,原來有一個庫一張表,現在要擴展為10個,可以新建9個slave庫,等9個slave庫數據都和主庫都同步了,然后修改路由算法。 
    2. 擴容后再擴容:比如現在3個表,要改為5個表,采用Hash算法,當數據擴容,則整體數據都要做遷移。 新增和刪除都要做。 技巧:選擇2個倍數,可以只做新表新增和刪除,這個可能需要用到雙寫。
    3. 划分樹形組,比如,之前一個數據庫,不需要Hash,現在新增9個,同步數據,完成后,修改路由算法(321%10),再刪除數據,如果10個數據庫不夠,則再次擴容,原來的10個庫,每個庫各自再擴展9個庫(可根據實際需要擴展),同步數據,方法和上面第一種類似,然后通過 321%10   321/10%10, 算出具體的數據庫。如果不夠,可以用同樣的方式擴容。 
 
2. 不遷移數據
    1. 根據時間定義,比如一個月一張表。
    2. 定義mapping關系,可以放入到分布式緩存中,寫入的時候,寫入緩存,讀取的時候讀取緩存, 如果緩存失效,全量讀取。
    3. 增量和Hash同時使用,定義策略,1-1000W一張組,1000-3000W一張組,組內,根據Hash避免熱點數據。 新數據都會到新的數據庫中。這里注意,這個組不要和機器綁定,比如1-1000W,在DB0機器,1000-3000W在DB1機器,雖然數據Hash, 熱點數據不會在同一個表中,但是還是在通一台機器。 要考慮如下的方式,熱點數據還是會分布在老機器上。 
  
    4. 生成的ID具有自描述性。 ID+DBIndex,比如Id為321,Hash命中到第一張表,變為32101,最后兩位表示具體的數據庫索引,老的數據庫也會寫數據,如果我們的表增多,則修改hash算法,只命中到新表。 
目前分庫分表的解決方案主要集中在這幾層:
    1. dao層,在dao層根據指定的分表鍵決定要操作那個數據庫。 
    2. ORM層
    2. JDBC層,比較有名的是sharding-jdbc. 
    3. 代理層,比如mycat. 
  分庫分表是一個大工程, 如何分, ID怎么取, 事物怎么做都是要考慮 的。另外大家不要用操作單表的思維去操作多表,有些人一接觸分表就去考慮gourp by 怎么做, join怎么做。 從我接觸的一些分表來看,如果牽扯到分表,操作都比較簡單,基本在執行sql前都已經確定了要操作哪張表。 當然如果真的遇到要join的操作或者沒有辦法確定數據在哪個表中,像sharding-jdbc,mycat這種透明分表分庫的都會幫你處理。 


免責聲明!

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



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