Sharding-JDBC從入門到精通 -1


《SpringCloud Nginx 高併發核心編程》 環境搭建 - 系列

組件 連接地址
windows centos 虛擬機 安裝&排坑 vagrant+java+springcloud+redis+zookeeper鏡像下載(&製做詳解))
centos mysql 安裝&排坑 centos mysql 筆記(內含vagrant mysql 鏡像)
linux kafka安裝&排坑 kafka springboot (或 springcloud ) 整合
Linux openresty 安裝 Linux openresty 安裝
【必須】Linux Redis 安裝(帶視頻) Linux Redis 安裝(帶視頻)
【必須】Linux Zookeeper 安裝(帶視頻) Linux Zookeeper 安裝, 帶視頻
Windows Redis 安裝(帶視頻) Windows Redis 安裝(帶視頻)
RabbitMQ 離線安裝(帶視頻) RabbitMQ 離線安裝(帶視頻)
ElasticSearch 安裝, 帶視頻 ElasticSearch 安裝, 帶視頻
Nacos 安裝(帶視頻) Nacos 安裝(帶視頻)
【必須】Eureka Eureka 入門,帶視頻
【必須】springcloud Config 入門,帶視頻 springcloud Config 入門,帶視頻
【必須】SpringCloud 腳手架打包與啓動 SpringCloud腳手架打包與啓動
Linux 自啓動 假死自啓動 定時自啓 Linux 自啓動 假死啓動

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

組件 連接地址
準備一: 在window安裝虛擬機集羣 vagrant+java+springcloud+redis+zookeeper鏡像下載(&製做詳解))
準備二:在虛擬機上安裝 mysql ,至少須要兩個mysql節點 centos mysql 筆記(內含vagrant mysql 鏡像)
Sharding-JDBC 從入門到精通之一 入門實戰
Sharding-JDBC 從入門到精通之二 基本原理
Sharding-JDBC 從入門到精通之源碼 git

1.有關Sharding-JDBC

有關Sharding-JDBC介紹這裏就不在多說,以前Sharding-JDBC是噹噹網自研的關係型數據庫的水平擴展框架,如今已經捐獻給Apache,其原理請參見後面的博客。linux

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

2 Sharding-JDBC 實戰的場景

在深刻了解以前,先實戰一把,增長印象, 激發興趣。git

通常狀況下,你們都會使用水平切分庫和表:將一張表水平切分紅多張表,還能夠放到多個庫中。這就涉及到數據分片的規則,比較常見的有:Hash取模分表、數值Range分表、一致性Hash算法分表。面試

一、Hash取模分表

概念 通常採用Hash取模的切分方式,例如:假設按goods_id分4張表。(goods_id%4 取整肯定表)redis

img

優勢

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

缺點

  • 後期分片集羣擴容時,須要遷移舊的數據很難。算法

  • 容易面臨跨分片查詢的複雜問題。好比上例中,若是頻繁用到的查詢條件中不帶goods_id時,將會致使沒法定位數據庫,從而須要同時向4個庫發起查詢,
    再在內存中合併數據,取最小集返回給應用,分庫反而成爲拖累。spring

二、數值Range分表

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

img

優勢

  • 單表大小可控
  • 自然便於水平擴展,後期若是想對整個分片集羣擴容時,只須要添加節點便可,無需對其餘分片的數據進行遷移
  • 使用分片字段進行範圍查找時,連續分片可快速定位分片進行快速查詢,有效避免跨分片查詢的問題。

缺點

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

三、一致性Hash算法

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

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

四、實戰:簡單的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高併發研習社羣,爲你們開啓大廠之門

相關文章
相關標籤/搜索