一、背景
最近在看 Sharding-JDBC
方面的内容,此处简略记录一下应用Sharding-JDBC中的复合分片键
来实现分表的办法。
二、需要
假如咱们有一张订单表customer_order
,为了避免单表数据量太大,须要进行分表操作。
此处须要分为3个表 customer_order_0
、customer_order_1
和customer_order_2
1、对于客户端操作而言
1、同一个客户的订单,须要放到同一个表中。
2、依据订单号,须要晓得这个订单在哪个中。
2、对于经营端操作而言
因为订单的数据量比拟大,咱们能够将一些须要作为搜寻条件的数据保留到elasticsearch
中,将订单的残缺数据保留到hive
中。Mysql
数据库中的数据能够通过阿里开源的canal
来同步到es
中,这步操作略。
三、分片算法
因为同一个客户的订单分到同一个表,那么客户id(customerId
)须要作为一个分片键。
因为须要依据订单id(orderId
)确定到那一个表,所有客户id的分片信息须要糅合到订单id中,所以订单id也须要作为一个分片键。
因而在Sharding-JDBC
中而言,这是一个复合分片算法。
1、客户id和订单id的生成规定
客户id
: 应用雪花算法生成
订单id
: 应用雪花算法生成 + 客户id的后2位
2、 确定数据落在那个表中
- 截取客户id后2位。
将后2位和3做取模操作,获取到表的后缀。
- 和3做取模操作,是因为需要中须要分为3个表。
- 将 customer_order_ 和上一步表的后缀拼接起来,就失去了一个实在表。
3、举例说明
1、客户id确定数据表
客户id | 截取后2位 | 和3做取模操作 | 确定表 |
---|---|---|---|
1397073528150429696 | 96 | 96 % 3 = 0 | customer_order_0 |
1397073798557208576 | 76 | 76 % 3 = 1 | customer_order_1 |
1397074377929003008 | 08 | 8 % 3 = 2 | customer_order_2 |
2、订单id确定数据表
订单id | 截取后2位(等价于客户id的后2位) | 和3做取模操作 | 确定表 |
---|---|---|---|
139707353565823385696 | 96 | 96 % 3 = 0 | customer_order_0 |
139707379855720857876 | 76 | 76 % 3 = 1 | customer_order_1 |
139707437792900301008 | 08 | 8 % 3 = 2 | customer_order_2 |
四、实现步骤
1、建表语句
create table customer_order_0( id int auto_increment, order_id decimal(21) null, customer_id bigint null, saller_id bigint null, product_name varchar(300) null, constraint customer_order_pk primary key (id))comment '优惠券订单' engine = innodb character set = utf8;create table customer_order_1( id int auto_increment, order_id decimal(21) null, customer_id bigint null, saller_id bigint null, product_name varchar(300) null, constraint customer_order_pk primary key (id))comment '优惠券订单' engine = innodb character set = utf8;comment '优惠券订单' engine = innodb character set = utf8;create table customer_order_2( id int auto_increment, order_id decimal(21) null, customer_id bigint null, saller_id bigint null, product_name varchar(300) null, constraint customer_order_pk primary key (id))comment '优惠券订单' engine = innodb character set = utf8;
2、引入Sharding-JDBC的jar包
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>4.1.1</version></dependency><!-- 为了生成id --><dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.6.5</version></dependency>
3、编写分片算法
package com.huan.study.sharding.algorithm;import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingAlgorithm;import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingValue;import java.math.BigDecimal;import java.util.ArrayList;import java.util.Collection;import java.util.List;import java.util.Objects;import java.util.stream.Collectors;/** * 复合分片算法 * 依据订单id(orderId)和客户id(customerId)后2位计算 * 订单id 蕴含客户id 的后2位 * 以客户id的后2位来确定是路由到那个表中 * 1、目前解决 = 和 in 操作,其余的操作,比方 >、< 等不反对。 * * @author huan.fu 2021/5/25 - 上午9:48 */public class OrderComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm<BigDecimal> { /** * 订单id列名 */ private static final String COLUMN_ORDER_ID = "order_id"; /** * 客户id列名 */ private static final String COLUMN_CUSTOMER_ID = "customer_id"; @Override public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<BigDecimal> shardingValue) { if (!shardingValue.getColumnNameAndRangeValuesMap().isEmpty()) { throw new RuntimeException("不反对除了=和in的操作"); } // 获取订单id Collection<BigDecimal> orderIds = shardingValue.getColumnNameAndShardingValuesMap().getOrDefault(COLUMN_ORDER_ID, new ArrayList<>(1)); // 获取客户id Collection<BigDecimal> customerIds = shardingValue.getColumnNameAndShardingValuesMap().getOrDefault(COLUMN_CUSTOMER_ID, new ArrayList<>(1)); // 整合订单id和客户id List<String> ids = new ArrayList<>(16); ids.addAll(ids2String(orderIds)); ids.addAll(ids2String(customerIds)); return ids.stream() // 截取 订单号或客户id的后2位 .map(id -> id.substring(id.length() - 2)) // 去重 .distinct() // 转换成int .map(Integer::new) // 对可用的表名求余数,获取到实在的表的后缀 .map(idSuffix -> idSuffix % availableTargetNames.size()) // 转换成string .map(String::valueOf) // 获取到实在的表 .map(tableSuffix -> availableTargetNames.stream().filter(targetName -> targetName.endsWith(tableSuffix)).findFirst().orElse(null)) .filter(Objects::nonNull) .collect(Collectors.toList()); } /** * 转换成String */ private List<String> ids2String(Collection<?> ids) { List<String> result = new ArrayList<>(ids.size()); ids.forEach(id -> result.add(Objects.toString(id))); return result; }}
留神⚠️:
1、此处为 订单id和客户id
的复合分片算法。
2、因为订单id太长,所以应用了 BigDecimal
类型。
3、订单id和客户id的后2位都能够确定数据最终是路由在哪张表中。
4、目前只实现了=
和in
的操作,不反对范畴操作。
4、分表配置
# 启用 sharding-jdbcspring.shardingsphere.enabled=true# 配置数据源的名字spring.shardingsphere.datasource.names=master# 数据源配置spring.shardingsphere.datasource.master.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driverspring.shardingsphere.datasource.master.jdbc-url=jdbc:mysql://127.0.0.1:3306/temp_work?useUnicode=true&characterEncoding=utf8&autoReconnectForPools=true&useSSL=falsespring.shardingsphere.datasource.master.username=rootspring.shardingsphere.datasource.master.password=root# 配置默认数据源为 master,即没有配置分表的数据,应用次数据源spring.shardingsphere.sharding.default-data-source-name=master# 数据库中理论的表spring.shardingsphere.sharding.tables.customer_order.actual-data-nodes=master.customer_order_$->{0..2}# 分片列spring.shardingsphere.sharding.tables.customer_order.table-strategy.complex.sharding-columns=order_id,customer_id# 分片算法spring.shardingsphere.sharding.tables.customer_order.table-strategy.complex.algorithm-class-name=com.huan.study.sharding.algorithm.OrderComplexKeysShardingAlgorithm# 显示sqlspring.shardingsphere.props.sql.show=true
spring.shardingsphere.sharding.tables.customer_order
: 咱们本人在程序中写sql时,订单表间接应用逻辑表customer_order
即可,而不要应用实在的表,比方(customer_order_0等)。
spring.shardingsphere.sharding.tables.customer_order.table-strategy.complex.sharding-columns
:指定须要分表的列。
spring.shardingsphere.sharding.tables.customer_order.table-strategy.complex.algorithm-class-name
:指定复合分表算法类,指定的类须要有一个无参的构造方法。
5、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.huan.study.sharding.mappers.CustomerOrderMapper"> <resultMap id="BaseResultMapper" type="com.huan.study.sharding.entity.CustomerOrder"> <id column="id" property="id"/> <result column="order_id" property="orderId"/> <result column="customer_id" property="customerId"/> <result column="saller_id" property="sallerId"/> <result column="product_name" property="productName"/> </resultMap> <insert id="createOrder"> insert into customer_order(order_id,customer_id,saller_id,product_name) values (#{orderId},#{customerId},#{sallerId},#{productName}) </insert> <select id="findOrder" resultMap="BaseResultMapper"> select * from customer_order where order_id = #{orderId} </select> <select id="findCustomerOrders" resultMap="BaseResultMapper"> select * from customer_order where customer_id = #{customerId} </select></mapper>
须要留神,此处写的是逻辑表(customer_order
),这个表在数据库中是不存在的,是在分表配置
时指定的逻辑表。
五、残缺代码
残缺代码: https://gitee.com/huan1993/sp...
git提交commitId: b14c1584b89991e909bd6852b1217872414d9db7
六、参考文档
1、https://shardingsphere.apache.org/document/legacy/4.x/document/cn/manual/sharding-jdbc/configuration/config-spring-boot/