Sharding-JDBC從入門到精通 -1



目錄:分庫分表 Sharding-JDBC從入門到精通

主題 鏈接地址
准備1: 在window安裝虛擬機集群 分布式 虛擬機 linux 環境制作 GO
准備2:在虛擬機的各個節點有 mysql centos mysql 筆記(內含vagrant mysql 鏡像)GO
分庫分表 -Sharding-JDBC- 從入門到精通 1 Sharding-JDBC 分庫、分表(入門實戰) GO
分庫分表 -Sharding-JDBC- 從入門到精通 2 Sharding-JDBC 基礎知識 GO
分庫分表 Sharding-JDBC 從入門到精通之 3 自定義主鍵、分布式雪花主鍵,原理與實戰 GO
分庫分表 -Sharding-JDBC- 從入門到精通 4 MYSQL集群主從復制,原理與實戰 GO
分庫分表 Sharding-JDBC 從入門到精通之 5 讀寫分離 實戰 GO
分庫分表 Sharding-JDBC 從入門到精通之 6 Sharding-JDBC執行原理 GO
分庫分表 Sharding-JDBC 從入門到精通之源碼 git倉庫地址GO

1.有關Sharding-JDBC

有關Sharding-JDBC介紹這里就不在多說,之前Sharding-JDBC是當當網自研的關系型數據庫的水平擴展框架,現在已經捐獻給Apache,其原理請參見后面的博客。

shardingsphere文檔地址是:https://shardingsphere.apache.org/document/current/cn/overview/。

2 Sharding-JDBC 實戰的場景

在深入了解之前,先實戰一把,增加印象, 激發興趣。

一般情況下,大家都會使用水平切分庫和表:將一張表水平切分成多張表,還可以放到多個庫中。這就涉及到數據分片的規則,比較常見的有:Hash取模分表、數值Range分表、一致性Hash算法分表。

1、Hash取模分表

概念 一般采用Hash取模的切分方式,例如:假設按goods_id分4張表。(goods_id%4 取整確定表)

img

優點

  • 數據分片相對比較均勻,不容易出現熱點和並發訪問的瓶頸。

缺點

  • 后期分片集群擴容時,需要遷移舊的數據很難。

  • 容易面臨跨分片查詢的復雜問題。比如上例中,如果頻繁用到的查詢條件中不帶goods_id時,將會導致無法定位數據庫,從而需要同時向4個庫發起查詢,
    再在內存中合並數據,取最小集返回給應用,分庫反而成為拖累。

2、數值Range分表

概念 按照時間區間或ID區間來切分。例如:將goods_id為11000的記錄分到第一個表,10012000的分到第二個表,以此類推。

img

優點

  • 單表大小可控
  • 天然便於水平擴展,后期如果想對整個分片集群擴容時,只需要添加節點即可,無需對其他分片的數據進行遷移
  • 使用分片字段進行范圍查找時,連續分片可快速定位分片進行快速查詢,有效避免跨分片查詢的問題。

缺點

  • 熱點數據成為性能瓶頸。
    例如按時間字段分片,有些分片存儲最近時間段內的數據,可能會被頻繁的讀寫,而有些分片存儲的歷史數據,則很少被查詢

3、一致性Hash算法

一致性Hash算法能很好的解決因為Hash取模而產生的分片集群擴容時,需要遷移舊的數據的難題。至於具體原理這里就不詳細說,

可以參考一篇博客:一致性哈希算法(分庫分表,負載均衡等)

4、實戰:簡單的Hash取模分表

假設一個訂單表的user_id和order_id 分布較為均勻,按照1000W的數據規模,可以使用如下的分庫、分表結構來保存:

db0
├── t_order0
└── t_order1
db1
├── t_order0
└── t_order1

簡單的進行分庫分表: 按照user_id %2 的規則進行分庫,按照 order_id %2 的規則進行分表

3 庫表的結構設計:

3.1 邏輯訂單表

邏輯訂單表的結構如下:

3.2 節點1 (cdh1)上的訂單庫

DROP TABLE IF EXISTS `t_order_0`;
DROP TABLE IF EXISTS `t_order_1`;


DROP TABLE IF EXISTS `t_config`;
CREATE TABLE `t_order_0` (`order_id` bigInt NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`));
CREATE TABLE `t_order_1` (`order_id` bigInt NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`));

3.3 節點2 (cdh2)上的訂單庫

DROP TABLE IF EXISTS `t_order_0`;
DROP TABLE IF EXISTS `t_order_1`;


DROP TABLE IF EXISTS `t_config`;
CREATE TABLE `t_order_0` (`order_id` bigInt NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`));
CREATE TABLE `t_order_1` (`order_id` bigInt NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`));

兩個db上,都有t_order_0,和t_order_1兩個表

4 Sharding-JDBC 分庫分表配置

  • 分庫

本文分庫樣例比較簡單,根據數據庫表中字段user_id%2進行判斷,如果user_id%2==0則使用ds0,否則使用ds1。

  • 分表

