分散式資料庫中介軟體 MyCat | 分庫分表實踐

AntonioPeng發表於2020-08-20

MyCat 簡介

MyCat 是一個功能強大的分散式資料庫中介軟體,是一個實現了 MySQL 協議的 Server,前端人員可以把它看做是一個資料庫代理中介軟體,用 MySQL 客戶端工具和命令列訪問;而後端人員可以用 MySQL 原生協議與多個 MySQL 伺服器通訊,也可以用 JDBC 協議與大多數主流資料庫伺服器通訊。可以用作 讀寫分離分庫分表(分片)容災備份多租戶應用開發大資料基礎設施,使底層資料架構具備很強的適應性和靈活性。

MyCat 的智慧優化模組可以使系統的資料訪問瓶頸和熱點一目瞭然,並且可以將這些統計分析資料自動或手工調整後端儲存,將不同的表對映到不同儲存引擎上,而整個應用的程式碼可以一行也不用變。

具體應用場景

  1. 讀寫分離:支援讀寫分離,主從切換,此配置最簡單;
  2. 分庫分表:對於超過 1000 萬的表進行分片,最大支援 1000 億的單表分片;
  3. 多租戶應用:每個應用一個庫,但應用程式只連線 MyCat,使程式不用改造本身,實現多租戶化;
  4. 替代 Hbase:用於分析大資料;
  5. 報表系統:藉助 MyCat 的分表能力,處理大規模報表的統計;
  6. 海量資料查詢:比如 10 億條頻繁查詢的記錄需要在 3 秒內查詢出來結果,除了基於主鍵的查詢,還可能存在範圍查詢或其他屬性查詢,此時 MyCat 可能是最簡單有效的選擇。

本文主要實踐核心功能 分庫分表

分庫分表原理剖析

指通過某種特定的條件,將存放在同一個資料庫中的資料分散存放到多個資料庫上面,以達到分散單臺裝置負載的效果。

根據切分規則的型別可以分為以下兩種切分模式。

  • 垂直切分:最大特點是規則簡單,適合各業務之間的的耦合度非常低、相互影響小、業務邏輯非常清晰的系統。在這種系統中,可以很容易 將不同業務模組所使用的的表切到不同的資料庫中

  • 水平切分:相對來說複雜一些,因為要 將同一個表中的不同資料切分到不同的資料庫中,後期的資料維護也更為複雜一些。

垂直切分

一個資料庫由很多表構成,每個表對應著不同的業務,垂直切分就是按照業務將表進行分類,從而分佈到不同的資料庫上面,這樣也就將壓力分擔到不同的資料庫上,如圖。

一個架構設計好的系統其總體功能通常是由多個功能模組所組成的,而每一個功能模組的資料對應到資料庫中就是一個或多個表。而在架構設計中,各個功能模組相互之間的互動點越少和越統一,系統的耦合度就越低,系統各個模組的維護性以及擴充套件性也就越好,這樣的系統也就越容易實現垂直切分。

但是往往系統中有些表難以做到完全的獨立,存在跨庫 join 的情況,對於這類分庫,可以共用一個資料來源,業務之間通過介面來呼叫。

優點:規則明確、業務清晰、更易於整合和擴充套件、維護簡單。

缺點:部分業務表無法 join,需要通過業務介面方式解決,提高系統複雜度;各業務存在單庫效能瓶頸,不易於資料擴充套件和效能提高;事務處理複雜問題。

由於垂直切分是將表按照業務分類切分到不同的單庫中,所有導致某些業務表過於龐大,存在單庫讀寫與儲存瓶頸,則需要水平切分來解決。

水平切分

水平切分不是將表按照業務分類,而是按照某個欄位的某種規則分散到多個庫中,每個表中包含一部分資料,如圖。

拆分資料需要定義分片規則,拆分的第一原則是找到 拆分維度。比如:從會員的角度來分析,需要查詢會員某天某月某個訂單,那麼就需要按照日期來拆分,不同的資料按照會員 ID 做分組。

優點:拆分規則抽象好;不存在單庫資料瓶頸問題;提高系統穩定性和負載能力。

缺點:事務一致性難以解決;資料擴充套件和維護的難度極大;跨庫 join 效能差。

附:多資料來源管理方案

  • 第一種:客戶端模式,在每個應用程式中配置管理自己需要的一個或多個資料來源,直接訪問各個資料庫。
  • 第二種:通過中間代理層來統一管理所有的資料來源。

搭建環境

均採用 Docker Compose 搭建服務

部署 3 臺 MySQL 容器

