數據庫分庫分表和帶來的唯一ID、分頁查詢問題的解決


需求緣起(用一個公司的發展作為背景)

        1.還是個小公司的時候,注冊用戶就20w,每天活躍用戶1w,每天最大單表數據量就1000,然后高峰期每秒並發請求最多就10,此時一個16核32G的服務器,每秒請求支撐在2000左右,負載合理,沒有太大壓力,基本沒有宕機風險。

        2.當注冊用戶達到2000W,每天活躍用戶數100W,每天單表新增數據量達到50W條,高峰期請求量達到1W。經過一段時間的運行,單標數據量會越來越多,帶來的問題

      2.1 數據庫服務器的IO,網絡寬帶,CPU負載,內存消耗都會達到非常高,服務器已經不堪重負

    2.2 高峰時期,單表數據量本來就很大,加上數據庫服務器負載太高,導致性能下降,此時SQL的性能就更差了,用戶體驗賊差, 點一個按鈕要很久才有響應,如果服務器的配置再低一點的話,數據庫可能直接宕機

   3. 實現一個基本的分庫分表的思路,將一台數據庫服務器變成5台數據庫,就能有5個庫,5個表,這樣可以將表中的數據按照ID分別通過同一個映射方法,分布到這5個庫中。此時寫入數據的時候,需要借助數據庫中間件,比如shardng-jdbc或者Mycat。查詢的時候先通過一步映射到具體的數據庫,再進行查詢。

   4. 當用戶量再次增長時,只能繼續分表,比如將一張表拆分成1024張表,這樣在操作數據的時候,需要兩次路由,一次找到在哪個數據庫,一次找到在哪張表。

   5. 除了分表,數據庫還可以做主從架構,主服務器用以寫入,從服務器用以查詢,根據業務需求具體實現即可。

