边看MHA源码边学Perl语言之二 ManagerUtil

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了边看MHA源码边学Perl语言之二 ManagerUtil相关的知识,希望对你有一定的参考价值。

边看MHA源码边学Perl语言之二ManagerUtil.pm

MHA版本

为了让大家有一个共同的代码学习环境,特别从网络找了mha4mysql-manager-0.56,mha4mysql-node-0.56稳定版作为学习和研究对象,大家可以到直接到github上进行clone:

https://github.com/mysql-dev-fun/mha4mysql-manager-0.56
https://github.com/mysql-dev-fun/mha4mysql-node-0.56

Window推荐用phpstrom + perl plugin进行代码查看志分析,测试环境推荐直接在Linux下直接执行。

我的基本环境

ip 说明
192.168.0.110 mha4mysql-manager
192.168.0.100 mysql主库
192.168.0.101   mysql从库一
192.168.0.102 mysql从库二

 

MYSQL主从环境搭建

这里就省略了,大家可以参考大师兄的博客。

http://www.cnblogs.com/gomysql/p/3675429.html

MHA安装

参考官方手册:https://github.com/yoshinorim/mha4mysql-manager/wiki/Installation

(官方的最新版可能会出现各种坑,所以建议大家用稳定版进行学习)。

参考安装步骤:

#安装依赖 ssh to 192.168.0.110
$ yum -y install perl-CPAN perl-devel
$ yum -y install perl-ExtUtils-MakeMaker
$ yum -y install perl-ExtUtils-Embed
$ yum -y install perl-ExtUtils-CBuilder perl-ExtUtils-MakeMaker
$ yum -y install rrdtool perl-rrdtool rrdtool-devel perl-Params-Validate

#下载学习使用的源码
$ cd /usr/local/src
$ git clone https://github.com/mysql-dev-fun/mha4mysql-manager-0.56.git

#编译安装
$ cd mha4mysql-manager-0.56
$ perl -MCPAN -e "install Config::Tiny"
$ perl -MCPAN -e "install Log::Dispatch"
$ perl -MCPAN -e "install Parallel::ForkManager"
$ perl Makefile.PL
$ make
$ sudo make install

MHA & Perl入门

ManagerUtil.pm是一个工具,很多代码都会用到它,且里面的代码也相对较少,非常适合作为我们的首个学习对象。学会看注释是程序的必备技能,你可以看不懂代码,但你不能看不懂注释,在Perl语言中#开头的都为注释,是我们要重点查看的对象。

#### _learn/ManagerUtil.pm

#!/usr/bin/env perl
# 申明脚本的执行环境为 perl,如果文件有执行权限,相关于:
# ./xxx.pl 等价于 perl xxx.pl   和shell 脚本类似

#  Copyright (C) 2011 DeNA Co.,Ltd.
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#  Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

# 申明package name,相当于OOP的Class,同一个项目中使用的package name不能重复。
# Perl 就是用 package 来实现OOP的一些特性,但并不所有的OOP特性都在Perl中找得到。
package MHA::ManagerUtil;

# Perl 浪起来太可怕了,所以建议使用严格语法格式
use strict;
use warnings FATAL => ‘all‘;

# Carp模块提供了carp(),croak(),confess(),cluck(),shortmess(),longmess()六个函数,这些函数产生的错误信息与warn(),die()类似。
# 不同之处在于,后者标识的是出现错误的行号,前者产生调用错误的子程序命令行位置。
use Carp qw(croak);

# 引入MHA的其它package
use MHA::ManagerConst;
use MHA::NodeUtil;

# 引用 Log::Dispatch package,在CPAN中有十万八千个现成的package,俗称造好的“轮子”,你可以利用这些“轮子”,制造出各种各样的“汽车”来实现你的价值。
use Log::Dispatch;
use Log::Dispatch::File;
use Log::Dispatch::Screen;


##### 程序正文开始 ######

# sub 用来申请子程序或方法,和其它语言比起来,特殊的地方是参数的传递,在Perl中,最其基本的参数传递方法为:
# my @argxArr = @_; 用一个数组来接收参数
# 另外一种方法是使用Perl的老地方 + shift的方式,如下面这个 init_log方法,将数组@_中的元素依次赋值给 $log_output,$level,较为常用。
# 延伸学习,shift的用法可以参考:http://perldoc.perl.org/functions/shift.html
# 关于 Log::Dispatch 的使用与说明会在下面章节中重点介绍

