分库分表—ShardingJDBC

简介

1、ShardingJDBC简介

官方中文文档:概览 :: ShardingSphere

定位为轻量级 Java框架,在 Java 的 JDBC层提供的额外服务,它使用客户端直连数据库,以 jar包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC驱动,完全兼容 JDBC 和各种 ORM框架。

  • 适用于任何基于 JDBC 的 ORM框架,如:JPA、Hibernate、Mybatis、Spring JDBC Template 或是直接使用 JDBC;

  • 支持任何第三方的数据库连接池,如:DBCP、C3P0、BoneCP、HikariCP 等;

  • 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,PostgreSQL,Oracle,SQLServer 以及任何可使用 JDBC 访问的数据库。

1.1、逻辑表、真实表

  • 逻辑表:相同结构的水平拆分数据库(表)的逻辑名称,是 SQL 中表的逻辑标识。 例如:订单数据根据主键尾数拆分为 10张表,分别是 t_order_0 到 t_order_9,他们的逻辑表名都为 t_order

  • 真实表:在水平拆分的数据库中真实存在的物理表。就是 t_order_0 到 t_order_9。

2、简单使用

2.1、实践1 - 分库写入

将数据按照规则分库写入进去。

2.1.1、引入依赖

<dependencies>
    <dependency>
        <groupId>org.apache.shardingsphere</groupId>
        <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
</dependencies>

2.1.2、准备数据库环境

需要两个相同库表结构的数据库,最好是两个主机的 MySQL数据库。我这里是同一个主机的两个数据库。

如果是云服务的话,需要开放外网连接:

update user set host = '%' where user = 'root';
FLUSH PRIVILEGES;

2.1.3、pojo、mapper、service、serviceImpl

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private int id;
    private String name;
    private String password;
}
public interface UserMapper extends BaseMapper<User> {}
public interface UserService extends IService<User> {}
​
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

2.1.4、修改配置文件

推荐查看官方文档。

server:
  port: 8888
spring:
  application:
    name: sharding-jdbc
  shardingsphere:
    datasource:
      # 有几个数据就配几个,这里是名称,按照下面的格式,名称+数字的形式
      names: db0,db1
      # 为每个数据源单独进行配置
      db0:
        # 数据源实现类,这里使用默认的 HikariDataSource
        type: com.zaxxer.hikari.HikariDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        jdbcUrl: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test1
        username: root
        password: 123456
      db1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test2
        username: root
        password: 123456
    rules:
      sharding:
        tables:
          #这里填写表名称,程序中对这张表的所有操作,都会先采用下面的路由方案
          user:
            #这里填写实际的路由节点,比如现在我们要分两个库,那么就可以把两个库都写上,以及对应的表
            #也可以使用表达式,比如下面的可以简写为 db$->{0..1}.user
            actual-data-nodes: db0.user,db1.user
            #这里是分库策略配置
            database-strategy:
              #这里选择标准策略,也可以配置复杂策略 complex,基于多个键进行分片
              standard:
                #参与分片运算的字段,下面的算法会根据这里提供的字段进行运算
                # 就是取模算法,为 0 就放 db0,为 1 就放 db1
                sharding-column: id
                #这里填写我们下面自定义的算法名称
                sharding-algorithm-name: my-alg
        sharding-algorithms:
          #自定义一个新的算法,名称随意
          my-alg:
            #算法类型,官方内置了很多种,这里演示最简单的一种
            type: MOD
            props:
              sharding-count: 2
    props:
      #开启日志,一会方便我们观察
      sql-show: true

2.1.5、分库插入测试

@SpringBootTest
class ShardingJdbcTestApplicationTest {
    @Resource
    UserService userService;
    @Test
    void batchInsertTest() {
        for (int i = 0; i < 9; i++) {
            userService.save(new User(i, "user-" + i, "password-" + i));
        }
    }
}
2.1.5.1、执行语句日志

ShardingJDBC 会把 MyBatis-Plus 要去执行的 SQL语句当作逻辑SQL 进行分析,然后修改后再执行。依次分为 Logic SQL、SQLStatement、Actual SQL。

2022-07-21 17:50:02.873  INFO 8552 --- [           main] com.chw.ShardingJdbcTestApplicationTest  : Started ShardingJdbcTestApplicationTest in 4.937 seconds (JVM running for 6.572)
ShardingSphere-SQL:Logic SQL:INSERT INTO user (id,name,password) VALUES (?,?,?)
ShardingSphere-SQL:SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
ShardingSphere-SQL:Actual SQL: db0 ::: INSERT INTO user (id,name,password )  VALUES  (?, ?, ?) ::: [0, user-0, password-0]
​
​
​
ShardingSphere-SQL                       : Logic SQL: INSERT INTO user  ( id,
name,
password )  VALUES  ( ?,
?,
? )
2022-07-21 17:50:04.133  INFO 8552 --- [           main] ShardingSphere-SQL                       : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
2022-07-21 17:50:04.133  INFO 8552 --- [           main] 
    ShardingSphere-SQL                       : Actual SQL: db1 ::: INSERT INTO user  ( id,
name,
password )  VALUES  (?, ?, ?) ::: [1, user-1, password-1]
2.1.5.2、数据库结果

