点击上方“方志朋”,选择“设为星标”

回复”666“获取新整理的面试文章

来源:juejin.im/post/6844904182365814797

  • 核心概念

  • 数据分片

  • 读写分离

  • 执行流程

  • 项目实战


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

本文主要介绍一些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);
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 只支持单分片键,提供 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。可以认为是精确分片算法的简易实现

Hint分片策略

对应 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.BasicDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds0.url=jdbc:mysql://localhost:3306/ds0
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=spring.shardingsphere.datasource.ds1.type=org.apache.commons.dbcp.BasicDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds1.url=jdbc:mysql://localhost:3306/ds1
spring.shardingsphere.datasource.ds1.username=root
spring.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 {@Autowiredprivate SpringBootShardingRuleConfigurationProperties shardingRule;@Autowiredprivate 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);// 创建shardingDataSourcereturn ShardingDataSourceFactory.createDataSource(dataSourceMap, new ShardingRuleConfigurationYamlSwapper().swap(shardingRule), props.getProps());}@Beanpublic 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。

@Data
public class SeqShardingKeyGenerator implements ShardingKeyGenerator {private Properties properties = new Properties();@Overridepublic String getType() {return "SEQ";}@Overridepublic 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_ciCOMMENT='回复明细记录';

分片方案确定

先查询目前目标表月新增趋势:

SELECT count(*), date_format(ctime, '%Y-%m') AS `日期`
FROM hc_question_reply_record
GROUP BY date_format(ctime, '%Y-%m');

目前月新增在180w左右,预估未来达到300w(基本以2倍计算)以上。期望单表数据量不超过1000w,可使用reply_wheel_time作为分片键按季度归档。

分片配置

spring:# sharing-jdbc配置shardingsphere:# 数据源名称datasource:names: defaultDataSource,slaveDataSourcesharding:# 主从节点配置master-slave-rules:defaultDataSource:# maser数据源master-data-source-name: defaultDataSource# slave数据源slave-data-source-names: slaveDataSourcetables:# hc_question_reply_record 分库分表配置hc_question_reply_record:# 真实数据节点  hc_question_reply_record_2020_q1actual-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: SEQcolumn: 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*/@Overridepublic 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*/@Overridepublic Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Date> shardingValue) {return ShardingUtils.quarterRangeSharding(availableTargetNames, shardingValue);}
}

具体分片实现逻辑:ShardingUtils

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

热门内容:程序员接私活完整攻略+赠开源管理系统
Docker 部署SpringBoot项目不香吗?为什么 Java 中“1000==1000”为false,而”100==100“为true?
面试官问我:如何设计 QQ、微信等第三方账号登陆 ?还要我说出数据库表设计!
为什么MySQL不推荐使用uuid或者雪花id作为主键?最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

明天见(。・ω・。)

数据量大了一定要分表,分库分表 Sharding-JDBC 入门与项目实战相关推荐

  1. 亿级大表分库分表实战总结(万字干货,实战复盘)

    亿级大表分库分表实战总结(万字干货,实战复盘) 以下文章来源于阿丸笔记 ,作者阿丸笔记 阿丸笔记 分库分表的文章网上非常多,但是大多内容比较零散,以讲解知识点为主,没有完整地说明一个大表的切分.新架构 ...

  2. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第六章:数据存储

    第 6 章 数据存储 本章介绍Android 4种存储方式的用法,包括共享参数SharedPreferences.数据库SQLite.存储卡文 件.App的全局内存,另外介绍Android重要组件-应 ...

  3. 数据量大了一定要分表,分库分表Sharding-JDBC入门与项目实战

    作者:六点半起床 juejin.im/post/6844904182365814797 最近项目中不少表的数据量越来越大,并且导致了一些数据库的性能问题.因此想借助一些分库分表的中间件,实现自动化分库 ...

  4. sharding分表后主键_分库分表【Sharding-JDBC】入门与项目实战

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

  5. mysql 分区 分表 分库分表

    分区 把一张表的数据分成N多个区块,这些区块可以在同一个磁盘上,也可以在不同的磁盘上 mysql数据库中的数据是以文件的形势存在磁盘上的,默认放在/mysql/data下面(可以通过my.ini中的d ...

  6. 数据分析TB级别数据量大了怎么办,不会代码模型训练怎么办?

    数据分析经常会遇到数据量大的问题,比如用R语言和Python语言时经常会遇到内存溢出的问题,即使把整个机器内存全部使用,达到最大使用率,还是无济于事,比如数据量是10T,并且在大数据量下,既要保证数据 ...

  7. 大数据开发基础入门与项目实战(三)Hadoop核心及生态圈技术栈之5.即席查询Impala介绍及入门使用

    文章目录 前言 1.Impala概述 (1)Impala的概念和优势 (2)Impala的缺点及适用场景 2.Impala的安装与入门 (1)准备工作 (2)制作本地yum源 (3)安装Impala ...

  8. 大数据开发基础入门与项目实战(三)Hadoop核心及生态圈技术栈之3.数据仓库工具Hive基础

    文章目录 1.Hive概述 (1)数仓工具Hive的产生背景 (2)数仓工具Hive与RDBMS对比 (3)数仓工具Hive的优缺点 (4)数仓工具Hive的架构原理 2.Hive安装与配置 (1)安 ...

  9. 大数据开发基础入门与项目实战(三)Hadoop核心及生态圈技术栈之2.HDFS分布式文件系统

    文章目录 前言 1.HDFS特点 2.命令行和API操作HDFS (1)Shell命令行客户端 (2)API客户端连接HDFS的两种方式 (3)API客户端上传下载文件 (4)API客户端文件详情及文 ...

最新文章

  1. iOS 解决导航栏pop返回时出现黑块问题!
  2. 这个操作稳!云厂商开设免费云计算课程,学员独享49元主机套餐包年!
  3. Spring5源码 - 13 Spring事件监听机制_@EventListener源码解析
  4. android listview 移动到顶部,android listview滚动到顶部
  5. 小数点进位 oracle,使用多个小数点(。)对Oracle中的记录进行排序
  6. if sql语句_SQL IF语句介绍和概述
  7. ORB_SLAM2探秘 第二章
  8. 微信小程序例子——点击发送信息清空输入框
  9. Android APP漏洞自动化静态扫描检测工具-Qark
  10. PADS Logic原理图设计
  11. 陈龙杰计算机专业,第四届学生职业技能大赛获奖名单
  12. 黑马程序员---java基础-Java之IO
  13. Python re模块 —— 从零基础开始入门正则表达式
  14. SAP_FICO常用事务代码
  15. c# 实现金山词霸一样的屏幕取词
  16. centos7软件仓库资源的配置目录
  17. Android Java 网络 OS等笔试题 -- 全
  18. oracle 冒泡,详解易语言的冒泡算法
  19. 黄仁宇走了,柏杨也走了
  20. 国家何时整治程序员的高薪现象

热门文章

  1. BZOJ1002 [FJOI2007]轮状病毒(最小生成树计数)
  2. 从网站上扒网页,保存为file文件格式
  3. django教程目录
  4. Python内部类,内部类调用外部类属性,方法
  5. 2018.5.29 Oracle连接到空闲例程
  6. tcpdump保存数据包
  7. 【python之旅】python的基础二
  8. UIButton长按事件
  9. LSGO代码小组第17周复盘日志
  10. 在IDEA 中为Maven 配置阿里云镜像源