SpringBoot整合sharding-jdbc教程

2,322 阅读8分钟

SpringBoot整合sharding-jdbc教程

1、什么是sharding-jdbc

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

  • 适用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
  • 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
  • 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL。

2、为什么需要sharding-jdbc

传统的将数据集中存储至单一数据节点的解决方案,在性能、可用性和运维成本这三方面已经难于满足互联网的海量数据场景。

性能方面来说,由于关系型数据库大多采用B+树类型的索引,在数据量超过阈值的情况下,索引深度的增加也将使得磁盘访问的IO次数增加,进而导致查询性能的下降;同时,高并发访问请求也使得集中式数据库成为系统的最大瓶颈。

可用性的方面来讲,服务化的无状态型,能够达到较小成本的随意扩容,这必然导致系统的最终压力都落在数据库之上。而单一的数据节点,或者简单的主从架构,已经越来越难以承担。数据库的可用性,已成为整个系统的关键。

运维成本方面考虑,当一个数据库实例中的数据达到阈值以上,对于DBA的运维压力就会增大。数据备份和恢复的时间成本都将随着数据量的大小而愈发不可控。一般来讲,单一数据库实例的数据的阈值在1TB之内,是比较合理的范围。

在传统的关系型数据库无法满足互联网场景需要的情况下,将数据存储至原生支持分布式的NoSQL的尝试越来越多。 但NoSQL对SQL的不兼容性以及生态圈的不完善,使得它们在与关系型数据库的博弈中始终无法完成致命一击,而关系型数据库的地位却依然不可撼动。

数据分片指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中以达到提升性能瓶颈以及可用性的效果。 数据分片的有效手段是对关系型数据库进行分库和分表。分库和分表均可以有效的避免由数据量超过可承受阈值而产生的查询瓶颈。 除此之外,分库还能够用于有效的分散对数据库单点的访问量;分表虽然无法缓解数据库压力,但却能够提供尽量将分布式事务转化为本地事务的可能,一旦涉及到跨库的更新操作,分布式事务往往会使问题变得复杂。 使用多主多从的分片方式,可以有效的避免数据单点,从而提升数据架构的可用性。

通过分库和分表进行数据的拆分来使得各个表的数据量保持在阈值以下,以及对流量进行疏导应对高访问量,是应对高并发和海量数据系统的有效手段。

面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。 对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库(读写分离),主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善

综上所述,可以看见shardingsphere-jdbc就是为了解决传统应用架构中数据库的性能瓶颈问题。