2.2、实践2 - 分表写入

将数据按照规则写入同一个数据库的不同表中。

2.2.1、准备数据库环境

创建 user_0、user_1。

2.2.2、修改配置文件

虽然我们想把数据分表写入 user_0、user_1,但是我们不需要修改业务代码,因为这两个表的逻辑表名称都是 user,所以只需要在逻辑上给 user表插入数据即可,ShardingJDBC 会在物理上根据规则进行分表。

server:
  port: 8888
spring:
  application:
    name: sharding-jdbc
  shardingsphere:
    datasource:
      # 有几个数据就配几个,这里是名称,按照下面的格式,名称+数字的形式
      names: db0,db1
      # 为每个数据源单独进行配置
      db0:
        # 数据源实现类,这里使用默认的 HikariDataSource
        type: com.zaxxer.hikari.HikariDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        jdbcUrl: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test1
        username: root
        password: 123456
      db1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test2
        username: root
        password: 123456
    rules:
      sharding:
        tables:
          user:
            # 也可以写成 db0.user0,db0.user1
            actual-data-nodes: db0.user_$->{0..1}
            #现在我们来配置一下分表策略,之前是 database-strategy
            table-strategy:
              #基本都跟之前是一样的
              standard:
                sharding-column: id
                sharding-algorithm-name: my-alg
        sharding-algorithms:
          my-alg:
            #这里我们演示一下INLINE方式,我们可以自行编写表达式来决定
            type: INLINE
            props:
              #比如我们还是希望进行模2计算得到数据该去的表
              #只需要给一个最终的表名称就行了 user_,后面的数字是表达式取模算出的
              #实际上这样写和 MOD模式一模一样
              algorithm-expression: user_$->{id % 2}
              #查询会根据分片策略进行,但范围查询会无视分片策略,所以要改成 true,
              # 让范围查询支持分片策略
              allow-range-query-with-inline-sharding: true
    props:
      #开启日志,一会方便我们观察
      sql-show: true

2.2.3、分表插入测试

测试的代码还是一样的。

2.2.3.1、执行语句日志

会自动把 user表变成 user0 或 user1

ShardingSphere-SQL: Logic SQL: INSERT INTO user  ( id,name,password )  VALUES  ( ?,?,? )
ShardingSphere-SQL: SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
ShardingSphere-SQL: Actual SQL: db0 ::: INSERT INTO user_0  ( id,name,password )  VALUES  (?, ?, ?) ::: [0, user-0, password-0]
​
2022-07-21 19:47:19.539  INFO 14084 --- [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO user  ( id,
name,
password )  VALUES  ( ?,
?,
? )
2022-07-21 19:47:19.539  INFO 14084 --- [           main] ShardingSphere-SQL                       : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
2022-07-21 19:47:19.539  INFO 14084 --- [           main] ShardingSphere-SQL                       : 
    Actual SQL: db0 ::: INSERT INTO user_1  ( id,name,password )  VALUES  (?, ?, ?) ::: [1, user-1, password-1]
2.2.3.2、数据库结果

2.2.4、分表多查询测试

@Test
void batchSelectByIdTest() {
    ArrayList<Integer> idList = new ArrayList<>();
    idList.add(0);
    idList.add(1);
    List<User> userList = userService.listByIds(idList);
    userList.forEach(System.out::println);
}
2.2.4.1、执行语句日志
ShardingSphere-SQL: Logic SQL: SELECT id,name,password FROM user WHERE id IN (  ?, ?)
ShardingSphere-SQL: SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
ShardingSphere-SQL:Actual SQL:db0:::SELECT id,name,password FROM user_0 WHERE id IN (  ? , ? ) 
    UNION ALL SELECT id,name,password FROM user_1 WHERE id IN (  ? , ? ) ::: [0, 1, 0, 1]

2.2.5、分表范围查询测试

查询会根据分片策略来,但是范围查询会无视分片策略,所以 allow-range-query-with-inline-sharding 要改成 true,让范围查询支持分片策略。

@Test
void rangeSelectByIdTest() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.ge("id",1);
    queryWrapper.le("id",7);
    List<User> userList = userService.list(queryWrapper);
    userList.forEach(System.out::println);
}
2.2.5.1、执行语句日志
ShardingSphere-SQL: Logic SQL: SELECT  id,name,password  FROM user WHERE (id >= ? AND id <= ?)
ShardingSphere-SQL: SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
ShardingSphere-SQL: Actual SQL: db0 ::: 
    SELECT  id,name,password  FROM user_0 WHERE (id >= ? AND id <= ?) 
    UNION ALL SELECT  id,name,password  FROM user_1 WHERE (id >= ? AND id <= ?) ::: [1, 7, 1, 7]

2.3、实践3 - 雪花算法

2.3.1、主键数据类型改成 bigint

