阿里云搭建mha高可用架构

弹性网卡介绍

  • 阿里云服务器在虚拟交换机中不能随意获取私网ip,也不能像在虚拟机中随意进行ip漂移,只能利用api接口切换弹性网卡的绑定与解绑来达到ip漂移的目的

服务器系统选择

  • 注意服务器最好选择以下版本,这样不需要配置就可以切换,其他版本的系统绑定网卡后需要手动进行配置官方文档地址
  • CentOS 7.3 64位
  • CentOS 6.8 64位
  • Window Server 2008 R2及更高版本

阿里云实现mha高可用应用透明

阿里云的虚拟交换机不能像在虚拟机中随意切换ip,所以需要另外通过api控制弹性网卡,所以之前在虚拟机中应用透明部分的脚本在绑定和解绑ip的部分需要替换为调用api接口脚本

#调用示例
#注意事项
#以下只是调用脚本方法,真实阿里云网络环境中数据库一般没有外网,所以需要在配置nat或者其他方式访问外网才可以调用脚本
#可以将以下命令写入shell脚本,分发到每个mysql服务器然后将php脚本放置在拥有公网ip的主机中,所有mysql服务器调用该公网ip主机的php脚本即可
#以下两个脚本需要当前主机安装php环境,如果没有安装php,就使用 yum install -y php 安装

/usr/bin/php ./aliclould_up_ip.php -h ecs实例id
/usr/bin/php ./aliclould_down_ip.php -h i-*********
vi /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 = '10.0.0.55/24';
#my $key = '1';
#my $ssh_start_vip = "/sbin/ifconfig eth0:$key $vip";
#my $ssh_stop_vip = "/sbin/ifconfig eth0:$key down";

#阿里云中使用
my $vip = '172.16.128.1/20';
my $ssh_start_vip = "/usr/bin/php ./aliclould_down_ip.php -h i-*********";
my $ssh_stop_vip = "/usr/bin/php ./aliclould_down_ip.php -h i-*********";

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 = 1;
        eval {
            print "Disabling the VIP on old master: $orig_master_host \n";
            &stop_vip();
            $exit_code = 0;
        };
        if ($@) {
            warn "Got Error: $@\n";
            exit $exit_code;
        }
        exit $exit_code;
    }
    elsif ( $command eq "start" ) {

        my $exit_code = 10;
        eval {
            print "Enabling the VIP - $vip on the new master - $new_master_host \n";
            &start_vip();
            $exit_code = 0;
        };
        if ($@) {
            warn $@;
            exit $exit_code;
        }
        exit $exit_code;
    }
    elsif ( $command eq "status" ) {
        print "Checking the Status of the script.. OK \n";
        exit 0;
    }
    else {
        &usage();
        exit 1;
    }
}

sub start_vip() {
    `ssh $ssh_user\@$new_master_host \" $ssh_start_vip \"`;
}
sub stop_vip() {
     return 0  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";
}
  • 实例绑定弹性网卡aliclould_up_ip.php
<?php 
/**
 * 阿里云绑定弹性网卡
 * AttachNetworkInterface
 * https://help.aliyun.com/document_detail/58515.html
 */


//oss账号密码
$accessKeyId = "填入参数";
$accessKeySecret = "填入参数";

// 实例ID
$host = getopt('h:');
if(empty($host['h']))
{
    exit;
}
else
{
    $InstanceId = $host['h'];
}
// 弹性网卡ID
$NetworkInterfaceId = '填入网卡id';
// 实例所在地域的ID
$RegionId = 'cn-hangzhou';


date_default_timezone_set("UTC");
// 阿里云 调用AttachNetworkInterface附加一个弹性网卡(ENI)到一台专有网络VPC类型ECS实例上
$input = [
    'Action' => 'AttachNetworkInterface', // 系统规定参数。取值:AttachNetworkInterface
    'InstanceId' => $InstanceId, // 实例ID
    'NetworkInterfaceId' => $NetworkInterfaceId, // 弹性网卡ID
    'RegionId' => $RegionId, // 实例所在地域的ID
];

