分表过多引起的问题/Apache ShardingSphere元数据加载慢

目录

环境

背景

探寻

元数据的加载策略

如何解决

升级版本到5.x

调大max.connections.size.per.query

max.connections.size.per.query分析

服务启动阶段相关源码

服务运行阶段相关源码

受到的影响

注意事项(重要)

其他


环境

  • Spring Boot 2.2.13
  • Sharding JDBC 4.1.1

背景

因项目特殊性问题,系统需要处理大量数据,有多个数据源,且因数据过多每个数据源都有分表,导致启动时加载过慢

2024-01-10 10:12:25:088[main][INFO][][c.alibaba.druid.pool.DruidDataSource.init(1009)]{dataSource-1} inited
2024-01-10 10:12:25:243[main][INFO][][ShardingSphere-metadata.loadShardingSchemaMetaData(131)]Loading 5 logic tables' meta data.
2024-01-10 10:12:25:527[main][INFO][][ShardingSphere-metadata.load(70)]Loading 4947 tables' meta data.
2024-01-10 10:13:14:312[main][INFO][][ShardingSphere-metadata.createMetaData(59)]Meta data load finished, cost 49078 milliseconds.

日志信息中,可以看出其中一个数据源ShardingSphere正在加载大量的表元数据(近5000个表)。耗时接近一分钟

探寻

元数据的加载策略

ShardingSphere元数据的加载策略和优化方式

  • 使用 SQL 查询替换原生 JDBC 驱动连接:在 5.0.0-beta 版本之前,采用的方式是通过原生 JDBC 驱动原生方式加载。在 5.0.0-beta 版本中,逐步采用了使用数据库方言,通过 SQL 查询的方式,多线程方式实现了元数据的加载,进一步提高了系统数据加载的速度。
  • 减少元数据的加载次数:对于系统通用的资源的加载,遵循一次加载,多处使用。在这个过程中,也要权衡空间和时间,不断的进行优化,减少元数据的重复加载,提高系统整体的效率。

如何解决

升级版本到5.x

升级版本到5.x【5.x版本对元数据的加载做了优化:多线程加载,且相同分表只加载一个】

调大max.connections.size.per.query

(记得看最后注意事项)

max.connections.size.per.query是ShardingSphere中的参数,表示每个查询请求在每个分片中能够使用的最大连接数, 也就是执行sql的时候,对每一个数据库进行操作的时候的connection数量

在 application.properties 或 application.yml 文件中添加自定义配置来调整每个查询请求在每个分片中能够使用的最大连接数

spring.shardingsphere.datasource.[name].max-connections-size-per-query=20

其中,[name] 是数据源名称。你可以根据实际情况调整 max-connections-size-per-query 的值。
重新启动应用程序,新的配置将生效。

如果有个性化数据源,可以这么修改

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;@Bean(name = "dataSourceSharding")
public DataSource getShardingDataSource(@Qualifier("dataSource") DataSource dataSource) throws SQLException {// 分表规则ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();shardingRuleConfig.getTableRuleConfigs().add(/** user分表规则 */);//数据源Map<String, DataSource> result = new HashMap<>(Numbers.INT_16);result.put("dataSource", dataSourceBill);Properties properties = new Properties();properties.put(ConfigurationPropertyKey.MAX_CONNECTIONS_SIZE_PER_QUERY.getKey(), 20);return ShardingDataSourceFactory.createDataSource(result, shardingRuleConfig, properties);
}

max.connections.size.per.query分析

升级版本需要考虑的太多了, 还是分析下修改max.connections.size.per.query的影响吧

分析源代码发现,元数据的加载可以是单线程串行加载,也可以是多线程并行加载,而使用哪种策略,最终基于sharding-jdbc的一个配置:max.connections.size.per.query

max.connections.size.per.query默认值是1,此时元数据加载是单线程串行加载。而配置大于1时,会根据该配置的值,采用多线程并行加载。

修改这个参数,受影响的有启动时加载元数据和sql执行时

服务启动阶段相关源码

