问题背景

业务组在最开始的数据库规划中,通过对未来数据规模的预估后,一开始就进行了分库分表的设计。简单的说,一个业务分为16个库,其中每个库有64张业务表…过去经常使用的和数据库交互的经验就完全派不上用场了,难道真的丑陋的在代码中组装16个连接然后每个语句都去拼接吗?对不起做不到。

将自己的需求梳理清楚后可以看清楚,我需要的是一个不涉及原数据库的改动(不通过增加中间件),可以简单封装我的查询请求发送到所有分库分表中,让代码简洁好看(虽然实际上可能真的要做n * 16个数据库连接)直接一次能查询 16个库 * 64张表 的一个依赖,通过谷歌后,发现了一个叫做sharding-jdbc的开源完全贴合我的需求。

工具介绍

首先放上github的链接sharding-jdbc

这边抄袭一段介绍来凑字数:

Sharding-JDBC直接封装JDBC API,可以理解为增强版的JDBC驱动,旧代码迁移成本几乎为零:

可适用于任何基于java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。

可基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid等。

理论上可支持任意实现JDBC规范的数据库。虽然目前仅支持MySQL,但已有支持Oracle,SQLServer,DB2等数据库的计划

事实上给我的使用体验还不错,当然也是因为我的查询语句是最简单的普通查询语句,并没有涉及到太复杂的操作。翻阅issues的时候还是能看到这种第三方的依赖对于一些复杂查询的支持不足。但现在已经完全满足我的需求了。

操作步骤

正常的一个ORM框架查询数据库的应用是比较固定和规范的,由DAO层来负责数据库的沟通,本次我的应用简单的使用了Mybatis来做和Mysql的沟通。DAO层代码简单如下

public interface MtxxTagMapper {

@Select("select tag_id,tag_name,uid,`desc`,status from tb_tag where tag_id = #{tag_id}")

public Map findTagByTagId(@Param("tag_id") String tag_id);

}

是的就是这么简单操作。但是最大的麻烦就在于,并没有tb_tab表,或者人人都是tb_tag表。根据tag_id进行hash过后,tb_tag表被横向分布在16个库和64张表中,这条语句其实是无法执行的。所以重点不在DAO层而在于配置层,这也应了上面的介绍的话

Sharding-JDBC直接封装JDBC API,可以理解为增强版的JDBC驱动,旧代码迁移成本几乎为零

确实在查询层几乎不需要怎么动,不过复杂应用另说吧。

重点其实在于,数据源的配置,这里我使用了javaconfig的方式来配置,代码如下

@Data

@Configuration

@ConfigurationProperties(prefix = "spring.datasource.feed")

@MapperScan(basePackages = "com.meitu.dump.mapper.mtxx.tag", sqlSessionFactoryRef = "mtxxTagSqlSessionFactory")