3、基于SpringBoot整合sharding-jdbc实现读写分离

  1. 新建SpringBoot项目,引入如下依赖

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
       <version>2.2.5.RELEASE</version>
    </dependency>
    <!-- 引入Mybatis依赖-->
    <dependency>
       <groupId>org.mybatis.spring.boot</groupId>
       <artifactId>mybatis-spring-boot-starter</artifactId>
       <version>2.1.1</version>
    </dependency>
    <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <version>8.0.26</version>
    </dependency>
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-jdbc</artifactId>
       <version>2.2.5.RELEASE</version>
    </dependency><!--引入sharding-sphere-jdbc依赖-->
    <dependency>
       <groupId>org.apache.shardingsphere</groupId>
       <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
       <version>4.1.1</version>
    </dependency>
    <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <version>1.18.20</version>
    </dependency>
    
  2. 新建test1、test2数据库,其中test1用作主库,test2用作从库(这里仅用作测试,test1与test2中并无主从关系,在实际项目中需要进行主从数据库架构搭建)

  3. 使用sql语句,创建ms表,sql语句如下

    `DROP TABLE IF EXISTSms; CREATE TABLEms(idbigint NOT NULL AUTO_INCREMENT,is_delbit(1) NOT NULL DEFAULT b'0' COMMENT '是否被删除',user_idint NOT NULL COMMENT '用户id',create_timedatetime NOT NULL COMMENT '创建时间',namevarchar(256) DEFAULT NULL COMMENT '用户名称', PRIMARY KEY (id), KEYindex_createTime(create_time) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=612939736465539074 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

  4. 编写application.properties文件,示例如下

    #包含哪些数据源
    spring.shardingsphere.datasource.names=master,slave
    ​
    #指定谁是主库谁是从库,可以看到从库的属性是slave-data-source-names,表明支持多个从库
    spring.shardingsphere.sharding.master-slave-rules.master.master-data-source-name=master
    spring.shardingsphere.sharding.master-slave-rules.master.slave-data-source-names=slave
    #多数据源配置
    #主数据库配置
    spring.shardingsphere.datasource.master.type=com.zaxxer.hikari.HikariDataSource
    spring.shardingsphere.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.shardingsphere.datasource.master.jdbc-url=jdbc:mysql://IP:PORT/test1
    spring.shardingsphere.datasource.master.username=数据库登陆用户名
    spring.shardingsphere.datasource.master.password=数据库登陆密码
    ​
    #从数据库配置
    spring.shardingsphere.datasource.slave.type=com.zaxxer.hikari.HikariDataSource
    spring.shardingsphere.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.shardingsphere.datasource.slave.jdbc-url=jdbc:mysql://IP:PORT/test2
    spring.shardingsphere.datasource.slave.username=数据库登陆用户名
    spring.shardingsphere.datasource.slave.password=数据库登陆密码
    ​
    #可以看到配置的格式为spring.shardingsphere.datasource.${datasource-name}.type等#表明ms表id列需要进行雪花算法生成
    #可以看到配置的格式为spring.shardingsphere.sharding.tables.${table-name}.column等
    spring.shardingsphere.sharding.tables.ms.key-generator.column=id
    spring.shardingsphere.sharding.tables.ms.key-generator.type=SNOWFLAKE
    
  1. 编写对应的mapper文件以及mapper接口,创建测试类

    mapper文件具体如下:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
           PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.xr.mapper.MasterSlaveMapper">
       <insert id="addMasterSlave" parameterType="com.xr.entity.MasterSlave">
          INSERT INTO ms(id,user_id,create_time,name)
          VALUES(#{ms.id},#{ms.userId},#{ms.createTime},#{ms.name})
       </insert>
    ​
       <resultMap id="masterSlave" type="com.xr.entity.MasterSlave">
           <result column="is_del" property="isDel"></result>
           <result column="id" property="id"></result>
           <result column="user_id" property="userId"></result>
           <result column="name" property="name"></result>
           <result column="create_time" property="createTime"></result>
       </resultMap>
       <select id="queryMasterSlave" resultMap="masterSlave">
          select * from ms
       </select>
    </mapper>
    

    mapper接口具体如下:

    @Mapper
    public interface MasterSlaveMapper {
       int addMasterSlave(@Param("ms") MasterSlave masterSlave);
    ​
       List<MasterSlavequeryMasterSlave();
    }
    

    测试类代码如下:

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = ShardingSphereDemo.class)
    public class MasterSlaveTest {
    ​
       @Autowired
       private MasterSlaveMapper masterSlaveMapper;
    ​
       @Test
       public void testAdd(){
           MasterSlave masterSlave = new MasterSlave();
           masterSlave.setName("111");
           masterSlave.setCreateTime("2022-07-13");
           masterSlave.setUserId("111");
           masterSlaveMapper.addMasterSlave(masterSlave);
      }
    ​
       @Test
       public void testQuery(){
           List<MasterSlave> masterSlaves = masterSlaveMapper.queryMasterSlave();
           System.out.println(masterSlaves);
      }
    }
    
  2. 测试结果

image.png 可以看到id不是数据库自增生成并且在测试代码中也没有指定ID,所以id是通过雪花算法生成

image.png 可以看到test2数据库没有数据,因为我们的主从不是真正意义上的主从架构,所以没有数据是正常的。

接下来看一下查询的验证结果,截图如下:

image.png 可以看到查询是没有数据的说明走到了从库进行查询

4、基于SpringBoot整合sharding-jdbc实现分库分表

  1. 新建SpringBoot项目,引入依赖,依赖可参考读写分离中依赖

  2. 数据库test1、test2已经新建好,无需新建了, 需要利用sql语句创建b_order0和b_order1表,sql如下:

    DROP TABLE IF EXISTS b_order0; CREATE TABLE b_order0 ( id bigint NOT NULL AUTO_INCREMENT, is_del bit(1) NOT NULL DEFAULT b'0' COMMENT '是否被删除', user_id int NOT NULL COMMENT '用户id', create_time datetime NOT NULL COMMENT '创建时间', name varchar(256) DEFAULT NULL COMMENT '用户名称', PRIMARY KEY (id), KEY index_createTime (create_time) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=612939736465539073 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

    SET FOREIGN_KEY_CHECKS = 1;

    --b_order1新建

    DROP TABLE IF EXISTS b_order1; CREATE TABLE b_order1(idbigint NOT NULL AUTO_INCREMENT,is_delbit(1) NOT NULL DEFAULT b'0' COMMENT '是否被删除',user_idint NOT NULL COMMENT '用户id',create_timedatetime NOT NULL COMMENT '创建时间',namevarchar(256) DEFAULT NULL COMMENT '用户名称', PRIMARY KEY (id), KEYindex_createTime(create_time) USING BTREE``) ENGINE=InnoDB AUTO_INCREMENT=612939736465539073 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;`

    SET FOREIGN_KEY_CHECKS = 1;

  3. 编写application.properties文件

    #包含哪些数据源
    spring.shardingsphere.datasource.names=ds0,ds1
    #多数据源配置
    #ds0数据源配置
    spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
    spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://IP:PORT/test1
    spring.shardingsphere.datasource.ds0.username=数据库连接用户名
    spring.shardingsphere.datasource.ds0.password=数据库连接密码
    ​
    #ds1数据源配置
    spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
    spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://IP:PORT/test2
    spring.shardingsphere.datasource.ds1.username=数据库连接用户名
    spring.shardingsphere.datasource.ds1.password=数据库连接密码
    #主键生成策略,此处的order是逻辑表,真实的表应该是test1和test2的b_order0和b_order1表
    #注释的原因是因为我们要自己显示生成id进行测试
    #spring.shardingsphere.sharding.tables.b_order.key-generator.column=id
    #spring.shardingsphere.sharding.tables.b_order.key-generator.type=SNOWFLAKE
    #根据id进行分库
    spring.shardingsphere.sharding.tables.b_order.database-strategy.inline.sharding-column=id
    spring.shardingsphere.sharding.tables.b_order.database-strategy.inline.algorithm-expression=ds${id % 2}
    #根据id进行分表
    spring.shardingsphere.sharding.tables.b_order.table-strategy.inline.sharding-column=id
    spring.shardingsphere.sharding.tables.b_order.table-strategy.inline.algorithm-expression=b_order${id % 2}
    #数据节点配置,真实配置的节点为:ds0.b_order0、ds0.b_order1、ds1.b_order0、ds1.b_order1
    spring.shardingsphere.sharding.tables.b_order.actual-data-nodes=ds${0..1}.b_order${0..1}
    
  4. 编写mapper接口、mapper的xml文件以及测试代码

    mapper接口:

    @Mapper
    public interface OrderMapper {
       int addOrder(@Param("order") Order order);
    ​
       Order queryOrderById(@Param("id") int id);
    }
    

    xml文件:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
           PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.xr.mapper.OrderMapper">
       <insert id="addOrder" parameterType="com.xr.entity.Order">
          INSERT INTO b_order(id,user_id,create_time,name)
          VALUES(#{order.id},#{order.userId},#{order.createTime},#{order.name})
       </insert>
    ​
       <resultMap id="order" type="com.xr.entity.Order">
           <result column="is_del" property="isDel"></result>
           <result column="id" property="id"></result>
           <result column="user_id" property="userId"></result>
           <result column="name" property="name"></result>
           <result column="create_time" property="createTime"></result>
       </resultMap>
       <select id="queryOrderById" resultMap="order">
          select * from b_order where id = #{id}
       </select>
     <!--都是逻辑表名称,由sharding-jdbc负责路由到真正的节点-->
    </mapper>
    

测试代码:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ShardingSphereDemo.class)
public class ShardingTest {

