关键词:MHA,mysql mha

【1】需求

  采用mysql技术,实现MHA高可用主从环境,预计未来数据量几百G

  MHA概念参考:MYSQL高可用技术概述

【2】环境技术架构

【2.1】MHA简介

该软件由两部分组成:

  • MHA Manager(管理节点)
  • MHA Node(数据节点)

MHA Manager可以单独部署在一台独立的机器上管理多个master-slave集群,也可以部署在一台slave节点上。

MHA Node运行在每台MySQL服务器上,MHA Manager会定时探测集群中的master节点,当master出现故障时,它可以自动将最新数据的slave提升为新的master,然后将所有其他的slave重新指向新的master。

整个故障转移过程对应用程序完全透明。

可以将MHA工作原理总结为如下

  1. 从宕机崩溃的master保存二进制日志事件(binlog events)
  2. 识别含有最新更新的slave
  3. 应用差异的中继日志(relay log)到其他的slave;
  4. 应用从master保存的二进制日志事件(binlog events);
  5. 提升一个slave为新的master;
  6. 使其他的slave连接新的master进行复制;

【2.2】MHA工具包

Manager工具包

组件名称 组件说明
masterha_check_ssh 检查MHA的SSH配置状况
masterha_check_repl 检查MySQL复制状况
masterha_manger 启动MHA
masterha_check_status 检测当前MHA运行状态
masterha_master_monitor 检测master是否宕机
masterha_master_switch 控制故障转移(自动或者手动)
masterha_conf_host 添加或删除配置的server信息

Node工具包

这些工具通常由MHA Manager的脚本触发,无需人为操作

组件名称 组件说明
save_binary_logs 保存和复制master的二进制日志
apply_diff_relay_logs 识别差异的中继日志事件并将其差异的事件应用于其他的slave
filter_mysqlbinlog 去除不必要的ROLLBACK事件(MHA已不再使用这个工具)
purge_relay_logs 清除中继日志(不会阻塞SQL线程)

【2.3】基本操作环境与架构

  操作系统:5台 centos7.5

  数据库版本:mysql5.7.24

  MHA 软件  :MHA 0.58

  数据库架构:基于MHA 软件实现主从复制,采用GTID+无损同步复制技术,双主多从。

项目具体部署信息
角色 ip地址 主机名 server_id 类型
Monitor host 192.168.1.201 db1   监控复制组
master 192.168.1.202 db2 2023306 写入
slave1 192.168.1.203 db3 2033306 读(备用master)
slave2 192.168.1.204 db4 2043306
slave3 192.168.1.205 db5 2053306

【3】实践环境准备(搭建GTID+半同步的1主3从)

【3.0】注意事项

(1)不要将read_only=1写进从库的配置文件,因为主库宕机时,从库要提升为主库接受写请求  mysql -e"set global read_only=1"
(2)主从节点复制的过滤规则要相同,即binlog_do_db 与 binlog_ignore_db 参数主从配置需要相同
(3)从节点需要修改配置参数 relay_log_purge=0 ,即关闭中继日志的清除

(4)serverid不能一样

#采用命令方式将从库设为只读,不要将该参数写进配置文件中
mysql -e"set global read_only=1"
#关闭中继日志的清除
mysql -e"set global relay_log_purge=0"

【3.1】host

echo "127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4">> /etc/hosts
echo "::1 localhost localhost.localdomain localhost6 localhost6.localdomain6" >>/etc/hosts echo "192.168.1.201 db1" >>/etc/hosts
echo "192.168.1.202 db2" >>/etc/hosts
echo "192.168.1.203 db3" >>/etc/hosts
echo "192.168.1.204 db4" >>/etc/hosts
echo "192.168.1.205 db5" >>/etc/hosts

【3.2】my.cnf

[client]
port =
socket = /mysql/data//mysql.sock
default-character-set=utf8 [mysql]
disable-auto-rehash #允许通过TAB键提示
default-character-set = utf8
connect-timeout = [mysqld]
server-id =
port =
user = mysql
socket = /mysql/data//mysql.sock
pid-file = /mysql/data//mysql.pid
basedir = /mysql/app/mysql/
datadir = /mysql/data//data
#bind_address = 10.10.10.11
autocommit = character-set-server=utf8
explicit_defaults_for_timestamp=true
lower_case_table_names=
back_log=
max_connections=
max_connect_errors=
table_open_cache=
external-locking=FALSE
max_allowed_packet=32M
sort_buffer_size=2M
join_buffer_size=2M
thread_cache_size=
query_cache_size=32M
#query_cache_limit=4M
transaction_isolation=READ-COMMITTED
tmp_table_size=96M
max_heap_table_size=96M ###***logs
long_query_time =
slow_query_log =
slow_query_log_file=/mysql/log//slow.log
log-error_verbosity=
log-error = /mysql/log//mysql.err
log_output = FILE #参数log_output指定了慢查询输出的格式,默认为FILE,你可以将它设为TABLE,然后就可以查询mysql架构下的slow_log表了 #log-queries-not-using-indexes
#log-slow-slave-statements
#general_log =
#general_log_file = /mysql/log//mysql.log
#max_binlog_size = 1G
#max_relay_log_size = 1G #replication_new
log_bin=/mysql/log//mysql-bin #开启binlog
log_bin_index=/mysql/log//mysql-bin.index
binlog_format=row
binlog_rows_query_log_events=on
max_binlog_size= bind-address=0.0.0.0
server_id= #从库务必记得修改
expire_logs_days= #超过7天的binlog清理
innodb_support_xa=
binlog_cache_size=1M
log_bin_trust_function_creators= #同步存储过程、函数、触发器
innodb_flush_log_at_trx_commit=
sync_binlog=
transaction-isolation=read-committed #slave parameter 如果是从库,务必放开
#relay_log=/mysql/log//relaylog/mysql-relay.log
#relay_log_purge=
#read_only=  
#slave-parallel-type=LOGICAL_CLOCK
#slave-parallel-workers=
#master_info_repository=table #master_info 会记录到 mysql.slave_master_info
#relay_log_info_repository=table #relay_log 会记录到,mysql.slave_relay_log_info
#relay_log_recovery=
#slave_skip_errors=ddl_exist_errors
#slave_preserve_commit_order= #.7的增强半同步
#如果是5.,参数前面加上loose_,如下列,如果是5. 则直接使用 rpl_semi_sync_master_enabled= 之类的就好了。
#我这里是5.7就直接做增强半同步了(loseless Semisynchronous ) plugin_dir=/mysql/app/mysql/lib/plugin/
plugin_load=rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so
loose_rpl_semi_sync_master_enabled= #MySQL开启主的半同步复制(rpl_semi_sync_master_enabled)
loose_rpl_semi_sync_slave_enabled= #MySQL5.6开启从的半同步复制
loose_rpl_semi_sync_master_timeout= #超时5秒,切回异步
rpl_semi_sync_master_wait_for_slave_count= #至少收到1个slave发会的ack
rpl_semi_sync_master_wait_point=AFTER_SYNC #MySQL .7的方法,AFTER_SYNC(default value,增强半同步) & AFTER_COMMIT(传统半同步) #GTID mode
gtid_mode=on
enforce_gtid_consistency=
log-slave-updates=
binlog_gtid_simple_recovery=