# 功能:初始化日志系统
# 参数1:可选,指定日志文件路径
# 参数2:可选,指定日志的level
# 返回值: $log 对象
sub init_log {
    #等价于 my $log_output = shift @_;
    my $log_output = shift;

    #等价于 my level = shift @_;
    my $level      = shift;

    #LOG level的默认值为info,等价于 $level = $level ? $level : "info",就是未指定 level时,就用"info";
    $level = "info" unless ($level);

    # 初始化log对象,并指定日志内容输出格式的回调函数,简单说就是 自定义 日志内容的记录格式
    # 在ManagerConst.pm中可以看出自定义格式为:
    # sprintf( "[%s][%s, ln%d] %s\\n", $args{level}, $script, $ln, $msg );
    # 具体含义以后再详细说明

    my $log = Log::Dispatch->new( callbacks => $MHA::ManagerConst::log_fmt );

    #默认日志输出到Screen,传入文件后将输出到File
    unless ($log_output) {
        $log->add(
            Log::Dispatch::Screen->new(
                name      => ‘screen‘,
                min_level => $level,
                callbacks => $MHA::ManagerConst::add_timestamp,
                mode      => ‘append‘,
            )
        );
    }
    else {
        # 有$log_output时,记录到文件
        $log->add(
            Log::Dispatch::File->new(
                name              => ‘file‘,
                filename          => $log_output,
                min_level         => $level,
                callbacks         => $MHA::ManagerConst::add_timestamp,
                mode              => ‘append‘,
                close_after_write => 1,
            )
        );
    }
    return $log;
}

# 功能:执行本机系统命令
# 参数1:系统从命令
# 参数2:[可选]文件路径
# 返回值:($high,$low)双零为执行成功,非双零为执行发生错误
sub exec_system {
    # 逐个获取参数
    my $cmd        = shift;
    my $log_output = shift;
    # 传入日志文件路径后,命令的正常输出和错误输出均记录到文件中
    # MHA::NodeUtil::system_rc 的功能是将错误代码拆分成高8位和低8位
    if ($log_output) {
        return MHA::NodeUtil::system_rc( system("$cmd >> $log_output 2>&1") );
    }
    else {
        # 直接执行命令,命令的输出将正常输出到屏幕上
        return MHA::NodeUtil::system_rc( system($cmd) );
    }
}

# 功能:在远程主机上执行系统命令,注意这里是没有传入密码的,所有我们需要先做好各主机间的无密码通道;
# 参数1:host
# 参数2:port
# 参数3:cmd
# 参数4:日志文件路径
# 特别说明:方法名后面的 ($$$$) 代表都是必填参数
sub exec_ssh_check_cmd($$$$) {
    my $ssh_host   = shift;
    my $ssh_port   = shift;
    my $ssh_cmd    = shift;
    my $log_output = shift;
    my $ret;
    return exec_system(
        "ssh $MHA::ManagerConst::SSH_OPT_CHECK -p $ssh_port $ssh_host \\"$ssh_cmd\\"",
        $log_output
    );
}

# 功能:在远程主机上执行系统命令,注意这里是没有传入密码的,所有我们需要先做好各主机间的无密码通道;
# 参数1:host
# 参数2:port
# 参数3:cmd
# 参数4:日志文件路径
sub exec_ssh_cmd($$$$) {
    my $ssh_host   = shift;
    my $ssh_port   = shift;
    my $ssh_cmd    = shift;
    my $log_output = shift;
    my $ret;
    return exec_system(
        "ssh $MHA::ManagerConst::SSH_OPT_ALIVE -p $ssh_port $ssh_host \\"$ssh_cmd\\"",
        $log_output
    );
}

# 功能:在远程主机上执行系统命令,注意这里是没有传入密码的,所有我们需要先做好各主机间的无密码通道;
sub get_node_version {
    my $log      = shift;
    my $ssh_user = shift;
    my $ssh_host = shift;
    my $ssh_ip   = shift;
    my $ssh_port = shift;
    my $ssh_user_host;
    my $node_version;
    my $command = "apply_diff_relay_logs --version";

    if ( $ssh_host || $ssh_ip ) {
        if ($ssh_ip) {
            $ssh_user_host = $ssh_user . ‘@‘ . $ssh_ip;
        }
        elsif ($ssh_host) {
            $ssh_user_host = $ssh_user . ‘@‘ . $ssh_host;
        }
        $command =
            "ssh $MHA::ManagerConst::SSH_OPT_ALIVE $ssh_user_host -p $ssh_port \\"$command\\" 2>&1";
    }
    my $v = `$command`;
    chomp($v);
    if ( $v =~ /version (\\d+\\.\\d+)/ ) {
        $node_version = $1;
    }
    else {
        $log->error("Got error when getting node version. Error:");
        $log->error("\\n$v") if ($v);
    }
    return $node_version;
}

