亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

作者:張洛丹


原愛(ài)可生 DBA 團(tuán)隊(duì)成員,現(xiàn)陸金所 DBA 團(tuán)隊(duì)成員,對(duì)技術(shù)執(zhí)著有追求!


本文來(lái)源:原創(chuàng)投稿


*愛(ài)可生開(kāi)源社區(qū)出品,原創(chuàng)內(nèi)容未經(jīng)授權(quán)不得隨意使用,轉(zhuǎn)載請(qǐng)聯(lián)系小編并注明來(lái)源。


背景

某天晚上,數(shù)據(jù)庫(kù) hang 住,現(xiàn)象是:

  • 應(yīng)用報(bào)錯(cuò)org.Apache.commons.dbcp.SQLNestedException: Cannot get a connection,pool error Timeout waiting for idle object
  • 無(wú)法登錄,輸入登錄命令就卡著不動(dòng),無(wú)法響應(yīng)

無(wú)奈之下通過(guò)強(qiáng)制 kill 掉進(jìn)程,重啟數(shù)據(jù)庫(kù)恢復(fù)。

這里暫且不說(shuō) hang 住的原因,僅分析數(shù)據(jù)庫(kù) hang 住,但是 MHA 未觸發(fā)切換。

結(jié)論

先說(shuō)下結(jié)論,MHA 默認(rèn)使用長(zhǎng)連接對(duì)數(shù)據(jù)庫(kù)做 ping 健康檢測(cè)(執(zhí)行select 1 as Value),4次無(wú)法連接 MySQL 則觸發(fā)切換。 前面數(shù)據(jù)庫(kù) hang 住只是新的連接無(wú)法建立,但是老連接卻沒(méi)有影響,且 MHA 的健康檢測(cè)語(yǔ)句很簡(jiǎn)單,只在 server 層進(jìn)行了檢測(cè),不涉及到 InnoDB 層,所以 MHA 認(rèn)為 MySQL 是健康的,并沒(méi)有作出任何決策。

解決

MHA 從 0.53 版本開(kāi)始支持 ping_type 參數(shù)設(shè)置如何檢查 master 的可用性。支持3個(gè) value :

  • select:使用長(zhǎng)連接連接到 MySQL 執(zhí)行select 1 as Value,這個(gè)長(zhǎng)連接被重復(fù)使用,但檢查過(guò)于簡(jiǎn)單,無(wú)法發(fā)現(xiàn)更多故障。
  • connect:在每次執(zhí)行select 1 as Value前后創(chuàng)建和斷開(kāi)連接,可以發(fā)現(xiàn)更多 TCP 連接級(jí)別的故障。

注意:此種情況,MHA 監(jiān)控進(jìn)程會(huì) fork 出一個(gè)子進(jìn)程進(jìn)行檢測(cè)

  • insert:基于一個(gè)到 MySQL 已經(jīng)存在的連接執(zhí)行 insert 語(yǔ)句,可以更好檢測(cè)到數(shù)據(jù)庫(kù)因磁盤空間耗盡或磁盤 IO 資源耗盡導(dǎo)致的故障。

通過(guò)將 ping_type 修改設(shè)置為connect,MHA 每次進(jìn)程狀態(tài)檢測(cè),需要新建連接,新鏈接無(wú)法成功建立,就觸發(fā)了切換。

三種檢測(cè)機(jī)制代碼:

##如果獲取分布式鎖失敗返回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 );
      }
    }
    # 調(diào)用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;
}

測(cè)試

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

注意:在測(cè)試的時(shí)候?qū)ing_interval設(shè)置成5,便于快速觀測(cè)到切換,實(shí)際生產(chǎn)中,可根據(jù)業(yè)務(wù)對(duì)故障的容忍能力進(jìn)行調(diào)整。

模擬服務(wù)器CPU滿負(fù)載,數(shù)據(jù)庫(kù)無(wú)法建立新連接 編寫一個(gè)簡(jiǎn)單的c程序,如下:

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

編譯:

gcc -o out test_cpu.c

執(zhí)行:

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

另外再跑兩個(gè) mysqlslap 壓測(cè)程序:

