边看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的主要内容,如果未能解决你的问题,请参考以下文章