    @Autowired
    private OrderMapper orderMapper;

    @Test
    public void testAdd(){
        for(int i = 1;i < 5;i++){
            Order order = new Order();
            //1:test2.order1  2:test1.order0  3:test2.order1 4:test1.order0
            order.setId(i);
            order.setCreateTime("2022-07-13");
            order.setName("name" + i);
            order.setUserId(String.valueOf(i));
            orderMapper.addOrder(order);
        }
    }

    @Test
    public void testQuery(){
        Order order = orderMapper.queryOrderById(1);
        System.out.println(order);
    }
}
  1. 测试效果

image.png

image.png

5、写在最后

  • shardingsphere的作用不仅仅在于数据分片以及读写分离,还有着很多其他的功能包括数据治理、强制路由、影子库等,各位如果对其他功能感兴趣可以自行前往shardingsphere官方文档学习:shardingsphere.apache.org/document/le…
  • 经过上面的介绍,可以看到引入sharding-jdbc依赖后,再通过部分配置就可以完成数据分片以及读写分离功能,那么sharding-jdbc是通过什么方式使得开发人员在日常的开发中无感的呢,这个问题可以在我后续的文章中知道答案
  • 最后Sharding-JDBC只是ShardingSphere大家族中的一员,各位有兴趣还可以去看看Sharding-Proxy怎么实现数据分片以及读写分离