一、问题来源

问题来自一位朋友,如下:

 

mysql客户端无法登陆,查看服务器负载没有发现高负载信息。通过pstack查看线程栈信息,没有发现异常信息。

二、问题诊断和解决

一般来讲出现这种情况,我们会使用pstack看看新建立的线程为在什么函数上卡住了,然后很容易就能找到原因。但是出现这个问题过后,当mysql发起连接后卡住后,使用pstack查看mysqld服务端的进程,发现根本就没有线程与之进行交互,因此mysqld怀疑监听线程是不是出了什么问题,因此对mysql客户端连接进程进行了pstack发现如下:

#0  0x00007f262e7889c0 in __connect_nocancel () from /lib64/libpthread.so.0
#1  0x0000000000435123 in inline_mysql_socket_connect (src_file=0x504490 "../../mysql-8.0.20/vio/viosocket.cc", src_line=1054, len=110, addr=0x7fffb38daa20, mysql_socket=...) at ../../mysql-8.0.20/include/mysql/psi/mysql_socket.h:647
#2  vio_socket_connect (vio=0x110d2c0, addr=addr@entry=0x7fffb38daa20, len=len@entry=110, nonblocking=<optimized out>, timeout=-1, connect_done=connect_done@entry=0x7fffb38da9ef) at ../../mysql-8.0.20/vio/viosocket.cc:1054
#3  0x0000000000422ad6 in csm_begin_connect (ctx=0x7fffb38daad0) at ../../mysql-8.0.20/sql-common/client.cc:250
#4  0x000000000041fe99 in mysql_real_connect (mysql=mysql@entry=0xaf16a0 <mysql>, host=host@entry=0x0, user=user@entry=0x10defc0 "root", passwd=passwd@entry=0x10deff0 "xsh0923", db=db@entry=0x0, port=<optimized out>, unix_socket=0x10e0860 "/data/mysql3306/tmp/mysql.sock", client_flag=66560) at ../../mysql-8.0.20/sql-common/client.cc:5600
#5  0x000000000040ddb6 in sql_real_connect (silent=0, password=0x10deff0 "xsh0923", user=0x10defc0 "root", database=0x0, host=0x0) at ../../mysql-8.0.20/client/mysql.cc:4515
#6  sql_connect (host=0x0, database=0x0, user=0x10defc0 "root", password=0x10deff0 "xsh0923", silent=<optimized out>) at ../../mysql-8.0.20/client/mysql.cc:4670
#7  0x000000000040968b in main () at ../../mysql-8.0.20/client/mysql.cc:1326
#8  0x00007f262ce3e495 in __libc_start_main () from /lib64/libc.so.6
#9  0x000000000040a4e1 in _start () at ../../mysql-8.0.20/include/my_alloc.h:86

实际上mysql卡住connect函数上面如下:

 

因此mysql客户端和mysqld服务端都还没有建立好连接,netstat查看如下:

unix  2      [ ACC ]     STREAM     LISTENING     8584712  29676/mysqld         /tmp/mysqlx.sock
unix  7      [ ACC ]     STREAM     LISTENING     8584713  29676/mysqld         /data/mysql3306/tmp/mysql.sock
unix  2      [ ]         STREAM     CONNECTING    0        -                    /data/mysql3306/tmp/mysql.sock
unix  2      [ ]         STREAM     CONNECTING    0        -                    /data/mysql3306/tmp/mysql.sock
unix  2      [ ]         STREAM     CONNECTING    0        -                    /data/mysql3306/tmp/mysql.sock
大量connectint状态的连接

然后做了strace mysqld的监听线程的操作,得到的结果如下:

 

正常的情况下这里应该是poll和accept然后开启新的(或者从缓存线程中拿一个)线程来处理交互信息了。但是这里我们可以清晰的看到出现了SIGSTOP信号。随即查看正mysqld线程的状态如下:

 

所有的线程都处于T状态下,这个状态正是由于信息SIGSTOP引起的。因此我们发一个SIGCONT信号给mysqld进程就好了如下:

kill -18 29676

推荐观看:Mysql精讲教程

三、关于信号

下面是我学习信号的时候一些笔记。

