SpringBoot & sharding-jdbc

1,252 阅读3分钟

前言

随着产品业务的扩展以及用户量上涨,一些数据表数据量会变得特别大,且每日数据记录增量也越来越大,导致查询效率低,影响用户体验。因此需要对数据库进行分表。

数据分片

垂直分片

数据库 - 根据业务将数据表分散到不同的数据库中,如订单库和用户库
数据表 - 将表数据根据字段分散到不同的表中

  • 缺点
    • 无法真正解决单点瓶颈问题
    • 可以缓解数据量和访问量带来的问题,但无法根治
    • 表中的数据量依然超过单节点所能承载的阈值,则需要水平分片来进一步处理

水平分片

将表数据分散到一个或多个数据库的相同结构的表中

技术选择

Sharding-jdbc

  • 需要新增代码和配置
  • 分表分库,读写分离
  • 可自定义分片策略

MyCat

  • 数据库中间层

Sharding-Jdbc

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

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

SpringBoot 集成 Sharding-jdbc

  • 添加依赖
<!--sharding-sphere版本号-->
<properties>
    <sharding-sphere.version>4.0.0-RC3</sharding-sphere.version>
</properties>

<!-- for spring boot -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>${sharding-sphere.version}</version>
</dependency>
  • 基于Spring boot的规则配置

spring:
  shardingsphere:
    # 数据源配置
    datasource:
      names: ds0
      ds0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/gaofenshuo_dev?useSSL=false&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
        username: root
        password: 123456
    
    # 分片规则配置
    sharding:
      # 默认分库策略,由于不需要分库,所以直接设置ds0
      default-database-strategy:
        inline:
          sharding-column: id
          algorithm-expression: ds0
      # 分表策略配置
      tables:
        uc_sys_evaluationlog:
          database-strategy:
            inline:
              sharding-column: id
              algorithm-expression: ds0
          table-strategy:
            standard:
              sharding-column: created_at
              precise-algorithm-class-name: com.gfs.shardingjdbcdemo.config.CreateDatePreciseShardingAlgorithm
              range-algorithm-class-name: com.gfs.shardingjdbcdemo.config.CreateDateRangeShardingAlgorithm
#            inline:
#              sharding-column: id
#              algorithm-expression: uc_sys_evaluationlog$->{id % 2}
#          actual-data-nodes: ds0.uc_sys_evaluationlog_$->{2019..2020}$->{['00','01','02','03','04','05','06','07','08','09','10','11','12']},
          actual-data-nodes: ds0.uc_sys_evaluationlog_$->{2019}$->{['01','02']},
          key-generator:
            column: id
            type: SNOWFLAKE
    props:
      sql:
        show: true

当项目已存在数据源情况下,需要删除原有数据源的配置,才可应用成功,否则会沿用原有数据源,不使用sharding-jdbc服务。

  • 编写测试代码
  1. controller
@RestController
public class DemoController {

    @Autowired
    private DemoDao demoDao;

    @GetMapping("add")
    public String demo(){
        demoDao.addDemo();
        return "success";
    }

    @GetMapping("query")
    public JSONArray query(){
        return demoDao.queryDemo();
    }
}
  1. demoDao
public interface DemoDao {
    void addDemo();
    JSONArray queryDemo();
}
  1. demo.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.gfs.shardingjdbcdemo.dao.DemoDao">
    <insert id="addDemo">
        INSERT INTO uc_sys_evaluationlog (
            student_id,
            created_at,
            evaluation_mode,
            evaluation_type,
            evaluation_id,
            evaluation_record_id,
            content_type,
            content_id,
            item_order,
            cloud_score,
            total_score,
            score,
            audio_url,
            cloud_json,
            study_time,
            cloud_type
        )
        VALUES
            (
                3145,
                '2019-01-02 10:00:00',
                'mode',
                'sharding',
                NULL,
                10021,
                'demo',
                101,
                NULL,
                100,
                NULL,
                100.00,
                NULL,
                NULL,
                9999,
        NULL
            );
    </insert>
    <select id="queryDemo" resultType="java.util.Map">
        select * from uc_sys_evaluationlog where id = 100
    </select>
</mapper>
  1. 精确分片策略
@Slf4j
public class CreateDatePreciseShardingAlgorithm implements PreciseShardingAlgorithm<String> {

    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
    /**
     * Sharding.
     *
     * @param availableTargetNames available data sources or tables's names
     * @param shardingValue        sharding value  uc_sys_evaluationlog_201901
     * @return sharding result for data source or table's name
     */
    @SneakyThrows
    @Override
    public String doSharding(final Collection<String> availableTargetNames, final PreciseShardingValue<String> shardingValue) {
        log.info("availableTargetNames:{}", JSON.toJSONString(availableTargetNames));
        log.info("shardingValue:{}", JSON.toJSONString(shardingValue));
        Date date = DateUtils.parseDate(shardingValue.getValue(),"yyyy-MM-dd HH:mm:ss");
        String realDate = sdf.format(date);
        final String tableName = shardingValue.getLogicTableName();
        final String split = "_";
        return tableName + split + realDate;
    }
}
  1. 范围分片策略
@Slf4j
public class CreateDateRangeShardingAlgorithm implements RangeShardingAlgorithm<String> {

    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");

    /**
     * Sharding.
     *
     * @param availableTargetNames available data sources or tables's names
     * @param shardingValue        sharding value
     * @return sharding results for data sources or tables's names
     */
    @SneakyThrows
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<String> shardingValue) {
        String lower = shardingValue.getValueRange().lowerEndpoint();
        String upper = shardingValue.getValueRange().upperEndpoint();
        Date lowerDate = DateUtils.parseDate(lower, "yyyy-MM-dd HH:mm:ss");
        Date upperDate = DateUtils.parseDate(upper, "yyyy-MM-dd HH:mm:ss");
        Date tempDate = lowerDate;
        Collection<String> result = Lists.newArrayList();
        final String tableName = shardingValue.getLogicTableName();
        final String spilt = "_";
        while (upperDate.after(tempDate)) {
            result.add(tableName + spilt + sdf.format(tempDate));
            tempDate = DateUtils.addMonths(tempDate, 1);
        }
        result.add(tableName + spilt + sdf.format(tempDate));
        return result;
    }
}
  1. 代码地址
    https://github.com/gaozaoshun/sharding-jdbc-demo.git