public class MtxxTagDataSourceConfig {

private String url;

private String username;

private String password;

@Bean(name = "mtxxTagDataSource")

public DataSource getDataSource() throws SQLException {

//设置分库映射

Map dataSourceMap = new HashMap<>(16);

dataSourceMap.put("xiuxiu_feed_0", mybatisDataSource("xiuxiu_feed_0"));

dataSourceMap.put("xiuxiu_feed_1", mybatisDataSource("xiuxiu_feed_1"));

dataSourceMap.put("xiuxiu_feed_2", mybatisDataSource("xiuxiu_feed_2"));

dataSourceMap.put("xiuxiu_feed_3", mybatisDataSource("xiuxiu_feed_3"));

dataSourceMap.put("xiuxiu_feed_4", mybatisDataSource("xiuxiu_feed_4"));

dataSourceMap.put("xiuxiu_feed_5", mybatisDataSource("xiuxiu_feed_5"));

dataSourceMap.put("xiuxiu_feed_6", mybatisDataSource("xiuxiu_feed_6"));

dataSourceMap.put("xiuxiu_feed_7", mybatisDataSource("xiuxiu_feed_7"));

dataSourceMap.put("xiuxiu_feed_8", mybatisDataSource("xiuxiu_feed_8"));

dataSourceMap.put("xiuxiu_feed_9", mybatisDataSource("xiuxiu_feed_9"));

dataSourceMap.put("xiuxiu_feed_10", mybatisDataSource("xiuxiu_feed_10"));

dataSourceMap.put("xiuxiu_feed_11", mybatisDataSource("xiuxiu_feed_11"));

dataSourceMap.put("xiuxiu_feed_12", mybatisDataSource("xiuxiu_feed_12"));

dataSourceMap.put("xiuxiu_feed_13", mybatisDataSource("xiuxiu_feed_13"));

dataSourceMap.put("xiuxiu_feed_14", mybatisDataSource("xiuxiu_feed_14"));

dataSourceMap.put("xiuxiu_feed_15", mybatisDataSource("xiuxiu_feed_15"));

List actualTables = new ArrayList<>();

for(int i = 0; i < XiuXiuTableItemHelper.tableNums; i++){

actualTables.add(String.format("tb_tag_%s", Integer.toString(i)));

}

//设置默认库,两个库以上时必须设置默认库。默认库的数据源名称必须是dataSourceMap的key之一

DataSourceRule dataSourceRule = new DataSourceRule(dataSourceMap, "xiuxiu_feed_0");

//设置分表映射

TableRule userTableRule = TableRule.builder("tb_tag")

.generateKeyColumn("tag_id") //将user_id作为分布式主键

.actualTables(actualTables)

.dataSourceRule(dataSourceRule)

.build();

//具体分库分表策略

ShardingRule shardingRule = ShardingRule.builder()

.dataSourceRule(dataSourceRule)

.tableRules(Collections.singletonList(userTableRule))

.databaseShardingStrategy(new DatabaseShardingStrategy("tag_id", new ModuloDatabaseShardingAlgorithm()))

.tableShardingStrategy(new TableShardingStrategy("tag_id", new ModuloTableShardingAlgorithm()))

.build();

DataSource dataSource = ShardingDataSourceFactory.createDataSource(shardingRule);

//return new ShardingDataSource(shardingRule);

return dataSource;

}

private DataSource mybatisDataSource(final String dataSourceName) throws SQLException {

DruidDataSource dataSource = new DruidDataSource();

dataSource.setDriverClassName("com.mysql.jdbc.Driver");

dataSource.setUrl(String.format(url, dataSourceName));

dataSource.setUsername(username);

dataSource.setPassword(password);

/* 配置初始化大小、最小、最大 */

dataSource.setInitialSize(1);

dataSource.setMinIdle(1);

dataSource.setMaxActive(20);

/* 配置获取连接等待超时的时间 */

dataSource.setMaxWait(60000);

/* 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */

dataSource.setTimeBetweenEvictionRunsMillis(60000);

/* 配置一个连接在池中最小生存的时间,单位是毫秒 */

dataSource.setMinEvictableIdleTimeMillis(300000);

dataSource.setValidationQuery("SELECT 'x'");

dataSource.setTestWhileIdle(true);

dataSource.setTestOnBorrow(false);

dataSource.setTestOnReturn(false);

/* 打开PSCache,并且指定每个连接上PSCache的大小。

如果用Oracle,则把poolPreparedStatements配置为true,

mysql可以配置为false。分库分表较多的数据库,建议配置为false */

dataSource.setPoolPreparedStatements(false);

dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);

/* 配置监控统计拦截的filters */

// dataSource.setFilters("stat,wall,log4j");

return dataSource;

}

/**

* Sharding-jdbc的事务支持

*

* @return

*/

@Bean(name = "mtxxTagTransactionManager")

public DataSourceTransactionManager mybatisTransactionManager(@Qualifier("mtxxTagDataSource") DataSource dataSource) throws SQLException {

return new DataSourceTransactionManager(dataSource);

}

@Bean(name = "mtxxTagSqlSessionFactory")

public SqlSessionFactory mybatisSqlSessionFactory(@Qualifier("mtxxTagDataSource") DataSource mybatisDataSource)

throws Exception {

final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();

sessionFactory.setDataSource(mybatisDataSource);

return sessionFactory.getObject();

}

}

