Shardingkey-Autofill 是一个针对分库分表的项目进行分片键(分库、分表键字段)自动填充的增强框架
假设项目有业务场景如下(以下为演示样例,主要描述改造原始业务的过程):
背景:每个用户只属于单个机构,每个用户在每次购买服务时会使用其名下账户之一进行下单;这里业务实现上,有用户表(user_info)、用户账户表(user_account_info)、机构表(org_info)、订单表(order_info)
升级原因:现在用户下单频率增长,订单表数据体量较大,考虑对其进行分库分表(引入相关分库分表框架),规则为:通过用户id进行分表(方便某个用户的所有订单信息都在同一张表中),机构id进行分库(方便某个机构的所有用户订单信息都在同一数据库中)
原始业务:查询某个账户下的某个订单信息
SELECT * FROM order_info WHERE account_id = '123' AND order_id = '001'
业务查询改造步骤:
对原始业务调用查询前,提取用户账户id:account_id
找到分表键,前往用户账户表查询用户id: user_id
SELECT user_id FROM user_account_info WHERE account_id = '123'
SELECT org_id FROM user_info WHERE user_id = '456'
SELECT * FROM order_info WHERE account_id = '123' AND order_id = '001' AND user_id = '456' AND org_id = '789'
总结:假设订单表有很多和上面类似的查询,条件各不相同,为此需要对每条SQL进行手动改造,不过基本步骤与上面类似:即在总体上保证最后条件都要有分库键字段org_id和分表键字段user_id;所以我们发现此类基本动作大致相同,为此开发了自动填充分片键框架,帮助提炼上述改造步骤
<dependency>
<groupId>io.github.windsbell</groupId>
<artifactId>shardingkey-autofill</artifactId>
<version>最新版本号</version>
</dependency>
springboot启动类:添加开启使用分片键自动填充注解(@EnableShardingKeyAutoFill)
@EnableShardingKeyAutoFill
@SpringBootApplication
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
配置application.yml(分布式则移步配置中心): 设置自动填充分片键策略(以下配置为演示样例,主要阐述数据表对应分片键和对应业务字段的关联映射)
spring:
## 自动填充分片键策略插件配置
shardingkeyAutofill:
# 分片键值对内容缓存[选填,若配置开启后:首次执行查找器得到分片键值对并缓存,之后则在有效期内从缓存提取进行填充]
cache:
# 是否启用缓存开关 [选填,true:开启 false:不开启,推荐开启提升体验,不填写默认不开启]
enabled: true
# 类型[选填,default(本地缓存)、redis(redis缓存)、spring(spring cache缓存),不填写默认default]
type: default
# 过期时间 [选填,单位:秒 ,不填写则默认一小时]
expire: 3600
# 启用拦截日志开关[选填,true:开启 false:不开启,不填写则默认开启]
logEnabled: false
# 策略集 [分片键都相同的一组数据表作为一个策略集进行配置]
strategies:
- # 策略一
# 适配策略的表集合[多个表则对应都所使用的分片键都相同]
suitableTables: [order_info]
# 分表键
tableShardKey: user_id
# 分库键
databaseShardKey: org_id
# 查找器类名 [继承ShardingValueFinder自定义实现目标业务键查询到分片键内容逻辑]
finderClassName: com.exmpale.finder.CustomerShardingValueFinder
#(选填)必要业务键列表[条件中必须出现的业务键,通过其中出现的所有业务键可查出分库分表等键值对]
necessaryBusinessKeys:
- account_id
#(选填)任意业务键列表[条件中出现以下任意一个业务键即可满足查出分库分表等键值对]
anyOneBusinessKeys:
- mobile
- # 策略二
# 多个策略依次如上面策略一的配置一样,自定义设置选择必要业务键或者任意业务键
****
- # 策略三
# 多个策略依次如上面策略一的配置一样,自定义设置选择必要业务键或者任意业务键
****
业务书写实现上面每个策略集中的分片键查找器finderClassName:实现接口com.windsbell.shardingkey.autofill.finder.ShardingValueFinder(自定义书写通过业务键查询到分片键内容逻辑,用来提供给框架调用)
public class CustomerShardingValueFinder implements ShardingValueFinder {
@Override
public ShardingValueStrategy find(BusinessKeyStrategy businessKeyStrategy) {
ShardingValueStrategy shardingValueStrategy = new ShardingValueStrategy();
String userId = null; // 分表键
String orgId = null; // 分库键
// 通过必要业务字段查出分片键
List<BusinessStrategy<?>> necessaryBusinessKeys = businessKeyStrategy.getNecessaryBusinessKeys();
for (BusinessStrategy<?> businessStrategy : necessaryBusinessKeys) {
String key = businessStrategy.getKey(); // "account_id"
String accountId = (String) businessStrategy.getValue(); // "123***"
if ("account_id".equals(key)) {
userId = findUserIdByAccountId(accountId);
orgId = findOrgIdByUserId(userId);
break;
}
}
// 若设有非必要业务字段,也支持通过其查出分片键
if (StringUtils.isBlank(userId) && StringUtils.isBlank(userId)) {
List<BusinessStrategy<?>> anyOneBusinessKeys = businessKeyStrategy.getAnyOneBusinessKeys();
for (BusinessStrategy<?> anyOneBusinessKey : anyOneBusinessKeys) {
String key = anyOneBusinessKey.getKey(); // "mobile"
String mobile = (String) anyOneBusinessKey.getValue(); // "130***"
if ("mobile".equals(key)) {
userId = findUserIdByMobile(mobile);
orgId = findOrgIdByUserId(userId);
break;
}
}
}
shardingValueStrategy.setTableShardValue(userId);
shardingValueStrategy.setDatabaseShardValue(orgId);
return shardingValueStrategy;
}
private String findUserIdByAccountId(String accountId) {
return null; // 补充通过用户账户id,查找用户id逻辑
}
private String findUserIdByMobile(String mobile) {
return null; // 补充通过电话号码,查找用户id逻辑
}
private String findOrgIdByUserId(String userId) {
return null; // 补充通过用户id,查找机构id逻辑
}
}
业务执行:
service (单表orm交互):
// 原始业务SQL--> mybatis-plus 查询某个账户下的某个订单信息
List<OrderInfo> orderInfoList = this.lambdaQuery()
.eq(OrderInfo::getAccountId, accountId)
.eq(OrderInfo::getOrderId, orderId)
.list();
// 框架自动填充分片键后等价于以下查询效果 -->
List<OrderInfo> orderInfoList = this.lambdaQuery()
.eq(OrderInfo::getAccountId, accountId)
.eq(OrderInfo::getOrderId, orderId)
.eq(OrderInfo::getUserId, userId) // 框架自动填充
.eq(OrderInfo::getOrgId, orgId) // 框架自动填充
.list();
mapper(多表sql交互):
<!-- 原始业务SQL 查询某个账户下的所有订单信息 -->
<select id="getUserOrderInfoList" resultType="java.util.Map">
SELECT t1.user_id,
t1.user_name AS fullName,
t1.org_name,
t2.*
FROM user_info t1
LEFT JOIN order_info t2 ON t1.org_id = t2.org_id
AND t1.user_id = t2.user_id
WHERE t2.account_id = '12345'
AND t2.mobile = '133'
AND t1.mobile = '133'
ORDER BY t2.order_time DESC
LIMIT 1,10
</select>
<!-- 框架自动填充分片键后等价于以下查询效果 -->
<select id="getUserOrderInfoList" resultType="java.util.Map">
SELECT
t1.user_id,
t1.user_name AS fullName,
t1.org_name,
t2.*
FROM
user_info t1
LEFT JOIN order_info t2 ON t1.org_id = t2.org_id
AND t1.user_id = t2.user_id
WHERE
t2.account_id = '12345'
AND t2.mobile = '133'
AND t1.mobile = '133'
AND t1.org_id = 'orgId:111 From:mobile' <!-- 框架自动填充 -->
AND t2.org_id = 'orgId:111 From:mobile' <!-- 框架自动填充 -->
AND t2.user_id = 'userid:111 From:accountId' <!-- 框架自动填充 -->
ORDER BY
t2.order_time DESC
LIMIT 1,10
</select>
备注:
这是笔者在日常工作中,对落地分库分表框架(sharding-sphere)之后,发现上述直面场景是经常会遇到的,很多SQL都需要做这种冗余动作,为此写了自动填充分片键框架工具,由工具自动提炼并设置分片键,让开发专注于业务SQL。欢迎留言和star使用!
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。