// 请求类型
$method = 'GET';
// 请求地址
$url = 'ecs.aliyuncs.com?';
// 请求参数
$common = [
    // 'Action' => 'AttachNetworkInterface', // API 的名称。取值参阅 API 概览。
    'AccessKeyId' => $accessKeyId, // 访问密钥 ID
    'SignatureMethod' => 'HMAC-SHA1', // 签名方式。取值范围:HMAC-SHA1。
    'SignatureVersion' => '1.0', // 签名算法版本。取值范围:1.0。
    'SignatureNonce' => rand(1000000,9999999), //  签名唯一随机数。用于防止网络重放攻击,建议您每一次请求都使用不同的随机数。
    'Timestamp' => date('Y-m-d',time()).'T'.date('H:i:s',time()).'Z', // 请求的时间戳。按照ISO8601标准表示,并需要使用UTC时间,格式为yyyy-MM-ddTHH:mm:ssZ。示例:2018-01-01T12:00:00Z 表示北京时间 2018 年 01 月 01 日 20 点 00 分 00 秒。
    'Version' => '2014-05-26', // API 的版本号,格式为 YYYY-MM-DD。取值范围:2014-05-26。
    'Format' => 'json', // 返回参数的语言类型。取值范围:json | xml。默认值:xml。
    // 'Signature' => '', // 您的签名
];
// 合并参数
$key = array_merge($input,$common);
// 进行键排序
ksort($key);
// 创建变量
$keystr = '';
// 循环拼接字符串
foreach ($key as $k => $v) 
{
    if($k == 'Timestamp')
    {
        $keystr .= $k.'='.urlencode($v).'&';
    }
    else
    {
        $keystr .= $k.'='.$v.'&';
    }
}
// 拼接签字字符串
$srcStr = $method.'&%2F&'.urlencode(rtrim($keystr,'&'));
$srcStr1 = $method.'&%2F&'.rtrim($keystr,'&');

// 生成签名字符串  请求签名,用来验证此次请求的合法性,需要用户根据实际的输入参数计算得出。
// 验证时,如果有中文 需要验证中文字符串 发送请求时改为中文的url编码
$signStr = base64_encode(hash_hmac('sha1', $srcStr, $accessKeySecret.'&', true));

// 拼接请求地址
$str = 'https://'.$url.http_build_query($key).'&Signature='.urlencode($signStr);

// 发送请求
$res = json_decode(curl($str,$method),true)['body']; echo $res;


/**
 * CURL发送请求
 * @param type $url 地址
 * @param type $method 请求方式
 * @param type $data 请求参数
 * @param type $header 请求头
 * @return 返回参数
 */
function curl($url,$method='GET',$data=null,$header=null)
{
    // 启动一个CURL会话
    $curl = curl_init();
    // 要访问的地址
    curl_setopt($curl,CURLOPT_URL,$url);
    //返回数据不直接输出
    curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
    curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
    curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
    curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
    curl_setopt($curl, CURLOPT_HEADER, true); // 显示返回的Header区域内容

    // curl 请求头设置
    if(!empty($header))
    {
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header); // 模拟参数传递
    }
    else
    {
        // curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器
    }

    // https
    // https请求 不验证证书和hosts  php版本过低需要开启此项 否则会无法访问https的地址
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); 
    // 从证书中检查SSL加密算法是否存在(默认不需要验证  php版本过低需要开启此项 否则会无法访问https的地址
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);

    //如果是post请求
    if($method === 'POST' || $method === 'post')
    {
        //开启发送post请求选项
        curl_setopt($curl,CURLOPT_POST,true);
        //发送post的数据
        curl_setopt($curl,CURLOPT_POSTFIELDS,$data);
    }
    //发送请求
    $res = curl_exec($curl);
    // 返回参数包含请求头 则此条无效
    // $data = json_decode($res,JSON_UNESCAPED_UNICODE);
    // 获取响应头大小
    $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
    // 根据头大小去获取头信息内容
    $header = substr($res, 0, $headerSize);
    // 根据头大小去获取响应信息内容
    $body = substr($res, $headerSize);
    // 获取http状态码
    $return_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    if(substr($body,0,1) != '{')
    {
        $body = htmlspecialchars($body);
    }
    //关闭连接
    curl_close($curl);
    $arr = [
        'header' => $header,
        'body' => $body,
        'return_code' => $return_code,
    ];
    $res = json_encode($arr);
    return $res;
}
  • 实例解绑弹性网卡aliclould_down_ip.php
<?php 
/**
 * 阿里云解绑弹性网卡
 * DetachNetworkInterface
 * https://help.aliyun.com/document_detail/58514.html
 */


//oss账号密码
$accessKeyId = "填入参数";
$accessKeySecret = "填入参数";

// 实例ID
$host = getopt('h:');
if(empty($host['h']))
{
    exit;
}
else
{
    $InstanceId = $host['h'];
}
// 弹性网卡ID
$NetworkInterfaceId = '填入网卡id';
// 实例所在地域的ID
$RegionId = 'cn-hangzhou';


date_default_timezone_set("UTC");
// 阿里云 调用DetachNetworkInterface从一台ECS实例上分离一个弹性网卡(ENI)
$input = [
    'Action' => 'DetachNetworkInterface', // 系统规定参数。取值:DetachNetworkInterface
    'InstanceId' => $InstanceId, // 实例ID
    'NetworkInterfaceId' => $NetworkInterfaceId, // 弹性网卡ID
    'RegionId' => $RegionId, // 实例所在地域的ID
];