在这里我确实配置了16个数据库的链接,不过是通过工厂方式生成的。对于shadring-jdbc来说,他引申出的概念叫做逻辑库和逻辑表。在配置中描述逻辑库和逻辑表的生成规则,然后以后用查询逻辑库与逻辑表来代替。我们看看是在哪里描述这个逻辑

//设置分表映射

TableRule userTableRule = TableRule.builder("tb_tag")

.generateKeyColumn("tag_id") //将user_id作为分布式主键

.actualTables(actualTables)

.dataSourceRule(dataSourceRule)

.build();

//具体分库分表策略

ShardingRule shardingRule = ShardingRule.builder()

.dataSourceRule(dataSourceRule)

.tableRules(Collections.singletonList(userTableRule))

.databaseShardingStrategy(new DatabaseShardingStrategy("tag_id", new ModuloDatabaseShardingAlgorithm()))

.tableShardingStrategy(new TableShardingStrategy("tag_id", new ModuloTableShardingAlgorithm()))

.build();

分表映射规则采用了链式生产对象的方式来描述,generateKeyColumn指定了虚拟表的主键,actualTables用来存储所有真实表的名字。

分库映射规则除了包含分表规则以外,更重要的是指定了如何计算分库和分表尾缀的逻辑。方法databaseShardingStrategy(new DatabaseShardingStrategy("tag_id", new ModuloDatabaseShardingAlgorithm()))指定了分库的逻辑类,方法.tableShardingStrategy(new TableShardingStrategy("tag_id", new ModuloTableShardingAlgorithm()))指定了分表的逻辑类。而这两个类的逻辑由我们自己继承接口实现而得。

再看分库实现类

@Slf4j

public class ModuloDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm {

@Override

public String doEqualSharding(Collection collection, ShardingValue shardingValue) {

int databaseNum = XiuXiuTableItemHelper.getDatabase(shardingValue.getValue().toString());

for (Object database : collection) {

if (database.toString().endsWith("_" + Integer.toString(databaseNum))) {

return database.toString();

}

}

return null;

}

@Override

public Collection doInSharding(Collection collection, ShardingValue shardingValue) {

return null;

}

@Override

public Collection doBetweenSharding(Collection collection, ShardingValue shardingValue) {

return null;

}

}

类只要实现了SingleKeyDatabaseShardingAlgorithm即可作为逻辑类使用,3个实现方法分别是对应sql的一些语句使用,目前我只实现了doEqualSharding方法,我的语句where xx =就会执行这个方法来获得具体库名,在逻辑更复杂的情况下旧需要考虑另外2个方法的实现。

public String doEqualSharding(Collection collection, ShardingValue shardingValue)方法中,collection是所有真实存在的库名,也就是刚刚配置代码中我们注册进去的16个库名的集合,而shardingValue指的是我们设置用来分片的字段,generateKeyColumn("tag_id")。根据业务,我需要通过对tag_id进行hash后得到在[0-15]中对应的数据做为库名尾缀,所以我的逻辑就变成这样:

循环所有真实库名

真实库名是否包含 "_n"(n为计算分库算法得到的尾缀)

返回真实库名

到此就完成了一个简单的分库查询