【3.3】准备测试数据

  (主库上跑,即202那台机器)

-- 【3.3.1】创建复制用户
create user 'rpl'@'192.168.1.%' identified by '';
grant replication slave on *.* to 'rpl'@'192.168.1.%';
flush privileges;
select user,host from mysql.user; -- 【3.3.2】构造测试数据
-- 构造test库和test库下的test1,test2,test3表。test4表用于模拟业务一直在运行
create database test;
use test;
create table test1(id int);
insert into test1 values(1);
create table test2(id int);
insert into test2 values(2);
create table test3(id int);
insert into test3 values(3);
commit;
create table test4(id int);
insert into test4 values(4);
commit; -- 构造存储过程sp_test4来循环插入test4表,模拟业务运行
use test;
drop procedure if exists sp_test4;
delimiter $$
create procedure sp_test4()
begin
declare n int;
set n=11;
while(n<=20)
do
insert into test.test4 values(n);
commit;
set n=n+1;
end while;
end $$
delimiter ; -- 构造事件,来调度sp_test4过程
use test;
set global event_scheduler=1; delimiter $$
create event if not exists event_test4
on schedule every 5 second
on completion preserve
enable
do
begin
call sp_test4();
end $$
delimiter ; -- 为了防止测试数据量累计导致卡顿,我这里5小时做一次truncate
delimiter $$
create event if not exists event_truncate_test4
on schedule every 5 hour
on completion preserve
enable
do
begin
truncate table test.test4;
end $$
delimiter ;

【3.4】基于xtrabackup的备份恢复初始化

-- 在202 主库上备份
innobackupex --defaults-file=/etc/my.cnf -uroot -p123456 --no-timestamp /mysql/backup/full.bak -- 传输到从库
scp -r /mysql/backup/full.bak root@192.168.1.203:/mysql/backup/
scp -r /mysql/backup/full.bak root@192.168.1.204:/mysql/backup/
scp -r /mysql/backup/full.bak root@192.168.1.205:/mysql/backup/ #在从服务器还原
innobackupex --apply-log /mysql/backup/full.bak
service mysql stop
cd /mysql/data/
mkdir data
innobackupex --defaults-file=/etc/my.cnf --copy-back /mysql/backup/full.bak
chown -R mysql:mysql /mysql
chmod -R /mysql

【3.5】主从配置

#在从服务器上执行
stop slave;
reset slave;
reset master;
SET @MYSQLDUMP_TEMP_LOG_BIN = @@SESSION.SQL_LOG_BIN;
SET @@SESSION.SQL_LOG_BIN= ;
set global gtid_purged='2c8b1813-e26f-11e9-adce-000c29658c19:1-417'; -- 这个从/mysql/backup/full.bak/xtrabackup_info 中获得
SET @@SESSION.SQL_LOG_BIN = @MYSQLDUMP_TEMP_LOG_BIN;
show master status; change master to
master_host='192.168.1.202',
master_user='rpl',
master_password='',
master_port=,
master_auto_position=; start slave;

【3.6】核验主从同步

(1)show slave status\G

(2)show processlist;

(3)select count(1) from test.test4;

【4】MHA

  安装参考:https://www.cnblogs.com/winstom/p/11022014.html

  概念:高可用架构方案  中的【3】MHA

【4.1】软件下载与依赖参考

【4.2】准备perl环境,安装 mha node 与 manager节点

需要在5台机器上都安装mha node,在monitor节点上安装 manager节点

#【4.2.1】安装依赖包: 监控节点manager(201)必须配置好网络yum源与epel源。其他的节点只需要本地yum源即可。 参考:yum源配置#monitor节点 一定要先安装epel源
yum install epel-release 
yum install -y perl-ExtUtils-CBuilder perl-ExtUtils-MakeMaker perl-CPAN perl-DBD-MySQL perl-Config-Tiny perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes
#【4.2.2】开始在所有节点安装node节点(这个可以只有操作系统的yum源)
cd mha4mysql-node-0.58
perl Makefile.PL
make && make install
#【4.2.2】/usr/local/bin/ 目录下会出现4个工具(node)
-r-xr-xr-x.  root root  Oct  : apply_diff_relay_logs
-r-xr-xr-x. root root Oct : filter_mysqlbinlog
-r-xr-xr-x. root root Oct : purge_relay_logs
-r-xr-xr-x. root root Oct : save_binary_logs
Node脚本说明:(这些工具通常由MHA Manager的脚本触发,无需人为操作) save_binary_logs //保存和复制master的二进制日志
apply_diff_relay_logs //识别差异的中继日志事件并将其差异的事件应用于其他的slave
filter_mysqlbinlog //去除不必要的ROLLBACK事件(MHA已不再使用这个工具)
purge_relay_logs //清除中继日志(不会阻塞SQL线程)

#【4.2.3】开始在201安装manager(【注意查看【4.2】的yum依赖包安装,否则该步骤会部分操作失败】)

cd mha4mysql-manager-0.58
perl Makefile.PL
make && make install

#【4.2.4】把上面生成的工具命令所在目录添加到环境变量

 echo "export PATH=${PATH}:/usr/local/bin">>/etc/profile source /etc/profile

#201机器,安装完node和manager节点之后,/usr/local/bin 目录如下

  

【4.3】秘钥互信及复制mysql共享库到系统库

  (要MHA故障自动转移必须要这个)

(1)秘钥互信
配置所有机器相互之间root用户秘钥互信
在所有机器上执行: 生成密钥对
  ssh-keygen -t dsa -f ~/.ssh/id_rsa -P "" #推送公钥
ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.1.201
ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.1.202
ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.1.203
ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.1.204
ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.1.205
#此时所有的机器之间以完成互信,无需密码等即可ssh登陆 (2)复制mysql客户端共享库到Linux系统库下
cp /mysql/app/mysql/lib/libmysqlclient.so.20 /lib64/