分庫分表帶來的問題

   1. 分庫分表之后一個必然的問題,如何獲取一個全局為一個ID?因為表中的數據是通過ID路由映射的,ID不能重復。

   2. 就算有了全局唯一的ID,那面對分頁查詢的需求,應該怎么處理呢?

 

  唯一ID的生成

  下面列舉幾種常見的唯一ID生成方案,需要滿足兩大核心需求:1.全局唯一  2趨勢有序

   1. 用數據庫的auto_increment(自增ID)來生成,每次通過寫入數據庫一條記錄,利用數據庫ID自增的特性獲取唯一,有序的ID。

     優點:使用數據庫原有的功能,相對簡單;能夠保證唯一;能夠保證遞增性;ID之間的步長是固定且可以自定義的

     缺點:可用性難以保證,當生成ID的那台服務器宕機,系統就玩不轉了;由於寫入是單點的,所以擴展性差,性能上限取決於數據庫的寫性能。

   2. 用UUID

     優點:簡單方便;全球唯一,在遇見數據遷移、合並或者變更時可以從容應對;

     缺點:沒有遞增性;UUID是很長的字符串,作為主鍵對存儲空間有一定要求,查詢效率也較低。

   3. 使用Redis生成ID,主要利用Redis是單線程的,所以也可以用來生成唯一ID。當使用的是Redis集群的時候,比如集群中有5台Redis,初始化每台Redis的值為1,2,3,4,5,設置步長為5,並且確定一個不隨機的負載均衡策略,能夠保證有序,唯一。

     優點:不依賴數據庫,靈活,且性能相對於數據庫有一定提高;使用Redis集群策略還能排除單點故障問題;ID天然有序

     缺點:如果系統中沒有Redis,還需要引入新的組件;編碼和配置工作量大

   4. 使用Twitter的snowflake算法;其核心思想是一個64位long型ID,使用41bit作為毫秒數,10bit作為機器的ID(5個bit是數據中心,5個bit的機器ID),12bit作為毫秒內的流水號(意味着每個節點在每毫秒可以產生 4096 個 ID),最后還有一個符號位,永遠是0。具體實現的代碼可以參看https://github.com/twitter/snowflake。可以根據自身需求進行一定的修改。

    優點:不依賴數據庫,靈活方便,性能優於數據庫;ID按照時間在單機上是遞增的

    缺點:單機上遞增,但是當分布式環境下每台機器的時鍾不可能完全同步,有時並不能做做全局遞增。

   5. 使用zookeeper生成唯一ID,主要通過znode數據版本來生成序列號,可以生成32為和64為的數據版本號。很少使用,因為是多步調用API,並發情況下還需要考慮分布式鎖,不是很理想。

   6. MongoDB的ObjectID,和snowflake算法類似。4字節Unix時間戳,3字節機器編碼,2字節進程編碼,3字節隨機數

  分庫分表下的分頁查詢

  假設有一張用戶表,經過分庫分表之后,現在均勻分布在2台服務器實例上。業務需要查詢“最近注冊的第3頁用戶”,雖然數據庫有分庫用的全局的ID,但是沒有排序條件time的全局視野,此時應該怎么做呢?

   1. 全局視野法:因為不清楚按照時間排序之后的第三頁數據到底是如何分布在數據庫上的,所以必須每個庫都返回3頁數據,所得到的6頁數據在服務層進行內存排序,得到全局視野,再取第3頁數據。

     優點:通過服務層修改,擴大數據查詢量,得到全局視野,業務無損,精確

     缺點(顯而易見):每個分庫都需要返回更多的數據,增大網絡傳輸量;除了數據庫要按照time排序,服務層也需要二次排序,損耗性能;隨着頁碼的增大,性能極具下降,數據量和排序量都將大增,性能平方級下降。

    2. 業務折中

     2.1 禁止跳頁查詢,不提供“直接跳到指定頁面”的功能,只提供下一頁的功能。極大的降低技術方案的復雜度。第一頁的選取方法和全局視野法一樣,但是點擊下一頁時:

       2.1.1先找到上一頁的time的最大值,作為第二頁數據拉去的查詢條件,只取每頁的記錄數,

       2.2.2這樣服務層還是獲得兩頁數據,再做一次排序,獲取一頁數據。

       2.2.3改進了不會因為頁碼增大而導致數據的傳輸量和排序量增大

    3. 允許數據精度丟失:需要考慮業務員上是否接受在頁碼較大是返回的數據不是精准的數據。

     3.1在數據量較大,且ID映射分布足夠隨機的話,應該是滿足等概率分布的情況的,所以取一頁數據,我們在每個數據庫中取前半頁。

     3.2當然這樣的到的結果並不是精准的,但是當實際業務可以接受的話, 此時的技術方案的復雜度變大大降低。也不需要服務層內存排序了。

    4. 二次查詢法:既滿足業務的精確需求,也無需業務折中。現在假設每頁顯示10條數據,要查第三頁,數據分了兩個庫。 正常的語句是 select * from table order by time offset 20 limit 10,取偏移20個之后的10個

     4.1首次查詢查詢每個庫的select * from table order by time offset 10 limit 10;得到10條數據。這里的offset是總offset/分庫數

     4.2 服務層得到來自兩個分庫的結果集,得到最小的time,也就是最頂層的time,這個time滿足最少有10條記錄在它前面,然后分別記錄每個庫的最大time

     4.3 分別再次查詢最小time->每個庫上一次的最大time的數據,得到每個庫的查詢結果

     4.4 在每個集合的最小time都是相同的,所以可以得到該最小time在整個數據庫中的offset,加起來就是這個最小time在全局庫的offset位置。

     4.5 再將第二次查詢的結果集拼起來和得到的最小time的offset,推導出 offset 20 limit 10的一頁記錄。

     優點:可以精確得到業務數據,且每次返回的數據量都非常小,不會隨着頁碼增加而數據量增大。

     缺點:需要進行兩次數據庫查詢


免責聲明!

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



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