前言
在前面进行了基本的Shardingsphere之后,在一些其他的复杂条件下,可以使用自定义精确分片算法(PreciseShardingAlgorithm),通常用来处理=或者in条件的情况比较多。在该demo中,通过user_id来分库,公司Id(company_id)来分表,实现精确的不同分库分表。
数据库表
CREATE TABLE `b_order0` (
`id` bigint NOT NULL AUTO_INCREMENT,
`is_del` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否被删除',
`company_id` int NOT NULL COMMENT '公司ID',
`position_id` bigint NOT NULL COMMENT '职位ID',
`user_id` int NOT NULL COMMENT '用户id',
`publish_user_id` int NOT NULL COMMENT '职位发布者id',
`resume_type` int NOT NULL DEFAULT '0' COMMENT '简历类型:0 附件 1 在线',
`status` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '投递状态 投递状态 WAIT-待处理 AUTO_FILTER-自动过滤 PREPARE_CONTACT-待沟通 REFUSE-拒绝 ARRANGE_INTERVIEW-通知面试',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '操作时间',
`work_year` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '工作年限',
`name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '投递简历人名字',
`position_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '职位名称',
`resume_id` int DEFAULT NULL COMMENT '投递的简历id(在线和附件都记录,通过resumeType进行区别在线还是附件)',
PRIMARY KEY (`id`),
KEY `i_comId_pub_ctime` (`company_id`,`publish_user_id`,`create_time`) USING BTREE,
KEY `index_companyId_positionId` (`company_id`,`position_id`) USING BTREE,
KEY `index_companyId_status` (`company_id`,`status`(255),`is_del`) USING BTREE,
KEY `index_createTime` (`create_time`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=983779921828511746 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
数据源和表截图:2个数据源,4个表
其他的配置
其他配置见该栏前一篇文章:配置信息
yml(application-custom-sharding.yml)配置:
spring:
sharding-sphere:
datasource:
names: ftdb0,ftdb1
ftdb0:
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://localhost:3306/ftdb0?useUnicode=true&autoReconnect=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123mysql
ftdb1:
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://localhost:3306/ftdb1?useUnicode=true&autoReconnect=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123mysql
sharding:
tables:
b_order:
actualDataNodes: ftdb${0..1}.b_order${0..1}
database-strategy: # 数据库精确精确分库策略
standard: #这个地方不可忽略
sharding-column: user_id
precise-algorithm-class-name: com.example.ftserver.sharding.shardingalgrithm.precise.FtDataBasePreciseShardingAlgorithm
key-generator:
column: id
type: SNOWFLAKE
tableStrategy: #表精确分片策略
standard:
shardingColumn: company_id
preciseAlgorithmClassName: com.example.ftserver.sharding.shardingalgrithm.precise.FtTablePreciseShardingAlgorithm
主配置文件application.yml需要引入自定义的分片配置:
简单的精确算法示例
在该demo中, 需要根据用户Id(user_id%2)取模来分库,尾数为奇数的去到数据库:ftdb1,尾数为偶数的去数据库:ftdb0;且还需要根据公司Id(company_id%2)取模,尾数为奇数的去到b_order1,尾数为偶数的去b_order0。其他的自定义算法可以根据自身实际情况重新编写
数据库分片算法示例:
package com.example.ftserver.sharding.shardingalgrithm.precise;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.springframework.stereotype.Component;
import java.util.Collection;
/**
* @author: zyh
* @description: 自定义精确分片算法测试:数据库分片,根据userId精确分片
*
*/
@Component
@Slf4j
public class FtDataBasePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Integer> {
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Integer> preciseShardingValue) {
// 获取分片键的值
Integer userId = preciseShardingValue.getValue();
String logicTableName = preciseShardingValue.getLogicTableName();
log.info("分片的userId: {},逻辑表名 logicTableName: {}",userId,logicTableName);
// 根据userId对数据库进行了分片,这里可以根据实际情况进行修改
String dataSourceName = "ftds" + (userId % 2); // 我们有两个数据源,这里取模得到分片结果
log.info("分片结果 dataSourceName: {}",dataSourceName);
// 返回对应的数据源名称
for (String name : collection) {
if (name.endsWith(String.valueOf(userId % 2))) {
return name;
}
}
return dataSourceName;
}
}
精确分表示例代码:
package com.example.ftserver.sharding.shardingalgrithm.precise;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Objects;
/**
* @author: zyh
* @description: 自定义精确分片算法测试:表分片,根据company_id精确分片
*
*/
@Component
@Slf4j
public class FtTablePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
// 获取分片的公司Id
long companyId = 0L;
if (Objects.nonNull(preciseShardingValue.getValue())) {
if (preciseShardingValue.getValue() instanceof Long) {
companyId = preciseShardingValue.getValue();
} else {
// 不知道为何返回的是Integer
// 这里需要转换成Long,否则会有异常
String value = String.valueOf(preciseShardingValue.getValue());
companyId=Long.parseLong(value);
}
}
// 模拟根据分片键的值取模,可以使用其他算法
long result = companyId % 2;
// 获取逻辑表名
String logicTableName = preciseShardingValue.getLogicTableName();
// 实际表名
String physicalTableName = logicTableName + result;
log.info("分片键的值:{},物理表名:{}", companyId, physicalTableName);
if (collection.contains(physicalTableName)) {
return physicalTableName;
}
throw new UnsupportedOperationException("未找到对应的分表信息,表:"+logicTableName+",分片键的值:"+companyId);
}
}
测试代码及结果:
package com.example.ftserver;
import cn.hutool.core.util.RandomUtil;
import com.example.ftserver.mapper.BOrderMapper;
import com.test.ft.common.entity.BOrderEntity;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Repeat;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest()
public class CustomPreciseShardingTest {
@Autowired
private BOrderMapper mapper;
@Test
@Repeat(4) // SpringBoot提供的重复执行注解,该配置相当于重复执行4次
public void testCustomPreciseSharding(){
BOrderEntity entity = new BOrderEntity();
entity.setIsDel(false);
// Hutool提供的随机数工具类
entity.setCompanyId(RandomUtil.randomInt(100,200));
entity.setPositionId(RandomUtil.randomLong(5,10));
entity.setUserId(RandomUtil.randomInt(2,5));
entity.setPublishUserId(101);
entity.setResumeType(1);
entity.setStatus("Auto");
entity.setWorkYear(String.valueOf(RandomUtil.randomInt(2)));
entity.setName("数据库运维工程师");
entity.setPositionName("数据库运维工程师");
entity.setResumeId(RandomUtil.randomInt(5));
int insert = mapper.insert(entity);
System.out.println("insert = "+ insert);
}
}
执行结果:
可以看到对应的数据已经根据设置的分片插入了不同的数据库
数据库数据:
精确分片查询注意事项:
1.根据Id查询时
可以看到,在每个库,每个表里面都做了一次查询,这是因为b_order表的Id没有作为分片键,走了全路由查询
2.使用库或者表分片键作为精确查询条件时:
使用表分片键查询:
使用库分片键查询时:
可以看到在使用库或者表分片键作为精确查询条件,只会执行该分片键相对应的查询路径
3.使用分库和分表的分片键查询:
可以看到SQL查询,走了全部的分库分表的路径查询
结论: 由此测试可以得到结论,在执行了精确分库分表算法之后,在查询之前,最好根据对应的分库分表算法,对查询的参数根据算法预处理和封装,以实现最佳的查询效果