
最近项目中不少表的数据量越来越大,并且导致了一些数据库的性能问题。因此想借助一些分库分表的中间件,实现自动化分库分表实现。调研下来,发现Sharding-JDBC目前成熟度最高并且应用最广的Java分库分表的客户端组件。本文主要介绍一些Sharding-JDBC核心概念以及生产环境下的实战指南,旨在帮助组内成员快速了解Sharding-JDBC并且能够快速将其使用起来。Sharding-JDBC官方文档
在使用Sharding-JDBC之前,一定是先理解清楚下面几个核心概念。
水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:订单数据根据主键尾数拆分为10张表,分别是t_order_0到t_order_9,他们的逻辑表名为t_order。
在分片的数据库中真实存在的物理表。即上个示例中的t_order_0到t_order_9。
数据分片的最小单元。由数据源名称和数据表组成,例:ds_0.t_order_0。
指分片规则一致的主表和子表。例如:t_order表和t_order_item表,均按照order_ID分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。举例说明,如果sql为:
SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_ID=i.order_ID WHERE o.order_ID in (10,11);假设t_order和t_order_item对应的真实表各有2个,那么真实表就有t_order_0、t_order_1、t_order_item_0、t_order_item_1。在不配置绑定表关系时,假设分片键order_ID将数值10路由至第0片,将数值11路由至第1片,那么路由后的sql应该为4条,它们呈现为笛卡尔积:
SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_ID=i.order_ID WHERE o.order_ID in (10,11);SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_ID=i.order_ID WHERE o.order_ID in (10,11);SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_ID=i.order_ID WHERE o.order_ID in (10,11);SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_ID=i.order_ID WHERE o.order_ID in (10,11);在配置绑定表关系后,路由的sql应该为2条:
SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_ID=i.order_ID WHERE o.order_ID in (10,11);广播表指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。
数据分片分片键用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。 sql 中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,Sharding-JDBC 也支持根据多个字段进行分片。
分片算法通过分片算法将数据分片,支持通过=、>=、<=、>、<、BETWEEN和IN分片。 分片算法需要应用方开发者自行实现,可实现的灵活度非常高。
目前提供4种分片算法。 由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。
精确分片算法对应 PreciseShardingalgorithm,用于处理使用单一键作为分片键的 = 与 IN 进行分片的场景。需要配合 StandardShardingStrategy 使用。
对应 RangeShardingalgorithm,用于处理使用单一键作为分片键的 BETWEEN AND、>、<、>=、<=进行分片的场景。需要配合 StandardShardingStrategy 使用。
对应 ComplexKeysShardingalgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合 ComplexShardingStrategy 使用。
对应 HintShardingalgorithm,用于处理通过Hint指定分片值而非从sql中提取分片值的场景。需要配合 HintShardingStrategy 使用。
包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片 *** 作的是分片键 + 分片算法,也就是分片策略。目前提供 5 种分片策略。
标准分片策略对应 StandardShardingStrategy。提供对 SQ L语句中的 =,>,<,>=,<=,IN 和 BETWEEN AND 的分片 *** 作支持。 StandardShardingStrategy 只支持单分片键,提供 PreciseShardingalgorithm 和 RangeShardingalgorithm 两个分片算法。 PreciseShardingalgorithm 是必选的,用于处理 = 和 IN 的分片。 RangeShardingalgorithm 是可选的,用于处理 BETWEEN AND,<=分片,如果不配置 RangeShardingalgorithm,sql 中的 BETWEEN AND 将按照全库路由处理。
对应 ComplexShardingStrategy。复合分片策略。提供对 sql 语句中的 =,IN 和 BETWEEN AND 的分片 *** 作支持。 ComplexShardingStrategy 支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片 *** 作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。
对应 InlineshardingStrategy。使用 Groovy 的表达式,提供对 sql 语句中的 = 和 IN的分片 *** 作支持,只支持单分片键。 对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$->{u_ID % 8} 表示 t_user 表根据 u_ID 模 8,而分成 8 张表,表名称为 t_user_0 到 t_user_7。 可以认为是精确分片算法的简易实现
对应 HintShardingStrategy。通过 Hint 指定分片值而非从 sql 中提取分片值的方式进行分片的策略。
分布式主键用于在分布式环境下,生成全局唯一的ID。Sharding-JDBC 提供了内置的分布式主键生成器,例如 UUID、SNowFLAKE。还抽离出分布式主键生成器的接口,方便用户自行实现自定义的自增主键生成器。为了保证数据库性能,主键ID还必须趋势递增,避免造成频繁的数据页面分裂。
提供一主多从的读写分离配置,可独立使用,也可配合分库分表使用。
同一线程且同一数据库连接内,如有写入 *** 作,以后的读 *** 作均从主库读取,用于保证数据一致性基于Hint的强制主库路由。主从模型中,事务中读写均用主库。执行流程Sharding-JDBC 的原理总结起来很简单: 核心由 sql解析 => 执行器优化 => sql路由 => sql改写 => sql执行 => 结果归并的流程组成。
spring-boot项目实战
引入依赖<dependency> <groupID>org.apache.shardingsphere</groupID> <artifactID>sharding-jdbc-spring-boot-starter</artifactID> <version>4.0.1</version></dependency>数据源配置如果使用sharding-jdbc-spring-boot-starter,并且数据源以及数据分片都使用shardingsphere进行配置,对应的数据源会自动创建并注入到spring容器中。
spring.shardingsphere.datasource.names=ds0,ds1spring.shardingsphere.datasource.ds0.type=org.apache.commons.dbcp.BasicdataSourcespring.shardingsphere.datasource.ds0.driver-class-name=com.MysqL.jdbc.Driverspring.shardingsphere.datasource.ds0.url=jdbc:MysqL://localhost:3306/ds0spring.shardingsphere.datasource.ds0.username=rootspring.shardingsphere.datasource.ds0.password=spring.shardingsphere.datasource.ds1.type=org.apache.commons.dbcp.BasicdataSourcespring.shardingsphere.datasource.ds1.driver-class-name=com.MysqL.jdbc.Driverspring.shardingsphere.datasource.ds1.url=jdbc:MysqL://localhost:3306/ds1spring.shardingsphere.datasource.ds1.username=rootspring.shardingsphere.datasource.ds1.password=# 其它分片配置但是在我们已有的项目中,数据源配置是单独的。因此要禁用sharding-jdbc-spring-boot-starter里面的自动装配,而是参考源码自己重写数据源配置。需要在启动类上加上@SpringBootApplication(exclude = {org.apache.shardingsphere.shardingjdbc.spring.boot.SpringBootConfiguration.class})来排除。然后自定义配置类来装配DataSource。
@Configuration@Slf4j@EnableConfigurationPropertIEs({ SpringBootShardingRuleConfigurationPropertIEs.class,SpringBootMasterSlaveRuleConfigurationPropertIEs.class,SpringBootEncryptRuleConfigurationPropertIEs.class,SpringBootPropertIEsConfigurationPropertIEs.class})@autoConfigureBefore(DataSourceConfiguration.class)public class DataSourceConfig implements ApplicationContextAware { @autowired private SpringBootShardingRuleConfigurationPropertIEs shardingRule; @autowired private SpringBootPropertIEsConfigurationPropertIEs props; private ApplicationContext applicationContext; @Bean("shardingDataSource") @Conditional(ShardingRuleCondition.class) public DataSource shardingDataSource() throws sqlException { // 获取其它方式配置的数据源 Map<String,DruIDDataSourceWrapper> beans = applicationContext.getBeansOfType(DruIDDataSourceWrapper.class); Map<String,DataSource> dataSourceMap = new HashMap<>(4); beans.forEach(dataSourceMap::put); // 创建shardingDataSource return ShardingDataSourceFactory.createDataSource(dataSourceMap,new ShardingRuleConfigurationYamlSwapper().swap(shardingRule),props.getProps()); } @Bean public sqlSessionFactory sqlSessionFactory() throws sqlException { sqlSessionfactorybean sqlSessionfactorybean = new sqlSessionfactorybean(); // 将shardingDataSource设置到sqlSessionFactory中 sqlSessionfactorybean.setDataSource(shardingDataSource()); // 其它设置 return sqlSessionfactorybean.getobject(); }}分布式ID生成器配置Sharding-JDBC提供了UUID、SNowFLAKE生成器,还支持用户实现自定义ID生成器。比如可以实现了type为SEQ的分布式ID生成器,调用统一的分布式ID服务获取ID。
@Datapublic class SeqShardingKeyGenerator implements ShardingKeyGenerator { private PropertIEs propertIEs = new PropertIEs(); @OverrIDe public String getType() { return "SEQ"; } @OverrIDe public synchronized Comparable<?> generateKey() { // 获取分布式ID逻辑 }}由于扩展ShardingKeyGenerator是通过JDK的serviceloader的SPI机制实现的,因此还需要在resources/meta-inf/services目录下配置org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator文件。 文件内容就是SeqShardingKeyGenerator类的全路径名。这样使用的时候,指定分布式主键生成器的type为SEQ就好了。
至此,Sharding-JDBC就整合进spring-boot项目中了,后面就可以进行数据分片相关的配置了。
数据分片实战如果项目初期就能预估出表的数据量级,当然可以一开始就按照这个预估值进行分库分表处理。但是大多数情况下,我们一开始并不能准备预估出数量级。这时候通常的做法是:
线上数据某张表查询性能开始下降,排查下来是因为数据量过大导致的。根据历史数据量预估出未来的数据量级,并结合具体业务场景确定分库分表策略。自动分库分表代码实现。下面就以一个具体事例,阐述具体数据分片实战。比如有张表数据结构如下:
CREATE table `hc_question_reply_record` ( `ID` bigint NOT NulL auto_INCREMENT COMMENT '自增ID',`reply_text` varchar(500) NOT NulL DEFAulT '' COMMENT '回复内容',`reply_wheel_time` datetime NOT NulL DEFAulT CURRENT_TIMESTAMP COMMENT '回复时间',`ctime` datetime NOT NulL DEFAulT CURRENT_TIMESTAMP COMMENT '创建时间',`mtime` datetime NOT NulL DEFAulT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`ID`),INDEX `IDx_reply_wheel_time` (`reply_wheel_time`)) ENGINE=InnoDB DEFAulT CHARSET=utf8mb4 ColLATE=utf8mb4_unicode_ci COMMENT='回复明细记录';分片方案确定先查询目前目标表月新增趋势:
SELECT count(*),date_format(ctime,'%Y-%m') AS `日期`FROM hc_question_reply_recordGROUP BY date_format(ctime,'%Y-%m');目前月新增在180w左右,预估未来达到300w(基本以2倍计算)以上。期望单表数据量不超过1000w,可使用reply_wheel_time作为分片键按季度归档。
spring: # sharing-jdbc配置 shardingsphere: # 数据源名称 datasource: names: defaultDataSource,slaveDataSource sharding: # 主从节点配置 master-slave-rules: defaultDataSource: # maser数据源 master-data-source-name: defaultDataSource # slave数据源 slave-data-source-names: slaveDataSource tables: # hc_question_reply_record 分库分表配置 hc_question_reply_record: # 真实数据节点 hc_question_reply_record_2020_q1 actual-data-nodes: defaultDataSource.hc_question_reply_record_$->{2020..2025}_q$->{1..4} # 表分片策略 table-strategy: standard: # 分片键 sharding-column: reply_wheel_time # 精确分片算法 全路径名 preciseAlgorithmClassname: com.xx.QuestionRecordPreciseShardingalgorithm # 范围分片算法,用于BETWEEN,可选。。该类需实现RangeShardingalgorithm接口并提供无参数的构造器 rangeAlgorithmClassname: com.xx.QuestionRecordRangeShardingalgorithm # 默认分布式ID生成器 default-key-generator: type: SEQ column: ID分片算法实现精确分片算法:QuestionRecordPreciseShardingalgorithm
public class QuestionRecordPreciseShardingalgorithm implements PreciseShardingalgorithm<Date> { /** * Sharding. * * @param availableTargetnames available data sources or tables's names * @param shardingValue sharding value * @return sharding result for data source or table's name */ @OverrIDe public String doSharding(Collection<String> availableTargetnames,PreciseShardingValue<Date> shardingValue) { return ShardingUtils.quarterPreciseSharding(availableTargetnames,shardingValue); }}范围分片算法:QuestionRecordRangeShardingalgorithm
public class QuestionRecordRangeShardingalgorithm implements RangeShardingalgorithm<Date> { /** * Sharding. * * @param availableTargetnames available data sources or tables's names * @param shardingValue sharding value * @return sharding results for data sources or tables's names */ @OverrIDe public Collection<String> doSharding(Collection<String> availableTargetnames,RangeShardingValue<Date> shardingValue) { return ShardingUtils.quarterRangeSharding(availableTargetnames,shardingValue); }}具体分片实现逻辑:ShardingUtils
@UtilityClasspublic class ShardingUtils { public static final String QUARTER_SHARDING_PATTERN = "%s_%d_q%d"; /** * logictablename_{year}_q{quarter} * 按季度范围分片 * @param availableTargetnames 可用的真实表集合 * @param shardingValue 分片值 * @return */ public Collection<String> quarterRangeSharding(Collection<String> availableTargetnames,RangeShardingValue<Date> shardingValue) { // 这里就是根据范围查询条件,筛选出匹配的真实表集合 } /** * logictablename_{year}_q{quarter} * 按季度精确分片 * @param availableTargetnames 可用的真实表集合 * @param shardingValue 分片值 * @return */ public static String quarterPreciseSharding(Collection<String> availableTargetnames,PreciseShardingValue<Date> shardingValue) { // 这里就是根据等值查询条件,计算出匹配的真实表 }}到这里,针对hc_question_reply_record表,使用reply_wheel_time作为分片键,按照季度分片的处理就完成了。还有一点要注意的就是,分库分表之后,查询的时候最好都带上分片键作为查询条件,否则就会使用全库路由,性能很低。 还有就是Sharing-JDBC对MysqL的全文索引支持的不是很好,项目有使用到的地方也要注意一下。总结来说整个过程还是比较简单的,后续碰到其它业务场景,相信大家按照这个思路肯定都能解决的。
原创不易,觉得文章写得不错的小伙伴,点个赞 总结
以上是内存溢出为你收集整理的分库分表Sharding-JDBC入门与项目实战,数据量大了一定要分表全部内容,希望文章能够帮你解决分库分表Sharding-JDBC入门与项目实战,数据量大了一定要分表所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)