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高并发研习社群,为你们开启大厂之门

相关文章
相关标签/搜索