分樣例比較簡單,根據數據庫表中字段order_id%2進行判斷,如果order_id%2==0則使用t_order_0,否則使用t_order_1。

對 t_order 表進行的如下圖所示的數據表水平 分庫和分表,具體如下圖所示:

(對 t_order_item 表也要進行類似的水平分片,但是這部分配置省略了):

在 yml 配置文件中,可以使用 Groovy 表達式,進行分庫分表的規則配置,具體的 Groovy 表達式如下:

表達式一: 例如 ds0.t_order_0
ds$->{0..1}.t_order_$->{0..1}

表達式一:db 維度的拆分, 例如 ds_0、ds_1
ds_${user_id % 2}

表達式一:table 維度的拆分, 例如 t_order_1
t_order_${order_id % 2}

這些表達式被稱為 Groovy 表達式,它們的含義很容易識別:

1)對 t_order 進行兩種維度的拆分:db 維度和 table 維度;

2)在db 維度,user_id % 2 == 0 的記錄全部落到 ds0,user_id % 2 == 1 的記錄全部落到 ds1;(有人稱這一過程為水平分庫,其實它的本質還是在水平地分表,只不過依據表中 user_id 的不同把拆分的后的表放入兩個數據庫實例。)

3)在表維度,order_id% 2 == 0 的記錄全部落到 t_order0,order_id% 2 == 1 的記錄全部落到 t_order1。

4)對記錄的讀和寫都按照這種方向進行,“方向”,就是分片方式,就是路由。

使用這種簡潔的 Groovy 表達式, 可以設置的分片策略和分片算法。但是這種方式所能表達的含義是有限的。因此,官方提供了分片策略接口和分片算法接口,讓你們利用 Java 代碼盡情表達更為復雜的分片策略和分片算法。

實際上,分片算法是分片策略的組成部分,分片策略設置=分片鍵設置+分片算法設置。上述配置里使用的策略是 Inline 類型的分片策略,使用的算法是 Inline 類型的行表達式算法。

具體的配置如下:

spring:
  application:
    name: sharding-jdbc-provider
  jpa:  #配置自動建表:updata:沒有表新建,有表更新操作,控制台顯示建表語句
    hibernate:
      ddl-auto: none
      dialect: org.hibernate.dialect.MySQL5InnoDBDialect
      show-sql: true
  freemarker:
    allow-request-override: false
    allow-session-override: false
    cache: false
    charset: UTF-8
    check-template-location: true
    content-type: text/html
    enabled: true
    expose-request-attributes: false
    expose-session-attributes: false
    expose-spring-macro-helpers: true
    prefer-file-system-access: true
    settings:
      classic_compatible: true
      default_encoding: UTF-8
      template_update_delay: 0
    suffix: .ftl
    template-loader-path: classpath:/templates/
  shardingsphere:
    props:
      sql:
        show: true
    # 配置真實數據源
    datasource:
      common:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        validationQuery: SELECT 1 FROM DUAL
      names: ds0,ds1
      ds0:
          url: jdbc:mysql://cdh1:3306/store?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=true&serverTimezone=UTC
          username: root
          password: 123456
      # 配置第 2 個數據源  org.apache.commons.dbcp2
      ds1:
          url: jdbc:mysql://cdh2:3306/store?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=true&serverTimezone=UTC
          username: root
          password: 123456
    # 配置分片規則和分片算法
    rules:
      # 配置分片規則
      sharding:
        tables:
          # 配置 t_order 表規則
          t_order:
            actualDataNodes: ds$->{0..1}.t_order_$->{0..1}
            # 配置分庫策略
            databaseStrategy:
              standard:
                shardingColumn: user_id
                shardingAlgorithmName: database-inline
            # 配置分表策略
            tableStrategy:
              standard:
                shardingColumn: order_id
                shardingAlgorithmName: table-inline
            keyGenerateStrategy:
              column: order_id
              keyGeneratorName: snowflake
        # 配置分片算法
        bindingTables: t_order
        sharding-algorithms:
          database-inline:
            type: INLINE
            props:
              algorithm-expression: ds$->{user_id % 2}
          table-inline:
            type: INLINE
            props:
              algorithm-expression: t_order_$->{order_id % 2}
        keyGenerators:
          snowflake:
            type: SNOWFLAKE
            props:
              workerId: 123

5.代碼實現

本文使用SpringBoot2,SpringData-JPA,Druid連接池,和當當的sharding-jdbc 5。

5.1 依賴文件

新建項目,加入當當的sharding-jdbc-core依賴和druid連接池。請參見源碼工程。

5.2 啟動類

使用@EnableTransactionManagement開啟事務,

使用@EnableConfigurationProperties注解加入配置實體,啟動類完整代碼請入所示。

package com.crazymaker.springcloud.sharding.jdbc.demo.start;
@EnableConfigurationProperties