ALTER TABLE `yyds`.`test` MODIFY COLUMN `id` bigint NOT NULL FIRST;

2.3.2、修改配置文件

server:
  port: 8888
spring:
  application:
    name: sharding-jdbc
  shardingsphere:
    datasource:
      # 有几个数据就配几个,这里是名称,按照下面的格式,名称+数字的形式
      names: db0,db1
      # 为每个数据源单独进行配置
      db0:
        # 数据源实现类,这里使用默认的 HikariDataSource
        type: com.zaxxer.hikari.HikariDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        jdbcUrl: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test1
        username: root
        password: 123456
      db1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test2
        username: root
        password: 123456
    rules:
      sharding:
        tables:
          user:
            actual-data-nodes: db0.user,db1.user
            # 分库策略
            database-strategy:
              standard:
                sharding-column: id
                sharding-algorithm-name: my-alg
            # 使用自定义的主键生成策略
            key-generate-strategy:
              column: id
              key-generator-name: my-gen
        key-generators:
          #这里写我们自定义的主键生成算法
          my-gen:
            # 使用雪花算法
            type: SNOWFLAKE
            props:
              # 工作机器ID,保证唯一就行
              worker-id: 666
        sharding-algorithms:
          my-alg:
            type: MOD
            props:
              sharding-count: 2
    props:
      #开启日志,一会方便我们观察
      sql-show: true

2.3.3、修改代码

因为我们使用的是 MyBatis-Plus,它提供的方法都是包含全部字段的,如果我们不传值,它就会设置一个默认值,那么 ShardingJDBC 就会以为你已经设置了主键值了,就不需要用雪花算法帮你生产了。所以我们需要自己指定一个 SQL语句。

@Mapper
public interface UserMapper extends BaseMapper<User> {
  @Insert("insert into user(name, password) values(#{name}, #{password})")
  int addUser(User user);
}

2.3.4、测试

@Resource
UserMapper userMapper;
@Test
void batchInsertWithoutIdTest() {
    for (int i = 0; i < 10; i++) {
        User user = new User();
        user.setName("user-" + i);
        user.setPassword("password-" + i);
        userMapper.addUser(user);
    }
}
2.3.4.1、执行语句日志
ShardingSphere-SQL: Logic SQL: insert into user(name, password) values(?, ?)
ShardingSphere-SQL: SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
ShardingSphere-SQL: Actual SQL: db0 ::: insert into user(name, password, id) values(?, ?, ?) ::: [user-10, password-10, 757242293794111488]
​
2022-07-22 14:10:31.814  INFO 11732 --- [           main] ShardingSphere-SQL                       : Logic SQL: insert into user(name, password) values(?, ?)
2022-07-22 14:10:31.814  INFO 11732 --- [           main] ShardingSphere-SQL                       : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
2022-07-22 14:10:31.814  INFO 11732 --- [           main] ShardingSphere-SQL                       : 
    Actual SQL: db1 ::: insert into user(name, password, id) values(?, ?, ?) ::: [user-11, password-11, 757242294175793153]

2.4、实践4 - 读写分离

2.4.1、数据库环境配置

记得创建好数据表。

2.4.1.1、从库改成只读模式
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
# 加入这行代码
read-only = 1
​
# 重启下 MySQL
sudo systemctl restart mysql.service
2.4.1.2、配置主从关系
# 主库
# 进入 MySQL数据库
sudo mysql -u root -p
# 展示状态数据
show master status
# 从库
# 进入 MySQL数据库
sudo mysql -u root -p
# 根据主库的状态数据配置主从关系
change replication source to SOURCE_HOST='192.168.0.8',SOURCE_USER='test',SOURCE_PASSWORD='123456',SOURCE_LOG_FILE='binlog.000007',SOURCE_LOG_POS=156;
# 开启主从关系
start replica;

2.4.2、修改配置文件

server:
  port: 8888
spring:
  application:
    name: sharding-jdbc
  shardingsphere:
    datasource:
      # 有几个数据就配几个,这里是名称,按照下面的格式,名称+数字的形式
      names: db0,db1
      # 为每个数据源单独进行配置
      db0:
        # 数据源实现类,这里使用默认的 HikariDataSource
        type: com.zaxxer.hikari.HikariDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        jdbcUrl: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test1
        username: root
        password: 123456
      db1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test2
        username: root
        password: 123456
    rules:
      #配置读写分离
      readwrite-splitting:
        data-sources:
          #这个名称随便写
          user-db:
            #使用静态类型,如果使用动态 Dynamic类型可以自动发现auto-aware-data-source-name
            type: Static
            props:
              #配置写库,即主库(只能一个)
              write-data-source-name: db0
              #配置从库(多个,逗号隔开)
              read-data-source-names: db1
              #读数据的负载均衡策略,可以自定义
              load-balancer-name: my-load
        load-balancers:
          #自定义的负载均衡策略
          my-load:
            type: ROUND_ROBIN
    props:
      #开启日志,一会方便我们观察
      sql-show: true

2.4.3、测试

可以插入一个数据,然后再查一下看看是哪个库。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值