美文网首页
Sharding-jdbc-v4.1.1实现按月份分表

Sharding-jdbc-v4.1.1实现按月份分表

作者: 请叫我比比比巴卜 | 来源:发表于2021-10-27 14:08 被阅读0次

    首先感谢原文章博主@simians提供的思路和解决方案,本文主要是对sharding-jdbc4.1.1版本的实现,后续会升级至5.0

    本文针对实际电商进销存项目中,订单数据一般根据时间可以分为冷热数据,针对此场景,实现订单表order按月分组的场景:order_202110,order_202211,order_202212
    springbootv2.4x kotlin1.5.x mysql8.0 sharding-jdbc4.1.1
    开发语言使用的Kotlin,不过不妨使用Java的小伙伴能清晰的理解,持续更新完善文章,有疑问可以留言


    1. 使用maven在pom中添加sharding-jdbc依赖
        <!--sharding-jdbc -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
        </dependency>
    
    2. 分表需求

    在实际项目中,电商订单表每个月的平均订单量稳定在300W左右,但是订单表一般有效的热数据为3个月以内,因此订单表order&订单子表(订单商品表)order_item,在实际使用的过程中,我们希望实现order_202110,order_item_202110的分表效果,再根据id或订单号能查询到订单详情的同时订单又支持前端可操作的时间范围202110~202111来进行跨表的列表查询。

    3. 分表思路
    1. 使用sharding-jdbc 自带的雪花算法可以生成连续递增的分布式主键id,并且可以逆推得到id的时间戳,因此在生成主键id的同时,也得到了该记录的created_at(创建时间)

    2. order在按月份进行实际的分片的过程中,并没有按照created_at作为分片键,这样会使我们只通过id去查询时,无法确定该条记录分布在哪个表里面;

    3. order表将id作为分片键,即sharding-column = id;将分布式主键生成算法定为sharding-jdbc自带的雪花算法,即key-generator.column=id;key-generator.type=snowflake,如果同时实现id精准查询时间范围查询,那么还需要我们自定义分片的算法TimeShardingAlgorithm

    4. order表在确定分片方案后,那么order_item表呢,实际需求中,对order_item的查询,仅仅只通过order_item表中的order_id进行结果集的查询,但是我们又希望orderorder_item的分表后缀时间时一致的,例如一个订单和商品明细分布在order_202110order_item_202110中,而不是有概率的出现在order_202110order_item_202111中;

    5. order_item表将order_id作为分片键,即sharding-column=order_id;order_item表中的主键id也要保证不重复,所以将分布式主键生成算法定为sharding-jdbc自带的雪花算法,即key-generator.column=id;key-generator.type=snowflake,如果实现order_id精准查询查询,那么还需要我们自定义分片的算法TimeShardingAlgorithm

    4. 分表的配置文件application-sharding.properties

    sharding-jdbc 按月份分表需要自己实现。需要实现PreciseShardingAlgorithm,RangeShardingAlgorithm的两个接口。并在配置文件里添加实现路径


    目前只有一个数据库,无多库的场景,为了方便我是使用的官方的properties文件,如果有yml文件的需求,可以通过百度在线转换

    # 显示SQL
    spring.shardingsphere.props.sql.show=true
    
    # 数据源,单数据库
    spring.shardingsphere.datasource.names=ds
    
    spring.shardingsphere.datasource.ds.type=com.zaxxer.hikari.HikariDataSource
    spring.shardingsphere.datasource.ds.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.shardingsphere.datasource.ds.jdbc-url=jdbc:mysql://127.0.0.1:3306/table_name?useUnicode=true&serverTimezone=Asia/Shanghai&useSSL=false&autoReconnect=true
    spring.shardingsphere.datasource.ds.username=root
    spring.shardingsphere.datasource.ds.password=root
    spring.shardingsphere.datasource.ds.max-active=16
    
    # order
    #跨年分表
    #spring.shardingsphere.sharding.tables.order.actual-data-nodes=ds.order_$->{2021..2025}0$->{1..9},ds.order_$->{2021..2025}$->{10..11}
    
    #不跨年
    spring.shardingsphere.sharding.tables.order.actual-data-nodes=ds.order_2021$->{10..12}
    # 配置分片键
    spring.shardingsphere.sharding.tables.order.table-strategy.standard.sharding-column=id
    # 配置精准匹配自定义的算法
    spring.shardingsphere.sharding.tables.order.table-strategy.standard.precise-algorithm-class-name=com.ice.uly.config.shading.TimeShardingAlgorithm
    # 配置范围查询自定义的算法
    spring.shardingsphere.sharding.tables.order.table-strategy.standard.range-algorithm-class-name=com.ice.uly.config.shading.TimeShardingAlgorithm
    # 配置分布式主键生成算法
    spring.shardingsphere.sharding.tables.order.key-generator.column=id
    spring.shardingsphere.sharding.tables.order.key-generator.type=snowflake
    
    # order_item
    #spring.shardingsphere.sharding.tables.order_item.actual-data-nodes=ds.order_item_$->{2021..2025}0$->{1..9},ds.order_item_$->{2021..2025}$->{10..11}
    spring.shardingsphere.sharding.tables.order_item.actual-data-nodes=ds.order_item_2021$->{10..12}
    spring.shardingsphere.sharding.tables.order_item.table-strategy.standard.sharding-column=order_id
    spring.shardingsphere.sharding.tables.order_item.table-strategy.standard.precise-algorithm-class-name=com.ice.uly.config.shading.TimeShardingAlgorithm
    spring.shardingsphere.sharding.tables.order_item.table-strategy.standard.range-algorithm-class-name=com.ice.uly.config.shading.TimeShardingAlgorithm
    spring.shardingsphere.sharding.tables.order_item.key-generator.column=id
    spring.shardingsphere.sharding.tables.order_item.key-generator.type=snowflake
    
    # order_receiver
    #spring.shardingsphere.sharding.tables.order.actual-data-nodes=ds.order_$->{2021..2025}0$->{1..9},ds.order_$->{2021..2025}$->{10..11}
    spring.shardingsphere.sharding.tables.order_receiver.actual-data-nodes=ds.order_receiver_2021$->{10..12}
    spring.shardingsphere.sharding.tables.order_receiver.table-strategy.standard.sharding-column=id
    spring.shardingsphere.sharding.tables.order_receiver.table-strategy.standard.precise-algorithm-class-name=com.ice.uly.config.shading.TimeShardingAlgorithm
    spring.shardingsphere.sharding.tables.order_receiver.table-strategy.standard.range-algorithm-class-name=com.ice.uly.config.shading.TimeShardingAlgorithm
    spring.shardingsphere.sharding.tables.order_receiver.key-generator.column=id
    spring.shardingsphere.sharding.tables.order_receiver.key-generator.type=snowflake
    
    spring.shardingsphere.sharding.binding-tables=order
    spring.shardingsphere.sharding.tables.order.key-generator.props.worker.id=123
    
    5. 自定义实现按月分表的算法,当然大家可以优化此实现过程的代码,此处只提供思路
    package com.ice.uly.config.shading
    
    import com.ice.uly.common.sharing.ShardingTool
    import com.ice.uly.enums.OmsErrorCodeEnum
    import com.ice.uly.exception.OmsBizException
    import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm
    import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue
    import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm
    import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue
    import java.time.LocalDate
    import java.time.YearMonth
    import java.time.format.DateTimeFormatter
    
    
    /**
     * @description: 业务表按时间分片的算法
     * @author ULY
     * @date 2021/10/8 17:25
     */
    class TimeShardingAlgorithm : PreciseShardingAlgorithm<Long>, RangeShardingAlgorithm<Long> {
        private val yearAndMonth = DateTimeFormatter.ofPattern("yyyyMM")
    
        override fun doSharding(availableTargetNames: Collection<String>, shardingValue: PreciseShardingValue<Long>): String {
            val tableName = StringBuffer()
            tableName.append(shardingValue.logicTableName)
                .append("_").append(ShardingTool.getYearAndMonth(shardingValue.value!!))
            return tableName.toString()
        }
    
        override fun doSharding(availableTargetNames: MutableCollection<String>, shardingValue: RangeShardingValue<Long>): MutableCollection<String> {
            val result: MutableCollection<String> = LinkedHashSet()
            val shardingKey = shardingValue.valueRange
            val startShardingKey = shardingKey.lowerEndpoint()
            val endShardingKey = shardingKey.upperEndpoint()
    
            // 获取到开始时间,202101
            val startTimeStr = ShardingTool.getYearAndMonth(startShardingKey)
            // 获取结束时间,202512
            val endTimeStr = ShardingTool.getYearAndMonth(endShardingKey)
    
            // 获取起止日期的月初日期
            val startYearMonth = YearMonth.parse(startTimeStr, yearAndMonth)
            val endYearMonth = YearMonth.parse(endTimeStr, yearAndMonth)
    
            if (startYearMonth.year == endYearMonth.year) { // 范围不跨年
                var start = startTimeStr.toInt()
                val end = endTimeStr.toInt()
                while (start <= end) {
                    val tableName = shardingValue.logicTableName.toString() + "_" + start
                    result.add(tableName)
                    start++
                }
            } else if (startYearMonth.year < endYearMonth.year) { // 范围跨年
                val startDate = LocalDate.of(startYearMonth.year, startYearMonth.monthValue, 1)
                val endDate = LocalDate.of(endYearMonth.year, endYearMonth.monthValue, 1)
    
                var currDate = startDate
                while (currDate.isBefore(endDate)) {
                    val tableName = shardingValue.logicTableName.toString() + "_" + currDate.format(yearAndMonth)
                    result.add(tableName)
                    currDate = currDate.plusDays(1)
                }
                result.add(shardingValue.logicTableName.toString() + "_" + endTimeStr)
            } else {
                // 此处自定义异常可替换
                throw OmsBizException(OmsErrorCodeEnum.DB_DATA_ACCESS)
            }
            // 过滤只保留配置中可用的表
            return result.intersect(availableTargetNames).toMutableSet()
        }
    }
    
    6. sharding-jdbc自带的雪花算法的逆向获取时间戳的工具类
    package com.ice.uly.common.sharing
    
    import org.apache.shardingsphere.core.strategy.keygen.SnowflakeShardingKeyGenerator
    import java.time.Instant
    import java.time.LocalDateTime
    import java.time.ZoneId
    import java.time.format.DateTimeFormatter
    
    
    /**
     * @description: TODO
     * @author ULY
     * @date 2021/10/8 17:36
     */
    class ShardingTool {
    
        companion object {
            private val yearAndMonth = DateTimeFormatter.ofPattern("yyyyMM")
            private val year = DateTimeFormatter.ofPattern("yyyy")
    
            fun getYear(shardingKey: Long): String {
                val instant = Instant.ofEpochMilli(SnowflakeShardingKeyGenerator.EPOCH + (shardingKey shr 22))
                val localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault())
                return year.format(localDateTime)
            }
    
            fun getYearAndMonth(shardingKey: Long): String {
                val instant = Instant.ofEpochMilli(SnowflakeShardingKeyGenerator.EPOCH + (shardingKey shr 22))
                val localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault())
                return yearAndMonth.format(localDateTime)
            }
    
            fun generateKey(time: Long): Long {
                val shardingKey = time - SnowflakeShardingKeyGenerator.EPOCH shl 22
                println(shardingKey)
                println(getYearAndMonth(shardingKey))
                return shardingKey
            }
        }
    }
    
    7.使用数据库

    order_202110,order_202111,order_202112
    order_item_202110,order_item_202111,order_item_202112

    相关文章

      网友评论

          本文标题:Sharding-jdbc-v4.1.1实现按月份分表

          本文链接:https://www.haomeiwen.com/subject/epoualtx.html