一、背景
最近在看 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做取模操作确定表
13970735281504296969696 % 3 = 0customer_order_0
13970737985572085767676 % 3 = 1customer_order_1
1397074377929003008088 % 3 = 2customer_order_2
2、订单id确定数据表
订单id截取后2位(等价于客户id的后2位)和3做取模操作确定表
1397073535658233856969696 % 3 = 0customer_order_0
1397073798557208578767676 % 3 = 1customer_order_1
139707437792900301008088 % 3 = 2customer_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包
org.apache.shardingsphere
sharding-jdbc-spring-boot-starter
4.1.1
cn.hutool
hutool-all
5.6.5
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 {
/**
* 订单id列名
*/
private static final String COLUMN_ORDER_ID = "order_id";
/**
* 客户id列名
*/
private static final String COLUMN_CUSTOMER_ID = "customer_id";
@Override
public Collection doSharding(Collection availableTargetNames, ComplexKeysShardingValue shardingValue) {
if (!shardingValue.getColumnNameAndRangeValuesMap().isEmpty()) {
throw new RuntimeException("不支持除了=和in的操作");
}
// 获取订单id
Collection orderIds = shardingValue.getColumnNameAndShardingValuesMap().getOrDefault(COLUMN_ORDER_ID, new ArrayList<>(1));
// 获取客户id
Collection customerIds = shardingValue.getColumnNameAndShardingValuesMap().getOrDefault(COLUMN_CUSTOMER_ID, new ArrayList<>(1));
// 整合订单id和客户id
List 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 ids2String(Collection ids) {
List 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-jdbc
spring.shardingsphere.enabled=true
# 配置数据源的名字
spring.shardingsphere.datasource.names=master
# 数据源配置
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://127.0.0.1:3306/temp_work?useUnicode=true&characterEncoding=utf8&autoReconnectForPools=true&useSSL=false
spring.shardingsphere.datasource.master.username=root
spring.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
# 显示sql
spring.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文件写法
insert into customer_order(order_id,customer_id,saller_id,product_name) values (#{orderId},#{customerId},#{sallerId},#{productName})
需要注意,此处写的是逻辑表(customer_order),这个表在数据库中是不存在的,是在分表配置时指定的逻辑表。
五、完整代码
完整代码: https://gitee.com/huan1993/spring-cloud-parent/blob/master/sharding-jdbc/src/main/java/com/huan/study/sharding/algorithm/OrderComplexKeysShardingAlgorithm.java
git提交commitId: b14c1584b89991e909bd6852b1217872414d9db7郑州做人流哪里好http://www.zzchxb120.com/