shardingsphere之sharding-jdbc分庫分表學習筆記

引言

隨着業務數據量的變大,單庫單表已經不能滿足需求了。當單表數據量超過五百萬行,查詢性能急劇下降。分庫分表迫在眉睫,尋找一個簡單實用的解決方案相信是很多小夥伴的想法。
我在看了好多的博客之後遇到了開源數據庫中間件mycat和shardingsphere(前身是sharding-jdbc),經過一番比較之後,我選了京東開源的shardingsphere作爲我的解決方案。
寫這篇文章的目的有兩個,一來是幫助剛入門學習shardingsphere的童鞋快速上手,減少時間成本,先看下怎麼用再去看官方文檔可以達到事半功倍的效果;二來是記錄自己在學習過程中遇到的問題,方便以後在項目中的使用。

小插曲

其實一開始我選的是mycat作爲解決方案,當時看到文檔齊全,又有官方羣啥的,以爲很適合我。後面二月初研究了幾天文檔然後自己學着搭建,結果沒成功。
在官方羣問問題被索要紅包,以前是問個問題要發五十塊的紅包,現在要發二十塊。前期學習成本太大,畢竟能不能用到項目還是未知數;而且羣主整天在吹自己如何牛逼,販賣中年焦慮(知乎可查),到後面一查mycat幾乎不更新了,issues基本沒人理,本身bug還是蠻多的,感覺在走向衰退和滅完,最後選擇了shardingsphere。

重要提示

用於演示的代碼和重要的參考鏈接已經放到文章的末尾,有需要的童鞋可直接下載查看。

sharding-jdbc簡介

概念

太多的理論知識我就不贅述了,麻煩自己到官網去看。
在這裏插入圖片描述

特點

一款簡單容易上手的數據庫中間件,很好的幫助我們處理分庫分表的問題,不需要對現有的業務代碼太多的修改,減少時間成本。

使用情況

目前生產環境已使用的公司
在這裏插入圖片描述

從零開始整合sharding-jdbc

整合前的思考

首先你要對業務需要用到的表有一個清晰的認識。哪些表不需要拆分,哪些表需要拆分,表跟表之間是否存在關聯。通過閱讀官網和我的理解,我覺得主要分爲這幾種表:

  • 單庫單表

這種表數據量不大,小於十萬這樣,而且跟其他表沒有關聯。這樣的表不需要拆分,放在一個默認庫中即可。比如:配置表,地區編碼表。

  • 廣播表

這種表數據量不大,沒有必要拆分;但是跟其他表有關聯關係。在每個庫都保存一個完整表,當讀取數據的時候隨機路由到任一庫,當寫入數據時每個庫下的表都寫入。

  • 邏輯表

數據量較大需要拆分的表。比如說訂單數據根據主鍵尾數拆分爲10張表,分別是t_order_0到t_order_9,他們的邏輯表名爲t_order。

  • 綁定表

按我的理解就是父子表,常見的就是訂單表和訂單詳情表,通過訂單id關聯。這種類型的表數據量大也是需要拆分的。

場景模擬

爲了加深對sharding-jdbc的理解,我在這裏模擬了一個場景,基本涵蓋了常見的情況,順便把實現步驟和使用過程的問題也提一提。
在這裏插入圖片描述

搭建項目

1. 建庫建表

按照前面表的關係圖,我們可以劃分一個默認庫(存放單庫單表和廣播表)和三個庫(存放邏輯表);再額外建一個庫存放所有的表便於代碼生成,如下所示。sql文件放在git地址的sql目錄下。

用於代碼生成的庫表

generator
	--area
	--config
	--factory
	--warehouse
	--code_relate
	--customer
	--indent_detail
	--indent
	--task_upload
	--task

業務需要的庫表

data_source
	--area
	--config
	--factory
	--warehouse
	
data_source0
	--code_relate0
	--code_relate1
	--customer0
	--customer1
	--factory
	--indent_detail0
	--indent_detail1
	--indent0
	--indent1
	--task_upload0
	--task_upload1
	--task0
	--task1
	--warehouse
	
data_source1
	--code_relate0
	--code_relate1
	--customer0
	--customer1
	--factory
	--indent_detail0
	--indent_detail1
	--indent0
	--indent1
	--task_upload0
	--task_upload1
	--task0
	--task1
	--warehouse

data_source2
	--code_relate0
	--code_relate1
	--customer0
	--customer1
	--factory
	--indent_detail0
	--indent_detail1
	--indent0
	--indent1
	--task_upload0
	--task_upload1
	--task0
	--task1
	--warehouse

