JavaScript回调函数的几种用法
一、背景
近期由测试反馈的问题有点多,其中关于系统可靠性测试提出的问题令人感到头疼,一来这类问题有时候属于“偶发”现象,难以在环境上快速复现;二来则是可靠性问题的定位链条有时候变得很长,极端情况下可能要从 A 服务追踪到 Z 服务,或者是从应用代码追溯到硬件层面。
本次分享的是一次关于 MySQL 高可用问题的定位过程,其中曲折颇多但问题本身却比较有些代表性,遂将其记录以供参考。
架构
首先,本系统以 MySQL 作为主要的数据存储部件。整一个是典型的微服务架构(SpringBoot + SpringCloud),持久层则采用了如下几个组件:
mybatis,实现 SQL <-> Method 的映射
hikaricp,实现数据库连接池
mariadb-java-client,实现 JDBC 驱动
在 MySQL 服务端部分,后端采用了双主架构,前端以 keepalived 结合浮动IP(VIP)做一层高可用。如下:
image.png
说明
MySQL 部署两台实例,设定为互为主备的关系。
为每台 MySQL 实例部署一个 keepalived 进程,由 keepalived 提供 VIP 高可用的故障切换。
实际上,keepalived 和 MySQL 都实现了容器化,而 VIP 端口则映射到 VM 上的 nodePort 服务端口上。
业务服务一律使用 VIP 进行数据库访问。
Keepalived 是基于 VRRP 协议实现了路由层转换的,在同一时刻,VIP 只会指向其中的一个虚拟机(master)。当主节点发生故障时,其他的 keepalived 会检测到问题并重新选举出新的 master,此后 VIP 将切换到另一个可用的 MySQL 实例节点上。这样一来,MySQL 数据库就拥有了基础的高可用能力。
另外一点,Keepalived 还会对 MySQL 实例进行定时的健康检查,一旦发现 MySQL 实例不可用会将自身进程杀死,进而再触发 VIP 的切换动作。
问题现象
本次的测试用例也是基于虚拟机故障的场景来设计的:
image.png
持续以较小的压力向业务服务发起访问,随后将其中一台 MySQL 的容器实例(master)重启。
按照原有的评估,业务可能会产生很小的抖动,但其中断时间应该保持在秒级。
然而经过多次的测试后发现,在重启 MySQL 主节点容器之后,有一定的概率会出现业务却再也无法访问的情况!
二、分析过程
在发生问题之后,开发同学的第一反应是 MySQL 的高可用机制出了问题。由于此前曾经出现过由于 keepalived 配置不当导致 VIP 未能及时切换的问题,因此对其已经有所戒备。
先是经过一通的排查,然后并没有找到 keepalived 任何配置上的毛病。
然后在没有办法的情况下,重新测试了几次,问题又复现了。
紧接着,我们提出了几个疑点:
1.Keepalived 会根据 MySQL 实例的可达性进行判断,会不会是健康检查出了问题?
但在本次测试场景中,MySQL 容器销毁会导致 keepalived 的端口探测产生失败,这同样会导致 keepalived 失效。如果 keepalived 也发生了中止,那么 VIP 应该能自动发生抢占。而通过对比两台虚拟机节点的信息后,发现 VIP 的确发生了切换。
- 业务进程所在的容器是否发生了网络不可达的问题?
尝试进入容器,对当前发生切换后的浮动IP、端口执行 telnet 测试,发现仍然能访问成功。
连接池
在排查前面两个疑点之后,我们只能将目光转向了业务服务的DB客户端上。
从日志上看,在产生故障的时刻,业务侧的确出现了一些异常,如下:
Unable to acquire JDBC Connection [n/a]
java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.
at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:669) ~[HikariCP-2.7.9.jar!/:?]
at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:183) ~[HikariCP-2.7.9.jar!/:?]
…
这里提示的是业务操作获取连接超时了(超过了30秒)。那么,会不会是连接数不够用呢?
业务接入采用的是 hikariCP 连接池,这也是市面上流行度很高的一款组件了。
我们随即检查了当前的连接池配置,如下:
//最小空闲连接数
spring.datasource.hikari.minimum-idle=10
//连接池最大大小
spring.datasource.hikari.maximum-pool-size=50
//连接最大空闲时长
spring.datasource.hikari.idle-timeout=60000
//连接生命时长
spring.datasource.hikari.max-lifetime=1800000
//获取连接的超时时长
spring.datasource.hikari.connection-timeout=30000
其中 注意到 hikari 连接池配置了 minimum-idle = 10,也就是说,就算在没有任何业务的情况下,连接池应该保证有 10 个连接。更何况当前的业务访问量极低,不应该存在连接数不够使用的情况。
除此之外,另外一种可能性则可能是出现了“僵尸连接”,也就是说在重启的过程中,连接池一直没有释放这些不可用的连接,最终造成没有可用连接的结果。
开发同学对"僵尸链接"的说法深信不疑,倾向性的认为这很可能是来自于 HikariCP 组件的某个 BUG…
于是开始走读 HikariCP 的源码,发现应用层向连接池请求连接的一处代码如下:
- 点赞
- 收藏
- 关注作者
评论(0)