1 背景
20250411下午四点左右发生yg机房故障,导致网络剧烈波动,大量的丢包和延迟。在生成逃生任务时,共有23个分组,再DB先生成了4个分组任务,后续16分钟后才生成了另外剩余的分组任务。基于此背景,通过线下模拟排查,最终确认是链接OceanBase数据引起的超时链接未断开,导致任务生成线程阻塞了。
2 根因分析
当前jdbc的客户端驱动仅配置了readTimeout和writeTimeout以及ob_query_timeot,其中ob_query_timeot未生效,因为客户端使用Mysql-Java驱动,不是标准的Ocean base驱动,该参数不支持。 使用 MyBatis、MySQL Connector/J 和 HikariCP 进行 SQL 查询时,可以将查询过程分解为几个阶段。下面是详细的阶段描述:
-
建立连接阶段:
- 获取连接:通过连接池获取已经创建好的数据库连接。
- 初始化连接:尽管连接池通常会事先建立连接,但在需要的时候可能会对连接进行一些必要的初始化。
-
SQL 查询阶段:
- 准备语句:在这个阶段,MyBatis 通过会话处理 SQL 查询,可能涉及到动态 SQL 拼接。
- 执行查询:数据库服务器开始执行 SQL 语句。
-
结果集传输阶段:
- 数据库服务器处理完 SQL 查询后,将结果集从服务器传输到客户端。这一阶段可能涉及多个数据包的传输。
-
客户端处理阶段:
- 结果集解析:客户端收到结果集后,开始使用 MyBatis 将结果集中的数据解析为 Java 对象。
- 映射:MyBatis 会根据预定义的映射规则将结果集映射成具体的实体类对象,供应用使用。
这几个阶段涵盖了 SQL 查询从连接池开始到客户端完成数据处理的完整流程。在使用连接池(例如 HikariCP)时,连接的建立大部分是预先完成的,通常直接从池中获取一个现有连接,而不是现时创建。
而我们的数据库配置并没有覆盖全部情况:
-
readTimeout(5s) :数据库请求发出后,客户端等待服务器响应并接收数据的过程。
具体而言,
readTimeout的计时开始时机包括以下过程:-
查询发送与等待阶段:
- 在客户端发送查询请求并等待服务器处理和准备结果的阶段,
readTimeout不生效。这个时段主要涉及 SQL 查询被服务器接收并处理。
- 在客户端发送查询请求并等待服务器处理和准备结果的阶段,
-
接收首包(开始读取) :
- 一旦服务器准备好结果集,客户端开始尝试读取结果。通常通过 JDBC 的
executeQuery()或类似方法触发数据的接收过程。 - 当客户端执行这些方法以获取数据时,读取过程正式开始,
readTimeout的计时也在此时开始。 - 计时作用于客户端从服务器接收数据包的操作上,无论数据包是首个还是后续数据包。
- 一旦服务器准备好结果集,客户端开始尝试读取结果。通常通过 JDBC 的
-
-
writeTimeout(5s) :客户端写入结果时的超时
此次延迟断开的代码在于数据库没有成功返回任何数据,导致客户端一直等待
3 改进措施
- 首先,加上socketTimeout参数,
socketTimeout参数用于控制 Socket 连接的读写超时时间,它覆盖了从应用程序发起数据库请求到接收响应的整个网络通信过程中可能发生的阻塞等待时间。 - 其次代码在生成任务尽量不要阻塞在这里,比如少计算和外部IO请求交互,生成任务处每个分组隔离,互不影响等。
4 反思
- 在驱动的参数理解上还不足够,很多时候自己写的代码还不够理解透彻。
- 在代码实现上,本身容错性、隔离性还不够。