1. 程式人生 > >[NewLife.XCode]分表分庫(百億級大資料儲存)

[NewLife.XCode]分表分庫(百億級大資料儲存)

NewLife.XCode是一個有15年曆史的開源資料中介軟體,支援netcore/net45/net40,由新生命團隊(2002~2019)開發完成並維護至今,以下簡稱XCode。

整個系列教程會大量結合示例程式碼和執行日誌來進行深入分析,蘊含多年開發經驗於其中,代表作有百億級大資料實時計算專案。

開源地址:https://github.com/NewLifeX/X (求star, 938+)

 

XCode是重度充血模型,以單表操作為核心,不支援多表關聯Join,複雜查詢只能在where上做文章,整個select語句一定是from單表,因此對分表操作具有天然優勢!

!! 閱讀本文之前,建議回顧《百億級效能》,其中“索引完備”章節詳細描述了大型資料表的核心要點。

 

100億資料其實並不多,一個比較常見的資料分表分庫模型:

MySql資料庫8主8從,每伺服器8個庫,每個庫16張表,共1024張表(從庫也有1024張表) ,每張表1000萬到5000萬資料,整好100億到500億資料!

 

例程剖析 

例程位置:https://github.com/NewLifeX/X/tree/master/Samples/SplitTableOrDatabase 

新建控制檯專案,nuget引用NewLife.XCode後,建立一個實體模型(修改Model.xml):

<Tables Version="9.12.7136.19046" NameSpace="STOD.Entity" ConnName="STOD" Output="" BaseClass="Entity" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="http://www.newlifex.com https://raw.githubusercontent.com/NewLifeX/X/master/XCode/ModelSchema.xsd" xmlns="http://www.newlifex.com/ModelSchema.xsd">
  <Table Name="History" Description="歷史">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="編號" />
      <Column Name="Category" DataType="String" Description="類別" />
      <Column Name="Action" DataType="String" Description="操作" />
      <Column Name="UserName" DataType="String" Description="使用者名稱" />
      <Column Name="CreateUserID" DataType="Int32" Description="使用者編號" />
      <Column Name="CreateIP" DataType="String" Description="IP地址" />
      <Column Name="CreateTime" DataType="DateTime" Description="時間" />
      <Column Name="Remark" DataType="String" Length="500" Description="詳細資訊" />
    </Columns>
    <Indexes>
      <Index Columns="CreateTime" />
    </Indexes>
  </Table>
</Tables>

在Build.tt上右鍵執行自定義工具,生成實體類“歷史.cs”和“歷史.Biz.cs”。不用修改其中程式碼,待會我們將藉助該實體類來演示分表分庫用法。

為了方便,我們將使用SQLite資料庫,因此不需要配置任何資料庫連線,XCode檢測到沒有名為STOD的連線字串時,將預設使用SQLite。

此外,也可以通過指定名為STOD的連線字串,使用其它非SQLite資料庫。

 

按數字雜湊分表分庫

大量訂單、使用者等資訊,可採用crc16雜湊分表,我們把該實體資料拆分到4個庫共16張表裡面:

static void TestByNumber()
{
    XTrace.WriteLine("按數字分表分庫");

    // 預先準備好各個庫的連線字串,動態增加,也可以在配置檔案寫好
    for (var i = 0; i < 4; i++)
    {
        var connName = $"HDB_{i + 1}";
        DAL.AddConnStr(connName, $"data source=numberData\\{connName}.db", null, "sqlite");
        History.Meta.ConnName = connName;

        // 每庫建立4張表。這一步不是必須的,首次讀寫資料時也會建立
        //for (var j = 0; j < 4; j++)
        //{
        //    History.Meta.TableName = $"History_{j + 1}";

        //    // 初始化資料表
        //    History.Meta.Session.InitData();
        //}
    }

    //!!! 寫入資料測試

    // 4個庫
    for (var i = 0; i < 4; i++)
    {
        var connName = $"HDB_{i + 1}";
        History.Meta.ConnName = connName;

        // 每庫4張表
        for (var j = 0; j < 4; j++)
        {
            History.Meta.TableName = $"History_{j + 1}";

            // 插入一批資料
            var list = new List<History>();
            for (var n = 0; n < 1000; n++)
            {
                var entity = new History
                {
                    Category = "交易",
                    Action = "轉賬",
                    CreateUserID = 1234,
                    CreateTime = DateTime.Now,
                    Remark = $"[{Rand.NextString(6)}]向[{Rand.NextString(6)}]轉賬[¥{Rand.Next(1_000_000) / 100d}]"
                };

                list.Add(entity);
            }

            // 批量插入。兩種寫法等價
            //list.BatchInsert();
            list.Insert(true);
        }
    }
}

通過 DAL.AddConnStr 動態向系統註冊連線字串:

var connName = $"HDB_{i + 1}";

DAL.AddConnStr(connName, $"data source=numberData\\{connName}.db", null, "sqlite");

連線名必須唯一,且有規律,後面要用到。資料庫名最好也有一定規律。 

使用時通過Meta.ConnName指定後續操作的連線名,Meta.TableName指定後續操作的表名,本執行緒有效,不會干涉其它執行緒。

var connName = $"HDB_{i + 1}";
History.Meta.ConnName = connName;

History.Meta.TableName = $"History_{j + 1}";

注意,ConnName/TableName改變後,將會一直維持該引數,直到修改為新的連線名和表名。

指定表名連線名後,即可在本執行緒內持續使用,後面使用批量插入技術,給每張表插入一批資料。

 

執行效果如下:

 

 

 

 

連線字串指定的numberData目錄下,生成了4個數據庫,每個資料庫生成了4張表,每張表內插入1000行資料。

指定不存在的資料庫和資料表時,XCode的反向工程將會自動建表建庫,這是它獨有的功能。(因非同步操作,密集建表建庫時可能有一定機率失敗,重試即可)

 

按時間序列分表分庫

日誌型的時間序列資料,特別適合分表分庫儲存,定型拆分模式是,每月一個庫每天一張表。

static void TestByDate()
{
    XTrace.WriteLine("按時間分表分庫,每月一個庫,每天一張表");

    // 預先準備好各個庫的連線字串,動態增加,也可以在配置檔案寫好
    var start = DateTime.Today;
    for (var i = 0; i < 12; i++)
    {
        var dt = new DateTime(start.Year, i + 1, 1);
        var connName = $"HDB_{dt:yyMM}";
        DAL.AddConnStr(connName, $"data source=timeData\\{connName}.db", null, "sqlite");
    }

    // 每月一個庫,每天一張表
    start = new DateTime(start.Year, 1, 1);
    for (var i = 0; i < 365; i++)
    {
        var dt = start.AddDays(i);
        History.Meta.ConnName = $"HDB_{dt:yyMM}";
        History.Meta.TableName = $"History_{dt:yyMMdd}";

        // 插入一批資料
        var list = new List<History>();
        for (var n = 0; n < 1000; n++)
        {
            var entity = new History
            {
                Category = "交易",
                Action = "轉賬",
                CreateUserID = 1234,
                CreateTime = DateTime.Now,
                Remark = $"[{Rand.NextString(6)}]向[{Rand.NextString(6)}]轉賬[¥{Rand.Next(1_000_000) / 100d}]"
            };

            list.Add(entity);
        }

        // 批量插入。兩種寫法等價
        //list.BatchInsert();
        list.Insert(true);
    }
}

時間序列分表看起來比數字雜湊更簡單一些,分表邏輯清晰明瞭。

 

 

 

 

 

 例程遍歷了今年的365天,在連線字串指定的timeData目錄下,生成了12個月份資料庫,然後每個庫裡面按月生成資料表,每張表插入1000行模擬資料。

 

綜上,分表分庫其實就是在操作資料庫之前,預先設定好 Meta.ConnName/Meta.TableName,其它操作不變!

 

分表查詢

說到分表,許多人第一反應就是,怎麼做跨表查詢?

不好意思,不支援!

只能在多張表上各自查詢,如果系統設計不合理,甚至可能需要在所有表上進行查詢。

不建議做檢視union,那樣會無窮無盡,業務邏輯還是放在程式碼中為好,資料庫做好儲存與基礎計算。

 

分表查詢的用法與分表添刪改一樣:

static void SearchByDate()
{
    // 預先準備好各個庫的連線字串,動態增加,也可以在配置檔案寫好
    var start = DateTime.Today;
    for (var i = 0; i < 12; i++)
    {
        var dt = new DateTime(start.Year, i + 1, 1);
        var connName = $"HDB_{dt:yyMM}";
        DAL.AddConnStr(connName, $"data source=timeData\\{connName}.db", null, "sqlite");
    }

    // 隨機日期。批量操作
    start = new DateTime(start.Year, 1, 1);
    {
        var dt = start.AddDays(Rand.Next(0, 365));
        XTrace.WriteLine("查詢日期:{0}", dt);

        History.Meta.ConnName = $"HDB_{dt:yyMM}";
        History.Meta.TableName = $"History_{dt:yyMMdd}";

        var list = History.FindAll();
        XTrace.WriteLine("資料:{0}", list.Count);
    }

    // 隨機日期。個例操作
    start = new DateTime(start.Year, 1, 1);
    {
        var dt = start.AddDays(Rand.Next(0, 365));
        XTrace.WriteLine("查詢日期:{0}", dt);
        var list = History.Meta.ProcessWithSplit(
            $"HDB_{dt:yyMM}",
            $"History_{dt:yyMMdd}",
            () => History.FindAll());

        XTrace.WriteLine("資料:{0}", list.Count);
    }
}

 

