这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战
背景
当遇到数据库表不断增大,一个接口查询的数据已经超过500ms了,就需要进行分库分表来保证查询效率了
问题
值得注意的是数据库表非常庞大时,而且主键ID不同库表时需要考虑主键ID唯一性问题。还有跨库join的问题
方案
目前分库分表有很多非常好的方案
- 框架层:通过实现一些拦截器(比如
Mybatis的Interceptor接口),增加一些自定义解析来控制数据的流向 - 驱动层的包括:
TDDL、ShardingJDBC - 代理层:
MySQL Router、MyCat
下面用ShardingJDBC的代码实现来看下分库分表在项目中如何使用。
代码实现
引入依赖
首先需要引入ShardingJDBC的maven依赖
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>1.5.4</version>
</dependency>
排除默认数据源
由于ShardingJDBC是在数据源上边做分库分表,所以排除原有的默认数据源
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
分库规则设置
设置分库策略为键值大于30000走db0,小与等于30000走db1
@Component
public class DbSharding implements SingleKeyDatabaseShardingAlgorithm<Integer> {
@Autowired
private Db0Config db0Config;
@Autowired
private Db1Config db1Config;
@Override
public String doEqualSharding(Collection<String> availableTargetNames, ShardingValue<Integer> shardingValue) {
Long value = shardingValue.getValue();
if (value > 30000) {
return db0Config.getDatabaseName();
} else {
return db1Config.getDatabaseName();
}
}
}
分表规则设置
设置分表规则,按奇数偶数分
@Component
public class TbSharding implements SingleKeyTableShardingAlgorithm<Integer> {
@Override
public String doEqualSharding(final Collection<String> tableNames, final ShardingValue<Integer> shardingValue) {
for (String table : tableNames) {
if (table.endsWith(shardingValue % 2 + "")) {
return table;
}
}
throw new IllegalArgumentException();
}
}
分库分表策略配置
采用均匀分布模式,最终数据分布如下:
db0
├── user_0
└── user_1
db1
├── user_0
└── user_1
最关键的代码!设置数据源的分库分表策略
@Configuration
public class DataSourceConfig {
@Autowired
private Db0Config db0Config;
@Autowired
private Db1Config db1Config;
@Autowired
private DbSharding dbSharding;
@Autowired
private TbSharding tbSharding;
@Bean
public DataSource getDataSource() throws SQLException {
return buildDataSource();
}
private DataSource buildDataSource() throws SQLException {
Map<String, DataSource> dataSourceMap = new HashMap<>(2);
// 添加数据源 db0和db1
dataSourceMap.put(db0Config.getDatabaseName(), db0Config.dataSource());
dataSourceMap.put(db1Config.getDatabaseName(), db1Config.dataSource());
// 设置db0为默认源
DataSourceRule dataSourceRule = new DataSourceRule(dataSourceMap, db0Config.getDatabaseName());
// 设置分表,将查询虚拟表user根据规则映射到真实表中去
TableRule userTableRule = TableRule.builder("user")
.actualTables(Arrays.asList("user_0", "user_1"))
.dataSourceRule(dataSourceRule)
.build();
// 分库分表策略
ShardingRule shardingRule = ShardingRule.builder()
.dataSourceRule(dataSourceRule)
.tableRules(Arrays.asList(userTableRule))
.databaseShardingStrategy(new DatabaseShardingStrategy("user_id", dbSharding))
.tableShardingStrategy(new TableShardingStrategy("id", tbSharding)).build();
DataSource dataSource = ShardingDataSourceFactory.createDataSource(shardingRule);
return dataSource;
}
@Bean
public KeyGenerator keyGenerator() {
return new DefaultKeyGenerator();
}
}
概念解释
TableRule
表规则配置对象,内嵌 TableRuleBuilder 对象进行创建。
数据单元
DataNode静态分库分表数据单元 数据分片的最小单元,由数据源名称和数据表组成。 例:ds_1.t_order_0。配置时默认各个分片数据库的表结构均相同,直接配置逻辑表和真实表对应关系即可。DynamicDataNode动态表的分库分表数据单元 逻辑表和真实表不一定需要在配置规则中静态配置。比如按照日期分片的场景,真实表的名称随着时间的推移会产生变化
TableRuleBuilder
TableRuleBuilder 调用 #build() 方法创建 TableRule
分库/分表策略
databaseShardingStrategy:分库策略tableShardingStrategy:分表策略
ShardingRule
分库分表规则配置对象,内嵌 ShardingRuleBuilder 对象进行创建。
dataSourceRule
dataSourceRule,数据源配置对象。ShardingRule需要数据源配置正确。这点和 TableRule 是不同的。TableRule 对 dataSourceRule 只使用数据源名字,最终执行SQL 使用数据源名字从 ShardingRule 获取数据源连接
主键生成
generateKeyColumn:主键字段keyGenerator:主键生成器
小结
本文简单的解释了为什么使用分库分表,以及分库分表的方案,以ShardingJDBC作为示例给出了一部分代码实现,并解释了其中的概念,文章中的部分内容参考各位大佬对分库分表的理解做出整理。