Spring事务超时时间可能存在的错误认识

本文详细解析了Spring事务超时的实现机制,并通过代码示例展示了如何正确理解和使用事务超时特性,以避免不必要的超时问题。

1、先看代码

1.1、spring-config.xml

Java代码   收藏代码
  1. <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">  
  2.     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>  
  3.     <property name="url" value="jdbc:mysql://localhost:3306/test?autoReconnect=true&amp;useUnicode=true&amp;characterEncoding=utf-8"/>  
  4.     <property name="username" value="root"/>  
  5.     <property name="password" value=""/>  
  6. </bean>  
  7.   
  8. <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  9.     <property name="dataSource" ref="dataSource"/>  
  10. </bean>  

 

1.2、测试用例

Java代码   收藏代码
  1. @RunWith(SpringJUnit4ClassRunner.class)  
  2. @ContextConfiguration(locations = "classpath:spring-config.xml")  
  3. @TransactionConfiguration(transactionManager = "txManager", defaultRollback = false)  
  4. @Transactional(timeout = 2)  
  5. public class Timeout1Test {  
  6.     @Autowired  
  7.     private DataSource ds;  
  8.     @Test  
  9.     public void testTimeout() throws InterruptedException {  
  10.         System.out.println(System.currentTimeMillis());  
  11.         JdbcTemplate jdbcTemplate = new JdbcTemplate(ds);  
  12.         jdbcTemplate.execute(" update test set name = name || '1'");  
  13.         System.out.println(System.currentTimeMillis());  
  14.         Thread.sleep(3000L);  
  15.     }  
  16. }  

 

我设置事务超时时间是2秒;但我事务肯定执行3秒以上;为什么没有起作用呢?  这其实是对Spring实现的事务超时的错误认识。那首先分析下Spring事务超时实现吧。

 

2、分析

2.1、在此我们分析下DataSourceTransactionManager;首先开启事物会调用其doBegin方法:

Java代码   收藏代码
  1. …………  
  2. int timeout = determineTimeout(definition);  
  3. if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {  
  4.     txObject.getConnectionHolder().setTimeoutInSeconds(timeout);  
  5. }  
  6. …………  

 其中determineTimeout用来获取我们设置的事务超时时间;然后设置到ConnectionHolder对象上(其是ResourceHolder子类),接着看ResourceHolderSupport的setTimeoutInSeconds实现:

Java代码   收藏代码
  1. public void setTimeoutInSeconds(int seconds) {  
  2.     setTimeoutInMillis(seconds * 1000);  
  3. }  
  4.   
  5. public void setTimeoutInMillis(long millis) {  
  6.     this.deadline = new Date(System.currentTimeMillis() + millis);  
  7. }  

大家可以看到,其会设置一个deadline时间;用来判断事务超时时间的;那什么时候调用呢?首先检查该类中的代码,会发现:

Java代码   收藏代码
  1. public int getTimeToLiveInSeconds() {  
  2.     double diff = ((double) getTimeToLiveInMillis()) / 1000;  
  3.     int secs = (int) Math.ceil(diff);  
  4.     checkTransactionTimeout(secs <= 0);  
  5.     return secs;  
  6. }  
  7.   
  8. public long getTimeToLiveInMillis() throws TransactionTimedOutException{  
  9.     if (this.deadline == null) {  
  10.         throw new IllegalStateException("No timeout specified for this resource holder");  
  11.     }  
  12.     long timeToLive = this.deadline.getTime() - System.currentTimeMillis();  
  13.     checkTransactionTimeout(timeToLive <= 0);  
  14.     return timeToLive;  
  15. }  
  16. private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException {  
  17.     if (deadlineReached) {  
  18.         setRollbackOnly();  
  19.         throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline);  
  20.     }  
  21. }  

会发现在调用getTimeToLiveInSeconds和getTimeToLiveInMillis,会检查是否超时,如果超时设置事务回滚,并抛出TransactionTimedOutException异常。到此我们只要找到调用它们的位置就好了,那什么地方调用的它们呢? 最简单的办法使用如“IntelliJ IDEA”中的“Find Usages”找到get***的使用地方;会发现:

DataSourceUtils.applyTransactionTimeout会调用DataSourceUtils.applyTimeout,DataSourceUtils.applyTimeout代码如下:

Java代码   收藏代码
  1. public static void applyTimeout(Statement stmt, DataSource dataSource, int timeout) throws SQLException {  
  2.     Assert.notNull(stmt, "No Statement specified");  
  3.     Assert.notNull(dataSource, "No DataSource specified");  
  4.     ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);  
  5.     if (holder != null && holder.hasTimeout()) {  
  6.         // Remaining transaction timeout overrides specified value.  
  7.         stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());  
  8.     }  
  9.     else if (timeout > 0) {  
  10.         // No current transaction timeout -> apply specified value.  
  11.         stmt.setQueryTimeout(timeout);  
  12.     }  
  13. }  

其中其在stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());中会调用getTimeToLiveInSeconds,此时就会检查事务是否超时;

 

 

然后在JdbcTemplate中,执行sql之前,会调用其applyStatementSettings:其会调用DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());设置超时时间;具体可以看其源码;

 

到此我们知道了在JdbcTemplate拿到Statement之后,执行之前会设置其queryTimeout,具体意思参考Javadoc:

 

3、结论

写道
Spring事务超时 = 事务开始时到最后一个Statement创建时时间 + 最后一个Statement的执行时超时时间(即其queryTimeout)。

 

4、因此

假设事务超时时间设置为2秒;假设sql执行时间为1秒;

如下调用是事务不超时的

Java代码   收藏代码
  1. public void testTimeout() throws InterruptedException {  
  2.     System.out.println(System.currentTimeMillis());  
  3.     JdbcTemplate jdbcTemplate = new JdbcTemplate(ds);  
  4.     jdbcTemplate.execute(" update test set hobby = hobby || '1'");  
  5.     System.out.println(System.currentTimeMillis());  
  6.     Thread.sleep(3000L);  
  7. }  

而如下事务超时是起作用的;

Java代码   收藏代码
  1. public void testTimeout() throws InterruptedException {  
  2.     Thread.sleep(3000L);  
  3.     System.out.println(System.currentTimeMillis());  
  4.     JdbcTemplate jdbcTemplate = new JdbcTemplate(ds);  
  5.     jdbcTemplate.execute(" update test set hobby = hobby || '1'");  
  6.     System.out.println(System.currentTimeMillis());  
  7. }  

  

 

因此,不要忽略应用中如远程调用产生的事务时间和这个事务时间是否对您的事务产生影响。

 

另外:

1、事务超时不起作用,您要首先检查您的事务起作用了没:可以参考使用Aop工具类诊断常见问题

2、如果您用的JPA,且spring版本低于3.0,可能您的事务超时不起作用:https://jira.springsource.org/browse/SPR-5195

3、如果您用JDBC,但没有用JdbcTemplate,直接使用DateSourceUtils进行事务控制时,要么自己设置Statement的queryTimeout超时时间,要么使用TransactionAwareDataSourceProxy,其在创建Statement时会自动设置其queryTimeout。

4、关于JDBC超时时间设置一篇不错的翻译:深入理解JDBC的超时设置 

http://www.cubrid.org/blog/dev-platform/understanding-jdbc-internals-and-timeout-configuration/

转自:http://jinnianshilongnian.iteye.com/blog/1986023