mysqlslap -c 30000 -i 100 --detach=1 --query="select 1 from dual" --delimiter=";" -uxxx -pxxx -S /xxxx/xxx.sock
  • ping_type=connect 時(shí),4次連接失敗觸發(fā)切換 此時(shí),在 MHA 切換日志中可以看到連接數(shù)據(jù)庫(kù)報(bào)錯(cuò)的輸出如下:
Got error on MySQL connect: 2013 (Lost connection to MySQL server at 'waiting for initial communication packet',system error: 110)
  • ping_type=select時(shí),未觸發(fā)切換

有興趣的同學(xué)可自行測(cè)試一下

MHA健康檢測(cè)機(jī)制

調(diào)用鏈路:

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 監(jiān)控進(jìn)程啟動(dòng)后,會(huì)持續(xù)監(jiān)控主節(jié)點(diǎn)的狀態(tài),主要的健康檢測(cè)函數(shù)是 wait_until_unreachable()。

PS:MHA 監(jiān)控進(jìn)程啟動(dòng)過(guò)程中,會(huì)讀取配置文件,對(duì)配置文件中的服務(wù)器進(jìn)行一系列檢查,包括存活狀態(tài)、版本信息、從庫(kù)配置(read_only,relay_log_purge,log-bin,復(fù)制過(guò)濾等),ssh狀態(tài)等,若檢查不通過(guò),則無(wú)法啟動(dòng)

在這個(gè)函數(shù)中會(huì)有一個(gè)死循環(huán),持續(xù)地進(jìn)行健康檢測(cè)

1.首先,測(cè)試連接,連接正確返回0,否則返回1。

  • 如果連接 MySQL 成功,則獲取分布式鎖, 如果獲取分布式鎖失敗,返回狀態(tài)值為 1
  • 如果連接 MySQL 失敗,則返回狀態(tài)值1和連接失敗的報(bào)錯(cuò),對(duì)于連接失敗的下面幾種情況(常見(jiàn)的有1040連接數(shù)滿和1045權(quán)限拒絕)MHA 會(huì)認(rèn)為 MySQL 進(jìn)程是正常的,并不會(huì)觸發(fā)切換,而是一直進(jìn)行連接檢測(cè)
our @ALIVE_ERROR_CODES = (
  1040,    # ER_CON_COUNT_ERROR
  1042,    # ER_BAD_HOST_ERROR
  1043,    # ER_HANDSHAKE_ERROR
  1044,    # ER_DBACCESS_DENIED_ERROR
  1045,    # ER_ACCESS_DENIED_ERROR
  1129,    # ER_HOST_IS_BLOCKED
  1130,    # ER_HOST_NOT_PRIVILEGED
  1203,    # ER_TOO_MANY_USER_CONNECTIONS
  1226,    # ER_USER_LIMIT_REACHED
  1251,    # ER_NOT_SUPPORTED_AUTH_MODE
  1275,    # ER_SERVER_IS_IN_SECURE_AUTH_MODE
);

2.測(cè)試連接成功后,則進(jìn)行健康狀態(tài)檢測(cè)(前面說(shuō)的3種方式);如果連續(xù)4次連接失敗,則在第4次的時(shí)候會(huì)使用第二腳本進(jìn)行檢測(cè)(如果定義了的話),如果檢測(cè)通過(guò),則認(rèn)為 master 掛掉

關(guān)鍵函數(shù) 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) {
            # 錯(cuò)誤代碼在ALIVE_ERROR_CODES中時(shí),不觸發(fā)切換,常見(jiàn)的有用戶密碼不正確,不會(huì)切換
            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直接進(jìn)入下次循環(huán)
              $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表示主庫(kù)down,0表示主庫(kù)沒(méi)有down
            $master_is_down = 1 if ( $self->is_secondary_down() );
            # 主庫(kù)down則跳出循環(huán)
            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,則斷開(kāi)連接
      $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 {
        # 調(diào)用檢測(cè)函數(shù)
        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成功的話,則更新?tīng)顟B(tài),并將計(jì)數(shù)器置為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 {  
        ## 創(chuàng)建連接失敗
        # 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;

分享到:
標(biāo)簽:故障 數(shù)據(jù)庫(kù)
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定