MySQL · 捉虫动态 · 信号处理机制分析

本文涉及的产品
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS AI 助手,专业版
简介:

背景

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 <pthread.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <unistd.h> 5 #include <signal.h> 6 #include <errno.h> 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 里面还有要初始化的变量呢。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
5月前
|
缓存 关系型数据库 BI
使用MYSQL Report分析数据库性能(下)
使用MYSQL Report分析数据库性能
444 158
|
4月前
|
存储 消息中间件 监控
MySQL 到 ClickHouse 明细分析链路改造:数据校验、补偿与延迟治理
蒋星熠Jaxonic,数据领域技术深耕者。擅长MySQL到ClickHouse链路改造,精通实时同步、数据校验与延迟治理,致力于构建高性能、高一致性的数据架构体系。
MySQL 到 ClickHouse 明细分析链路改造:数据校验、补偿与延迟治理
|
5月前
|
缓存 监控 关系型数据库
使用MYSQL Report分析数据库性能(中)
使用MYSQL Report分析数据库性能
421 156
|
5月前
|
缓存 监控 关系型数据库
使用MYSQL Report分析数据库性能(上)
最终建议:当前系统是完美的读密集型负载模型,优化重点应放在减少行读取量和提高数据定位效率。通过索引优化、分区策略和内存缓存,预期可降低30%的CPU负载,同时保持100%的缓冲池命中率。建议每百万次查询后刷新统计信息以持续优化
524 161
|
4月前
|
NoSQL 算法 Redis
【Docker】(3)学习Docker中 镜像与容器数据卷、映射关系!手把手带你安装 MySql主从同步 和 Redis三主三从集群!并且进行主从切换与扩容操作,还有分析 哈希分区 等知识点!
Union文件系统(UnionFS)是一种**分层、轻量级并且高性能的文件系统**,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem) Union 文件系统是 Docker 镜像的基础。 镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
633 5
|
6月前
|
存储 关系型数据库 MySQL
深入理解MySQL索引类型及其应用场景分析。
通过以上介绍可以看出各类MySQL指标各自拥有明显利弊与最佳实践情墁,在实际业务处理过程中选择正确型号极其重要以确保系统运作流畅而稳健。
224 12
|
7月前
|
存储 SQL 关系型数据库
MySQL的Redo Log与Binlog机制对照分析
通过合理的配置和细致的管理,这两种日志机制相互配合,能够有效地提升MySQL数据库的可靠性和稳定性。
253 10
|
7月前
|
SQL 关系型数据库 MySQL
MySQL group by 底层原理详解。group by 执行 慢 原因深度分析。(图解+秒懂+史上最全)
MySQL group by 底层原理详解。group by 执行 慢 原因深度分析。(图解+秒懂+史上最全)
MySQL group by 底层原理详解。group by 执行 慢 原因深度分析。(图解+秒懂+史上最全)
|
存储 关系型数据库 MySQL
MySQL MVCC全面解读:掌握并发控制的核心机制
【10月更文挑战第15天】 在数据库管理系统中,MySQL的InnoDB存储引擎采用了一种称为MVCC(Multi-Version Concurrency Control,多版本并发控制)的技术来处理事务的并发访问。MVCC不仅提高了数据库的并发性能,还保证了事务的隔离性。本文将深入探讨MySQL中的MVCC机制,为你在面试中遇到的相关问题提供全面的解答。
956 2
|
缓存 关系型数据库 MySQL
MySQL并发支撑底层Buffer Pool机制详解
【10月更文挑战第18天】在数据库系统中,磁盘IO操作是性能瓶颈之一。为了提高数据访问速度,减少磁盘IO,MySQL引入了缓存机制。其中,Buffer Pool是InnoDB存储引擎中用于缓存磁盘上的数据页和索引页的内存区域。通过缓存频繁访问的数据和索引,Buffer Pool能够显著提高数据库的读写性能。
632 2

热门文章

最新文章

推荐镜像

更多