<think>好的,我现在需要帮用户深度理解Spring Cloud Alibaba。首先,用户可能已经对Spring Cloud有一定了解,但想进一步了解Alibaba的组件。我需要从基础概念入手,逐步深入。 首先,我应该先介绍Spring Cloud Alibaba是什么,它的定位和优势。比如,它是Spring Cloud的子项目,由阿里开源,提供微服务解决方案。然后,对比原生Spring Cloud,说明它的优势,比如更好的阿里生态集成,更丰富的功能模块。 接下来,用户可能需要了解核心组件。这时候要列举Nacos、Sentinel、Seata等,每个组件的作用和特点。比如Nacos作为注册配置中心,对比Eureka和Config;Sentinel做流量控制,对比Hystrix;Seata处理分布式事务。需要详细说明每个组件的功能和使用场景。 然后,用户可能想知道如何与Spring Cloud集成,这里需要给出一些例子,比如依赖配置,注解使用。例如,使用Nacos作为注册中心,如何在pom.xml中添加依赖,配置文件怎么写,启动类如何标注。这部分要具体,方便用户实践。 另外,用户可能关心实际应用中的最佳实践,比如服务发现、配置管理、限流降级、事务处理。可以分点说明每个场景下如何选择和使用对应的组件,结合代码示例或配置片段。 还要考虑用户可能遇到的问题,比如版本兼容性,常见错误,如何排查。这部分可能需要经验积累,比如Spring Cloud Alibaba与Spring Boot版本的对应关系,启动时的常见异常及解决方法。 最后,总结Spring Cloud Alibaba的优势,比如社区活跃、文档丰富、适合国内场景,以及未来发展的趋势,比如云原生支持、Service Mesh整合等。这样用户能有一个全面的认识,并知道进一步学习的方向。 需要确保语言口语化,避免使用太多技术术语,但又要准确。结构要清晰,分点说明,重点部分用例子或代码加强理解。同时检查LaTeX格式是否正确,数学公式或代码块是否符合要求。</think>要深度理解Spring Cloud Alibaba,需要从以下五个层次逐步拆解: ### 一、定位与生态位 Spring Cloud Alibaba是Spring Cloud规范的阿里实现版,提供: 1. 与阿里云服务无缝集成的能力(如OSS、SchedulerX) 2. 更适合中国开发者习惯的中间件(Nacos替代Eureka+Config) 3. 解决原生Spring Cloud未覆盖的场景(如分布式事务) 4. 与Dubbo生态的深度整合(RPC通信) ### 二、核心组件矩阵 | 组件 | 作用领域 | 对比对象 | 核心优势 | |---------------|-------------------|----------------|------------------------------| | Nacos | 服务发现/配置中心 | Eureka+Config | 动态配置推送、AP/CP模式切换 | | Sentinel | 流量治理 | Hystrix | 可视化控制台、多样流量控制规则 | | Seata | 分布式事务 | - | AT/TCC/XA模式自由选择 | | RocketMQ | 消息队列 | Kafka/RabbitMQ | 事务消息、顺序消息 | | Dubbo | RPC通信 | Feign | 高性能、协议扩展性强 | ### 三、典型场景实现原理 **1. Nacos配置热更新机制** ```java @RefreshScope // 关键注解 @RestController public class ConfigController { @Value("${custom.config}") private String dynamicConfig; // 值变化时自动注入 } ``` 通过长轮询(Long Polling)实现配置变更的秒级推送,对比Spring Cloud Config需要手动触发/refresh端点 **2. Sentinel熔断策略** - 滑动时间窗口算法:统计最近10秒QPS $$ QPS = \frac{\sum_{i=1}^{10} reqCount_i}{10} $$ - 熔断条件:当QPS >阈值且异常比例>50%,触发熔断 ### 四、架构设计最佳实践 1. **混合部署模式**: - 传统Spring Cloud服务与Dubbo服务共存 - 通过Nacos统一注册发现 2. **多环境配置策略**: ```yaml spring: profiles: active: @profiles.active@ cloud: nacos: config: namespace: ${NACOS_NAMESPACE_${profiles.active}} ``` 3. **灰度发布方案**: - 使用Nacos元数据标记版本 - Sentinel结合标签路由实现流量染色 ### 五、深度优化方向 1. **Nacos内核调优**: - 调整心跳周期(默认5秒→10秒) - 集群使用Raft协议时优化选举超时时间 2. **Sentinel规则持久化**: ```java // 自定义DataSource扩展 public class NacosDataSource implements ReadableDataSource<String, List<FlowRule>> { // 实现规则从Nacos读取 } ``` 3. **Seata性能优化**: - 使用Redis作为事务日志存储 - 调整全局锁重试次数(默认30次→10次) ### 六、常见陷阱规避 1. **版本兼容问题**: - Spring Boot 2.4.x需使用2021.0.x版本 - JDK 17需升级到Nacos 2.1.x 2. **配置覆盖顺序**: ```properties # 优先级:环境变量 > Nacos配置 > 本地配置 spring.cloud.config.override-none=true ``` 3. **Sentinel误熔断**: ```java // 需排除业务异常计入熔断统计 Tracer.traceEntry(ex, EntryType.OUT); ``` 理解Spring Cloud Alibaba的关键在于抓住其"云原生中间件集成平台"的定位,通过实际业务场景反推技术选型,例如:秒杀场景需组合使用Sentinel(限流)+Seata(库存事务)+RocketMQ(削峰填谷)。建议从官方提供的[Spring Cloud Alibaba Example](https://github.com/alibaba/spring-cloud-alibaba/tree/master/spring-cloud-alibaba-examples)入手实践。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值