信号数据库mysql开发_MySQL · 捉虫动态 · 信号处理机制分析-阿里云开发者社区...

背景

在 AliSQL 上面有人提交了一个 bug,在使用主备的时候 service stop mysql 不能关闭主库,一直显示 shutting down mysql …,到底怎么回事呢,先来看一下 service stop mysql 是怎么停止数据库的。配置 MySQL 在系统启动时启动需要把 MYSQL_BASEDIR/support-files 目录下的脚本 mysql.sever 放到 /etc/init.d/ 目录下,脚本来控制 mysqld 的启动和停止。看一下脚本中的代码 :

if test -s "$mysqld_pid_file_path"

then

mysqld_pid=`cat "$mysqld_pid_file_path"` if (kill -0 $mysqld_pid 2>/dev/null)

then

echo $echo_n "Shutting down MySQL" kill $mysqld_pid # mysqld should remove the pid file when it exits, so wait for it.

wait_for_pid removed "$mysqld_pid" "$mysqld_pid_file_path"; return_value=$?

...

实际上的关闭动作就是向 mysqld 进程发送一个 kill pid 的信号,也就是 TERM , wait_for_pid 函数中就是不断检测 $MYSQL_DATADIR 下面的 pid 文件是否存在,并且打印 ‘.’,所以上述问题应该是 mysqld 没有正确处理接收到的信号。

信号处理机制

多线程信号处理

进程中的信号处理是异步的,当信号发送给进程之后,就会中断进程当前的执行流程,跳到注册的对应信号处理函数中,执行完毕后再返回进程的执行流程。在多线程信号处理中,一般采用一个单独的线程阻塞的等待信号集,然后处理信号,重新阻塞等待。线程的信号处理有以下几个特点:

每个线程都有自己的信号屏蔽字(单个线程可以屏蔽某些信号)

信号的处理是整个进程中所有线程共享的(某个线程修改信号处理行为后,也会影响其它线程)

进程中的信号是递送到单个线程的,如果一个信号和硬件故障相关,那么该信号就会被递送到引起该事件的线程,否是是发送到任意一个线程。

int pthread_sigmask(int how, const sigset_t * restrict set, sigset_t *restrict oset);

在进程中使用 sigprocmask 设置信号屏蔽字,在线程中使用 pthread_sigmask,他们的基本相同,pthread_sigmask 工作在线程中,失败时返回错误码,而 sigprocmask 会设置 errno 并返回 -1。参数 how 控制设置屏蔽字的行为,值为 SIG_BLOCK(把信号集添加到现有信号集中,取并集), SIG_SET_MASK(设置信号集为 set), SIG_UNBLOCK(从信号集中移除 set 中的信号)。set 表示需要操纵的信号集合。oset 返回设置之前的信号屏蔽字,如果设置 set 为 NULL,可以通过 oset 获得当前的信号屏蔽字。

int sigwait(const sigset_t \*restrict set, int \*restrict sig)

sigwait 将会挂起调用线程,直到接收到 set 中设置的信号,具体的信号将会通过 sig 返回,同时会从 set 中删除 sig 信号。 在调用 sigwait 之前,必须阻塞那些它正在等待的信号,否则在调用的时间窗口就可能接收到信号。

int pthread_kill(pthread_t thread, int sig)

发送信号到指定线程,如果 sig 为 0,可以用来判断线程是否还活着。

man pthread_sigmask 里面给了一个例子:

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 8 /* Simple error handling functions */ 9 10 #define handle_error_en(en, msg) \

11 do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0) 12 13 static void *

14 sig_thread(void *arg)

15 {

16 sigset_t *set = (sigset_t *) arg;

17 int s, sig;

18 19 for (;;) {

20 s = sigwait(set, &sig);

21 if (s != 0)

22 handle_error_en(s, "sigwait");

23 printf("Signal handling thread got signal %d\n", sig);

24 }

25 }

26 27 int main(int argc, char *argv[])