2.在pom.xml加入依賴

    <dependency>
        <groupId>org.apache.shardingsphere</groupId>
        <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
        <version>${sharding-jdbc-spring-boot-starter.version}</version>
    </dependency>
    <!-- 使用XA事務時,需要引入此依賴, 4.1.x發佈 -->
	<!-- <dependency>
	    <groupId>org.apache.shardingsphere</groupId>
	    <artifactId>sharding-transaction-xa-core</artifactId>
	    <version>${sharding-transaction-xa-core.version}</version>
	</dependency> -->
	<!-- 使用編排治理 -->
	<dependency>
	    <groupId>org.apache.shardingsphere</groupId>
	    <artifactId>sharding-jdbc-orchestration-spring-boot-starter</artifactId>
	    <version>${sharding-jdbc-orchestration-spring-boot-starter.version}</version>
	</dependency>
	<!-- 引入zookeeper註冊中心依賴 -->
	<dependency>
	    <groupId>org.apache.shardingsphere</groupId>
	    <artifactId>sharding-orchestration-reg-zookeeper-curator</artifactId>
	    <version>${sharding-orchestration-reg-zookeeper-curator.version}</version>
	</dependency>

3.編寫yml配置文件

yml配置文件

spring:
  shardingsphere:
    props:     
      sql:      #sql打印
        show: true
      #executor:     #工作線程數量,默認值: CPU核數
      #  size: 4

  
    orchestration:   ###數據庫治理功能  配置了zk但是看不到節點
      name: spring_boot_ds_sharding     #治理實例名稱
      overwrite: true     #本地配置是否覆蓋註冊中心配置。如果可覆蓋,每次啓動都以本地配置爲準
      registry:
        type: zookeeper   #配置中心類型。如:zookeeper
        namespace: orchestration-spring-boot-sharding-test   #註冊中心的命名空間
        server-lists: localhost:2181    #連接註冊中心服務器的列表。包括IP地址和端口號。多個地址用逗號分隔。如: host1:2181,host2:2181
        digest: admin     #連接註冊中心的權限令牌。缺省爲不需要權限驗證
        operation-timeout-milliseconds: 500     #操作超時的毫秒數,默認500毫秒
        max-retries: 3    #連接失敗後的最大重試次數,默認3次
        retry-interval-milliseconds: 500      #重試間隔毫秒數,默認500毫秒
        time-to-live-seconds: 60    #臨時節點存活秒數,默認60秒
        props:        #配置中心其它屬性
          author: huangjg
          blog-url: https://www.flyxiaopang.top/
        
          
    datasource:            #數據源配置
      names: db,db0,db1,db2   #數據庫別名
      db:                 #數據源具體配置,這個可作爲默認庫
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/data_source?characterEncoding=utf-8
        username: root
        password: root
      db0:                 #數據源具體配置
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/data_source0?characterEncoding=utf-8
        username: root
        password: root
      db1:                 #數據源具體配置
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/data_source1?characterEncoding=utf-8
        username: root
        password: root
      db2:                 #數據源具體配置
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/data_source2?characterEncoding=utf-8
        username: root
        password: root
  
  
    sharding:       ##分庫分表規則
      default-data-source-name: db      #默認數據源,放置不需要分片的表和廣播表
      broadcast-tables: factory,warehouse    #廣播表,每個庫都有獨立的表
      binding-tables: indent,indent_detail,task_upload,code_relate       ##綁定表配置
      default-database-strategy:    #默認的分庫規則,如果邏輯表沒單獨配置則使用這個
        inline:
          sharding-column: customer_id    #默認按照customer_id分庫,避免跨庫查詢
          algorithm-expression: db$->{customer_id % 3}
      tables:       #邏輯表配置
        config:               ###單庫單表,使用UUID作爲主鍵
          actual-data-nodes: db.config
          key-generator:
            column: code
            type: UUID
        customer: 
          actual-data-nodes: db$->{0..2}.customer$->{0..1}  #具體的數據節點
          table-strategy:     ##分表策略
            inline:
              sharding-column: customer_name     #根據hash值取模確定落在哪張表
              algorithm-expression: customer$->{Math.abs(customer_name.hashCode() % 2)}
          key-generator:    #配置主鍵生成策略,默認使用SNOWFLAKE
            column: customer_id
            type: SNOWFLAKE
            props:
              worker:
                id: 20200422
        indent:
          actual-data-nodes: db$->{0..2}.indent$->{0..1}
          table-strategy:
            inline: 
              sharding-column: indent_id
              algorithm-expression: indent$->{indent_id % 2}
          key-generator:
            column: indent_id
            type: SNOWFLAKE 
        indent_detail:
          actual-data-nodes: db$->{0..2}.indent_detail$->{0..1}
          table-strategy:
            inline:
              sharding-column: indent_id
              algorithm-expression: indent_detail$->{indent_id % 2}
          key-generator:
            column: detail_id
            type: SNOWFLAKE
        task:
          actual-data-nodes: db$->{0..2}.task$->{0..1}  #具體的數據節點   
          database-strategy:   #分庫規則 
            inline:
              sharding-column: task_id
              algorithm-expression: db$->{task_id % 3}         
          table-strategy:
            inline:
              sharding-column: task_id
              algorithm-expression: task$->{task_id % 2}        
        task_upload:
          actual-data-nodes: db$->{0..2}.task_upload$->{0..1}  #具體的數據節點   
          database-strategy:   #分庫規則 
            inline:
              sharding-column: task_id
              algorithm-expression: db$->{task_id % 3}         
          table-strategy:
            inline:
              sharding-column: stack_code
              algorithm-expression: task_upload$->{Math.abs(stack_code.hashCode() % 2)}        
          key-generator:
            column: upload_id
            type: SNOWFLAKE      
        code_relate:
          actual-data-nodes: db$->{0..2}.code_relate$->{0..1}  #具體的數據節點   
          database-strategy:   #分庫規則 
            inline:
              sharding-column: task_id
              algorithm-expression: db$->{task_id % 3}         
          table-strategy:
            inline:
              sharding-column: stack_code
              algorithm-expression: code_relate$->{Math.abs(stack_code.hashCode() % 2)}        
          key-generator:
            column: relate_id
            type: SNOWFLAKE     