@SpringBootApplication(scanBasePackages =
        {"com.crazymaker.springcloud.sharding.jdbc.demo",
//                 "com.crazymaker.springcloud.base",
//                 "com.crazymaker.springcloud.standard"
        }, exclude = {
        DataSourceAutoConfiguration.class,
        SecurityAutoConfiguration.class,
        DruidDataSourceAutoConfigure.class})
@EnableScheduling
@EnableSwagger2
@EnableJpaRepositories(basePackages = {
        "com.crazymaker.springcloud.sharding.jdbc.demo.dao.impl",
//        "com.crazymaker.springcloud.base.dao"
})
@EnableTransactionManagement(proxyTargetClass = true)

@EntityScan(basePackages = {
//        "com.crazymaker.springcloud.user.*.dao.po",
        "com.crazymaker.springcloud.sharding.jdbc.demo.entity.jpa",
//        "com.crazymaker.springcloud.standard.*.dao.po"
})
/**
 * 啟用 Hystrix
 */
@EnableHystrix
@EnableFeignClients(
        basePackages = "com.crazymaker.springcloud.user.info.remote.client",
        defaultConfiguration = FeignConfiguration.class)
@Slf4j
@EnableEurekaClient
public class ShardingJdbcDemoCloudApplication
{
    public static void main(String[] args)
    {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(ShardingJdbcDemoCloudApplication.class, args);


        Environment env = applicationContext.getEnvironment();
        String port = env.getProperty("server.port");
        String path = env.getProperty("server.servlet.context-path");
        System.out.println("\n----------------------------------------------------------\n\t" +
                "Application is running! Access URLs:\n\t" +
                "Local: \t\thttp://localhost:" + port + path + "/index.html\n\t" +
                "swagger-ui: \thttp://localhost:" + port + path + "/swagger-ui.html\n\t" +
                "----------------------------------------------------------");

    }

}

5.3實體類和數據庫操作層

就是簡單的實體和Repository,更多詳細內容請參見源碼工程。

/*
 * Copyright 2016-2018 shardingsphere.io.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * </p>
 */

package com.crazymaker.springcloud.sharding.jdbc.demo.entity.jpa;

import com.crazymaker.springcloud.sharding.jdbc.demo.entity.Order;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "t_order")
public final class OrderEntity extends Order
{
    
    @Id
    @Column(name = "order_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Override
    public long getOrderId() {
        return super.getOrderId();
    }
    
    @Column(name = "user_id")
    @Override
    public int getUserId() {
        return super.getUserId();
    }
    
    @Column(name = "status")
    public String getStatus() {
        return super.getStatus();
    }
}

在這里插入圖片描述

5.4 服務層

在這里插入圖片描述

更多詳細內容請參見源碼工程。

5.4 Controller

接下來創建一個Controller進行測試,保存方法使用了插入數據和查看數據,根據我們的規則,會每個庫插入數據,同時我這里還創建了一個查詢方法,查詢全部訂單。

package com.crazymaker.springcloud.sharding.jdbc.demo.controller;

@RestController
@RequestMapping("/api/sharding/")
@Api(tags = "sharding jdbc 演示")
public class ShardingJdbcController
{

    @Resource
    JpaEntityService jpaEntityService;



    @PostMapping("/order/add/v1")
    @ApiOperation(value = "插入訂單")
    public RestOut<Order> orderAdd(@RequestBody Order dto)
    {
        jpaEntityService.addOrder(dto);

        return RestOut.success(dto);
    }



    @PostMapping("/order/list/v1")
    @ApiOperation(value = "查詢訂單")
    public RestOut<List<Order>> listAll()
    {
        List<Order> list = jpaEntityService.selectAll();

        return RestOut.success(list);
    }


}

6 執行測試

6.1 打開swagger

啟動應用。

然后,在瀏覽器或HTTP請求工具訪問http://localhost:7700/sharding-jdbc-provider/swagger-ui.html,如圖所示

在這里插入圖片描述

6.2 加入兩條數據

在這里插入圖片描述

使用插入訂單的接口,可以插入訂單, 注意 userid %2 ==0 進入 db1, 注意 userid %2 ==1進入 db2, 具體在哪個表呢?

因為 orderid是通過雪花算法生成的,如果orderid%2==0 ,則進入t_order_0,否則使用t_order_1。

插入之后,可以通過數據庫,看結果。具體如下圖:

在這里插入圖片描述

6.3 查看數據

在這里插入圖片描述

使用程序的查詢全部的方法,shardingjdbc ,會查出所有的訂單。

7 總結

使用shardingjdbc ,除了數據源的配置有些特殊的規則外, 持久層程序和普通的 JPA代碼,區別並不大。

當然,如果要實現特殊的分庫分表邏輯,還是需要動代碼的,請看后續分解。

回到◀瘋狂創客圈

瘋狂創客圈 - Java高並發研習社群,為大家開啟大廠之門


免責聲明!

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



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