# 功能:获取node节点的版本,发生错误时,记录错误信息后,就die(退出程序)了;
sub check_node_version {
    my $log      = shift;
    my $ssh_user = shift;
    my $ssh_host = shift;
    my $ssh_ip   = shift;
    my $ssh_port = shift;
    my $node_version;
    eval {
        $node_version =
            get_node_version( $log, $ssh_user, $ssh_host, $ssh_ip, $ssh_port );
        my $host = $ssh_host ? $ssh_host : $ssh_ip;
        croak "node version on $host not found! Is MHA Node package installed ?\\n"
            unless ($node_version);
        if ( $node_version < $MHA::ManagerConst::NODE_MIN_VERSION ) {
            $host = "local" unless ($host);
            my $msg =
                sprintf( "Node version(%s) on %s must be equal or higher than %s.\\n",
                    $node_version, $host, $MHA::ManagerConst::NODE_MIN_VERSION );
            croak $msg;
        }
    };

    # [email protected]为eval命令的错误消息.如果为空,则表示上一次eval命令执行成功
    if ([email protected]) {
        $log->error([email protected]);
        die;
    }
    return $node_version;
}

# 功能:获取node节点的版本,发生错误时返回错误代码
sub check_node_version_nodie {
    my $log      = shift;
    my $ssh_user = shift;
    my $ssh_host = shift;
    my $ssh_ip   = shift;
    my $ssh_port = shift;
    my $rc       = 1;
    eval {
        check_node_version( $log, $ssh_user, $ssh_host, $ssh_ip, $ssh_port );
        $rc = 0;
    };
    if ([email protected]) {
        undef [email protected];
    }
    return $rc;
}

# 功能:输出错误信息,如果不确认 $log 是否有初始化时使用
# 参数1:信息内容
# 参数2:$log 对象

# should be used when it is unclear whether $log is initialized or not
sub print_error {
    my $str = shift;
    my $log = shift;
    if ($log) {
        $log->error($str);
    }
    else {
        warn "$str\\n";
    }
}

# 在Perl中,return语句可以返回一个标量值或者一个列表,这个标量值可以是一个变量,或者一个表达式的最后求值
# 不过在 package 的含义有些费解
1;

总结一下,通过ManagerUtil.pm我们学到什么:
1.使用package声明"类"名,用来给其它perl程序进行调用,实现代码复用;
2.使用use引入外部package,这些package需存在于perl的lib库中,通过@INC可以查看和改变lib库的路径;
3.使用my声明局部变量,用our可以申表全局变量;
4.使用sub声明方法或函数,参数可以用特殊符号@_来接收;
5.perl函数的返回值可以是多个,可以用列表来接收,如 my($a,$b) = function();
6.特殊符号[email protected]为上一个eval命令的错误消息,如果为空,则表示上一次eval命令执行成功;
7.perl的变量以$开头,数组以@开头,另外还有%开头的是hash;

快速了解perl推荐:
https://learnxinyminutes.com/docs/zh-cn/perl-cn/

ManagerUtil应用实战

在ManagerUtil中,定义了几个非常简单且很常用的方法,以方便在其它脚本中进行使用,接下就是动手实战一下,体验下perl在coding fun:

####  _learn/test/TestManagerUtil.pl 

