记一次 ThreadLocal 泄漏导致的 shardingsphere-jdbc-core 单元测试偶发失败的排查与修复

本文描述了一次GitHub Actions CI中shardingsphere-jdbc-core单元测试偶然失败的问题,重点在于排查TransactionType mock对象导致的ThreadLocal泄漏,并通过调整测试执行顺序和修复代码实现本地复现。解决策略包括清理mockTransactionRule的ThreadLocal并讨论如何避免此类问题。

现象

https://github.com/apache/shardingsphere/issues/19346

本地运行 shardingsphere-jdbc-core 的单元测试没问题,但 GitHub Actions 中的 CI 有一定概率失败。

排查过程

尝试本地复现

尝试 Maven 运行单个模块的测试

使用 Maven 构建 shardingsphere-jdbc-core,多次尝试未能复现:

mvn clean install -pl shardingsphere-jdbc/shardingsphere-jdbc-core

尝试使用不同 JDK 运行模块的测试

在这里插入图片描述

尝试了以下几种 JDK:

  • Oracle Java 17.0.1
  • Eclipse Temurin 17.0.1
  • Docker Official OpenJDK 17.0.1

均未能复现问题。

分析问题

分析日志

重新观察报错信息:

ShardingSphere-JDBC 中实现 `dynamic-datasource` 的多数据源兼容性配置,主要思路是将 ShardingSphere-JDBC 生成的分片数据源(`ShardingSphereDataSource`)作为 `dynamic-datasource` 的一个目标数据源(`targetDataSource`),从而实现多数据源切换分库分表能力的结合。以下是具体的实现步骤和配置方式: ### 数据源初始化配置 在 Spring Boot 应用中,通常通过 Java 配置类来初始化数据源。首先需要创建 ShardingSphere 的数据源实例,然后将其注册到 `DynamicDataSource` 中作为其中一个数据源。 ```java @Configuration public class DataSourceConfig { @Bean public DataSource shardingSphereDataSource() throws SQLException { // 通过 YAML 文件加载 ShardingSphere 数据源 return YamlShardingSphereDataSourceFactory.createDataSource(getFile("META-INF/sharding-databases-tables.yaml")); } @Bean public DataSource dynamicDataSource(DataSource shardingSphereDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); // 添加 ShardingSphere 数据源作为 dynamic-datasource 的一个目标数据源 targetDataSources.put("shardingSphereDataSource", shardingSphereDataSource); // 可以继续添加其他普通数据源 // targetDataSources.put("otherDataSource", otherDataSource()); AbstractRoutingDataSource dynamicDataSource = new AbstractRoutingDataSource(); dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.setDefaultTargetDataSource(shardingSphereDataSource); // 设置默认数据源 return dynamicDataSource; } @Bean public PlatformTransactionManager transactionManager(DataSource dynamicDataSource) { return new DataSourceTransactionManager(dynamicDataSource); } } ``` ### YAML 配置示例 以下是一个典型的 ShardingSphere-JDBC 的 YAML 配置文件内容,用于定义分库分表策略和数据源信息: ```yaml dataSources: ds0: driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ds0 username: root password: root ds1: driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ds1 username: root password: root rules: - !SHARDING tables: user: actualDataNodes: ds$->{0..1}.user_$->{0..1} tableStrategy: standard: shardingColumn: user_id shardingAlgorithmName: user-table-inline keyGenerateStrategy: column: user_id keyGeneratorName: snowflake shardingAlgorithms: user-table-inline: type: INLINE props: algorithm-expression: user_$->{user_id % 2} keyGenerators: snowflake: type: SNOWFLAKE ``` ### 模式配置 ShardingSphere 支持多种运行模式,例如单机模式(`Standalone`)和集群模式(`Cluster`),可以通过以下方式配置: ```yaml mode: type: Standalone repository: type: JDBC ``` 在实际应用中,若使用 Spring Boot,可将 `mode` 配置项写入 `application.yml` 或通过环境变量控制,以适配不同部署环境。 ### Props 配置 ShardingSphere 提供了丰富的配置项(`props`),例如用于控制 SQL 日志输出、执行引擎线程池等。以下是一个典型的 `props` 配置示例: ```yaml props: sql-show: true executor-size: 5 ``` ### 动态数据源切换实现 为了实现动态切换数据源,需要结合 `ThreadLocal` 实现一个数据源路由机制。以下是一个简单的实现类: ```java public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); public static void setDataSourceKey(String key) { CONTEXT_HOLDER.set(key); } public static String getDataSourceKey() { return CONTEXT_HOLDER.get(); } public static void clearDataSourceKey() { CONTEXT_HOLDER.remove(); } } public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceKey(); } } ``` 在业务代码中,通过调用 `DynamicDataSourceContextHolder.setDataSourceKey("shardingSphereDataSource")` 来切换数据源。 ### 依赖配置 确保在 `pom.xml` 或 `build.gradle` 中引入了以下依赖: **Maven 示例:** ```xml <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.1.1</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.1</version> </dependency> ``` ### 注意事项 - **数据源生命周期管理**:确保 ShardingSphere 数据源在应用启动时正确初始化,并在关闭时释放资源。 - **事务管理**:如果使用了分布式事务,需要额外引入事务管理组件,如 Seata 或 Atomikos。 - **性能优化**:根据业务场景调整 ShardingSphere 的执行线程池大小、SQL 日志级别等参数[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wuweijie@apache.org

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值