Sharding-JDBC从入门到放弃入门之先入门^_^

1,986 阅读8分钟

Sharding-Jdbc概念

简介

Sharding-JDBC是ShardingSphere的一个子项目。

ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、云原生等各种多样化的应用场景。(官网)

Sharding-JDBC是ShardingSphere的一个子项目。定位是一个轻量级的Java框架,是在Jdbc层的基础上提供额外的服务,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。

github地址: github.com/apache/shar…
码云: gitee.com/Sharding-Sp…
Sharding-jdbc文档:shardingsphere.apache.org/

文档需要注意的是选择相应的版本文档阅读

image.png

功能列表

数据分片
  • 分库 & 分表
  • 读写分离
  • 分片策略定制化
  • 无中心化分布式主键
分布式事务
  • 标准化事务接口
  • XA强一致事务
  • 柔性事务
数据库治理
  • 配置动态化
  • 编排 & 治理
  • 数据脱敏
  • 可视化链路追踪
  • 弹性伸缩(规划中)

数据库中间件横向比较

从开源社区来看,几乎所有的开源数据库中间件都是国内公司开源的。

知乎上有个问题为什么几乎所有的开源数据库中间件都是国内公司开源的?

感兴趣的可以看看,其中ShardingSphere负责人张亮也有回答。

目前国内比较成熟的开源数据库中间件有Sharding-Jdbc、Mycat,还有DRDS是阿里云最近推出的商业产品,考虑到大部分公司都在使用阿里云,做一个全家桶,也是一个不错的选择。接下来将对这三款产品的优缺点及适用场景做以介绍。

sharding-JDBC mycat drds
性能
应用场景限制 java应用
是否支持自定义sharding路由
最大支持sharding路由维度 2 1 2
分布式事务 标准化事务接口、XA强一致事务、柔性事务 支持弱xa、支持XA分布式事务(1.6.5) 支持以下分布式事务策略:FREE、2PC、XA、FLEXIBLE
限制 详见:shardingsphere.apache.org/document/le… 详见《MYCAT权威指南》——5.6 Mycat 目前存在的限制 未明确说明
是否开源

image.png

Sharding-jdbc是打包成一个jar包,作为一个组件集成在我们的应用中,直连数据库,没有中间应用,理论上性能是最高的。

Mycat 是单独作为一个独立的应用程序,需要我们在服务器中安装才能够使用。

image.png

DRDS 是阿里云一个商业化产品,需要配合阿里云RDS一起使用。

分库分表概念

为什么要分库分表

在用到MySQL的地方,只有涉及到可能会出现很大数据量的表时,马上就会想到要通过分库分表来解决,是Mysql处理不了大表吗?实际上并不是这样。因此首先讨论下什么样的情况下适合分表?经过测试,在MySQL数据量到达千万级别时,写入性能才会大幅度的下降,因此1000万数据量就是一个门槛。在可以预想的一段时间内,如果表行数不太可能超过,并且对响应时间要求不高的时候,是不需要考虑分库分表的。阿里Java开发手册推荐是当单表行数超过500万行,或者单表容量超过2GB,才建议开始进行分库分表规划。

但是实际情况也应该根据具体的应用场景要求的请求响应时间来做出合理的分库分表选择的判断。

垂直拆分与水平拆分

垂直拆分

在传统的单机应用中,一个应用数据库往往包含这个项目中所有用到的表,垂直拆分就是把一个大数据库按照各个独立的小业务拆分成不同的小数据库,这样就分担了一个大数据库的压力到多个小数据库中。

但是当我们业务发展的足够快时,依然发现,在一个拆分之后的小业务依旧会数据量爆涨,到达数据性能的瓶颈,从而影响业务。并且拆分中单独的小数据是,有数据需要做关联查询,如订单和商品,垂直拆分之后就增加了额外的代码,进而需求对系统进行修改,从而影响了线上环境的稳定。

水平拆分

水平拆分就是把一个大数据量的表按照一定的维度拆分成n张子表。如订单表t_order可以对订单号order_id进行取模,按照取模的结果存储到t_order_0,t_order_1...中。这样的话随着订单数据量的增大,把数据量均匀的分配到不同表当中,单表的压力始终在一个可以接受的范围内。

水平拆分表的方式有很多,除了取模算法之外常见的还有按时间拆分,根据不同的业务场景选择适合的分片算法

单库多表与多库多表

单库多表是指水平拆分时可以把拆分之后的表放在单个db中,这样的话性能瓶颈就是单个数据库的瓶颈,网络io,磁盘io都是共享的。多库多表就是拆分之后不同的子表按照某些规则分部在多个数据当中,性能上就是多个数据库的集合,理论上性能是可以无线扩展的。

