[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抽取,調度計算處理,結果持久化 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章