分別建立 3 份 docker-compose.yml 檔案

  • mysql-01

    version: '3.1'
    services:
      mysql-1:
        image: mysql
        container_name: mysql-01
        environment:
          MYSQL_ROOT_PASSWORD: 123456
        command:
          --default-authentication-plugin=mysql_native_password
          --character-set-server=utf8mb4
          --collation-server=utf8mb4_general_ci
          --explicit_defaults_for_timestamp=true
          --lower_case_table_names=1
        ports:
          - 3306:3306
        volumes:
          - ./data:/var/lib/mysql
    
  • mysql-02

    version: '3.1'
    services:
      mysql-2:
        image: mysql
        container_name: mysql-02
        environment:
          MYSQL_ROOT_PASSWORD: 123456
        command:
          --default-authentication-plugin=mysql_native_password
          --character-set-server=utf8mb4
          --collation-server=utf8mb4_general_ci
          --explicit_defaults_for_timestamp=true
          --lower_case_table_names=1
        ports:
          - 3307:3306
        volumes:
          - ./data:/var/lib/mysql
    
  • mysql-03

    version: '3.1'
    services:
      mysql-3:
        image: mysql
        container_name: mysql-03
        environment:
          MYSQL_ROOT_PASSWORD: 123456
        command:
          --default-authentication-plugin=mysql_native_password
          --character-set-server=utf8mb4
          --collation-server=utf8mb4_general_ci
          --explicit_defaults_for_timestamp=true
          --lower_case_table_names=1
        ports:
          - 3308:3306
        volumes:
          - ./data:/var/lib/mysql
    

分別啟動 3 臺容器

$ docker-compose up -d

部署 MyCat 容器

  • 克隆專案

    $ git clone https://github.com/antoniopeng/docker.mycat.git
    
  • 構建映象

    $ cd docker.mycat
    $ docker-compose build
    
  • 啟動容器

    $ docker-compose up -d
    

配置資料庫分片

此處以 水平切分 為例

配置使用者名稱和密碼

$ vi config/mycat/server.xml

找到第 90 行,參考配置如下:

<mycat:server xmlns:mycat="http://io.mycat/">
    
    <user name="root">
        
        <property name="password">123456</property>

        
        <property name="schemas">hellomycat</property>

        
        <property name="usingDecrypt">0</property>
    </user>
</mycat:server>

配置資料庫節點

$ vi config/mycat/schema.xml

參考配置如下:

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

        <schema name="hellomycat" checkSQLschema="true" sqlMaxLimit="100">
                
                <table />
        </schema>

        
        <dataNode name="dataNode1" dataHost="dataHost1" database="hellomycat_1" />
        <dataNode name="dataNode2" dataHost="dataHost2" database="hellomycat_2" />
        <dataNode name="dataNode3" dataHost="dataHost3" database="hellomycat_3" />

        
        <dataHost name="dataHost1" maxCon="1000" minCon="10" balance="0"
                          writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                
                <writeHost
                                host="192.168.127.130"
                                url="jdbc:mysql://192.168.127.130:3306?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8"
                                user="root" password="123456">
                        
                </writeHost>
        </dataHost>
        <dataHost name="dataHost2" maxCon="1000" minCon="10" balance="0"
                          writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <writeHost
                                host="192.168.127.130"
                                url="jdbc:mysql://192.168.127.130:3307?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8"
                                user="root" password="123456">
                        
                </writeHost>
        </dataHost>
        <dataHost name="dataHost3" maxCon="1000" minCon="10" balance="0"
                          writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <writeHost
                                host="192.168.127.130"
                                url="jdbc:mysql://192.168.127.130:3308?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8"
                                user="root" password="123456">
                        
                </writeHost>
        </dataHost>
        
</mycat:schema>

分片規則配置如下

$ vi config/mycat/rule.xml

第 32 行配置

<tableRule name="auto-sharding-long">
    <rule>
        
        <columns>id</columns>
        
        <algorithm>rang-long</algorithm>
    </rule>
</tableRule>

第 105 行配置

<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
    
    <property name="mapFile">autopartition-long.txt</property>
</function>

自定義數字範圍分片規則

$ vi config/mycat/autopartition-long.txt

配置如下:

# range start-end ,data node index
# K=1000,M=10000.
# ID 0-5000000 儲存在 dataNode1
0-500M=0
# ID 5000000-10000000 儲存在 dataNode2
500M-1000M=1
# ID 10000000-15000000 儲存在 dataNode3
1000M-1500M=2

驗證是否成功

使用 MySQL 客戶端工具連線 MyCat,預設埠號為 8066

  • 分別在 3 個資料庫中建立表,只需要 ID 一個欄位即可:

    create table sys_user (id int not null primary key);
    
  • 新增資料:

    insert into sys_user(id) values(2000000);
    insert into sys_user(id) values(7000000);
    insert into sys_user(id) values(1200000);
    

新增後如果 3 個資料庫的表中都出現了一條資料,則說明分片成功。

相關文章