作者:张洛丹

原爱可生 DBA 团队成员,现陆金所 DBA 团队成员,对技术执著有追求!

本文来源:原创投稿

*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。


背景

某天晚上,数据库 hang 住,现象是:

  • 应用报错org.apache.commons.dbcp.SQLNestedException: Cannot get a connection,pool error Timeout waiting for idle object
  • 无法登录,输入登录命令就卡着不动,无法响应

无奈之下通过强制 kill 掉进程,重启数据库恢复。

这里暂且不说 hang 住的原因,仅分析数据库 hang 住,但是 MHA 未触发切换。

结论

先说下结论,MHA 默认使用长连接对数据库做 ping 健康检测(执行select 1 as Value),4次无法连接 MySQL 则触发切换。
前面数据库 hang 住只是新的连接无法建立,但是老连接却没有影响,且 MHA 的健康检测语句很简单,只在 server 层进行了检测,不涉及到 InnoDB 层,所以 MHA 认为 MySQL 是健康的,并没有作出任何决策。

解决

MHA 从 0.53 版本开始支持 ping_type 参数设置如何检查 master 的可用性。支持3个 value :

  • select:使用长连接连接到 MySQL 执行select 1 as Value,这个长连接被重复使用,但检查过于简单,无法发现更多故障。
  • connect:在每次执行select 1 as Value前后创建和断开连接,可以发现更多 TCP 连接级别的故障。

注意:此种情况,MHA 监控进程会 fork 出一个子进程进行检测

  • insert:基于一个到 MySQL 已经存在的连接执行 insert 语句,可以更好检测到数据库因磁盘空间耗尽或磁盘 IO 资源耗尽导致的故障。

通过将 ping_type 修改设置为connect,MHA 每次进程状态检测,需要新建连接,新链接无法成功建立,就触发了切换。

三种检测机制代码:

##如果获取分布式锁失败返回2,正常返回0,异常返回1
sub ping_connect($) {my $self = shift;my $log  = $self->{logger};my $dbh;my $rc          = 1;my $max_retries = 2;eval {my $ping_start = [gettimeofday];# 连接max_retries次,连接失败则退出while ( !$self->{dbh} && $max_retries-- ) {eval { $rc = $self->connect( 1, $self->{interval}, 0, 0, 1 ); };if ( !$self->{dbh} && $@ ) {die $@ if ( !$max_retries );}}# 调用ping_select$rc = $self->ping_select();# To hold advisory lock for some periods of time$self->sleep_until( $ping_start, $self->{interval} - 1.5 );$self->disconnect_if();};if ($@) {my $msg = "Got error on MySQL connect ping: $@";undef $@;$msg .= $DBI::err if ($DBI::err);$msg .= " ($DBI::errstr)" if ($DBI::errstr);$log->warning($msg) if ($log);$rc = 1;}return 2 if ( $self->{_already_monitored} );return $rc;
}# 正常返回0,异常返回1
sub ping_select($) {my $self = shift;my $log  = $self->{logger};my $dbh  = $self->{dbh};my ( $query, $sth, $href );eval {$dbh->{RaiseError} = 1;$sth = $dbh->prepare("SELECT 1 As Value");$sth->execute();$href = $sth->fetchrow_hashref;if ( !defined($href)|| !defined( $href->{Value} )|| $href->{Value} != 1 ){die;}};if ($@) {my $msg = "Got error on MySQL select ping: ";undef $@;$msg .= $DBI::err if ($DBI::err);$msg .= " ($DBI::errstr)" if ($DBI::errstr);$log->warning($msg) if ($log);return 1;}return 0;
}# 正常返回0,异常返回1
sub ping_insert($) {my $self = shift;my $log  = $self->{logger};my $dbh  = $self->{dbh};my ( $query, $sth, $href );eval {$dbh->{RaiseError} = 1;$dbh->do("CREATE DATABASE IF NOT EXISTS infra");$dbh->do(
"CREATE TABLE IF NOT EXISTS infra.chk_masterha (`key` tinyint NOT NULL primary key,`val` int(10) unsigned NOT NULL DEFAULT '0')");$dbh->do(
"INSERT INTO infra.chk_masterha values (1,unix_timestamp()) ON DUPLICATE KEY UPDATE val=unix_timestamp()");};if ($@) {my $msg = "Got error on MySQL insert ping: ";undef $@;$msg .= $DBI::err if ($DBI::err);$msg .= " ($DBI::errstr)" if ($DBI::errstr);$log->warning($msg) if ($log);return 1;}return 0;
}

测试

MHA 配置文件

[server default]
manager_log=/Data/mha/log/workdir/my3306tst.log
manager_workdir=/Data/mha/workdir/my3306tst
remote_workdir=/Data/mysql/my3306/mha
master_binlog_dir=/Data/mysql/my3306/log
password=xxx
ping_interval=5
repl_password=xxx
repl_user=xxx
ssh_user=mysql
ssh_port=xxx
user=mha
master_ip_online_change_script="/usr/local/bin/master_ip_online_change"
master_ip_failover_script="master_ip_failover"[server1]
hostname=xxx
port=3306
candidate_master=1[server2]
hostname=xxx
port=3306
candidate_master=1

注意:在测试的时候将ping_interval设置成5,便于快速观测到切换,实际生产中,可根据业务对故障的容忍能力进行调整。

模拟服务器CPU满负载,数据库无法建立新连接
编写一个简单的c程序,如下:

# include <stdio.h>
int main()
{while(1);return 0;
}

编译:

gcc -o out test_cpu.c

执行:

for in in `seq 1 $(cat /proc/cpuinfo | grep "physical id" | wc -l)`; do ./out & done

另外再跑两个 mysqlslap 压测程序:

mysqlslap -c 30000 -i 100 --detach=1 --query="select 1 from dual" --delimiter=";" -uxxx -pxxx -S /xxxx/xxx.sock
  • ping_type=connect 时,4次连接失败触发切换
    此时,在 MHA 切换日志中可以看到连接数据库报错的输出如下:
Got error on MySQL connect: 2013 (Lost connection to MySQL server at 'waiting for initial communication packet',system error: 110)
  • ping_type=select时,未触发切换

有兴趣的同学可自行测试一下

MHA健康检测机制

调用链路:

MasterMonitor.pm|MHA::MasterMonitor::main()
-->
MasterMonitor.pm|MHA::MasterMonitor::wait_until_master_is_dead()
-->
MasterMonitor.pm|MHA::MasterMonitor::wait_until_master_is_unreachable()
-->
MHA::HealthCheck::wait_until_unreachable();
-->
HealthCheck.pm|MHA::HealthCheck::ping_select(或者)
HealthCheck.pm|MHA::HealthCheck::ping_insert(或者)
HealthCheck.pm|MHA::HealthCheck::ping_connect(或者)

MHA 监控进程启动后,会持续监控主节点的状态,主要的健康检测函数是 wait_until_unreachable()。

PS:MHA 监控进程启动过程中,会读取配置文件,对配置文件中的服务器进行一系列检查,包括存活状态、版本信息、从库配置(read_only,relay_log_purge,log-bin,复制过滤等),ssh状态等,若检查不通过,则无法启动

在这个函数中会有一个死循环,持续地进行健康检测

1.首先,测试连接,连接正确返回0,否则返回1。

  • 如果连接 MySQL 成功,则获取分布式锁, 如果获取分布式锁失败,返回状态值为 1
  • 如果连接 MySQL 失败,则返回状态值1和连接失败的报错,对于连接失败的下面几种情况(常见的有1040连接数满和1045权限拒绝)MHA 会认为 MySQL 进程是正常的,并不会触发切换,而是一直进行连接检测
our @ALIVE_ERROR_CODES = (1040,    # ER_CON_COUNT_ERROR1042,    # ER_BAD_HOST_ERROR1043,    # ER_HANDSHAKE_ERROR1044,    # ER_DBACCESS_DENIED_ERROR1045,    # ER_ACCESS_DENIED_ERROR1129,    # ER_HOST_IS_BLOCKED1130,    # ER_HOST_NOT_PRIVILEGED1203,    # ER_TOO_MANY_USER_CONNECTIONS1226,    # ER_USER_LIMIT_REACHED1251,    # ER_NOT_SUPPORTED_AUTH_MODE1275,    # ER_SERVER_IS_IN_SECURE_AUTH_MODE
);

2.测试连接成功后,则进行健康状态检测(前面说的3种方式);如果连续4次连接失败,则在第4次的时候会使用第二脚本进行检测(如果定义了的话),如果检测通过,则认为 master 挂掉

关键函数 wait_until_unreachable()代码:

# main function
sub wait_until_unreachable($) {my $self           = shift;my $log            = $self->{logger};my $ssh_reachable  = 2;my $error_count    = 0;my $master_is_down = 0;eval {while (1) {$self->{_tstart} = [gettimeofday];## 判断是否需要建立连接if ( $self->{_need_reconnect} ) {my ( $rc, $mysql_err ) =$self->connect( undef, undef, undef, undef, undef, $error_count );if ($rc) {if ($mysql_err) {# 错误代码在ALIVE_ERROR_CODES中时,不触发切换,常见的有用户密码不正确,不会切换if (grep ( $_ == $mysql_err, @MHA::ManagerConst::ALIVE_ERROR_CODES )> 0 ){$log->info(
"Got MySQL error $mysql_err, but this is not a MySQL crash. Continue health check..");# next直接进入下次循环$self->sleep_until();next;}}$error_count++;$log->warning("Connection failed $error_count time(s)..");$self->handle_failing();if ( $error_count >= 4 ) {$ssh_reachable = $self->is_ssh_reachable();# 返回1表示主库down,0表示主库没有down$master_is_down = 1 if ( $self->is_secondary_down() );# 主库down则跳出循环last if ($master_is_down);$error_count = 0;}$self->sleep_until();next;}# connection ok$self->{_need_reconnect} = 0;$log->info(
"Ping($self->{ping_type}) succeeded, waiting until MySQL doesn't respond..");}# 如果ping_type为connect,则断开连接$self->disconnect_if()if ( $self->{ping_type} eq $MHA::ManagerConst::PING_TYPE_CONNECT );# Parent process forks one child process. The child process queries# from MySQL every <interval> seconds. The child process may hang on# executing queries.# DBD::mysql 4.022 or earlier does not have an option to set# read timeout, executing queries might take forever. To avoid this,# the parent process kills the child process if it won't exit within# <interval> seconds.my $child_exit_code;eval {# 调用检测函数if ( $self->{ping_type} eq $MHA::ManagerConst::PING_TYPE_CONNECT ) {$child_exit_code = $self->fork_exec( sub { $self->ping_connect() },"MySQL Ping($self->{ping_type})" );}elsif ( $self->{ping_type} eq $MHA::ManagerConst::PING_TYPE_SELECT ) {$child_exit_code = $self->fork_exec( sub { $self->ping_select() },"MySQL Ping($self->{ping_type})" );}elsif ( $self->{ping_type} eq $MHA::ManagerConst::PING_TYPE_INSERT ) {$child_exit_code = $self->fork_exec( sub { $self->ping_insert() },"MySQL Ping($self->{ping_type})" );}else {die "Not supported ping_type!\n";}};if ($@) {my $msg = "Unexpected error heppened when pinging! $@";$log->error($msg);undef $@;$child_exit_code = 1;}if ( $child_exit_code == 0 ) {#ping ok## ping成功的话,则更新状态,并将计数器置为0$self->update_status_ok();if ( $error_count > 0 ) {$error_count = 0;}$self->kill_sec_check();$self->kill_ssh_check();}elsif ( $child_exit_code == 2 ) {$self->{_already_monitored} = 1;croak;}else {  ## 创建连接失败# failed on fork_exec$error_count++;$self->{_need_reconnect} = 1;$self->handle_failing();}$self->sleep_until();}$log->warning("Master is not reachable from health checker!");};if ($@) {my $msg = "Got error when monitoring master: $@";$log->warning($msg);undef $@;return 2 if ( $self->{_already_monitored} );return 1;}return 1 unless ($master_is_down);return ( 0, $ssh_reachable );
}1;

