TR2021_0000偶发数据库连接异常问题排查

摘要:
数据库连接异常是很难排查的一类问题。任何一个组件异常,都会导致数据库连接失败。下面是一个数据库偶发连接不上的例子:步骤分析S(主观)某应用程序,有40台左右应用服务器,时不时的会报数据库连接异常。但这个情有可原,因为DB端超过1.05秒还没有响应客户端的SYN请求,客户端不耐烦,直接中止了连接。

【问题描述】

数据库连接异常是很难排查的一类问题。因为它牵涉到应用端,网络层和服务器端。任何一个组件异常,都会导致数据库连接失败。开发遇到数据库连接不上的问题,都会第一时间找DBA来协助查看,DBA除了需要懂得数据库以外,还需要对应用,对网络有所了解,知道在哪里看应用程序的日志,以及看网络交换机性能指标,才能清晰的定位问题。下面是一个数据库偶发连接不上的例子:

步骤分析
S(主观)某应用程序,有40台左右应用服务器,时不时的会报数据库连接异常。报错后迅速自愈。报错内容为: Communications link failure. The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. 报错频率大概每天有四五次左右。应用是新上的。
O(客观)MySQL版本为5.7.23, 服务端操作系统版本为7.6.1810. 瞬间有20多台应用服务器都报这个错误。马上自愈。应用部署在同城两个机房,每个机房都有报错。每天总会出现四五次。
A(评估)同时有20多台应用服务器报错,而且分布在两个机房。问题应该出在服务端。
P(计划)建议在服务端抓网络包,进一步定位问题。并进一步查看服务端的性能计数器。

【问题分析】

通过问题的初步评估,故障应该是出现在服务端,也有可能是服务端的网络。我们随之查看了服务端MySQL的各项性能指标,在故障期间,性能指标都是正常的。在服务器端单边抓包,抓到的内容如下:

时间戳目标Info
2021-01-08 17:15:05.667331应用DB48184 -> 3309 [SYN]
2021-01-08 17:15:06.717752应用DB[TCP Retransmission] 48144 -> 3309 [SYN]
2021-01-08 17:15:06.717762DB应用3309 -> 48184 [SYN, ACK]
2021-01-08 17:15:06.719101应用DB48144 -> 3309 [RST]

上述网络包解读如下:

  • 17:15:05.66 应用服务器(端口号48184)向DB服务器(端口号3309) 发送一个SYN请求。
  • 过了1.05秒左右,应用服务器再次向DB服务器发送SYN请求
  • 在17:15:06.71的时候,DB服务器响应了客户端的请求,发送了确认包ACK
  • 在17:15:06.71的时候,应用服务器发送了连接重置的请求,要求中止连接。

从上述的特征来看,连接重置是客户端发起的。但这个情有可原,因为DB端超过1.05秒还没有响应客户端的SYN请求,客户端不耐烦,直接中止了连接。而这个1.05秒,恰好是开发在客户端的Tomcat JDBC connect Timeout连接超时设定。所以问题进一步缩小到服务端的TCP/IP连接层面。

我们用下面的命令,来监控服务端丢包情况:

# netstat -s | egrep "listen|LISTEN"
    5268 times the listen queue of a socket overflowed
    5268 SYNs to LISTEN sockets dropped

发现确实在服务端丢包,而且出现的频率和客户端报错的时间吻合。每次应用程序报错,服务端的drop数也会随之增加。

对于Linux服务器端,server端建连过程需要两个队列,一个是SYN queue,一个是accept queue。前者叫半开连接(或者半连接)队列,后者叫全连接队列。从服务器角度看,建立TCP连接的过程如下图:
TR2021_0000偶发数据库连接异常问题排查第1张

SYNs to LISTEN sockets dropped的丢包类型,说明在半连接队列出现了问题。

分析发现,该实例部署的DB数量比较多,且对应的应用服务器数量也比较多,应用会有短时间创建大量连接的情况,导致半连接队列溢出。半连接队列的大小取决于linux的参数tcp_max_syn_backlog、somaxconn和应用程序调用Listen方法时的backlog参数。

关于tcp_max_syn_backlog、somaxconn与半连接队列的分析,网上的资料非常多,这里不展开细节分析,我们可以简单地认为:

全连接队列长度 = min(somaxconn, backlog)
半连接队列长度 = min(应用调用Listen的backlog,somaxconn,tcp_max_syn_backlog)

我们可以用下面的命令,来监控全连接的队列大小,发现值为128,可以说明我们somaxconn值或者mysqld的backlog设置比较小,从而导致半连接队列也比较小。

#ss -ntlp | more
LISTEN     0      128        :::3309                    :::*    

对于这次的案例,我们先调大了linux操作系统的两个参数。

#echo 'net.ipv4.tcp_max_syn_backlog=65535' >> /etc/sysctl.conf
#echo 'net.core.somaxconn=65535' >> /etc/sysctl.conf
#sysctl –p

接下来,我们分析下mysqld调用Listen方法时,backlog相关的代码。
MYSQLD在启动时会调用network_init的方法,会传入back_log参数,代码如下:


static bool network_init(void)
{
  if (opt_bootstrap)
    return false;
  set_ports();
  if (!opt_disable_networking || unix_sock_name != "")
  {
    std::string const bind_addr_str(my_bind_addr_str ? my_bind_addr_str : "");

    Mysqld_socket_listener *mysqld_socket_listener=
      new (std::nothrow) Mysqld_socket_listener(bind_addr_str,
                                                mysqld_port, back_log,
                                                mysqld_port_timeout,
                                                unix_sock_name);
    if (mysqld_socket_listener == NULL)
      return true;
    mysqld_socket_acceptor=
      new (std::nothrow) Connection_acceptor(mysqld_socket_listener);
}

创建出mysqld_socket_listener对象后,传递给Connection_acceptor后,最终会调用操作系统的Listen方法,代码如下:


static inline int inline_mysql_socket_listen
(
MYSQL_SOCKET mysql_socket, int backlog)
{
  int result;
  if (mysql_socket.m_psi != NULL)
  {
    /* Instrumentation start */
    PSI_socket_locker *locker;
    PSI_socket_locker_state state;
    locker= PSI_SOCKET_CALL(start_socket_wait)
      (&state, mysql_socket.m_psi, PSI_SOCKET_CONNECT, (size_t)0, src_file, src_line);

    /* Instrumented code */
    result= listen(mysql_socket.fd, backlog);

对于back_log值的确定,mysql有2种处理方式。
一种是情况默认情况,会设置50 + (max_connections / 5)且不超过900。具体的代码如下:


int init_common_variables()
{
  umask(((~my_umask) & 0666));
  my_decimal_set_zero(&decimal_zero); // set decimal_zero constant;
  tzset();      // Set tzname

  /* Fix back_log */
  if (back_log == 0 && (back_log= 50 + max_connections / 5) > 900)
      back_log= 900;
}

另外一种情况,也可以手动设置mysqld的启动参数。


static Sys_var_ulong Sys_back_log(
       "back_log", "The number of outstanding connection requests "
       "MySQL can have. This comes into play when the main MySQL thread "
       "gets very many connection requests in a very short time",
       READ_ONLY GLOBAL_VAR(back_log), CMD_LINE(REQUIRED_ARG),
       VALID_RANGE(0, 65535), DEFAULT(0), BLOCK_SIZE(1));

为了保持服务器配置的统一化,我们这次调整了mysqld的启动back-log=2048,调整完成后,重启mysql服务,观察了2天,再未发生半连接丢包的问题。

【问题总结】

全连接队列、半连接队列溢出这种问题很容易发生,但可能报错具有偶然性,很容易被忽视,linux版本的不断进化,TCP的设计也越来越复杂,作为一名合格的DBA,也需要对网络有所了解,才能更好地定位和处理日常的运维问题。

免责声明:文章转载自《TR2021_0000偶发数据库连接异常问题排查》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Buffer Cache 原理Cannot find module '@babel/core'下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

CentOS 7下Cloudera Manager及CDH 6.0.1安装过程详解

目录 一、概念介绍 1、CDH 概览 2、Cloudera Manager 概览 二、环境准备 1、软件版本选择 2、节点准备(四个节点) 3、配置主机名和hosts解析(所有节点) 4、关闭防火墙 5、关闭SELinux 6、添加定时任务 7、禁用透明大页面压缩 8、优化交换分区 三、安装 CM 和 CDH 1、配置 Cloudera...

CentOS7安装mysql后无法启动服务,提示Unit not found

1、最近在centos7上面进行mysql安装完成后,无法启动,报如下错误: Failed to start mysql.server.service: Unit not found. 2、这个是知乎上面找到的无法启动的答案:https://www.zhihu.com/question/41832866 3、解决办法一:使用MariaDB代替mysql数...

python网络/并发编程部分简单整理

软件开发架构C/S架构:Client与Server客户端与服务器端架构 .exeB/S架构:Browser与Server浏览器端与服务器端架构IP地址: IP地址是指互联网协议地址 IP地址通常用“点分十进制”表示,实际上是32位二进制数,通常被分割为4个“8位二进制数”(也就是4个字节)port: 设备与外界通讯交流的出口...

Ubuntu查看crontab运行日志

Ubuntu服务器/var/log下没有cron日志,这里记录一下如何ubuntu server如何查看crontab日志 crontab记录日志 修改rsyslog sudo vim /etc/rsyslog.d/50-default.conf cron.* /var/log/cron.log #将cron前面的注释符去掉 重启rsyslog sudo...

Kafka集群搭建及安全机制手册

Mycat+Mysql的读写分离 前言: 在MySQL中间件出现之前,对于MySQL主从集群,如果要实现其读写分离,一般是在程序端实现,这样就带来一个问题,即数据库和程序的耦合度太高,如果我数据库的地址发生改变了,那么我程序端也要进行相应的修改,如果数据库不小心挂掉了,则同时也意味着程序的不可用,而这对很多应用来说,并不能接受。 在这里,我用三个实例组成M...

基于PySpark的网络服务异常检测系统 (四) Mysql与SparkSQL对接同步数据 kmeans算法计算预测异常

基于Django Restframework和Spark的异常检测系统,数据库为MySQL、Redis, 消息队列为Celery,分析服务为Spark SQL和Spark Mllib,使用kmeans和随机森林算法对网络服务数据进行分析;数据分为全量数据和正常数据,每天通过自动跑定时job从全量数据中导入正常数据供算法做模型训练。 使用celery批量导入...