【4.4】MHA配置及relay_log清理脚本(4.4.3)

在monitor,即201机器上配置

#【4.4.1】复制监控节点配置文件
mkdir -p /etc/masterha
cp /soft/mha4mysql-manager-0.58/samples/conf/app1.cnf  /etc/masterha/ #默认引用配置文件位置为:/etc/masterha_default.cnf #【4.4.2】修改监控节点配置文件
mkdir -p /var/log/masterha/app1
touch /var/log/masterha/app1/manager.log
chmod -R 777 /var/log/masterha/
vim /etc/masterha/app1.cnf
[server default]
manager_workdir=/var/log/masterha/app1 #管理节点工作目录
manager_log=/var/log/masterha/app1/manager.log #管理节点日志
master_binlog_dir=/mysql/log/3306 #数据库Binlog所在日志
master_ip_failover_script=/usr/local/bin/master_ip_failover #自动故障转移的脚本
master_ip_online_change_script=/usr/local/bin/master_ip_online_change #手动在线切换主节点的脚本
password= #mysql数据库监控密码
user=root #mysql数据库监控用户
ping_interval= #发送ping间隔包的时间,默认3S,这里1是1S,如果3次没有响应则故障切换
remote_workdir=/tmp #mysql切换的时候binlog保存路径
repl_password= #mysql复制的mysql用户密码
repl_user=rpl #mysql复制的Mysql用户账户
report_script=/usr/local/bin/send_report #发生切换之后的报警报告脚本
secondary_check_script=/usr/local/bin/masterha_secondary_check -s db2 -s db3 -s db4 -s db5 #检测哪个库是最新的,以便故障转移时为新主库
shutdown_script="" #关闭的脚本
ssh_user=root #SSH通信的用户 [server1]
hostname=192.168.1.202
port= [server2]
hostname=192.168.1.203
port=
candidate_master= #设置为备用主库,当多个SERVER都有设置它,用最新的做主库
check_repl_delay= #切换的时候忽略复制延迟,

#默认情况下如果一个slave落后master 100M的relay logs的话,MHA将不会选择该slave作为一个新的master,因为对于这个slave的恢复需要花费很长时间,通过设置check_repl_delay=0,MHA触发切换在选择一个新的master的时候将会忽略复制延时,这个参数对于设置了candidate_master=1的主机非常有用,因为这个候选主在切换的过程中一定是新的master

[server3]
port=
hostname=192.168.1.204 [server4]
hostname=192.168.1.205
port=
no_master= #不会成为主库 直接可用配置参数代码:
[server default]
manager_workdir=/var/log/masterha/app1
manager_log=/var/log/masterha/app1/manager.log master_binlog_dir=/mysql/log/
master_ip_failover_script=/usr/local/bin/master_ip_failover
master_ip_online_change_script=/usr/local/bin/master_ip_online_change
password=
user=root
ping_interval=
remote_workdir=/tmp
repl_password=
repl_user=rpl
report_script=/usr/local/bin/send_report
secondary_check_script=/usr/local/bin/masterha_secondary_check -s db2 -s db3 -s db4 -s db5
shutdown_script=""
ssh_user=root [server1]
hostname=192.168.1.202
port=
candidate_master= [server2]
hostname=192.168.1.203
port=
candidate_master=
check_repl_delay= [server3]
port=
hostname=192.168.1.204 [server4]
hostname=192.168.1.205
port=
no_master=

#配置文件说明
MHA主要配置文件说明

manager_workdir=/var/log/masterha/app1.log:设置manager的工作目录
manager_log=/var/log/masterha/app1/manager.log:设置manager的日志文件
master_binlog_dir=/data/mysql:设置master 保存binlog的位置,以便MHA可以找到master的日志
master_ip_failover_script= /usr/local/bin/master_ip_failover:设置自动failover时候的切换脚本
master_ip_online_change_script= /usr/local/bin/master_ip_online_change:设置手动切换时候的切换脚本
user=root:设置监控mysql的用户
password=dayi123:设置监控mysql的用户,需要授权能够在manager节点远程登录
ping_interval=:设置监控主库,发送ping包的时间间隔,默认是3秒,尝试三次没有回应的时候自动进行railover
remote_workdir=/tmp:设置远端mysql在发生切换时binlog的保存位置
repl_user=repl :设置mysql中用于复制的用户密码
repl_password=replication:设置mysql中用于复制的用户
report_script=/usr/local/send_report:设置发生切换后发送的报警的脚本
shutdown_script="":设置故障发生后关闭故障主机脚本(该脚本的主要作用是关闭主机放在发生脑裂,这里没有使用)
ssh_user=root //设置ssh的登录用户名
candidate_master=:在节点下设置,设置当前节点为候选的master
slave check_repl_delay= :在节点配置下设置,默认情况下如果一个slave落后master 100M的relay logs的话,MHA将不会选择该slave作为一个新的master;这个选项对于对于设置了candidate_master=1的主机非常有用
secondary_check_script=/usr/local/bin/masterha_secondary_check -s db2 -s db3 -s db4 -s db5 #检测哪个库是最新的,以便故障转移的时候确认信主库
 

【4.4.3】设置定期清理relay脚本(node节点服务器,即202~205)

#在202机器上执行
弄之前,先给主库,再加个IP网卡 :/sbin/ifconfig ens34:1 192.168.1.210/24,然后作为VIP使用
mysql -uroot -p123456 -e"set global relay_log_purge=0;" #所有202~205都执行
vim /usr/local/bin/purge_relay_log.sh #新建/修改如下
#!/bin/bash
user=root #mysql的账户密码
password=
port=
socket=/mysql/data//mysql.sock
log_dir='/var/log/masterha/log'
work_dir='/mysql/log/3306/relaylog'
purge='/usr/local/bin/purge_relay_logs'
if [ ! -d $log_dir ]
then
mkdir -p $log_dir
fi
$purge --user=${user} --password=${password} -S ${socket} --host=localhost --disable_relay_log_purge --port=${port}

#复制到202~205上的每一台机器上去

  scp root@192.168.1.202:/usr/local/bin/purge_relay_log.sh /usr/local/bin

#添加到crontab,每4小时执行一次

  chmod +x /usr/local/bin/purge_relay_log.sh

  crontab -e

  0 4 * * * /bin/bash /usr/local/bin/purge_relay_log.sh

  #4个小时清理一次

#手工执行以下查看该脚本是否报错