故障分析 | 数据库故障 MHA 未切换相关推荐

  1. MySQL数据库之MHA高可用配置及故障切换实例

    文章目录 一.MHA概念 1.1 MHA 的组成 1.2 MHA 的特点 二.搭建MySQL+MHA 2.1 实验思路 2.2 实验环境.安装包 2.3 服务搭建 2.3.1 修改 master(19 ...

  2. ProxySQL+MGR实现读写分离和主节点故障无感知切换

    ProxySQL+MGR实现读写分离和主节点故障无感知切换 一.环境准备 192.168.153.149 mgr-node1 192.168.153.150 mgr-node2 192.168.153 ...

  3. ProxySQL+MGR实现读写分离和主节点故障无感知切换 - 完整操作记录

    ProxySQL+MGR实现读写分离和主节点故障无感知切换 - 完整操作记录 前面的文章介绍了ProxySQL用法,这里说下ProxySQL中间件针对Mysql组复制模式实现读写分离以及主节点故障时能 ...

  4. oracle数据库故障紧急处理预案-----各种物理文件损坏和逻辑故障的解决办法

    Oracle物理结构故障是指构成数据库的各个物理文件损坏而导致的各种数据库故障.这些故障可能是由于硬件故障造成的,也可能是人为误操作而引起.所以我们首先要判断问题的起因, 如果是硬件故障则首先要解决硬 ...

  5. 常见的数据库故障有哪些?

    故障治理就得对症下药,所以治理的第一步就是明确常见的数据库故障有哪些,今天就 MySQL.缓存两个大方向来跟大家一起梳理一下.   1.MySQL 作为在互联网公司广泛使用的传统意义上的数据库,MyS ...

  6. xp系统怎么关dhcp服务器,怎样解决Win XP操作系统DHCP故障:获取未使用的IP地址

    怎样解决Win XP操作系统DHCP故障:获取未使用的IP地址 发布时间:2011-05-12 14:57:29   作者:佚名   我要评论 在我们解决Windows 操作系统的DHCP故障时,有时 ...

  7. 定位排除数据库故障_对数据库邮件故障进行故障排除

    定位排除数据库故障 描述 (Description) Database Mail is a convenient and easy way to send alerts, reports, or da ...

  8. 分享一次生产应用无法连接Oracle数据库故障

    故障简介 今天一个应用运维同事微信联系我,说有一个应用日志,显示连接Oracle数据库报错,我问了一下是什么应用,对方回答是报表应用,这下心里有谱了. 第一:报表应用重要性不高 第二:一般这种情况,都 ...

  9. 故障分析 | 数据库表空间被 rm 后,怎么处理

    作者:肖亚洲 爱可生 DBA 团队成员,负责项目中数据库故障与平台问题解决,对数据库高可用与分布式技术情有独钟. 本文来源:原创投稿 *爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编 ...

最新文章

  1. 代码实现把字符串反转
  2. 有关函数模板和类模板的说法
  3. 为什么大型科技公司更会发生人员流失 标准 ceo 软件 技术 图 阅读2479 原文:Why Good People Leave Large Tech Companies 作者:steve
  4. python中分支结构包括哪些_python中的分支结构
  5. LeetCode 484. 寻找排列(找规律+贪心)
  6. (二)注册服务提供者
  7. Linux日常运维管理技巧(四)文件同步工具-rsync、Linux系统日志、dmesg命令、lastb命令查看登录失败的用户、screen工具虚拟屏幕
  8. oracle 添加监听地址,oracle批改监听地址为localhost
  9. idea redis 插件_Intellij IDEA 实用插件安利
  10. CSS显示属性上的过渡
  11. fedora core 7下如何安装Fcitx小企鹅输入法
  12. thymeleaf 中 通用的分页方法
  13. Python进阶(十)多进程multiprocessing和subprocess模块
  14. SpringMVC源码解析 - HandlerAdapter - @SessionAttributes注解处理
  15. 幼儿园php源码,v2.8XYCMS幼儿园建站系统
  16. python xlsx文件与csv文件转换
  17. Latex中将图例(图1:)改为(图1-1)
  18. 学习嵌入式Linux,做底层还是应用?底层要掌握哪些技能?
  19. 文件传输助手——自同步、使用方法
  20. 计算机桌面点不进系统,电脑开机进不了桌面,小编教你电脑开机进不了桌面怎么办...

热门文章

  1. 七巧节前夕,恋爱仙子探访黑店记
  2. 微信小程序数据 \n 换行符失效解决办法
  3. JSP流浪动物收容与领养管理系统
  4. 将PKCS12文件转成JKS
  5. c sharp连接mysql
  6. html清除盒子间距,inline-block元素默认间距清除
  7. LCD Keypad Shield
  8. USBCNC自动换刀教程
  9. docker 高级 compose/swarm
  10. 视频质量评价技术零基础学习方法