前言

最近项目中不少表的数据量越来越大,并且导致了一些数据库的性能问题。因此想借助一些分库分表的中间件,实现自动化分库分表实现。调研下来,发现Sharding-JDBC目前成熟度最高并且应用最广的Java分库分表的客户端组件

本文主要介绍一些Sharding-JDBC核心概念以及生产环境下的实战指南,旨在帮助组内成员快速了解Sharding-JDBC并且能够快速将其使用起来。

Sharding-JDBC官方文档:https://shardingsphere.apache.org/document/current/cn/overview/

核心概念

在使用Sharding-JDBC之前,一定是先理解清楚下面几个核心概念。

逻辑表

水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:订单数据根据主键尾数拆分为10张表,分别是t_order_0t_order_9,他们的逻辑表名为t_order

真实表

在分片的数据库中真实存在的物理表。即上个示例中的t_order_0t_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_ordert_order_item对应的真实表各有2个,那么真实表就有t_order_0t_order_1t_order_item_0t_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);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 中如果无分片字段,将执行全路由,性能较差。除了对单分片字段的支持,Sharding-JDBC 也支持根据多个字段进行分片。

分片算法

通过分片算法将数据分片,支持通过=、>=、<=、>、<、BETWEEN和IN分片。分片算法需要应用方开发者自行实现,可实现的灵活度非常高。

目前提供4种分片算法。由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

精确分片算法

对应 PreciseShardingAlgorithm用于处理使用单一键作为分片键的 = 与 IN 进行分片的场景。需要配合 StandardShardingStrategy 使用。

范围分片算法

对应 RangeShardingAlgorithm用于处理使用单一键作为分片键的 BETWEEN AND、>、<、>=、<=进行分片的场景。需要配合 StandardShardingStrategy 使用。

复合分片算法

对应 ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合 ComplexShardingStrategy 使用。

Hint分片算法

对应 HintShardingAlgorithm用于处理通过Hint指定分片值而非从SQL中提取分片值的场景。需要配合 HintShardingStrategy 使用。

分片策略

包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供 5 种分片策略。

标准分片策略

对应 StandardShardingStrategy。提供对 SQ L语句中的 =, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持。StandardShardingStrategy 只支持单分片键,提供 PreciseShardingAlgorithmRangeShardingAlgorithm 两个分片算法。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_0t_user_7可以认为是精确分片算法的简易实现

Hint分片策略

对应 HintShardingStrategy。通过 Hint 指定分片值而非从 SQL 中提取分片值的方式进行分片的策略。

分布式主键

用于在分布式环境下,生成全局唯一的id。Sharding-JDBC 提供了内置的分布式主键生成器,例如 UUIDSNOWFLAKE。还抽离出分布式主键生成器的接口,方便用户自行实现自定义的自增主键生成器。为了保证数据库性能,主键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提供了UUIDSNOWFLAKE生成器,还支持用户实现自定义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项目中了,后面就可以进行数据分片相关的配置了。

数据分片实战

如果项目初期就能预估出表的数据量级,当然可以一开始就按照这个预估值进行分库分表处理。但是大多数情况下,我们一开始并不能准备预估出数量级。这时候通常的做法是:

  1. 线上数据某张表查询性能开始下降,排查下来是因为数据量过大导致的。
  2. 根据历史数据量预估出未来的数据量级,并结合具体业务场景确定分库分表策略。
  3. 自动分库分表代码实现。

下面就以一个具体事例,阐述具体数据分片实战。比如有张表数据结构如下:

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-JDBCmysql的全文索引支持的不是很好,项目有使用到的地方也要注意一下。总结来说整个过程还是比较简单的,后续碰到其它业务场景,相信大家按照这个思路肯定都能解决的。

原文作者: 伍陆七
原文链接:http://juejin.cn/post/6844904182365814797