【4.5】VIP配置

  这些官方有脚本,但是这是比较新的脚本,我们还是用我们自己的老脚本

    

  (1)手工用脚本实现  

    【4.5.1】自动故障转移VIP脚本(master_ip_failover)    
    【4.5.2】手动故障转移VIP配置脚本(master_ip_online_change)

  (2)keepalived

手动配置VIP

#在主库上,手动添加vip地址
nohup ping -c 192.168.1.210
if [ $? != ];then
/sbin/ifconfig ens34: 192.168.1.210/
fi #在主、备主上,根据情况脚本设置开机自启
cat << eof >>/etc/rc.d/rc.local
nohup ping -c 192.168.1.210
if [ $? != ];then
/sbin/ifconfig ens34: 192.168.1.210/
fi
eof

如何删掉这个手动添加的VIP地址?

ifconfig ens34:1 down
#ip addr del 192.168.1.210 dev ens34 #开启
ifconfig ens34:1 up
ifup ifcfg-ens34:1

也可以通过构建一个新的网口网卡:http://www.lwops.cn/forum.php?mod=viewthread&tid=311&fromuid=1&tdsourcetag=s_pctim_aiomsg

大致步骤:

  (1)在主、备主上,复制 cp /etc/sysconfig/network-script/ifcfg-ens34   /etc/sysconfig/network-script/ifcfg-ens34:1 ,配置好IP地址为VIP

  (2)先在主库上启动这个网卡 ifup ifcfg-ens34:1  ,(注意不同主和备注 两个同事开启,会IP地址冲突的)

  (3)在两台机器上都添加下面脚本,并添加到开机启动,以防有机器宕机重启后 脚本不执行了。  避免配置问题不要使用 systemctl restart network 以防有问题直接远程都连不上,单独开启这个网卡用这个 ifup ifcfg-ens34:1

#在主、备主上,根据情况脚本设置开机自启
cat << eof >>/etc/rc.d/rc.local
nohup ping -c 2 192.168.1.210
if [ $? != 0 ];then
/sbin/ifconfig ens34:1 192.168.1.210/24
fi
eof

 手动故障转移等脚本配置


【4.5.1】MHA自动故障转移VIP脚本(master_ip_failover)
弄之前,先给主库,再加个IP网卡 :/sbin/ifconfig ens34:1 192.168.1.210/24,然后作为VIP使用
#修改 master_ip_failover脚本,使用脚本管理VIP
vim /usr/local/bin/master_ip_failover
#!/usr/bin/env perl

