1 故障显现
1.1 告警
告警通知群报错:数据库连接池的连接获取异常,导致tomcat线程池活跃的线程参数达到阈值。
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 1000, active 100, maxActive 100, creating 0, runningSqlCount 5
1.2 排查:查看了服务和数据库监控和添加索引的时间,发现时间差不多吻合
因为给线上数据库的一张表临时增加了一个索引,引发了一个
1.3 原因深究:为什么创建索引会导致线程池参数接近阈值?
我们公司使用的mysq版本是5.1.7,创建索引时,表会被完全锁定,阻塞所有的读写操作。
2 两个年薪60w和一个年薪百万的技术专家讨论:关于数据库连接池超时时间设置的合理值。
(1)技术人员A:连接池中设置线程1s超时会让tocamt线程池打满 (2)B:拿链接100ms,感觉可能会导致失败变多?小了等于快速失败,没有容错,多了线程池打满拒绝请求,所以我得先看看 (3)专家:tomcat响应不了请求就都错了,快速超时返回,我们还可以处理一部分请求。就设100ms 观察一下,我以前都设50ms的。这个保守一点,可以多调几次,慢慢往下调,1s肯定是不行的。
2.1 原因深究
技术人员之间的讨论反映了不同的设计哲学和对系统健壮性的关注。以下是对这些讨论的详细分析和理解:
A 的观点
1s超时会让Tomcat线程池打满
A 认为设置 1 秒的超时时间会导致 Tomcat 线程池被占满。原因如下:
- 如果每个请求需要等待 1 秒才能知道获取连接是否成功,这段时间内 Tomcat 线程都会被阻塞。
- 在高并发情况下,很多线程可能同时挂起等待数据库连接,使得 Tomcat 的线程池达到上限,无法处理更多请求。
B 的观点
拿链接100ms,感觉可能会导致失败变多?小了等于快速失败,没有容错,多了线程池打满拒绝请求
B 的担心是:
- 缩短超时时间至 100ms 可能会增加获取连接失败的次数,因为系统可能一时无法在如此短时间内提供连接。
- 这种设置减少了容错性,而增加了失败的可能性,导致更多请求被拒绝。
- 但如果超时时间过长,确实会像 A 所说,线程池被占满的问题。
专家的观点
没事的,Tomcat响应不了请求,就都错了,快速超时返回,我们还可以处理一部分请求。就设100ms,观察一下,我以前都设50ms的。这个保守一点,可以多调几次,慢慢往下调,1s肯定是不行的
专家解释了更短的超时时间的优势:
- 即使请求失败,也会立即返回而不是长时间占用资源。这样可以保持系统的一部分处理能力。
- 快速失败机制会使系统更快腾出线程去处理其他请求,而不是等待连接。
- 专家有既往经验表明,较短的超时时间(如 50ms)在实际操作中有效。
总结与建议
- 快速失败:
- 短超时时间(如 100ms 或更短)能确保在数据库连接耗尽的情况下,线程会快速失败,释放资源,保持系统的敏捷性。
- 动态调整:
- 对于每个系统,最佳的超时时间可能不同。在实际环境中,可以从1000ms开始,逐步观察系统行为和性能,逐渐调整至最适设置。
- 负载监控与调优:
- 不断监控系统负载和数据库连接池的使用情况,并相应优化连接池的配置参数(如最大连接数、释放策略等)。
- 整体优化:
- 除了调整超时时间,还应评估数据库查询效率、索引优化、应用程序逻辑等因素,以降低数据库压力和提高应用响应能力。
这段对话中的主要观点强调了协作和逐步优化的重要性。通过持续监控和调整,可以找到一个平衡点,使系统在高效和稳定间取得最佳状态。
2.2 调整数据库连接池的连接超时时间是一个重要的决策,因为它直接影响应用的性能和稳定性。让我们更深入地理解为什么资深技术专家会建议将数据库连接池的连接超时时间设为 100ms,而不是 1s。
为什么 1s 等同于“永久不超时”
这里的意思并不是说1s真的等同于无限长时间,但在实际系统应用中,1s 是一个很长的等待时间,特别是在高并发和高要求的系统中。
资源占用:
- 如果每个获取连接的请求都要等待 1 秒,那么在这 1 秒内,所有执行连接请求的线程都在占用资源,但没有实际进展。这会导致线程池很快被占满。
- 尤其在高并发情况下,许多请求在等待很长时间后仍然可能失败,这种情况下,长时间的等待就没有任何意义,反而拖慢了系统的响应速度。
系统反应迟缓:
- 设置 1 秒超时时间会延长系统检测到“数据库连接池耗尽”这一问题的时间。当大量请求等待 1 秒后才反馈出问题,系统会变得非常迟钝。
- 这类似于一个快速的负载检测和应对机制。通过快速失败方法,系统可以迅速检测到连接池的压力并做出应对,避免进一步恶化。
为什么选择 100ms
选择 100ms 作为超时时间是基于经验和系统对延迟的承受能力,这会使系统更快地反应并调整。以下是选择 100ms 的一些理由:
快速反馈:
- 100ms 是一个足够短的时间,可以确保线程不会被长时间阻塞,同时又足够长可以让一些短时的连接忙碌得到解决。
- 在 100ms 内完成数据库连接请求,说明系统资源较为充足,一旦超时,说明资源紧张,系统需要快速感知并做出响应。
负载均衡:
- 通过快速报错,可以更好地进行负载均衡。上层的应用逻辑可以根据连接池反馈的快速失败情况,进行重试或者其他处理策略。
- 这可以避免所有请求等待超时,同时让部分请求能够得到及时处理。
逐步调整:
- 100ms 作为初始设置,并结合系统具体行为进行调整(例如往 50ms 调整)。这是一个渐进的过程,可以通过监控和调优来找到最佳设置。
- 每个系统的负载和性能要求不同,100ms 是一个较为保守的起点,可以通过实践逐步优化。
总结
资深技术专家建议将数据库连接池的连接超时时间设为 100ms,是为了通过快速失败机制尽快释放线程资源,提升系统整体响应能力,提高系统在高并发和资源紧张时候的表现。
具体数值(1s 和 100ms)是基于经验和对系统性能的需求得出来的:
- 1秒对于高并发系统过长,会造成线程池被占满,系统反应迟钝;
- 100毫秒作为初始设置,介于性能需求和资源消耗之间,通过实践可以进一步优化。
2.3 其实设置成100ms,也是跟数据库的处理时间有关吧,有计算公式吗?【建议直接看2.3.5的举例】
你说得对,设置数据库连接池的超时时间确实和数据库的处理时间密切相关。虽然没有一个具体的“一刀切”公式,但我们可以通过以下思路和假设来推导一个合理的超时时间设置。
1 假设条件
假设我们有以下系统特性:
- 数据库处理时间:平均处理一个查询的时间($ T_{db} $)。
- 最大并发连接数:数据库连接池能处理的最大并发连接数($ N_{max} $)。
- 并发请求数:系统在高峰期的并发请求数($ N_{concurrent} $)。
2 基本思路
**数据库处理一个查询的时间($ T_{db} $)**是系统设置超时的基础。我们假设我们希望设置的超时时间必须保证大部分请求不会因为等待连接而失败。
**并发连接数($ N_{max} $)**决定了同时能够处理多少个请求。如果高于这个数值,新的请求将需要等待连接释放。
**超时时间($ T_{timeout} $)**是我们希望每个连接请求等待的最大时间。
3 推导公式
我们希望超时时间能够容忍在高并发情况下的正常操作,而不会造成大量的超时失败。一个简单的推导公式可以基于以下事实:
T t i m e o u t ≥ T d b × N c o n c u r r e n t N m a x T_{timeout} \geq \frac{T_{db} \times N_{concurrent}}{N_{max}} Ttimeout≥NmaxTdb×Nconcurrent
4 推导过程
假设系统高峰期的最大并发请求数($ N_{concurrent} $) 和数据库能处理的最大并发连接数($ N_{max} $)。如果 $ N_{concurrent} \leq N_{max} $,那么超时相对可忽略。
但在高峰期,如果超过最大并发连接数,一些请求就需要等待。假设一个查询平均处理时间为 $ T_{db} $,则所有请求完成时间为:
T t o t a l = N c o n c u r r e n t N m a x × T d b T_{total} = \frac{N_{concurrent}}{N_{max}} \times T_{db} Ttotal=NmaxNconcurrent×Tdb超时时间需要大于等于每个请求的等待时间,
T t i m e o u t ≥ T d b × N c o n c u r r e n t N m a x T_{timeout} \geq \frac{T_{db} \times N_{concurrent}}{N_{max}} Ttimeout≥NmaxTdb×Nconcurrent
5 实际举例
假设:
- 数据库的平均处理时间 $ T_{db} $ 为 50ms。
- 数据库连接池的最大连接数 $ N_{max} $ 为 100。
- 高峰期的并发请求数 $ N_{concurrent} $ 为 200。
则:
T t i m e o u t ≥ 50 ms × 200 100 = 100 ms T_{timeout} \geq \frac{50 \text{ms} \times 200}{100} = 100 \text{ms} Ttimeout≥10050ms×200=100ms
根据上述公式,超时设置为 100ms 是合理的,因为它能够在这种高并发情况下,让大多数请求在合理的时间内获得连接。
6 调整和监控
实际系统中,最好结合实际监控数据逐步调整和优化超时时间:
- 持续监控:通过 APM 工具(如 New Relic、DataDog)持续监控请求的响应时间和失败率,根据数据微调参数。
- 负载测试:定期进行负载测试,模拟高并发情况,评估参数设置的合理性。
7 小结
超时时间的设置必须考虑数据库的处理时间、并发请求数量以及连接池的最大连接数。尽管没有一个普适的公式,上述推导可以作为一个合理起点,结合实际应用中的监控和调优使系统保持高效和稳定。