28{

29 pthread_t thread;

30 sigset_t set;

31 int s;

32 /* Block SIGINT; other threads created by main() will inherit

33 * a copy of the signal mask. */ 32 /* Block SIGINT; other threads created by main() will inherit

33 * a copy of the signal mask. */ 34 35 sigemptyset(&set);

36 sigaddset(&set, SIGQUIT);

37 sigaddset(&set, SIGUSR1);

38 s = pthread_sigmask(SIG_BLOCK, &set, NULL);

39 //s = sigprocmask(SIG_BLOCK, &set, NULL); 40 if (s != 0)

41 handle_error_en(s, "pthread_sigmask");

42 43 s = pthread_create(&thread, NULL, &sig_thread, (void *) &set);

44 if (s != 0)

45 handle_error_en(s, "pthread_create");

46 47 /* Main thread carries on to create other threads and/or do

48 * other work */ 49 50 pause(); /* Dummy pause so we can test program */ 51 return 0;

52 }

执行一下:

$ ./a.out &

[1] 5423

$ kill -QUIT %1

Signal handling thread got signal 3

$ kill -USR1 %1

Signal handling thread got signal 10

$ kill -TERM %1

[1]+ Terminated ./a.out

测试了一下,把上面代码的 pthread_sigmask 替换成 sigprocmask ,同样能够正确执行,说明线程也能够继承原进程的屏蔽字,不过还是尽量使用 pthread_sigmask, 表述清楚点,而且说不定还有其它坑。

MySQL 信号处理

MySQL 是典型的多线程处理,它的信号处理形式和上一小节介绍的差不多,在 mysqld 启动的时候调用 my_init_signal 初始化信号屏蔽字,把需要信号处理线程处理的信号屏蔽起来,然后启动信号处理函数,入口是 signal_hand 。

在 my_init_signal 函数中,设置 SIGSEGC, SIGABORT, SIGBUS, SIGILL, SIGFPE 的处理函数为 handle_fatal_signal,把 SIGPIPE,SIGQUIT, SIGHUP, SIGTERM, SIGTSTP 加入到信号屏蔽字里,调用 sigprocmask 和 pthread_sigmask 设置屏蔽字。这一系列动作是在 mysql 启动其它辅助线程之前完成的动作,意图很明显,就是让之后的线程都继承设置的信号屏蔽字,把所有的信号交给信号处理线程去处理。

signal_hand 函数首先把需要处理的信号放到信号集合里去,然后完成 create_pid_file ,data 目录下的 pid 文件实际上是由信号处理线程创建的。接着等待 mysqld 完成启动,各个线程之间需要同步,核心代码是一个死循环,通过 my_sigwait 调用 sigwait 阻塞的等待信号的到来。我们目前主要关心 SIGTERM 的处理,和 SIGQUIT, SIGKILL 处理方式相同,都是调用 kill_server 关闭整个数据库。

Bug Fix

文中开头的链接中提到 loose-rpl_semi_sync_master_enabled = 0 关闭就不会有问题, 如果为 1 就会出现无法关闭的情况,顺着这个线索寻找,rpl_semi_sync_master_enabled 在主备使用 semisync 情况下控制启动 Master 节点的 Ack Receiver 线程,初始化阶段的调用堆栈为:

init_common_variables

|

|----- ReplSemiSyncMaster::initObject

|

|----- Ack_receiver::start

而 init_common_variables 的调用是在 my_init_signal 之前,也就是 Ack Receiver 线程没有办法继承信号屏蔽字,不会屏蔽 SIGTERM 信号。在 my_init_signal 中还有一段这样的代码:

/* Fix signals if blocked by parents (can happen on Mac OS X) */

....

sa.sa_handler = print_signal_warning;

sigaction(SIGTERM, &sa, (struct sigaction\*) 0);

...

对于信号的修改的作用于整个进程的,也就是说之前启动的 Ack Receiver 线程没有信号屏蔽字,而且注册了信号处理函数。当 SIGTERM 发生后,信号处理线程和 Ack Receiver 线程都可以接收信号处理,信号被随机的分发(测试高概率都是发给 Ack Receiver),print_signal_warning 仅仅打印信息到 errlog,就出现了无法关闭 mysqld 的情况了。

修改也比较简单,把 initObject 的操作放到 my_init_signal 之后就好,注意不能把 init_common_variables 整个移到 my_init_signal 之前,因为 my_init_signal 里面还有要初始化的变量呢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值