// 请求类型
$method = 'GET';
// 请求地址
$url = 'ecs.aliyuncs.com?';
// 请求参数
$common = [
    // 'Action' => 'DetachNetworkInterface', // API 的名称。取值参阅 API 概览。
    'AccessKeyId' => $accessKeyId, // 访问密钥 ID
    'SignatureMethod' => 'HMAC-SHA1', // 签名方式。取值范围:HMAC-SHA1。
    'SignatureVersion' => '1.0', // 签名算法版本。取值范围:1.0。
    'SignatureNonce' => rand(1000000,9999999), //  签名唯一随机数。用于防止网络重放攻击,建议您每一次请求都使用不同的随机数。
    'Timestamp' => date('Y-m-d',time()).'T'.date('H:i:s',time()).'Z', // 请求的时间戳。按照ISO8601标准表示,并需要使用UTC时间,格式为yyyy-MM-ddTHH:mm:ssZ。示例:2018-01-01T12:00:00Z 表示北京时间 2018 年 01 月 01 日 20 点 00 分 00 秒。
    'Version' => '2014-05-26', // API 的版本号,格式为 YYYY-MM-DD。取值范围:2014-05-26。
    'Format' => 'json', // 返回参数的语言类型。取值范围:json | xml。默认值:xml。
    // 'Signature' => '', // 您的签名
];
// 合并参数
$key = array_merge($input,$common);
// 进行键排序
ksort($key);
// 创建变量
$keystr = '';
// 循环拼接字符串
foreach ($key as $k => $v) 
{
    if($k == 'Timestamp')
    {
        $keystr .= $k.'='.urlencode($v).'&';
    }
    else
    {
        $keystr .= $k.'='.$v.'&';
    }
}
// 拼接签字字符串
$srcStr = $method.'&%2F&'.urlencode(rtrim($keystr,'&'));
$srcStr1 = $method.'&%2F&'.rtrim($keystr,'&');

// 生成签名字符串  请求签名,用来验证此次请求的合法性,需要用户根据实际的输入参数计算得出。
// 验证时,如果有中文 需要验证中文字符串 发送请求时改为中文的url编码
$signStr = base64_encode(hash_hmac('sha1', $srcStr, $accessKeySecret.'&', true));

// 拼接请求地址
$str = 'https://'.$url.http_build_query($key).'&Signature='.urlencode($signStr);

// 发送请求
$res = json_decode(curl($str,$method),true)['body']; echo $res;
sleep(3);





/**
 * CURL发送请求
 * @param type $url 地址
 * @param type $method 请求方式
 * @param type $data 请求参数
 * @param type $header 请求头
 * @return 返回参数
 */
function curl($url,$method='GET',$data=null,$header=null)
{
    // 启动一个CURL会话
    $curl = curl_init();
    // 要访问的地址
    curl_setopt($curl,CURLOPT_URL,$url);
    //返回数据不直接输出
    curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
    curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
    curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
    curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
    curl_setopt($curl, CURLOPT_HEADER, true); // 显示返回的Header区域内容

    // curl 请求头设置
    if(!empty($header))
    {
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header); // 模拟参数传递
    }
    else
    {
        // curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器
    }

    // https
    // https请求 不验证证书和hosts  php版本过低需要开启此项 否则会无法访问https的地址
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); 
    // 从证书中检查SSL加密算法是否存在(默认不需要验证  php版本过低需要开启此项 否则会无法访问https的地址
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);

    //如果是post请求
    if($method === 'POST' || $method === 'post')
    {
        //开启发送post请求选项
        curl_setopt($curl,CURLOPT_POST,true);
        //发送post的数据
        curl_setopt($curl,CURLOPT_POSTFIELDS,$data);
    }
    //发送请求
    $res = curl_exec($curl);
    // 返回参数包含请求头 则此条无效
    // $data = json_decode($res,JSON_UNESCAPED_UNICODE);
    // 获取响应头大小
    $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
    // 根据头大小去获取头信息内容
    $header = substr($res, 0, $headerSize);
    // 根据头大小去获取响应信息内容
    $body = substr($res, $headerSize);
    // 获取http状态码
    $return_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    if(substr($body,0,1) != '{')
    {
        $body = htmlspecialchars($body);
    }
    //关闭连接
    curl_close($curl);
    $arr = [
        'header' => $header,
        'body' => $body,
        'return_code' => $return_code,
    ];
    $res = json_encode($arr);
    return $res;
}