仍然是通過設定 Meta.ConnName/Meta.TableName 來實現分表分庫。日誌輸出可以看到查找了哪個庫哪張表。

這裡多了一個 History.Meta.ProcessWithSplit  ,其實是快捷方法,在回撥內使用連線名和表名,退出後復原。

 

分表分庫後,最容易犯下的錯誤,就是使用時忘了設定表名,在錯誤的表上查詢資料,然後怎麼也查不到……

 

分表策略

根據這些年的經驗:

  • Oracle適合單表1000萬~1億行資料,要做分割槽
  • MySql適合單表1000萬~5000萬行資料,很少人用MySql分割槽

如果統一在應用層做拆分,資料庫只負責儲存,那麼上面的方案適用於各種資料庫。

同時,單表資料上限,就是大家常問的應該分為幾張表?在系統生命週期內(一般1~2年),確保拆分後的每張表資料總量在1000萬附近最佳。

根據《百億級效能》,常見分表策略如下:

  • 日誌型時間序列表,如果每月資料不足1000萬,則按月分表,否則按天分表。缺點是資料熱點極為明顯,適合熱表、冷表、歸檔表的梯隊架構,優點是批量寫入和抽取效能顯著;
  • 狀態表(訂單、使用者等),按Crc16雜湊分表,以1000萬為準,決定分表數量,向上取整為2的指數倍(為了好算)。資料冷熱均勻,利於單行查詢更新,缺點是不利於批量寫入和抽取;

至於是否需要分庫,主要由儲存空間以及效能要求決定。

 

分表與分割槽對比

還有一個很常見的問題,為什麼使用分表而不是分割槽?

大型資料庫Oracle、MSSQL、MySql都支援分割槽,前兩者較多使用分割槽,MySql則較多分表。

分割槽和分表並沒有本質的不同,兩者都是為了把海量資料按照一定的策略拆分儲存,以優化寫入和查詢。

  • 分割槽除了能建立子索引外,還可以建立全域性索引,而分表不能建立全域性索引;
  • 分割槽能跨區查詢,但非常非常慢,一不小心就掃描所有分割槽;
  • 分表架構,很容易做成分庫,支援輕易擴充套件到多臺伺服器上去,分割槽只能要求資料庫伺服器更強更大;
  • 分割槽主要由DBA操作,分表主要由程式設計師控制;

 

 

!!!某專案使用XCode分表功能,已經過生產環境三年半考驗,日均新增4000萬~5000萬資料量,2億多次添刪改,總資料量數百億。

 

博文答疑

2019年9月9日晚上19點,釘釘企業群“新生命團隊”,視訊直播博文答疑。

今晚之後,如有問題,可以提問:https://github.com/NewLifeX/X/issues

 

 

 

系列教程

NewLife.XCode教程系列[2019版]

  1. 增刪改查入門。快速展現用法,程式碼配置連線字串
  2. 資料模型檔案。建立表格欄位和索引,名字以及資料型別規範,推薦欄位(時間,使用者,IP)
  3. 實體類詳解。資料類業務類,泛型基類,介面
  4. 功能設定。連線字串,除錯開關,SQL日誌,慢日誌,引數化,執行超時。程式碼與配置檔案設定,連線字串區域性設定
  5. 反向工程。自動建立資料庫資料表
  6. 資料初始化。InitData寫入初始化資料
  7. 高階增刪改。過載攔截,自增欄位,Valid驗證,實體模型(時間,使用者,IP)
  8. 髒資料。如何產生,怎麼利用
  9. 增量累加。高併發統計
  10. 事務處理。單表和多表,不同連線,多種寫法
  11. 擴充套件屬性。多表關聯,Map對映
  12. 高階查詢。複雜條件,分頁,自定義擴充套件FieldItem,查總記錄數,查彙總統計
  13. 資料層快取。Sql快取,更新機制
  14. 實體快取。全表整理快取,更新機制
  15. 物件快取。字典快取,適用使用者等資料較多場景。
  16. 百億級效能。欄位精煉,索引完備,合理查詢,充分利用快取
  17. 實體工廠。元資料,通用處理程式
  18. 角色許可權。Membership
  19. 匯入匯出。Xml,Json,二進位制,網路或檔案
  20. 分表分庫。常見拆分邏輯
  21. 高階統計。聚合統計,分組統計
  22. 批量寫入。批量插入,批量Upsert,非同步儲存
  23. 實體佇列。寫入級快取,提升效能。
  24. 備份同步。備份資料,恢復資料,同步資料
  25. 資料服務。提供RPC介面服務,遠端執行查詢,例如SQLite網路版
  26. 大資料分析。ETL抽取,排程計算處理,結果持久化