学习日志 211223 MySQL水平扩展
MySQL数据库横向扩展
===================
# 背景
- 解决方案的任意一层 都应该有水平扩展的能力
- 数据库水平扩展的方案很多, 但是大多是商业收费的
- sharding的优点是什么
- 可以水平扩展
- 基本不要钱
- sharding的缺点
- 严重依赖于sharding key
- 有sharding key限定的语句 和原有SQL区别不大
- 没有sharding key限定的语句 会丧失大多数SQL的能力
- 原子性等
- 由于大部分的分析功能是没有sharding key限定的 所以分析需求需要另找数据仓库的解决方案支持
- sharding是穷人的数据库层水平扩展方案
- 商业的数据库集群 往往是按装机量正比收费的
- 所以sharding能节省的费用 实际上是数据量越大 节省的越多
- 如果看很多软件的发展趋势
- 往往就是一个免费干爆收费的过程
- android和ios
- mysql
# 目标
- 选取一个解决方案
- insert sharding with key
- select sharding with key
- DDL sharding
- select sharding without key
- scale-out
# sharding的解决方案
- application aware的
- JDBC
- 缺点 连接比较多
- 每个datasource只有一个application可以使用
- proxy的
- proxy的缺点是有集群单点的问题
- 不满足全栈可水平扩展的要求
- 基于JDBC的方案
- 首先看一下Apache的方案
- https://shardingsphere.apache.org/
# 配置数据源
- 参考
- https://shardingsphere.apache.org/document/current/en/user-manual/shardingsphere-jdbc/java-api/rules/sharding/
## 目标
- 两个库 不分表
- id做键, 简单取余
## 流程
- 数据库建表
- 两个库的建表语句一致
- 配置一个基于shardingSphere的数据源
- 配置两个Hikari数据源作为原始数据源 分别连接到两个库实例
- 配置分表规则
- 配置表名格式 ShardingTableRuleConfiguration 部分
- 配置分库规则
- 配置算法 ShardingSphereAlgorithmConfiguration 部分
- (后续构造总规则时会把底层数据源列表传入)
- 配置主键生成器 (未验证)
- ShardingSphereAlgorithmConfiguration 部分
- 其它配置
- 显示SQL到日志
- 构建sharding数据源
- 底层数据源列表(实际上是map)
- 规则列表(目前只有sharding规则)
- 其它配置
- 说明
- 原始示例中还有很多其它有用的配置, 如其它规则等
- 后续用到了再专门讲
- 配置mybatis使用这个新的数据源
- 显示配置以下三项
- SqlSessionFactory
- DataSourceTransactionManager
- SqlSessionTemplate
- 另外, 使用 MapperScan 注解的参数 指定对应的Dao使用新数据源
- sqlSessionTemplateRef 部分
- 完整源码如下
```
@Configuration
@MapperScan(basePackages = "org.kaien.springbootdemo.dao", sqlSessionTemplateRef = "mySessionTemplate")
public class DBConfiguration {
@Bean
@Primary
public DataSource shardingDataSource() throws SQLException {
Map<String, DataSource> dataSourceMap = new HashMap<>();
// Configure the 1st data source
HikariDataSource dataSource0 = new HikariDataSource();
dataSource0.setDriverClassName("com.mysql.jdbc.Driver");
dataSource0.setJdbcUrl("jdbc:mysql://mycluster-mysql-0.mycluster-mysql:3306/test_db");
dataSource0.setUsername("root");
dataSource0.setPassword("Mysql123");
dataSourceMap.put("ds_0", dataSource0);
// Configure the 2nd data source
HikariDataSource dataSource1 = new HikariDataSource();
dataSource1.setDriverClassName("com.mysql.jdbc.Driver");
dataSource1.setJdbcUrl("jdbc:mysql://mycluster-mysql-1.mycluster-mysql:3306/test_db");
dataSource1.setUsername("root");
dataSource1.setPassword("Mysql123");
dataSourceMap.put("ds_1", dataSource1);
// 表配置
ShardingRuleConfiguration shardingRuleConfiguration = new ShardingRuleConfiguration();
ShardingTableRuleConfiguration table = new ShardingTableRuleConfiguration("test_doc",
"ds_${0..1}.test_doc");
shardingRuleConfiguration.getTables().add(table);
shardingRuleConfiguration.getBindingTableGroups().add("test_doc");
// 数据库配置
Properties defaultModShardingAlgorithm = new Properties();
defaultModShardingAlgorithm.setProperty("sharding-count", "2");
shardingRuleConfiguration.getShardingAlgorithms().putIfAbsent("defaultModShardingAlgorithm",
new ShardingSphereAlgorithmConfiguration("MOD", defaultModShardingAlgorithm));
ShardingStrategyConfiguration defaultDatabaseShardingStrategy = new StandardShardingStrategyConfiguration(
"id",
"defaultModShardingAlgorithm"
);
shardingRuleConfiguration.setDefaultDatabaseShardingStrategy(defaultDatabaseShardingStrategy);
// 主键生成配置
Properties snowflakeProperties = new Properties();
snowflakeProperties.setProperty("worker-id", "123"); // ? TODO
shardingRuleConfiguration.getKeyGenerators().put("snowflake", new ShardingSphereAlgorithmConfiguration(
"SNOWFLAKE",
snowflakeProperties
));
KeyGenerateStrategyConfiguration defaultKeyGenerateStrategy = new KeyGenerateStrategyConfiguration(
"id",
"snowflake"
);
shardingRuleConfiguration.setDefaultKeyGenerateStrategy(defaultKeyGenerateStrategy);
// 其它配置
Properties otherProperties = new Properties();
otherProperties.setProperty("sql-show", "true");
DataSource dataSource = ShardingSphereDataSourceFactory.createDataSource(
dataSourceMap,
Collections.singleton(shardingRuleConfiguration),
otherProperties);
return dataSource;
}
@Bean(name = "mySessionFactory")
@Primary
public SqlSessionFactory mySessionFactory(@Qualifier("shardingDataSource") DataSource shardingDataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(shardingDataSource);
return bean.getObject();
}
@Bean(name = "myTransactionManager")
@Primary
public DataSourceTransactionManager myTransactionManager(@Qualifier("shardingDataSource") DataSource shardingDataSource) {
return new DataSourceTransactionManager(shardingDataSource);
}
@Bean(name = "mySessionTemplate")
@Primary
public SqlSessionTemplate mySessionTemplate(@Qualifier("mySessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
```
- 检查
- 编写dao层 实现queryById
- `select ... where id = #{id}`
- 再编写一个controller 调用dao层
- 由path传入id 形如
- `@RequestMapping(value = "/doc/{id}", method = RequestMethod.GET)`
- 从web访问 尝试id取奇数 或 偶数 看日志分别如下
```
Actual SQL: ds_1 ::: select id, gmt_create, gmt_modify, doc from test_doc where id = ? ::: [1]
```
或者
```
Actual SQL: ds_0 ::: select id, gmt_create, gmt_modify, doc from test_doc where id = ? ::: [2]
```
## Q&A
- Q: 报SQL错误
- A: mybatis变量写成了 #id, 实际上应为 #{id}
- 这次特别顺利 没报什么其它的错误 如果读者有相关的错误欢迎评论补充
# insert sharding with key TODO