在Linux中信号是一种由内核处理的一种软中断机制,他满足简单、不能携带大量信息、并且要满足一定条件才会发送等特征。信号会经历如下过程:

  • 产生-->阻塞信号集-->未决信号集-->信号递达-->信号处理方式

首先信号的产生可以有多种方式比如我们经常用的kill命令就是发起信号的一种手段如下:

[root@mgr4 8277]# kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

我们经常的按键也可以产生

  • Ctrl+c 2)SIGINT
  • Ctrl+\ 3)SIGQUIT
  • Ctrl+z 4)SIGTSTP

当然还有很多其他触发方式比如硬件异常,raise函数,abort函数,alarm函数等等。其次是阻塞信号集,阻塞信号集能够对想除(9/19号信号以外)的信号进行屏蔽,如果屏蔽后信号自然不会到达 递达 状态,也就谈不上处理了。通过sigprocmask函数进行阻塞信号集的设置,但之前必须要设置sigset_t 集合,通过sigaddset、sigdelset、sigemptyset、sigfillset等函数设置。

未决信号集是不能被操作的,只能被获取通过sigpending函数获取,但是他和阻塞信号集一起可以控制信号的递达。

信号递达后就需要处理信号,默认的行为上面都列举了,但是信号(9/19号信号以外)是可以被捕获改变其处理方式的,我们可以自定义函数作为某个信号的处理方式,这可以通过signal函数和sigaction函数进行捕获和处理,sigaction函数相对复杂需要有一个struct sigaction的结构体变量,其中包含了sa_handler\sa_mask\sa_flags\sa_siaction 成员,这里不做解释可以自行查看Linux man page。

上面是单进程下的信号处理方式,在多线程下,线程之间公用处理方式,但是可以有不同的信号屏蔽集,在多线程下一般采用设置统一的信号屏蔽字和信号处理方式使用pthread_mask函数继承到各个线程,同时使用sigwait/sigwaitinfo等函数设置一个单独的信号处理线程来进行统一处理,MySQL就是这样处理的。MySQL实际上有一个信号处理线程,

|     36 |         1927 | sql/signal_handler              |    NULL | BACKGROUND | NULL   | NULL         |

如果需要了解可以参考如下文章: MySQL中对信号的处理(SIGTERM,SIGQUIT,SIGHUP等 ) , http://blog.itpub.net/7728585/viewspace-2142060/

四、pstack和SIGSTOP

这里简单提一下pstack抓取线程栈的时候会触发一个SIGSTOP信号,因为pstack实际上调用的就是gdb的如下命令:

/usr/bin/gdb --quiet -nx /proc/8277/exe 8277
thread apply all bt
输出所有线程信息

gdb通常会发起SIGSTOP信号来停止进程的运行,因此我们在线上执行pstack命令的时候一定要小心,pstack可能导致你的MySQL停止运行一小会,特别是高压力线程很多负载很高的情况下,可能需要很长的时候, pstack应该作为线上重要数据库最后不得已而为之的诊断方式 。

如下我们使用pstack 来获取mysqld的栈,运行一开始就关闭窗口,此时我们的mysqld已经处于停止状态如下:

因此使用pstack一定要格外小心。一般来讲从库线程不多压力不大重要性不高可以使用pstack进行信息采集。当然也可以手动发起SIGSTOP信号来达到测试效果。

 

五、捕获所有的信号

下面一个小代码可以捕获所有的信号供测试:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include "unistd.h"

void handler(int sig)
{
  printf("get signal %d\n",sig);
}

void test()
{
 ;
}

int main(void)
{
  struct sigaction s;
  int* a=NULL;
  int  i=0;
  
  a=(int*)calloc(32,sizeof(int));

  for(i=0;i<32;i++)
  {
    *(a+i) = (i+1);
  }
  
  s.sa_handler=handler;

  for(i=0;i<32;i++)
  {
   sigaction(*(a+i),&s,NULL); 
  }
 
  while(1)
  {
    test();
    sleep(1);
  }
 
 return 0;
}

全文完。

原文链接:https://www.jianshu.com/p/93b69955344b

如果觉得本文对你有帮助,可以关注一下我公众号,回复关键字【面试】即可得到一份Java核心知识点整理与一份面试大礼包!另有更多技术干货文章以及相关资料共享,大家一起学习进步!