Sharding-JDBC分库分表
一、 背景
在互联网海量数据时代的今天,我们需要存储的数据也越来越多,在使用关系型数据库例如mysql等时,单表所需要存储的数据也越来越多,但是关系型数据库在单表数据库量较大的情况下,单表性能会急剧下降,面对这个问题,常见的做法就是进行分表,但是单纯的分表只能减少单表的压力,不能减轻数据库的压力,所以在分表的同时往往也会进行分库操作。
二、分库分表
分库分表主要是为了解决互联网应用的大数据量存储问题,分表通常分为:垂直划分、水平划分;垂直划分通常是根据业务场景将一个多字段大表拆分成多个多个少字段小表,水平划分是根据某个分片策略将同一个表的数据分开存到多个相同结构表中。
三、sharding-jdbc核心概念
1、ShardingSphere简介
sharding-jdbc是ShardingSphere的其中一个模块,摘抄官网一段简介:
(官方中文文档:https://shardingsphere.apache.org/document/current/cn/overview/)
ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。
ShardingSphere定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 它与NoSQL和NewSQL是并存而非互斥的关系。NoSQL和NewSQL作为新技术探索的前沿,放眼未来,拥抱变化,是非常值得推荐的。反之,也可以用另一种思路看待问题,放眼未来,关注不变的东西,进而抓住事物本质。 关系型数据库当今依然占有巨大市场,是各个公司核心业务的基石,未来也难于撼动,我们目前阶段更加关注在原有基础上的增量,而非颠覆。
2、sharding-jdbc简介
定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
- 适用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
- 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
- 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL。
功能列表:
数据分片
- 分库 & 分表
- 读写分离
- 分片策略定制化
- 无中心化分布式主键
分布式事务
- 标准化事务接口
- XA强一致事务
- 柔性事务
数据库治理
- 配置动态化
- 编排 & 治理
- 数据脱敏
- 可视化链路追踪
- 弹性伸缩(规划中)
3、sharding-jdbc核心概念
- 逻辑表(LogicTable):进行水平拆分的时候同一类型(逻辑、数据结构相同)的表的总称。例:订单数据根据主键尾数拆分为10张表,分别是t_order_0到t_order_9,他们的逻辑表名为t_order。
- 真实表(ActualTable):在分片的数据库中真实存在的物理表。即上个示例中的t_order_0到t_order_9。
- 数据节点(DataNode):数据分片的最小单元。由数据源名称和数据表组成,例:ds_0.t_order_0。
- 动态表(DynamicTable):逻辑表和物理表不一定需要在配置规则中静态配置。如,按照日期分片的场景,物理表的名称随着时间的推移会产生变化。
- 绑定表(BindingTable):指分片规则一致的主表和子表。
官方图:

Sharding-JDBC可以通过Java,YAML,Spring命名空间和Spring Boot Starter四种方式配置,开发者可根据场景选择适合的配置方式。
下为spring命名空间示例:
<!-- 配置两个数据源 --> <bean id="ds_0" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close"> <property name="driverClassName" value="${pool.driver}" /><!-- 无需指定,除非系统无法自动识别 --> <property name="jdbcUrl" value="${pool.url}" /> <property name="username" value="${pool.username}" /> <property name="password" value="${pool.password}" /> <!-- 连接只读数据库时配置为true, 保证安全 --> <property name="readOnly" value="false" /> <!-- 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 缺省:30秒 --> <property name="connectionTimeout" value="30000" /> <!-- 一个连接idle状态的最大时长(毫秒),超时则被释放(retired),缺省:10分钟 --> <property name="idleTimeout" value="6000000" /> <!-- 一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒,参考MySQL wait_timeout参数(show variables like '%timeout%';) --> <property name="maxLifetime" value="28700000" /> <!-- 连接池中允许的最大连接数。缺省值:10;推荐的公式:((core_count * 2) + effective_spindle_count) --> <property name="maximumPoolSize" value="${maximum.pool.size.one:50}" /> </bean> <bean id="ds_1" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close"> <property name="driverClassName" value="${pool.driver}" /><!-- 无需指定,除非系统无法自动识别 --> <property name="jdbcUrl" value="${pool.url_1}" /> <property name="username" value="${pool.username}" /> <property name="password" value="${pool.password}" /> <!-- 连接只读数据库时配置为true, 保证安全 --> <property name="readOnly" value="false" /> <!-- 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 缺省:30秒 --> <property name="connectionTimeout" value="30000" /> <!-- 一个连接idle状态的最大时长(毫秒),超时则被释放(retired),缺省:10分钟 --> <property name="idleTimeout" value="6000000" /> <!-- 一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒,参考MySQL wait_timeout参数(show variables like '%timeout%';) --> <property name="maxLifetime" value="28700000" /> <!-- 连接池中允许的最大连接数。缺省值:10;推荐的公式:((core_count * 2) + effective_spindle_count) --> <property name="maximumPoolSize" value="${maximum.pool.size.two:20}" /> </bean>
<bean id="orderDataBaseShardingAlgorithm" class="com.jms.db.base.OrderDataBaseShardingAlgorithm"/> <!-- 配置分库策略bean --> <bean id="orderTableShardingAlgorithm" class="com.jms.db.base.OrderTableShardingAlgorithm"/> <!-- 配置分表策略bean --> <sharding:complex-strategy algorithm-ref="orderDataBaseShardingAlgorithm" sharding-columns="order_id" id="ds_algorithm"/> <!-- --> <sharding:complex-strategy algorithm-ref="orderTableShardingAlgorithm" sharding-columns="order_id" id="tb_algorithm"/> <sharding:data-source id="shardingDataSource"><!-- default-database-strategy-ref 默认数据库分片策略,对应<sharding:xxx-strategy>中的策略Id,缺省表示不分库 default-table-strategy-ref默认表分片策略,对应<sharding:xxx-strategy>中的策略Id,缺省表示不分表--> <sharding:sharding-rule data-source-names="ds_0, ds_1" default-data-source-name="ds_0" default-database-strategy-ref="ds_algorithm" default-table-strategy-ref="tb_algorithm"> <sharding:table-rules><!-- 行表达式标识符建议使用$->{0..1} ds_0.order_0,ds_0.order_1--> <sharding:table-rule logic-table="deleted_order_info" actual-data-nodes="ds_0.deleted_order_info_$->{2..3},ds_1.deleted_order_info_$->{0..1}"/><!-- logic-table:逻辑表 --> <sharding:table-rule logic-table="order_bill_fee" actual-data-nodes="ds_0.order_bill_fee_$->{2..3},ds_1.order_bill_fee_$->{0..1}" /> </sharding:table-rules> <sharding:binding-table-rules> <!--绑定表之间的多表关联查询不会出现笛卡尔积关联 --> <sharding:binding-table-rule logic-tables="order_goods,order_info"/> </sharding:binding-table-rules> </sharding:sharding-rule> <sharding:props> <prop key="sql.show">false</prop> </sharding:props> </sharding:data-source>
分库策略代码:
实现ComplexKeysShardingAlgorithm接口 重写doSharding方法:
package com.jms.db.base; import java.util.Collection; import java.util.HashSet; import java.util.Map; import org.apache.commons.beanutils.ConvertUtils; import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingAlgorithm; import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingValue; import com.google.common.collect.Range; import com.jms.common.util.CommonUtils; /** * 分库算法 * @author Administrator * */ public class OrderDataBaseShardingAlgorithm implements ComplexKeysShardingAlgorithm{ private static Range group0 = Range.closed(1l, 39999999l); private static Range group1 = Range.closed(40000000l, 79999999l); private static Range group2 = Range.closed(80000000l, 119999999l); private static Range group3 = Range.closed(120000000l, 159999999l); private static Range group4 = Range.closed(160000000l, 199999999l); /** * 0库里面存最新的 * 库编号越大数据越老 * 1-3999万 1库 * 4000-7999 1库 * 8000-11999 0库无后缀 * 12000-15999 0库无后缀 */ @Override public Collection<String> doSharding(Collection availableTargetNames, ComplexKeysShardingValue shardingValue) { Collection<String> routTables = new HashSet<String>(); //用于between,基本不用 Map<String, Range> rangMap=shardingValue.getColumnNameAndRangeValuesMap(); if(CommonUtils.isNotEmpty(rangMap)){//不为空判断 Range range=rangMap.get("order_id"); if(range!=null){ if(range.isConnected(group0)){ routTables.add("ds_1"); } if(range.isConnected(group1)){ routTables.add("ds_1"); } if(range.isConnected(group2)){ routTables.add("ds_0"); } if(range.isConnected(group3)){ routTables.add("ds_0"); } if(range.isConnected(group4)){ routTables.add("ds_0"); } } } //用于= 或者 in Map<String, Collection> shardingMap=shardingValue.getColumnNameAndShardingValuesMap(); if(CommonUtils.isNotEmpty(shardingMap)){//不为空判断 Collection collection=shardingMap.get("order_id"); if(collection!=null){ for (Object object : collection) { Long order_id=Long.valueOf(ConvertUtils.convert(object)); if(group0.contains(order_id)){ routTables.add("ds_1"); } if(group1.contains(order_id)){ routTables.add("ds_1"); } if(group2.contains(order_id)){ routTables.add("ds_0"); } if(group3.contains(order_id)){ routTables.add("ds_0"); } if(group4.contains(order_id)){ routTables.add("ds_0"); } } } } return routTables; } }
分表策略代码:
package com.jms.db.base; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.zip.CRC32; import org.apache.commons.beanutils.ConvertUtils; import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingAlgorithm; import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingValue; import com.google.common.collect.Range; import com.jms.common.util.CommonUtils; /** * 分表算法 * */ public class OrderTableShardingAlgorithm implements ComplexKeysShardingAlgorithm{ private static Range group0 = Range.closed(1l, 39999999l); private static Range group1 = Range.closed(40000000l, 79999999l); private static Range group2 = Range.closed(80000000l, 119999999l); private static Range group3 = Range.closed(120000000l, 159999999l); private static Range group4 = Range.closed(160000000l, 199999999l); public static void main(String[] args) { System.out.println(group1.contains(91000000l)); } /** * 分割手动分表 * @param values 订单order_id * @return */ public static Map<String,List<Long>> split(List<Long> values){ Map<String,List<Long>> map = new HashMap<String, List<Long>>(); for (Long v : values) { String group = null; if(group0.contains(v)){ group = "group0"; } if(group1.contains(v)){ group = "group1"; } if(group2.contains(v)){ group = "group2"; } if(group3.contains(v)){ group = "group3"; } if(group4.contains(v)){ group = "group4"; } List<Long> temp = map.get(group); if(CommonUtils.isEmpty(temp)){ temp = new ArrayList<>(); map.put(group, temp); } temp.add(v); } return map; } /** * 字符串hash算法分表 * 分6个表 * 一个表一亿数据 * @param values * @return */ public static Map<String,List<String>> codeSplit(List<String> values){ Map<String,List<String>> map = new HashMap<>(); for (String v : values) { String group = crc32(v)%6+""; List<String> temp = map.get(group); if(CommonUtils.isEmpty(temp)){ temp = new ArrayList<>(); map.put(group, temp); } temp.add(v); } return map; } public static long crc32(String v){ CRC32 crc32 = new CRC32(); crc32.update(v.getBytes()); return Math.abs(crc32.getValue()); } /** * 数据越新编号越大 * 1-3999万 0表 * 4000-7999 1表 * 8000-11999 2表 * 12000-15999 3表 */ @Override public Collection<String> doSharding(Collection availableTargetNames, ComplexKeysShardingValue shardingValue) { Collection<String> routTables = new HashSet<String>(); //用于between,基本不用 Map<String, Range<Long>> rangMap=shardingValue.getColumnNameAndRangeValuesMap(); if(CommonUtils.isNotEmpty(rangMap)){//不为空判断 Range range=rangMap.get("order_id"); if(range!=null){ Collection<String> x=new HashSet<String>(); if(range.isConnected(group0)){ x.add(shardingValue.getLogicTableName()+"_0"); } if(range.isConnected(group1)){ x.add(shardingValue.getLogicTableName()+"_1"); } if(range.isConnected(group2)){ x.add(shardingValue.getLogicTableName()+"_2"); } if(range.isConnected(group3)){ x.add(shardingValue.getLogicTableName()+"_3"); } if(range.isConnected(group4)){ x.add(shardingValue.getLogicTableName()+"_4"); } for (String string : x) { if(availableTargetNames.contains(string)){ routTables.add(string); } } } } //用于= 或者 in Map<String, Collection> shardingMap=shardingValue.getColumnNameAndShardingValuesMap(); if(CommonUtils.isNotEmpty(shardingMap)){//不为空判断 Collection collection=shardingMap.get("order_id"); if(collection!=null){ Collection<String> x=new HashSet<String>(); for (Object object : collection) { Long order_id=Long.valueOf(ConvertUtils.convert(object)); if(group0.contains(order_id)){ x.add(shardingValue.getLogicTableName()+"_0"); } if(group1.contains(order_id)){ x.add(shardingValue.getLogicTableName()+"_1"); } if(group2.contains(order_id)){ x.add(shardingValue.getLogicTableName()+"_2"); } if(group3.contains(order_id)){ x.add(shardingValue.getLogicTableName()+"_3"); } if(group4.contains(order_id)){ x.add(shardingValue.getLogicTableName()+"_4"); } } for (String string : x) { if(availableTargetNames.contains(string)){ routTables.add(string); } } } } return routTables; } }
浙公网安备 33010602011771号