Sharding-jdbc QuickStart

Sharding-Jdbc目前正在开发的5.x版本,但是最新的release版本是4.1.1。我们开发采用最新release版本

demo准备是分两个库(trade),每个库四个表(order)。

项目地址:sharding-jdbc-demo

idea新建项目

image.png

sql初始化
-------------trade_0,第一个库执行
create database trade_0;
use trade_0;
create table order_1
(
    order_id bigint(20) not null primary key,
    user_id  bigint(20),
    status   int
) character set = utf8mb4;

create table order_2
(
    order_id bigint(20) not null primary key,
    user_id  bigint(20),
    status   int
) character set = utf8mb4;

create table order_3
(
    order_id bigint(20) not null primary key,
    user_id  bigint(20),
    status   int
) character set = utf8mb4;

create table order_4
(
    order_id bigint(20) not null primary key,
    user_id  bigint(20),
    status   int
) character set = utf8mb4;

-------------trade_1,第二个库执行
create database trade_1;
use trade_1;
create table order_1
(
    order_id bigint(20) not null primary key,
    user_id  bigint(20),
    status   int
) character set = utf8mb4;

create table order_2
(
    order_id bigint(20) not null primary key,
    user_id  bigint(20),
    status   int
) character set = utf8mb4;

create table order_3
(
    order_id bigint(20) not null primary key,
    user_id  bigint(20),
    status   int
) character set = utf8mb4;

create table order_4
(
    order_id bigint(20) not null primary key,
    user_id  bigint(20),
    status   int
) character set = utf8mb4;
pom

用到了spring boot、mybatis-plus、sharding-jdbc、lombok等jar包

用mybatis-plus简化dao层操作,详细用法见mybatis-plus官网

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.wzc</groupId>
    <artifactId>sharding-jdbc-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sharding-jdbc-demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- mysql-connector-java -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.14</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>


        <!-- sharding-jdbc -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-namespace</artifactId>
            <version>4.1.1</version>
        </dependency>

        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
新建OrderDO
//省略import
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_order")
public class OrderModel {

    @TableId
    private Long orderId;
    private Long userId;
    private String status;
}
Mapper
@Mapper
public interface OrderMapper extends BaseMapper<OrderModel> {
}
Service interface
public interface IOrderService extends IService<OrderModel> {
}
Service impl
@Service
public class OrderService extends ServiceImpl<OrderMapper, OrderModel> implements IOrderService {
}
配置文件yml

分片策略:

分库:以user_id为分片键,分片策略为user_id % 2

分表:以order_id为分片键,四个表取模因子是4,算法为order_id % 4 + 1(从1开始)

spring:
  main:
    allow-bean-definition-overriding: true
  shardingsphere:
    datasource:
      names: ds0,ds1
      ds0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.111.1:3306/trade_0?serverTimezone=Hongkong&autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
        username: root
        password: 123456
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.111.1:3307/trade_1?serverTimezone=Hongkong&autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
        username: root
        password: 123456
    sharding:
      tables:
        t_order:
          key-generator: # 主键生产策略
            column: order_id
            type: SNOWFLAKE
          actual-data-nodes: ds$->{0..1}.order_$->{1..4}   
          database‐strategy:  #分库策略
            inline:
              sharding‐column: user_id
              algorithm‐expression: ds$->{user_id % 2}
          table‐strategy: #分表策略
            inline:
              sharding‐column: order_id
              algorithm‐expression: order_$->{order_id % 4 + 1}
单元测试
@SpringBootTest
public class OrderServiceTest {

    @Resource
    private IOrderService orderService;

    ObjectMapper json = new ObjectMapper();

    //查询所有
    @Test
    public void test(){
        orderService.list().forEach(a -> {
            try {
                System.out.println(json.writeValueAsString(a));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        });
    }

    @Test
    public void testQueryById() throws JsonProcessingException {
        OrderModel orderModel = orderService.getById(1);
        System.out.println(json.writeValueAsString(orderModel));
    }

    @Test
    public void insert() throws Exception {
        OrderModel entity = new OrderModel();
        entity.setStatus("1");
        entity.setUserId(10L);
        orderService.save(entity);
        OrderModel orderModel = orderService.getById(entity.getOrderId());
        System.out.println(json.writeValueAsString(orderModel));
    }
}
执行单侧insert()方法

image.png

查看数据库

发现根据我们配置的分片算法,这条数据进入了trade_1.order_2表中

image.png

sharding-jdbc快速入门结束