{@link org.apache.shardingsphere.sql.parser.binder.metadata.schema.SchemaMetaDataLoader#load}

    /*** Load schema meta data.** @param dataSource data source* @param maxConnectionCount count of max connections permitted to use for this query* @param databaseType database type* @return schema meta data* @throws SQLException SQL exception*/public static SchemaMetaData load(final DataSource dataSource, final int maxConnectionCount, final String databaseType) throws SQLException {List<String> tableNames;try (Connection connection = dataSource.getConnection()) {tableNames = loadAllTableNames(connection, databaseType);}log.info("Loading {} tables' meta data.", tableNames.size());if (0 == tableNames.size()) {return new SchemaMetaData(Collections.emptyMap());}List<List<String>> tableGroups = Lists.partition(tableNames, Math.max(tableNames.size() / maxConnectionCount, 1));Map<String, TableMetaData> tableMetaDataMap = 1 == tableGroups.size()? load(dataSource.getConnection(), tableGroups.get(0), databaseType) : asyncLoad(dataSource, maxConnectionCount, tableNames, tableGroups, databaseType);return new SchemaMetaData(tableMetaDataMap);}private static Map<String, TableMetaData> load(final Connection connection, final Collection<String> tables, final String databaseType) throws SQLException {try (Connection con = connection) {Map<String, TableMetaData> result = new LinkedHashMap<>();for (String each : tables) {result.put(each, new TableMetaData(ColumnMetaDataLoader.load(con, each, databaseType), IndexMetaDataLoader.load(con, each, databaseType)));}return result;}}

maxConnectionCount对应的就是max.connections.size.per.query

服务运行阶段相关源码

假设我们的用户很多,进行了分表,分表数量10,对应的表为:user_1,user_10
当我们在查询用户,如select * from user where name='张三',这个是逻辑sql
sharding-jdbc会将逻辑sql改写成真实sql,也就是这样:

select * from user_1 where name='张三'
...
select * from user_10 where name='张三'
共10条真实sql

{@link org.apache.shardingsphere.sharding.execute.sql.prepare.SQLExecutePrepareTemplate#getSQLExecuteGroups}

{@link org.apache.shardingsphere.shardingjdbc.jdbc.adapter.AbstractConnectionAdapter#createConnections }

这两处源码涉及的max.connections.size.per.query包括两点:

  • 计算需要一次性获取多少个连接去执行所有的真实sql;
  • 归并方式,也就是源码中的ConnectionMode,它分为两种,一种叫内存限制模式,一种叫连接限制模式

当max.connections.size.per.query小于真实sql数量时,走的是连接限制模式(通俗理解:因为连接不够用,需要把sql执行完后,将查询结果先放到内存,然后释放连接用于查询其他sql),反之走的是内存限制模式(连接足够用,每个sql占据一个连接,查询结果不需要一次性放到内存,而是分批次拉取数据,在内存中做归并聚合)。

    private List<InputGroup<StatementExecuteUnit>> getSQLExecuteGroups(final String dataSourceName,final List<SQLUnit> sqlUnits, final SQLExecutePrepareCallback callback) throws SQLException {List<InputGroup<StatementExecuteUnit>> result = new LinkedList<>();int desiredPartitionSize = Math.max(0 == sqlUnits.size() % maxConnectionsSizePerQuery ? sqlUnits.size() / maxConnectionsSizePerQuery : sqlUnits.size() / maxConnectionsSizePerQuery + 1, 1);List<List<SQLUnit>> sqlUnitPartitions = Lists.partition(sqlUnits, desiredPartitionSize);ConnectionMode connectionMode = maxConnectionsSizePerQuery < sqlUnits.size() ? ConnectionMode.CONNECTION_STRICTLY : ConnectionMode.MEMORY_STRICTLY;List<Connection> connections = callback.getConnections(connectionMode, dataSourceName, sqlUnitPartitions.size());int count = 0;for (List<SQLUnit> each : sqlUnitPartitions) {result.add(getSQLExecuteGroup(connectionMode, connections.get(count++), dataSourceName, each, callback));}return result;}

受到的影响

默认情况下,max.connections.size.per.query=1

  • 如果分片数据在两个数据库,默认情况下,执行引擎执行的时候,就是每个数据库都会有一个connection去查询。
  • 如果是一个数据库两个表,就是串行查询的,第一次查询的全部结果会全部放在了内存里面等待第二次查询的结果然后再一起合并

配置的变更影响有三点

  • 启动时加载元数据的逻辑
  • sql执行时的逻辑
  • 查询结果归并的逻辑

注意事项(重要)

  • max.connections.size.per.query的配置不能大于datasource的最大线程数,否则一旦分表数量大,就会因为无法一次获取足够的连接而报错
  • 如果代码中有很多不带分片参数的分表查询,而max.connections.size.per.query又设置的比较大,会极大的消耗数据库连接,可能导致其他业务逻辑无法获取连接而报错
  • 如果代码中有不带分片参数的分表查询,而max.connections.size.per.query又设置的比较小,会走连接限制模式,所有数据会放到内存后再做聚合,如果查询结果较大,可能爆掉内存;
  • 只要代码中避免掉不带分片参数的查询更新操作,适当加大max.connections.size.per.query的值,可以提升启动速度而不会对项目的运行造成任何影响。

其他

Apache ShardingSphere分表的简单使用和常见问题-CSDN博客

持续更新ing!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/438632.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

70从零开始学Java之Collection与Collections有什么区别?

作者:孙玉昌,昵称【一一哥】,另外【壹壹哥】也是我哦 CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者 前言 截止到现在,壹哥已经把Java里的List、Set和Map这三大集合都给大家讲解完毕了,不知道各位掌握了多少呢?如果你对之前的内容还没有熟练掌握,可以把壹哥前…

AI新工具(20240129) AI红包封面;Baichuan 3-超千亿参数的大语言模型;腾讯文档智能助手

AI红包封面-定制微信红包封面 AI红包封面生成器利用AI技术&#xff0c;为用户定制微信红包封面&#xff0c;生成精美作品。产品定位于为用户提供个性化、精美的微信红包封面定制服务。定价根据封面定制的复杂程度而定。 AI: Art Impostor-AI绘画多人联机休闲派对游戏 AI: Art…

计网Lesson12 - UDP客户服务器模型和UDP协议

文章目录 丢个图在这&#xff0c;实在不是很明白在讲啥&#xff0c;等学完网编的我归来狠狠拿下它

微信小程序如何搜索iBeacon设备

1.首先在utils文件夹下创建bluetooth.js和ibeacon.js 2.在 bluetooth.js文件中写入 module.exports {initBluetooth: function () {// 初始化蓝牙模块wx.openBluetoothAdapter({success: function (res) {console.log(蓝牙模块初始化成功);},fail: function (res) {console.l…

贪吃蛇项目

引言&#xff1a; 本文章使用C语言在Windows环境的控制台中模拟实现经典小游戏贪吃蛇。 实现基本功能&#xff1a; 1.贪吃蛇地图绘制。 2.蛇吃食物的功能&#xff08;上、下、左、右方向键控制蛇的动作&#xff09; 3.蛇撞墙死亡 4.蛇咬到自己死亡 5.计算得分 6.蛇加速…

Spark3内核源码与优化

文章目录 一、Spark内核原理1、Spark 内核概述1.1 简介1.2 Spark 核心组件1.3 Spark 通用运行流程概述 2、Spark 部署模式2.1 YARN Cluster 模式(重点)2.2 YARN Client 模式2.3 Standalone Cluster 模式2.4 Standalone Client 模式 3、Spark 通讯架构3.1 Spark 通信架构概述3.2…

【React教程】(2) React之JSX入门与列表渲染、条件渲染详细代码示例

目录 JSX环境配置基本语法规则在 JSX 中嵌入 JavaScript 表达式在 JavaScript 表达式中嵌入 JSXJSX 中的节点属性声明子节点JSX 自动阻止注入攻击在 JSX 中使用注释JSX 原理列表循环DOM Elements 列表渲染语法高亮 条件渲染示例1&#xff1a;示例2&#xff1a;示例3&#xff08…

Spring Security的入门案例!!!

一、导入依赖 <dependencies><!--web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--security--><dependency><groupId>…

幻兽帕鲁服务器出租,腾讯云PK阿里云怎么收费?

幻兽帕鲁服务器价格多少钱&#xff1f;4核16G服务器Palworld官方推荐配置&#xff0c;阿里云4核16G服务器32元1个月、96元3个月&#xff0c;腾讯云换手帕服务器服务器4核16G14M带宽66元一个月、277元3个月&#xff0c;8核32G22M配置115元1个月、345元3个月&#xff0c;16核64G3…

SpringBoot项目接入MQTT协议

mqtt是一种类似于mq的通讯技术 1、mqtt服务端搭建 创建docker网络 docker network create --driver bridge --subnet 172.18.0.0/24 --gateway 172.18.0.1 emqx-net创建容器 docker run -d \--name emqx1 \-e "EMQX_NODE_NAMEemqx172.18.0.2" \--network emqx-ne…

【LeetCode每日一题】56. 合并区间插入区间

一、判断区间是否重叠 力扣 252. 会议室 给定一个会议时间安排的数组 intervals &#xff0c;每个会议时间都会包括开始和结束的时间 intervals[i] [starti, endi] &#xff0c;请你判断一个人是否能够参加这里面的全部会议。 思路分析 因为一个人在同一时刻只能参加一个会…

Zygote的启动流程

在zygote进程对应的文件是app_main.cpp文件&#xff0c;在app_main.cpp文件的main()方法中先解析了init.rc中配置的参数并根据配置的参数设置zygote的状态。 在状态设置阶段主要做了&#xff1a; 设置进程名称为zygote通过startSystemServer true标示启动的是systemServer调…