rust大油井频率怎么用_数据量大怎么搞?当然是用这个了!相关推荐

  1. rust大油井频率怎么用_「Rust每日新闻」本周精选 • 第二十四期

    前言: 从2018年开始,我每天会花1个小时关注Rust社区动态,并且在Rust.CC论坛.tg channel.Steemit.GitHub.语雀订阅都开通了Rust每日新闻,分享我每天的见闻,偶尔 ...

  2. rust大油井频率怎么用_短波通信频率使用指南

    短波通信短波通信实际使用的频率范围:1.6MHz-30MHz (1)1600kHz-1800 kHz:主要是些灯塔和导航信号,用来给鱼船和海上油井勘探的定位信号. (2)1800 kHz -2000 ...

  3. for循环数据量太大_中文文本分类roberta大力出奇迹之数据量大的问题

    问题描述: 笔者在文本分类场景中使用了roberta+pool+dense的三分类模型.采用预训练模型做项目的时候经常苦于数据太少,模型泛化性差,因此收集了1300W数据.在我尝试暴力出奇迹的时候,遇 ...

  4. 历史数据如何处理_数据库表数据量大读写缓慢如何优化(1)【冷热分离】

    今天讨论的内容是冷热分离,也许概念并不陌生,对其使用场景也比较熟悉,但涉及锁的内容时仍然需要认真思考,这部分内容在我们实际开发中的"坑"还是不少的. 业务场景一 曾经经历过供应链相 ...

  5. 面试官问单表数据量大一定要分库分表吗?我们用六个字和十张图回答

    1 文章概述 在业务发展初期单表完全可以满足业务需求,在阿里巴巴开发手册也建议:单表行数超过500万行或者单表容量超过2GB才推荐进行分库分表,如果预计三年后数据量根本达不到这个级别,请不要在创建表时 ...

  6. Java List数据量大, 需要分片批次操作

    很久前,写过关于list数据量大,分批处理的相关介绍文章(自定义的java代码实现): SSM Mybatis 批量插入 采用分批处理一次500条_默默不代表沉默-CSDN博客_mybatis分批插入 ...

  7. 我艹,MySQL数据量大时,delete操作无法命中索引。

    来自:Java面试那些事儿 最近,在脉脉上看到一个楼主提出的问题:MySQL数据量大时,delete操作无法命中索引:并且还附上了相关案例截图. 最终,楼主通过开启MySQL分析优化器追踪,定位到是优 ...

  8. mysql给数据量大的表添加索引的办法

    有一个问题,一张表有3百万条记录,随着时间的增加,记录量会更多,此时查询速度很慢.在创建此表前没有未相应字段添加索引,所以此时需要为表添加索引.但是因为数据量大的原因,索引添加不成功,想了很多办法,终 ...

  9. 解决Mysql数据量大的时候 分页优化(使用limit)的问题

    解决Mysql数据量大的时候 分页优化(使用limit)的问题 参考文章: (1)解决Mysql数据量大的时候 分页优化(使用limit)的问题 (2)https://www.cnblogs.com/ ...

最新文章

  1. 学python要多少钱-培训python大概要多少钱?
  2. docker: 解决centos7下cgroup.procs: no such device的错误
  3. Python入门100题 | 第029题
  4. 在cmd中使用指令来执行jar包
  5. CSS实现自适应不同大小屏幕的背景大图
  6. Android开发中手机存储路径问题
  7. 关于Synchornized,Lock,AtomicBoolean和volatile的区别介绍
  8. 凑算式——第七届蓝桥杯C语言B组(省赛)第三题
  9. Django的Model上都有些什么
  10. 4.Linux性能诊断 --- Linux工作流程内存管理
  11. 三对角矩阵(特殊矩阵)
  12. 事关美亚柏科未来发展!智慧城市建设注入新动力
  13. 数组之concat注意事项-不更改原数组
  14. 【分享】ArcGIS实现分子分母等标注实用技巧
  15. win10 linux安卓模拟器,WIN10电脑安卓模拟器逍遥安卓唯一真正支持
  16. 无损数据压缩 Lossless Compression
  17. 测试人员需要具备哪些基本技能
  18. 关于Halcon::threshold 用时
  19. 《用户增长方法论》从产品、渠道、营销创意等多个维度,搭建了一套完整的用户增长方法体系
  20. 联想电脑开机卡在logo界面

热门文章

  1. 配置LYNC和Exchange 2010 SP1 OWA集成
  2. 手摸手入门前端--01.webpack4
  3. OC_UITextField
  4. Centos7网络正常,但使用yum提示安装源无法连接
  5. Mesos各种存储处理方式
  6. linux--nfs 网络文件共享
  7. 关于XML在与FLASH交互应用中的理解
  8. Ansible playbook 备份Cisco ios 配置
  9. 第88天:HTML5中使用classList操作css类
  10. 4.4学习笔记-REGEXP1(正则表达式)