注意事項

(1)分片鍵分爲分庫鍵和分表鍵。

(2)主鍵生成默認使用SNOWFLAKE算法,使用UUID主鍵的話需要配置。

(3)如果分片鍵的值爲long型,分片規則爲分片字段取模即可;如果是String型,分片規則爲分片字段的哈希值取模再求絕對值,因爲哈希值取模之後也許會出現負數。

(4)邏輯表和綁定表配置建議,儘可能的讓同一類型的數據落在同一個庫中。比如用戶的信息和他產生的訂單以及訂單詳情,可以通過consumer_id作爲分庫鍵,indent_id作爲分表鍵存放,這樣如果查詢命中分片鍵的話可以提高查詢效率(少查了不必要的表)。

(5)綁定表建表的時候,子表最好增加分庫鍵字段便於新增數據時確定落到哪個庫中。比如用戶表、訂單表和訂單詳情表,consumer_id作爲分庫鍵,訂單表需要有這個字段,訂單詳情表也需要這個字段,否則訂單詳情新增數據的時候會在每個庫都新增數據,很明顯是不合理的情況。

4.基礎CRUD代碼生成

工具代碼

通過配置下方的文件連接generator庫可以快速生成基礎的CRUD代碼

src/test/java/com/project/generator/MybatisGenerator.java

注意事項

(1)框架版本的選擇,目前下方這個組合是正常的,其他的版本組合啓動時可能會爆異常

mybatis-plus-boot-starter 3.1.0
sharding-jdbc-spring-boot-starter
3.1.0 spring-boot 2.0.6.RELEASE

其他版本組合時異常信息

The bean ‘dataSource’, defined in class path resource
[io/shardingsphere/shardingjdbc/spring/boot/SpringBootConfiguration.class],
could not be registered. A bean with that name has already been
defined in class path resource
[com/alibaba/druid/spring/boot/autoconfigure/DruidDataSourceAutoConfigure.class]
and overriding is disabled.

(2)實體主鍵類型的選擇

  • 如果主鍵是long型的話,可以這麼配置,個人建議選擇type = IdType.ID_WORKER這樣更直白明瞭。否則會報錯。
/**
     * id
     */
    @TableId(value = "id", type = IdType.ID_WORKER)
    private Long id;

或者

  /**
     * id
     */
    @TableId(value = "id", type = IdType.NONE)
    private Long id;

使用type= ID.AUTO的異常信息

Caused by: java.sql.SQLException: Field ‘id’ doesn’t have a default
value at
com.mysql.jdbc.SQLError.createSQLException(SQLError.java:965) at
com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3978) at
com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3914) at
com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2530) at
com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2683) at
com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2495) at
com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1903)
at
com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2124)
at
com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.java:1801)
… 89 common frames omitted

  • 如果主鍵是String型的話,可以這麼配置。因爲默認的是SNOWFLAKE生成,否則會插入一個long型的主鍵值導致報錯。
   /**
     * 編號
     */
    @TableId(value = "code", type = IdType.AUTO)
    private String code;

或者

  /**
     * 編號
     */
    @TableId(value = "code", type = IdType.UUID)
    private String code;

5.項目運行

測試代碼已經寫到裏面了,通過發起請求和觀察控制檯的sql你會發現邏輯SQL和真實SQL,從而發現他的查詢規則:

1、如果表沒配置規則,那麼直接到默認庫去訪問
2、如果訪問的是廣播表,那麼讀的時候是隨機路由到一個庫,寫的時候是全部庫都寫數據。
3、邏輯表查詢,查詢字段命中了分庫鍵,那麼路由到指定庫下的所有表查詢;命中了分表鍵,到所有庫下指定表查詢。如果都沒命中,那麼將發生笛卡爾積,進行全路由所有的庫和表都查詢一遍,效率不高。所以合理的配置分片規則是很重要的。

分佈式事務

sharding-jdbc的XA分佈式事務要到4.1.x版本才發佈,不過可以在主版本測試,詳情請看
ShardingTransactionType cannot be resolved to a type

彈性伸縮

這個也是在4.1.x發佈,詳情請看
彈性伸縮(Alpha)

配置zookeeper

目前我將zookeeper跑起來的時候不懂如何跟項目對接起來,如果有成功的同學麻煩將方法告知下。

結語

官網的文檔比較詳細和社區都是很活躍的,這些可以減少我們的學習成本,快速用於項目。如果在學習的過程中遇到問題可以多看看官方文檔或者直接到github上面提issues,官方人員會很快給予答覆的。

相關鏈接

演示代碼地址

shardingsphere官網地址

shardingsphere github地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章