use strict;
use warnings FATAL => 'all'; use Getopt::Long; my (
$command, $ssh_user, $orig_master_host, $orig_master_ip,
$orig_master_port, $new_master_host, $new_master_ip, $new_master_port
); my $vip = '192.168.1.210/24';
my $key = '';
my $ssh_start_vip = "/sbin/ifconfig ens34:$key $vip";
my $ssh_stop_vip = "/sbin/ifconfig ens34:$key down"; GetOptions(
'command=s' => \$command,
'ssh_user=s' => \$ssh_user,
'orig_master_host=s' => \$orig_master_host,
'orig_master_ip=s' => \$orig_master_ip,
'orig_master_port=i' => \$orig_master_port,
'new_master_host=s' => \$new_master_host,
'new_master_ip=s' => \$new_master_ip,
'new_master_port=i' => \$new_master_port,
); exit &main(); sub main { print "\n\nIN SCRIPT TEST====$ssh_stop_vip==$ssh_start_vip===\n\n"; if ( $command eq "stop" || $command eq "stopssh" ) { my $exit_code = ;
eval {
print "Disabling the VIP on old master: $orig_master_host \n";
&stop_vip();
$exit_code = ;
};
if ($@) {
warn "Got Error: $@\n";
exit $exit_code;
}
exit $exit_code;
}
elsif ( $command eq "start" ) { my $exit_code = ;
eval {
print "Enabling the VIP - $vip on the new master - $new_master_host \n";
&start_vip();
$exit_code = ;
};
if ($@) {
warn $@;
exit $exit_code;
}
exit $exit_code;
}
elsif ( $command eq "status" ) {
print "Checking the Status of the script.. OK \n";
exit ;
}
else {
&usage();
exit ;
}
} sub start_vip() {
`ssh $ssh_user\@$new_master_host \" $ssh_start_vip \"`;
}
sub stop_vip() {
return unless ($ssh_user);
`ssh $ssh_user\@$orig_master_host \" $ssh_stop_vip \"`;
} sub usage {
print
"Usage: master_ip_failover --command=start|stop|stopssh|status --orig_master_host=host --orig_master_ip=ip --orig_master_port=port --new_master_host=host --new_master_ip=ip --new_master_port=port\n";
}
【4.5.2】手动故障转移VIP配置脚本(master_ip_online_change)
弄之前,先给主库,再加个IP网卡 :/sbin/ifconfig ens34:1 192.168.1.210/24,然后作为VIP使用
vim /usr/local/bin/master_ip_online_change
(1)官方版
#!/usr/bin/env perl
use strict;
use warnings FATAL => 'all';
use Getopt::Long;
use MHA::DBHelper;
use MHA::NodeUtil;
use Time::HiRes qw( sleep gettimeofday tv_interval );
use Data::Dumper; my $_tstart;
my $_running_interval = 0.1;
my (
  $command,          $orig_master_host, $orig_master_ip,
  $orig_master_port, $orig_master_user, 
  $new_master_host,  $new_master_ip,    $new_master_port,
  $new_master_user,  
); my $vip = '192.168.1.210/24';  # Virtual IP 
my $key = "1"; 
my $ssh_start_vip = "/sbin/ifconfig ens34:$key $vip";
my $ssh_stop_vip = "/sbin/ifconfig ens34:$key down";
my $ssh_user = "root";
my $new_master_password='123456';
my $orig_master_password='123456';
GetOptions(
  'command=s'              => \$command,
  #'ssh_user=s'             => \$ssh_user,  
  'orig_master_host=s'     => \$orig_master_host,
  'orig_master_ip=s'       => \$orig_master_ip,
  'orig_master_port=i'     => \$orig_master_port,
  'orig_master_user=s'     => \$orig_master_user,
  #'orig_master_password=s' => \$orig_master_password,
  'new_master_host=s'      => \$new_master_host,
  'new_master_ip=s'        => \$new_master_ip,
  'new_master_port=i'      => \$new_master_port,
  'new_master_user=s'      => \$new_master_user,
  #'new_master_password=s'  => \$new_master_password,
); exit &main(); sub current_time_us {
  my ( $sec, $microsec ) = gettimeofday();
  my $curdate = localtime($sec);
  return $curdate . " " . sprintf( "%06d", $microsec );
} sub sleep_until {
  my $elapsed = tv_interval($_tstart);
  if ( $_running_interval > $elapsed ) {
    sleep( $_running_interval - $elapsed );
  }
} sub get_threads_util {
  my $dbh                    = shift;
  my $my_connection_id       = shift;
  my $running_time_threshold = shift;
  my $type                   = shift;
  $running_time_threshold = unless ($running_time_threshold);
  $type                   = unless ($type);
  my @threads;   my $sth = $dbh->prepare("SHOW PROCESSLIST");
  $sth->execute();   while ( my $ref = $sth->fetchrow_hashref() ) {
    my $id         = $ref->{Id};
    my $user       = $ref->{User};
    my $host       = $ref->{Host};
    my $command    = $ref->{Command};
    my $state      = $ref->{State};
    my $query_time = $ref->{Time};
    my $info       = $ref->{Info};
    $info =~ s/^\s*(.*?)\s*$/$/ if defined($info);
    next if ( $my_connection_id == $id );
    next if ( defined($query_time) && $query_time < $running_time_threshold );
    next if ( defined($command)    && $command eq "Binlog Dump" );
    next if ( defined($user)       && $user eq "system user" );
    next
      if ( defined($command)
      && $command eq "Sleep"
      && defined($query_time)
      && $query_time >= );     if ( $type >= ) {
      next if ( defined($command) && $command eq "Sleep" );
      next if ( defined($command) && $command eq "Connect" );
    }     if ( $type >= ) {
      next if ( defined($info) && $info =~ m/^select/i );
      next if ( defined($info) && $info =~ m/^show/i );
    }     push @threads, $ref;
  }
  return @threads;
} sub main {
  if ( $command eq "stop" ) {
    ## Gracefully killing connections on the current master
    # . Set read_only= on the new master
    # . DROP USER so that no app user can establish new connections
    # . Set read_only= on the current master
    # . Kill current queries
    # * Any database access failure will result in script die.
    my $exit_code = ;
    eval {
      ## Setting read_only= on the new master (to avoid accident)
      my $new_master_handler = new MHA::DBHelper();       # args: hostname, port, user, password, raise_error(die_on_error)_or_not
      $new_master_handler->connect( $new_master_ip, $new_master_port,
        $new_master_user, $new_master_password, );
      print current_time_us() . " Set read_only on the new master.. ";
      $new_master_handler->enable_read_only();
      if ( $new_master_handler->is_read_only() ) {
        print "ok.\n";
      }
      else {
        die "Failed!\n";
      }
      $new_master_handler->disconnect();       # Connecting to the orig master, die if any database error happens
      my $orig_master_handler = new MHA::DBHelper();
      $orig_master_handler->connect( $orig_master_ip, $orig_master_port,
        $orig_master_user, $orig_master_password, );       ## Drop application user so that nobody can connect. Disabling per-session binlog beforehand
      #$orig_master_handler->disable_log_bin_local();
      #print current_time_us() . " Drpping app user on the orig master..\n";
      #FIXME_xxx_drop_app_user($orig_master_handler);       ## Waiting for N * milliseconds so that current connections can exit
      my $time_until_read_only = ;
      $_tstart = [gettimeofday];
      my @threads = get_threads_util( $orig_master_handler->{dbh},
        $orig_master_handler->{connection_id} );
      while ( $time_until_read_only > && $#threads >= ) {
        if ( $time_until_read_only % == ) {
          printf
"%s Waiting all running %d threads are disconnected.. (max %d milliseconds)\n",
            current_time_us(), $#threads + , $time_until_read_only * ;
          if ( $#threads < ) {
            print Data::Dumper->new( [$_] )->Indent()->Terse()->Dump . "\n"
              foreach (@threads);
          }
        }
        sleep_until();
        $_tstart = [gettimeofday];
        $time_until_read_only--;
        @threads = get_threads_util( $orig_master_handler->{dbh},
          $orig_master_handler->{connection_id} );
      }       ## Setting read_only= on the current master so that nobody(except SUPER) can write
      print current_time_us() . " Set read_only=1 on the orig master.. ";
      $orig_master_handler->enable_read_only();
      if ( $orig_master_handler->is_read_only() ) {
        print "ok.\n";
      }
      else {
        die "Failed!\n";
      }       ## Waiting for M * milliseconds so that current update queries can complete
      my $time_until_kill_threads = ;
      @threads = get_threads_util( $orig_master_handler->{dbh},
        $orig_master_handler->{connection_id} );
      while ( $time_until_kill_threads > && $#threads >= ) {
        if ( $time_until_kill_threads % == ) {
          printf
"%s Waiting all running %d queries are disconnected.. (max %d milliseconds)\n",
            current_time_us(), $#threads + , $time_until_kill_threads * ;
          if ( $#threads < ) {
            print Data::Dumper->new( [$_] )->Indent()->Terse()->Dump . "\n"
              foreach (@threads);
          }
        }
        sleep_until();
        $_tstart = [gettimeofday];
        $time_until_kill_threads--;
        @threads = get_threads_util( $orig_master_handler->{dbh},
          $orig_master_handler->{connection_id} );
      }                 print "Disabling the VIP on old master: $orig_master_host \n";
                &stop_vip();            ## Terminating all threads
      print current_time_us() . " Killing all application threads..\n";
      $orig_master_handler->kill_threads(@threads) if ( $#threads >= );
      print current_time_us() . " done.\n";
      #$orig_master_handler->enable_log_bin_local();
      $orig_master_handler->disconnect();       ## After finishing the script, MHA executes FLUSH TABLES WITH READ LOCK
      $exit_code = ;
    };
    if ($@) {
      warn "Got Error: $@\n";
      exit $exit_code;
    }
    exit $exit_code;
  }
  elsif ( $command eq "start" ) {
    ## Activating master ip on the new master
    # . Create app user with write privileges
    # . Moving backup script if needed
    # . Register new master's ip to the catalog database # We don't return error even though activating updatable accounts/ip failed so that we don't interrupt slaves' recovery.
# If exit code is or , MHA does not abort
    my $exit_code = ;
    eval {
      my $new_master_handler = new MHA::DBHelper();       # args: hostname, port, user, password, raise_error_or_not
      $new_master_handler->connect( $new_master_ip, $new_master_port,
        $new_master_user, $new_master_password, );       ## Set read_only= on the new master
      #$new_master_handler->disable_log_bin_local();
      print current_time_us() . " Set read_only=0 on the new master.\n";
      $new_master_handler->disable_read_only();       ## Creating an app user on the new master
      #print current_time_us() . " Creating app user on the new master..\n";
      #FIXME_xxx_create_app_user($new_master_handler);
      #$new_master_handler->enable_log_bin_local();
      $new_master_handler->disconnect();       ## Update master ip on the catalog database, etc
                print "Enabling the VIP - $vip on the new master - $new_master_host \n";
                &start_vip();
                $exit_code = ;
    };
    if ($@) {
      warn "Got Error: $@\n";
      exit $exit_code;
    }
    exit $exit_code;
  }
  elsif ( $command eq "status" ) {     # do nothing
    exit ;
  }
  else {
    &usage();
    exit ;
  }
} # A simple system call that enable the VIP on the new master 
sub start_vip() {
    `ssh $ssh_user\@$new_master_host \" $ssh_start_vip \"`;
}
# A simple system call that disable the VIP on the old_master
sub stop_vip() {
    `ssh $ssh_user\@$orig_master_host \" $ssh_stop_vip \"`;
} sub usage {
  print
"Usage: master_ip_online_change --command=start|stop|status --orig_master_host=host --orig_master_ip=ip --orig_master_port=port --new_master_host=host --new_master_ip=ip --new_master_port=port\n";
  die;
}
(2)简单版
#!/bin/bash
source /root/.bash_profile vip=`echo '192.168.1.210/24'` #设置VIP
key=`echo ''` command=`echo "$1" | awk -F = '{print $2}'`
orig_master_host=`echo "$2" | awk -F = '{print $2}'`
new_master_host=`echo "$7" | awk -F = '{print $2}'`
orig_master_ssh_user=`echo "${12}" | awk -F = '{print $2}'`
new_master_ssh_user=`echo "${13}" | awk -F = '{print $2}'` #要求服务的网卡识别名一样
stop_vip=`echo "ssh root@$orig_master_host /usr/sbin/ifconfig bond0:$key down"`
start_vip=`echo "ssh root@$new_master_host /usr/sbin/ifconfig bond0:$key $vip"` if [ $command = 'stop' ]
then
echo -e "\n\n\n****************************\n"
echo -e "Disabled thi VIP - $vip on old master: $orig_master_host \n"
$stop_vip
if [ $? -eq ]
then
echo "Disabled the VIP successfully"
else
echo "Disabled the VIP failed"
fi
echo -e "***************************\n\n\n"
fi if [ $command = 'start' -o $command = 'status' ]
then
echo -e "\n\n\n*************************\n"
echo -e "Enabling the VIP - $vip on new master: $new_master_host \n"
$start_vip
if [ $? -eq ]
then
echo "Enabled the VIP successfully"
else
echo "Enabled the VIP failed"
fi
echo -e "***************************\n\n\n"
fi
【4.5.3】授权
chmod +x /usr/local/bin/master_ip_failover
chmod +x /usr/local/bin/master_ip_online_change

【4.5.4】keepalive(待写)

【4.6】配置检查(在201上,即manager节点上)

【4.6.1】检查SSH配置
#检查mha manager 到所有的 MHA Node 的SSH链接状态
masterha_check_ssh --conf=/etc/masterha/app1.cnf
【4.6.2】检查整个复制环境状态
#通过 masterha_check_repl 脚本查看整个集群的状态
masterha_check_repl --conf=/etc/masterha/app1.cnf
【4.6.3】检查 MHA Manager 的状态
masterha_check_status --conf=/etc/masterha/app1.cnf
【4.6.4】手动启动MHA监控
mkdir -p /var/log/masterha/app1
chmod -R 777 /var/log/masterha/app1 #启动MHA监控
nohup masterha_manager --conf=/etc/masterha/app1.cnf --ignore_laster_failover &
#--remove_dead_master_conf & (1)--remove_dead_master_conf #当发生故障切换后,老的主库配置会从配置文件中移除掉(比如发生故障切换了,202主库切换到了203,那么在app1.cnf配置文件中,[server1]描述的ip为192.168.1.202内容会被清除)
(2)--manger_log #管理节点的日志位置
(3)--ignore_last_failover
  #在默认情况下,如果mha检查到主库连续发现宕机,且两次宕机间隔不超过8小时,则不会发生继续切换。(会生成一个文件,判断文件存在就不允许继续故障转移切换)
  #加了这个参数的话,就是来避免上述情况的,以便可以无限制的故障切换。 # 停止MHA监控
masterha_stop --conf=/etc/masterha/app1.cnf
 

【4.7】自动故障转移切换测试

切换之后,注意修改my.cnf,比如read_only等等

【4.7.1】直接关闭主库202机器的mysql

systemctl stop mysql #service mysql stop

【4.7.2】查看日志了解故障转移切换原理

#查看日志 /var/log/masterha/app1/manager.log
#这里只筛选了一下步骤 ** Phase : Configuration Check Phase completed.
* Phase : Dead Master Shutdown Phase..
* Phase : Dead Master Shutdown Phase completed.
* Phase : Master Recovery Phase..
* Phase 3.1: Getting Latest Slaves Phase..
* Phase 3.3: Determining New Master Phase..
* Phase 3.3: New Master Recovery Phase..
* Phase : Master Recovery Phase completed.
* Phase : Slaves Recovery Phase..
* Phase 4.1: Starting Slaves in parallel..
* Phase : New master cleanup phase.. 由此可以看出,MHA故障转移详细步骤;
(1)配置文件检查阶段:通过检查MHA的配置文件,获取相关的MHA集群机器信息,故障转移脚本信息等等
(2)宕机主库关闭阶段:将VIP删除
(3)复制宕机master库的binlog与最新slave库的差异relay log,保存到monitor节点下。
(4)推选识别含有最新数据的slave库,提升为master库。
(5)新master库应用(3)中保存下来的二进制日志
(6)将其他的slave库连接到新的master库,进行复制

【4.7.3】核验数据

(1)打开 schedure_event,查看test.test4

(2)查看show slave status

(3)建立一个库、表、数据,查看是否同步

(4)如何查看vip是否切换成功?

  mysql -uroot -p123456 -h192.168.1.210 #通过VIP登录

  show variables like 'hostname';

    

  最终发现,VIP还是切换过来了。

【4.7.4】MHA 官方的 BUG

对于 201 机器 monitor ,发生故障转移后,manager进程直接死掉了,即MHA监控脚本已经自动停止。

这个时候如果再次发生故障,就无法自动故障转移切换了。详情如下图:

  

解决办法:

  官方的回复是,不想让这个进程死掉,就放到后台运行,但我们是后台启动的,并没有效果;

  我们可以写一个脚本,监控进程是否存在,如果进程不存在则启动它。感觉就如同mysqld_safe一样,守护着mysql,mysql挂了就自动拉起来。

vim /usr/local/bin/manager_status_check

#!/bin/bash
while true
do
mha_check=`ps -ef|grep masterha_manager|grep -v grep|wc -l`
if [ ${mha_check} -eq ];then
nohup masterha_manager --conf=/etc/masterha/app1.cnf --ignore_laster_failover &
#--remove_dead_master_conf &
else
echo "MHA manager start"
fi
sleep
done #授权及加入开机自启
#chmod u+x /usr/local/bin/manager_status_check
#echo "nohup /usr/local/bin/manager_status_check">>/etc/rc.d/rc.local

【4.7.5】原本宕机的主节点重新加入回

(1)如果掉线时间长,宕机时间段内数据量大,建议操作如下(或者直接进行备份还原操作

增强半同步从库宕机如何重新连入主库?

. 此2个参数rpl_semi_sync_master_enabled  和rpl_semi_sync_slave_enabled  不要直接写入到my.cnf配置文件开启。
.在slave库上先 stop slave io_thread ;set global rpl_semi_sync_slave_enabled= 关闭此参数。
然后start slave io_thread 或者start slave 开启异步复制,让slave库追赶上master库。
.然后在slave库 set global rpl_semi_sync_slave_enabled= ;stop slave io_thread;start slave io_thread;

(2)否则直接运行下列脚本重新制定主从即可

现在主库是203啦。

change master to
master_host='192.168.1.203',
master_user='rpl',
master_password='',
master_port=,
master_auto_position=;

注意,如果monitor 启动的时候加了

--remove_dead_master_conf

那么配置文件会把之前宕机的机器信息(也就是202)清除掉,所以我们需要修改一下配置文件,把202加回来

左边修改前,右边修改后。

【4.8】手工切换主库(宕机切换与在线切换,masterha_master_switch 与 master_ip_online_change)

切换之后,注意修改my.cnf

做这块操作之前,必须要先关掉 masterha_manger.

ps -ef|grep manager*

【4.8.1】 masterha_master_switch
#在线切换(当前主库是203,想切换到202为主库)masterha_master_switch --conf=/etc/masterha/app1.cnf --master_state=alive --new_master_host=192.168.1.202 --new_master_port=3306 --orig_master_is_new_slave --running_updates_limit=10000

#其实会调用到 master_ip_online_change 脚本
#master_ip_online_change 具体文件配置参考【4.5】VIP配置 【4.8.1】核验
参考:【4.7.3】核验数据 记得把监控启动起来

【4.8.2】MHA在线切换的考虑与基本原理

在许多情况下, 需要将现有的主服务器迁移到另外一台服务器上。 比如主服务器硬件故障,RAID 控制卡需要重建,将主服务器移到性能更好的服务器上等等。维护主服务器引起性能下降, 导致停机时间至少无法写入数据。 另外, 阻塞或杀掉当前运行的会话会导致主主之间数据不一致的问题发生。 MHA 提供快速切换和优雅的阻塞写入,这个切换过程只需要 0.5-2s 的时间,这段时间内数据是无法写入的。在很多情况下,0.5-2s 的阻塞写入是可以接受的。因此切换主服务器不需要计划分配维护时间窗口。

MHA在线切换的大概过程:

  1. 检测复制设置和确定当前主服务器
  2. 确定新的主服务器
  3. 阻塞写入到当前主服务器
  4. 等待所有从服务器赶上复制
  5. 授予写入到新的主服务器
  6. 重新设置从服务器

注意,在线切换的时候应用架构需要考虑以下两个问题:

  1. 自动识别master和slave的问题(master的机器可能会切换),如果采用了vip的方式,基本可以解决这个问题。
  2. 负载均衡的问题(可以定义大概的读写比例,每台机器可承担的负载比例,当有机器离开集群时,需要考虑这个问题)

为了保证数据完全一致性,在最快的时间内完成切换,MHA的在线切换必须满足以下条件才会切换成功,否则会切换失败。

  1. 所有slave的IO线程都在运行
  2. 所有slave的SQL线程都在运行
  3. 所有的show slave status的输出中Seconds_Behind_Master参数小于或者等于running_updates_limit秒,如果在切换过程中不指定running_updates_limit,那么默认情况下running_updates_limit为1秒。
  4. 在master端,通过show processlist输出,没有一个更新花费的时间大于running_updates_limit秒。

【5】增删节点

【5.1】增加节点

(1)新节点机器 安装agent节点环境,初始化mysql数据库

(2)新节点机器 异步复制跟上

(3)新节点机器 半同步复制跟上

(4)与manager 秘钥互信

(5)修改manager配置文件

(6)重启manager服务

(7)复制检查

【5.2】删除节点

(1)关闭删除节点复制
(2)停掉删除节点服务/清除slave 连接 master信息(reset slave)
(3)修改配置文件
(4)重启manager

(5)复制检查

附录:故障解决

(1)perl-Log-Dispatch no available packages

     perl-Parallel-ForkManager no available packages

  解决办法:

    yum -y install epel-release ,安装了之后, 再重新

      yum install -y perl-ExtUtils-CBuilder perl-ExtUtils-MakeMaker perl-CPAN perl-DBD-MySQL perl-Config-Tiny perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes

(2)手动VIP如何在重启后正确的重新设置?

cat << eof >>/etc/rc.d/rc.local
nohup ping -c 192.168.1.210
if [ $? != ];then
/sbin/ifconfig ens34: 192.168.1.210/
fi
eof #判断主库 select * from information_schema.processlist where state like 'Master%';
#判断从库 select * from information_schema.processlist where state like 'Slave%';

(3)官方BUG,故障转移之后,monitor服务器下的manager程序死掉了

详情见本文【4.7.4】

(4) masterha_check_repl(1130,1045) :Access denied for user 'root'@'mha1'

故障原因:

此账号没有权限登录到对应的机器上

处理方法:

为对应的用户授权即可

(5)masterha_check_repl(Can't exec "mysqlbinlog"):从当前环境变量中找不到binlog

故障原因:

从当前的环境变量中找不到 mysqlbinlog 命令

解决方法:

将 mysqlbinlog 的路径添加到 环境变量中

(6) masterha_check_repl(rep no exist or does not have REPLICATIONSLAVE privilege)

故障原因

缺少 REPLICATION SLAVE 权限

解决方法:

为同步账号添加 REPLICATION SLAVE 权限即可, 注意,是所有节点都添加, 保证主从切换后都可以正常使用。

(7)event_scheduler导致常连接问题

  

故障原因:

这个是由于部属的 mha 版本没有跟上 数据库的版本. 在检测长连接时, 由于系统新增加了event_scheduler 功能,且属于打开的状态,那么此用户会一直存在, mha 检测时将其列为长连接,所以出现上面错误

解决方法:

临时解决方法: 禁用 event_scheduler, set global event_scheduler = 0;

长久之计,按下面方式修改源码:

(8) mha 管理 vip, 节点之间的网卡名不一样,切换会失败

解决方法:

  * 改网卡名

  * 改切换脚本

2.6、 mha 管理 vip, ssh 默认端口非22

  切换会失败

解决方法:

  * 改默认端口

  * 改切换脚本

注: 在线切换 和 故障切换脚本QQ群中提供 群号:748415432

(9) 使用 GTID 时切换的坑(gtid_mode=1; auto_position=0)

  gtid_mode=1; auto_position=0 模式, 配置 binlog server 选项

虽然打开了 GTID, 但同步依旧使用的是log_file + position 模式同步数据, 切换时依旧自动转成 auto_position=1 模式, 转换后很有可能出来 1236 同步错误. 下面两段代码解释了为什么会依旧使用 auto_position=1 模式 .

  
  

2.8、 gtid_mode=1; auto_position=0模式,

  配置 binlog server 选项, 同时配置了 use_gtid_auto_pos=0

  看似解决了上面的问题, 但引入了一个最大的问题, 不补尝原主实例的差异数据了, 这就是说, 原主库任何情况下出现异常都属于机器挂的情况‘

2.9、 gtid_mode=1; auto_position=1模式,

  没有配置 binlog server 选项, 依旧补不了日志。

2.7 ~ 2.9 解决方案:

  开启 gitd 后, 最好的方案就是 基于 gtid 同步, 且使用 auto_position=1, 同时配置 binlog server 选项。

(10)如果新实例,则需要执行一个事务,才可以被识别为开启 了 GTID 模式

[server default]

# 这边是 连接 MySQL 的账号与密码, 如果端口发生改变, 也要写上相应的端口, 默认为
port=
user=rootpassword=# 这边是连接机器的 ssh 用户,密码使用互信方式实现
ssh_user=root# 这边复制账号
repl_user=repl
repl_password=123456
master_binlog_dir= /data/mysql/mysqldata3306/binlog
master_ip_failover_script= /etc/mha/scripts/
master_ip_failover_new
master_ip_online_change_script= /etc/mha/scripts/master_online_change_new
manager_workdir=/etc/mha/app1manager_log=/etc/mha/log/mha/manager.log

[server1]
hostname=192.168.1.20
candidate_master=
master_binlog_dir= /data/mysql/mysqldata3306/binlog

[server2]
hostname=192.168.1.2
1candidate_master=
master_binlog_dir= /data/mysql/mysqldata3306/binlog

[server3]
hostname=192.168.1.22
candidate_master=
master_binlog_dir= /data/mysql/mysqldata3306/binlog

[server4]
hostname=192.168.1.23
candidate_master=
master_binlog_dir= /data/mysql/mysqldata3306/binlog

[binlog1]
hostname=192.168.1.20
[binlog2]
hostname=192.168.1.21
[binlog3]
hostname=192.168.1.22
[binlog4]
hostname=192.168.1.23

结尾

想要完美的避开上面的坑, 建议:

* 使用高版本的 MHA, 可以解决上面切换的坑.

* 如果打开了 GTID 模式,则使用 auto_position=1 同步模式,同时 MHA 的配置文件中 配置[binlog1] 选项, 地址写上原主库地址就好, 不需要真实配置一个 binlog server 服务器

本文分享自微信公众号 - 3306pai(pai3306)

参考文献

参考:mysql5.7.24 GTID+半同步 1主3从

参考:一步一个坑搭建MHA http://www.ttlsa.com/mysql/step-one-by-one-deploy-mysql-mha-cluster/

参考:MHA官网 https://code.google.com/p/mysql-master-ha/

参考:比较详细的MHA部署搭建:https://blog.csdn.net/qq_35209838/article/details/86497864

软件下载:https://cbs.centos.org/koji/buildinfo?buildID=1261

安装参考:https://www.cnblogs.com/winstom/p/11022014.html

概念:高可用架构方案  中的【3】MHA

mysql故障应用参考:https://cloud.tencent.com/developer/article/1339797

最新文章

  1. java12
  2. Linux环境下段错误的产生原因及调试方法小结(转)
  3. 查看sql语句执行的消耗
  4. jquery的ajax提交form表单
  5. php插件开发
  6. Oracle PL/SQL中如何使用%TYPE和%ROWTYPE
  7. iOS - OC 内存管理
  8. OneProxy FAQ 之proxy-user-list
  9. ES6 — 数组Array
  10. Node.js 入门(2)
  11. C# System.Uri类_获取Url的各种属性_文件名_参数_域名_端口等等
  12. Log4j、Log4j 2、Logback、SFL4J、JUL、JCL的比较
  13. 1.移植3.4内核-分析内核启动过程,重新分区,烧写jffs2文件系统
  14. Sublime text3所遇到的问题
  15. MySql实现分页查询的SQL,mysql实现分页查询的sql语句 (转)
  16. 结构体重载运算符&amp;srand&amp;rand
  17. Bootice1.34版本把grub4dos0.46a写入硬盘MBR失败一个例子
  18. Linux命令:builtin
  19. face parsing
  20. 最小生成树之克鲁斯卡尔(kruskal)算法

热门文章

  1. 2017.9.23 NOIP2017 金秋杯系列模拟赛 day1 T1
  2. 【CUDA 基础】5.0 共享内存和常量内存
  3. vue子组件通知父组件使用方法
  4. Java中String、StringBuffer、StringBuilder
  5. myeclipse使用SVN分支与合并详解
  6. 手把手教你在Linux系统下安装MySQL
  7. 使用Pillow(PIL)库实现中文字符画
  8. tensorflow feeddict问题unhashable type: &#39;numpy.ndarray&#39;
  9. 解决 无法启动此程序,因为计算机中丢失opencv_world341.dll。请尝试重新安装改程序已解决此问题
  10. 生成 XML 文档时出错。