#!/usr/bin/perl use strict; use warnings FATAL => ‘all‘; # 引入package use MHA::ManagerUtil; #在远程主机上执行命令 #参数说明: # 四个参数依次为: # $ssh_host 远程主机IP # $ssh_port 端口 # $ssh_cmd 命令 # $log_output 日志文件路径 #返回值说明: # 正常的shell出错误的返回值为一个非0的数字,作者经过封装后,进一步把错误代码分别高8位($high)和低8位数字($low),下面一个完整的调用例子 my ( $high, $low ) = MHA::ManagerUtil::exec_ssh_cmd("192.168.0.202","22","/usr/local/mysql/bin/mysql --version","/tmp/mha_fun.log"); #高8位和低8位均为0,代表远程命令执行成功 if ( $high == 0 && $low == 0 ) { MHA::ManagerUtil::print_error("execute command successed.") }else{ #执行失败,原因会很多:如ip不对,port不对,mysql的路径不对,未作ssh无密码通道等等,具体的原因需要查看 /tmp/mha_fun.log 日志文件 #从这里也可以看出一个日志系统的重要性,学习查看和分析日志的重要性 MHA::ManagerUtil::print_error("execute command failed.") } #执行本机命令 #查看刚才(MHA::ManagerUtil::exec_ssh_cmd)的日志文件 MHA::ManagerUtil::exec_system("cat /tmp/mha_fun.log"); #查看mha node的版本 #先初始化一个$log对象,然后再作传 参数传给MHA::ManagerUtil::get_node_version,在MHA的代码类似的代码非常多 my $log = MHA::ManagerUtil::init_log("/tmp/mha_fun.log","debug"); my $node_version = MHA::ManagerUtil::get_node_version($log,"root",undef,"192.168.0.202","22"); print $node_version; ### END, Just run it,so easy,so fun. ###

日志系统快速撑握

日志系统是我们查找一切问题最直接,最快速的"捷径",基本每个系统都有自己完善日志记录,在学习别人项目的时候,可以把自己想要查看的内容通过日志系统记录起来,即快速也不影响原因的代码,所以大家要学会多加利用日志系统来解决自己的问题.接下来我们来体验下CPAN中提供的Log"类":

#### _learn/test/TestLogDispatch.pl

#!/usr/bin/perl
use strict;
use warnings FATAL => ‘all‘;

# Perl日志系统 Log:Dispatch 使用实例代码
# Log::Dispatch,可以把日志信息输入到file,screen,email中,且使用起来非常的简单和方便;
# 以下代码来自CPAN的Log::Dispath项目:
# http://search.cpan.org/~drolsky/Log-Dispatch-2.66/lib/Log/Dispatch.pm

use Log::Dispatch;

# 初始化一个 $log 对象,同时绑定记录到File和输出到Screen,且指定了不同的level
my $log = Log::Dispatch->new(
    outputs => [
        [ ‘File‘,   min_level => ‘debug‘, filename => ‘/tmp/perl.log‘ ],
        [ ‘Screen‘, min_level => ‘info‘ ],
    ],
);

# 生成info日志
$log->info(‘Info:Blah, blah‘);

# 生成debug日志
$log->debug(‘Debug:Blah, blah‘);

# 生成error日志
$log->error(‘Error:Blah, blah‘);

# 运行这个程序后,观察一下Screen和/tmp/perl.log的内容,并思考一下如果使Screen和File的内容完全一样,需要如何修改代码.

# Log::Dispatch 总共有7个级别,具体可能参考文档,关于日志级别,简单总结一下:
# 1.在MHA中可以通过 log_level = [No|App/Global|info|debug] 配置 日志级别.
#    参考:https://raw.githubusercontent.com/wiki/yoshinorim/mha4mysql-manager/Parameters.md
# 2.debug是最低的日志级别,一般用于开发人员记录排错的相关信息.
# 3.程序在运行时,将忽略低于配置log_level的日志输出.如:配置log_level=info时,所有的 $log->debug(‘Blah, blah‘)信息都会被忽略.
# 4.log_level的主要作用是在不用修改代码的前提下,通过简单的配置就可以区分生成环境和开发环境日志内容.

### END, Just sun it,so easy,so fun. ###

总的来说,perl还是很容易上手的,不过那个众多的"特殊符号"确实要比学习其它语言花更多的时间.

整个MHA代码的结构还是非常清晰和易于扩展的,通MHA不仅可以学会perl的语法,整个MHA代码构架也是值得学习的.

 

以上是关于边看MHA源码边学Perl语言之二 ManagerUtil的主要内容,如果未能解决你的问题,请参考以下文章

使用mha 构建mysql高可用碰到几个问题

Centos 6.5 安装配置Mysql MHA

MHA环境搭建node相关依赖的解决

MHA-Atlas-MySQL高可用集群2

MHA搭建及故障维护

面试:MySql-MHA