jdbc shadring 扩容_shadring-jdbc解决查询数据库分库分表的问题相关推荐

  1. 数据库分库分表和带来的唯一ID、分页查询问题的解决

    数据库分库分表和带来的唯一ID.分页查询问题的解决 参考文章: (1)数据库分库分表和带来的唯一ID.分页查询问题的解决 (2)https://www.cnblogs.com/hanzhong/p/1 ...

  2. 数据库分库分表(sharding)系列(五) 一种支持自由规划无须数据迁移和修改路由代码的Sharding扩容方案...

    为什么80%的码农都做不了架构师?>>>    版权声明:本文由本人撰写并发表于2012年9月份的<程序员>杂志,原文题目<一种支持自由规划的Sharding扩容方 ...

  3. Mycat - 数据库分库分表中间件,国内最活跃的、性能最好的开源数据库中间件

    转载自 Mycat - 数据库分库分表中间件,国内最活跃的.性能最好的开源数据库中间件 Mycat是什么 Mycat - 数据库分库分表中间件,国内最活跃的.性能最好的开源数据库中间件! 一个彻底开源 ...

  4. 数据库分库分表中间件对比(很全)

    数据库(分库分表)中间件对比 分区:对业务透明,分区只不过把存放数据的文件分成了许多小块,例如mysql中的一张表对应三个文件.MYD,MYI,frm. 根据一定的规则把数据文件(MYD)和索引文件( ...

  5. 亿级流量网站架构核心技术之“数据库分库分表策略”

    本文节选自<亿级流量网站架构核心技术--跟开涛学搭建高可用高并发系统>一书 张开涛 著 电子工业出版社出版 小编会从留言中选择获赞最多的前五名用户免费送出此书哦!规则见文末. 数据库分库分 ...

  6. 一文搞懂MySQL数据库分库分表

    如果数据量过大,大家一般会分库分表.分库需要注意的内容比较少,但分表需要注意的内容就多了. 工作这几年没遇过数据量特别大的业务,那些过亿的数据,因为索引设置合理,单表性能没有影响,所以实战中一直没用过 ...

  7. 数据库分库分表(sharding)系列(三) 关于使用框架还是自主开发以及sharding实现层面的考量...

    当团队对系统业务和数据库进行了细致的梳理,确定了切分方案后,接下来的问题就是如何去实现切分方案了,目前在sharding方面有不少的开源框架和产 品可供参考,同时很多团队也会选择自主开发实现,而不管是 ...

  8. 别再问什么是数据库分库分表了,看这里!

    编者语:为了避免被误解为:「手里有把锤子,看什么都是钉子!」,说明一下不是什么业务都适合分布式数据库,更不是用了分布式数据库性能就一定能得到扩展. 其次:本文为纯干货,建议先转发.收藏再观看. 分布式 ...

  9. 数据库分库分表,分片配置轻松入门!

    上次和大伙聊了 MyCat 的安装,今天来说一个新的话题,就是数据库的分片. 当我们把 MyCat + MySQL 的架构搭建完成之后,接下来面临的一个问题就是,数据库的分片规则:有那么多 MySQL ...

最新文章

  1. spdlog源码阅读 (1): sinks
  2. .net firamework 框架里面的控件的继承关系。
  3. nginx配合modsecurity实现WAF功能
  4. oracle12c分页,ArcSDE10.2.1使用Oracle12c新特性分页
  5. 一篇很全面的freemarker 前端web教程
  6. sqlserver使用存储过程发送http请求
  7. android 手动 打包,android 手动打包apk
  8. 下载Java Jar包的网站(托管厂库)
  9. 12-基于selenium实现12306模拟登录,及京东登录滑动缺口验证模拟登录
  10. C++中vector作为参数的三种传参方式
  11. linux每天一小步---sed命令详解
  12. 鸿蒙系统的升级名单,定档6月2日!鸿蒙“首批”升级名单公布,共计11款华为机型!...
  13. react-native 解决“Could not get BatchedBridge...” 的问题
  14. 《概念与类比》:侯世达的双翼
  15. hsqldb mysql_HSQLDB的研究与性能测试(与Mysql对比)
  16. 线性代数知识点(行列式篇)
  17. VBA 获取某列最后非空单元格的行数
  18. python闰年统计_利用Python写一个闰年计算器和每月天数计算器
  19. 深度学习经典试题29道
  20. ViewPager(一屏多页、无限滑动、自动切换)

热门文章

  1. 最新分布式存储解决方案zData将于闪存论坛上正式发布!
  2. 损失函数及对应的任务(待续)
  3. 「 神器 」绝不简单的截图神器
  4. DVWA-XSS(Reflected) 全级别教程
  5. java lisp_AI编程:5种最流行的人工智能编程语言!
  6. 多目标优化问题及求解
  7. 一个好的软件,除了给我们带来效率,更重要的是为我们带来了快乐!
  8. linux、FTP中查看隐藏文件夹(.pm2等是隐藏的)
  9. 网站运营中长尾关键词优化策略
  10. 第一Python第一个爬虫项目