`
javafan_303
  • 浏览: 958901 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

mysql 动态迁移架构(转)

阅读更多

mysql 的架构已经讨论很多了,这里最为经典的要算 ( 主  -> 从 ) 结构了。( 下面用 M 表示Master  S 表示Slave  S1 S2 分别表示一级Slave  二级Slave )

这个架构的优点是 S 不唯一 分担了查询的压力 , 即使 1两个 S 损坏也不会影响正常的使用 ,缺点是 M 是唯一的,一旦顺坏,将影响所有写入的请求。

对于这个缺点又有很多不同的解决方案。

方案1:
这个结构,当 S 损坏不会影响任何业务,只需要重装即可,当 M 损坏,其中的一台 S 升级成为 M ,问题是所有的 S 的同步这时候会有问题。

方案2:
这个结构使用双 M 解决 M 损坏的问题。 一般有 DRBD 或 MMM 这两种。
在 MMM 这个方案中,如果其中一个 M 在切换之前重启过几次,那么就会出现其中含有浮动 ip 的M在损坏以后虽然另一个切换接管了,但在没有人工干预的情况下 S 的同步会断掉。
在 DRBD 的这个解决方案中,更适用 Innodb ,在使用 Myisam 表的情况下会出现在切换之后表损坏,也在某些情况下需要人工干预。

方案3:
除了上面的两个方案,还有一个方案3 做的是 M -> S1 -> S2 这样的结构。
这个方案解决了在 M 死亡以后 S2 的同步情况的问题。但在 S1 死亡以后仍需要人工干预,但在不干预的情况下既不会影响插入,也不会影响查询,只不过数据不同步了。
情况 1 Master 宕机 : S1 接管 M 的浮动 IP ,这时候任何都不需要改动,数据库结构完好
情况 2 Slave1 宕机 :  既不会影响插入,也不会影响查询,只不过数据不同步了。需要人工干预才能恢复。
情况 3 Slave2 宕机 :  只要不是全宕了就没事。

方案4 :
NDB cluster 集群,和这次要讨论的没啥关系。
优点: 多点写入,完全代替主从结构。
缺点: 增加节点的时候要集体拆迁。这对于数据增长快的网站基本就是难以接受。
图: 略 ,这里不详细讨论这个东西。

下面简单介绍一下我的情况。
我们是一个做求职招聘的公司,主要业务就是 企业搜索个人简历,个人搜索企业职位,所以数据库就是我们的核心业务。我们所采用用的全部都是 mysql 。
我们的特点是查询量大,写入量小,我们采用了 标准的 M -> S 结构,并且全部引擎采用了 MYISAM 来满足我们的查询 使用这个结构的同时也带来了上面提到的问题,M是一个单点故障的问题。
为了解决这个问题,我采用了一个类似 方案2 的结构,没用 DRBD,也没用 MMM 而是使用了一个双通道的盘阵来挂两台机器来做 HA
结构图如下:

在这个结构里面我模仿了 MMM 和 DRBD 中的部分思想,使用一个 浮动 IP 给 Master ,使用 HA 软件检测 Master 的存活,并切换。
使用盘阵的目的是,考虑我的 bin-log mysql 的数据文件是放在同一个地方的,我希望我在切换Master的时候 S 同步不会掉。
做完这个工作 因自己的小聪明 沾沾自喜 若干天 ……

之后灾难降临了 ~~

首先是一次文件系统的 bug ,这让我看到了盘阵是个单点,同时也在安慰自己 用DRBD 碰到这个情况一样~~
在来一次主板的bug 非正常断电,造成Master 切换。很多表损坏了,光修表就好长时间,然后同步也掉了。
紧接着就是一次意外的断电,起来以后和上面的情况一样、修表、重做同步 ~~。
经过这些问题我发现这个架构简直就是灾难,看来耍小聪明是不行了,要来点实际的了。
痛定思痛之后认真的读了一遍 mysql 的官方手册最终吧问题集中在了两个点上。
1、 mysql master 的HA
2、 当 master 死亡以后 Slave 如何处理。
针对这两问题我和我们二个同事一起讨论了一下,最终我们确定了如下结构:

先说这个结构和上面提到的第三种情况非常类似,只不过我多增加了一个 S1 节点。然后分别在两个节点里放置了 S2 节点。
这个结构的巧妙之处就是我可以动态的吧他们变成 方案1 方案3 的任何一种形式,下面就看我如何动态迁移这个架构
情况 1: S2 宕机, 解决方法:因S2 很多不会影响任何使用。
情况 2: S1 其中之一宕机, 即使我什么都不修改也只是变成了 方案3 的情况。仍旧可以冗余。(断掉的机器动态迁移到完好的那个数的底下)
情况 3: Master 宕机,  两台S1 中的1台升级成为 Master 成为标准 M -> S 的情况。(断掉的机器动态迁移到完好的那个数的底下)
为了不使机器浪费,我们需要能动态迁移同步断掉的机器到完好的那个树的底下。

这里写一个动态迁移的技巧。也就是我写这个文章的核心技术。

1、要动态迁移 S1 要打开 binlog 并记录同步的binlog日志,S2一级可以不必打开。
    在 S1 一级配置 my.cnf 打开其中的
    # 我一般喜欢把 log 和数据分开存放。
    log-bin = /var/log/mysqllog/bin-log/mysql-bin 
    log-slow-queries = /var/log/mysqllog/db-slow.log
    log-error = /var/log/mysqllog/db.err
    log-slave-updates
    # 我的 relay log 也存放在 /var/log/mysql/log
    relay-log = /var/log/mysqllog/relay-log
    relay-log-index = /var/log/mysqllog/relay-log-index
    relay-log-info-file = /var/log/mysqllog/relay-log.info
    # master info 我喜欢和数据放在一起。方便做Slave
    master-info-file = /var/lib/mysql/master.info

2、在Master里面建立一个表用于打标记和记录整个树的结构。
    首先添加一个用户要对 monitor_db 有 完全的控制权,其次这个用户还要有Select_priv,Reload_priv,File_priv,Super_priv,Lock_tables_priv,Repl_slave_priv这些权限。
     后面会用这个用户 改变结构,所以权限一定要加够。

INSERTINTO mysql.user VALUES('%','monitoruser',password("monitoruserpasswd"),'Y','N','N','N','N','N','Y','N','N','Y','N','N','N','N','N','Y','N','Y','N','Y','N','N','N','N','N','N','','','','',0,0,0,0);
INSERT INTO mysql.db VALUES
('%','monitor_db','monitoruser','Y','Y','Y','Y','Y','Y','N','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y');

     创建库、表,并定期更新,具体看程序吧。
     为了防止一台机器死亡,我把这个分别放在了 M 和两台 S1 上,并利用 crontab 执行。
     M 和 S 的 crontab 分别配置
     0-54/6 * * * * /usr/local/bin/insert_uuid
     2-56/6 * * * * /usr/local/bin/insert_uuid
     4-58/6 * * * * /usr/local/bin/insert_uuid
     这样就能保证 2 分钟就有一次更新了,即使机器宕机了,最少也能保证 4-6 分钟有一次更新。
     insert_uuid 代码如下。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#include <mysql.h>

int main(int argc,char *argv[])
{
    char *server="127.0.0.1",*user="monitoruser",*password="monitoruserpasswd";
    MYSQL *conn;
    MYSQL_RES *res;
    MYSQL_ROW row;
    conn = mysql_init(NULL);
    if (!mysql_real_connect(conn, server,user, password, "monitor_db", 0, NULL, 0)) {
        fprintf(stderr, "%s\n", mysql_error(conn));
        exit(EXIT_FAILURE);
    }
    uuid_t uu;
    char myuuid[37];
    uuid_generate(uu);
    uuid_unparse(uu,myuuid);
    char sqlstr[512];
    if (argc == 2) {
        char *opt = "--install";
        if (!strcmp(argv[1],opt))
        {
            strcpy(sqlstr,"DROP DATABASE monitor_db");
            mysql_query(conn,sqlstr);
            strcpy(sqlstr,"CREATE DATABASE monitor_db");
            mysql_query(conn,sqlstr);
           strcpy(sqlstr,"CREATE TABLE monitor_db.monitor_uuid (`uuid` char(41)NOT NULL,`time` int(11) NOT NULL) ENGINE=MyISAM DEFAULT CHARSET=utf8");
            mysql_query(conn,sqlstr);
            strcpy(sqlstr,"INSERT INTO monitor_db.monitor_uuid VALUES ('UUID_00000000-0000-0000-0000-0000000',0000000000)");
            mysql_query(conn,sqlstr);
            if(mysql_affected_rows(conn) != 1)
                printf("install monitor_db error.\n");
        }
    } else {
    strcpy(sqlstr,"UPDATE monitor_db.monitor_uuid SET uuid=\"UUID_");
    strcat(strcat(sqlstr,myuuid),"\",time=UNIX_TIMESTAMP(NOW())");
    mysql_query(conn,sqlstr);
    if(mysql_affected_rows(conn) <= 0)
       printf("update uuid error.");
    }
    mysql_close(conn);
    exit(EXIT_SUCCESS);
}

首先编辑 char *server="127.0.0.1",*user="你的用户名",*password="你的密码"; 吧这里的 127.0.0.1 改成你 M 的ip ,user 改成 用户名,passowrd 改成密码
使用 gcc -O2 -I/usr/local/mysql/include/mysql -rdynamic-L/usr/local/mysql/lib/mysql -lmysqlclient -lz -lcrypt -lnsl -lm -luuid-o insert_uuid insert_uuid.c 编译
我的 mysql 是安装在 /usr/local/mysql 下面的,不同的情况请自己修改路径。
初始化 monitor_db  insert_uuid --install
然后在 crontab 里面添加即可。

3、写程序在 Slave 上面运行,当检测到自己的父节点死亡以后,利用 change master 语句迁移为新的父节点。
这部是成功的关键,如果你上一步成功了那么这步也就很容易了。下面是 change master 的脚本。

#!/usr/bin/python
"""Change the master from mysql slave"""

import os,sys,re,string,time,struct,MySQLdb
DBuser='monitoruser'
DBpasswd='monitoruserpasswd'

def main(masterip):
    try:
        slave_link = MySQLdb.connect(host='127.0.0.1',user=DBuser,passwd=DBpasswd,db='mysql')
    except Exception, e:
        print e
        sys.exit(1)

    slave_connect = slave_link.cursor()
    
    # slave stop
    sql = "slave stop"
    slave_connect.execute(sql)

    # get uuid
    sql = "select uuid from monitor_db.monitor_uuid"
    slave_connect.execute(sql)
    UUID=slave_connect.fetchall()[0][0]

    # get Exec_Master_Log_Pos
    sql = "show slave status"
    slave_connect.execute(sql)
    local_var = slave_connect.fetchall()[0]
    Exec_Master_Log_Pos=local_var[21]

    # get uuid form relaylog
    fp = open("/var/log/mysqllog/"+local_var[7],"rb")
    binlogstr=struct.unpack("4s",fp.read(4))
    if binlogstr[0] != chr(0xfe) + chr(0x62) + chr(0x69) + chr(0x6e):
        print "binlogfile error !"
        sys.exit(1)
    relay_log_pos = None
    while relay_log_pos == None:
        try:
            binlogstr=struct.unpack("4s 5x 4s 4s 2x",fp.read(19))
        except:
            print "Error: get uuid from relaylog failed"
            sys.exit(1)
       event_length = ord(binlogstr[1][0]) + ord(binlogstr[1][1])*256 +ord(binlogstr[1][2])*65536 + ord(binlogstr[1][3])*16777216
        event = struct.unpack(str(event_length-19)+"s",fp.read(event_length-19))
        if re.search(UUID,event[0]):
           relay_log_pos = ord(binlogstr[2][0]) + ord(binlogstr[2][1])*256 +ord(binlogstr[2][2])*65536 + ord(binlogstr[2][3])*16777216
            break
    fp.close()

    # connect mysql master
    try:
        master_link = MySQLdb.connect(host=masterip,user=DBuser,passwd=DBpasswd,db='mysql')
    except Exception, e:
        print e
        sys.exit(1)
    master_connect = master_link.cursor()

    # get master binlog file and size
    sql = "show binary logs"
    master_connect.execute(sql)
    file_size = master_connect.fetchall()

    # get uuid from master binlog
    pos = 0
    sql_cmd = None
    for fz in file_size[::-1]:
        sql = "show binlog events in '%s'" % fz[0]
        master_connect.execute(sql)
        for remote_var in master_connect.fetchall():
            if re.search(UUID,remote_var[5]):
                pos = Exec_Master_Log_Pos - relay_log_pos + remote_var[4]
                for fixsize in file_size[list(file_size).index(fz)::]:
                    if pos <= fixsize[1]:
                        sql_cmd = "change master to MASTER_HOST='%s',\
                                                    MASTER_USER='%s',\
                                                    MASTER_PASSWORD='%s',\
                                                    MASTER_LOG_FILE='%s',\
                                                    MASTER_LOG_POS=%s"\
                                  % (masterip,DBuser,DBpasswd,fixsize[0],pos)
                        break
                    else:
                        pos = pos - fixsize[1] + 117
                break
        if sql_cmd is not None: break 

    master_connect.close()
    master_link.close()
    
    # change master
    for sql in  ["flush tables","reset slave",sql_cmd,"slave start"]:
        slave_connect.execute(sql)

    time.sleep(1)
   event_name=['Slave_IO_State:','Master_Host:','Master_User:','Master_Port:','Connect_Retry:','Master_Log_File:','Read_Master_Log_Pos:',
                'Relay_Log_File:','Relay_Log_Pos:','Relay_Master_Log_File:','Slave_IO_Running:','Slave_SQL_Running:','Replicate_Do_DB:',
                'Replicate_Ignore_DB:','Replicate_Do_Table:','Replicate_Ignore_Table:','Replicate_Wild_Do_Table:','Replicate_Wild_Ignore_Table:',
                'Last_Errno:','Last_Error:','Skip_Counter:','Exec_Master_Log_Pos:','Relay_Log_Space:','Until_Condition:','Until_Log_File:',
                'Until_Log_Pos:','Master_SSL_Allowed:','Master_SSL_CA_File:','Master_SSL_CA_Path:','Master_SSL_Cert:','Master_SSL_Cipher:',
                 'Master_SSL_Key:','Seconds_Behind_Master:']
   
    sql = "show slave status"
    slave_connect.execute(sql)
    event_status = slave_connect.fetchall()[0]
    for i in range(0,33): print event_name.rjust(28),event_status
  
    slave_connect.close()
    slave_link.close()

if (len(sys.argv) == 3):
    if (sys.argv[1] == '--masterip'):
        iplist=string.split(sys.argv[2],'.')
        if len(iplist) == 4:
           if ( 0 <= int(iplist[0]) < 256 ) and ( 0 <= int(iplist[1])< 256 ) and ( 0 <= int(iplist[2]) < 256 ) and ( 0 <=int(iplist[3]) < 256 ):
                main(sys.argv[2])
                sys.exit(0)
else: 
    print "%s --masterip xx.xx.xx.xx" % sys.argv[0]
    sys.exit(1)

这个脚本需要依赖 MySQL-python
一切正常之后我们 只需要在 Slave2 的任意一台机器上测试即可.编辑上面的 
DBuser='monitoruser'
DBpasswd='monitoruserpasswd'
用show slave status ,查看现在的 master 是谁,然后使用 change_master.py --masterip 另一 S1 的ip,等待大约 1~2 分钟你就只要不报错你就可以查看你的 master 是否已经改变了。

如果是新做的数据库还不是一个从,你如果上面的 log 配置方式是按照我的方式配置的,你可以在装一个 rsync ,并配置这个 rsync 到 /var/lib/mysql 目录下 ,使用下面这个脚本快速制作一个从数据库。
脚本中不同处请自行修改。

#!/bin/bash
Slave_IP="192.168.1.100"
PATH=${PATH}:/usr/local/bin
/etc/init.d/mysqld stop
rm -rf /var/log/mysqllog/relay*
mysql -u'monitoruser' -p'monitoruserpasswd' -h${Slave_IP} -e "slave stop;flush tables;"
Exec_pos=($(mysql-u'replication_user' -p'800HRreplication' -h${Slave_IP} -e "show slavestatus\G"|awk '$0~/Exec_Master_Log_Pos/{print $2}'))
typeset -x RSYNC_PASSWORD='rsyncpassword';
rsync -av --delete mysql-data@${Slave_IP}::mysql-data /var/lib/mysql/
sed '3 c\'${Exec_pos} -i /var/lib/mysql/master.info
mysql -u'monitoruser' -p'monitoruserpasswd' -h${Slave_IP} -e "slave start;"
/etc/init.d/mysqld start
sleep 3
mysql -u'monitoruser' -p'monitoruserpasswd' -h127.0.0.1 -e "show slave status\G"

有了这些东西,我们就可以在联机的情况下,动态的迁移数据库集群到任何一个样子,如果是多机房的那就更适合不过了,在中心机房放置 mysql master,在其他的每个机房部署  S1 -> S2 就是一个标准的主从结构。
即使某个节点的mysql坏掉了我们也可以动态迁移到另一个节点去继续做同步,而不影响整体集群。
转载 http://bbs.linuxtone.org/forum-viewthread-tid-6514-highlight-%E6%9E%B6%E6%9E%84.html

分享到:
评论

相关推荐

    MySQL迁移SQLServer工具

    MySQL到SQL Server迁移工具,全称为SQL Server Migration Assistant (SSMA) for MySQL,是由微软公司开发的一款专业级数据库迁移解决方案。这款工具旨在帮助用户将他们的MySQL数据库无缝地迁移到SQL Server环境中,...

    mysql数据在线热迁移+mysql双主+keepalived高可用集群环境

    MySQL 数据在线热迁移 + MySQL 双主 + Keepalived 高可用集群环境 本文档将详细介绍如何实现 MySQL 数据在线热迁移、MySQL 双主高可用集群环境,并使用 Keepalived 实现高可用性。 MySQL 数据在线热迁移 在 MySQL...

    MySQL向Sybase ASE迁移指南

    从MySQL迁移到Sybase ASE的过程中,还需要注意以下几方面的管理差异: ##### 4.1 存储管理与数据库创建 Sybase ASE提供了不同的存储管理模式,例如基于文件的存储和基于设备的存储。此外,在创建数据库时,需要...

    MSsql数据库迁移到MYsql数据库工具(免费试用30天)

    1. **数据和架构分析**:首先,工具会分析MSsql数据库的架构,包括表、视图、存储过程、触发器、索引等,并映射到MySQL对应的结构。 2. **数据转换**:然后,工具会执行数据的转换,将MSsql中的数据转换为MySQL兼容...

    mysql语句转postgres的工具

    MySQL到PostgreSQL的迁移是一项常见的任务,特别是在企业级应用中,由于PostgreSQL的特性或业务需求,可能需要将数据从MySQL迁移到PostgreSQL。在这个过程中,`MysqlToPostgresUtil`工具扮演着重要的角色,它能帮助...

    从Oracle迁移到MySQL经典实战

    《从Oracle迁移到MySQL经典实战》是一本针对企业节省成本、优化数据库架构的重要指南。Oracle数据库系统以其强大的功能和稳定性在企业级应用中占据主导地位,然而,随着MySQL的不断发展和成熟,其开源、免费的特性使...

    mysql到gbase 8s迁移指南

    随着数据库技术的发展,企业对于数据处理的需求日益增长,从MySQL迁移到其他更符合业务需求的数据库系统成为了一种趋势。GBase 8s作为一款由南大通用数据技术股份有限公司自主研发的国产数据库管理系统,具有高性能...

    MySQL迁移、拆分技巧.pptx

    MySQL迁移是数据库管理中的一个常见需求,尤其是在系统升级、架构调整等场景下尤为重要。本文档将从以下几个方面详细介绍MySQL迁移的相关技巧: 1. **迁移类别**:包括实例迁移、数据库迁移、表迁移等。 2. **迁移...

    php-mysql-diff, MySQL架构差异比较/迁移脚本生成.zip

    php-mysql-diff, MySQL架构差异比较/迁移脚本生成 PHP MySQL差异MySQL架构差异- 比较/迁移脚本生成 为什么不从MySQL实用程序中选择 mysqldiff?MySQL工具包括一个类似的工具 mysqldiff,它绝对是的 。 本项目的目的...

    Google高级架构师谈大规模MySQL架构

    通过Jeremy Cole的分享,我们可以得出,在大规模架构下,MySQL不仅满足了基础的数据处理需求,而且在系统的高可用性、在线迁移、变更管理、以及处理并发查询等方面都有较好的表现。但同时,他也指出了MySQL在大规模...

    mysql迁移postgres之navicat篇

    在实际工作中,我们经常需要将 MySQL 数据库迁移到 PostgreSQL,以满足不同的业务需求或技术架构。这篇文章将详细介绍如何使用 Navicat 客户端工具将 MySQL 数据库迁移到 PostgreSQL。 为什么要迁移 MySQL 到 ...

    aarch64架构下mysql数据库安装.zip

    而在新兴的aarch64(ARM64)架构上,MySQL的安装与传统x86平台有所不同。本文将详细介绍如何在aarch64架构下进行MySQL数据库的安装。 首先,让我们了解一下aarch64架构。aarch64是ARM公司为64位计算设计的一种指令...

    MySQL to SQL Server 2005 迁移工具

    综上所述,从MySQL迁移到SQL Server 2005的过程需要全面规划,选择合适的工具,处理各种兼容性问题,并进行充分的测试,以确保迁移的成功。这个过程中,专业的迁移工具可以极大地简化工作,提高效率。

    DM迁移之MySql迁移

    【标题】:“DM迁移之MySQL迁移” 【描述】:“Mysql迁移到DM手记” 在当前的信息化环境中,由于国家对自主创新技术的大力推广,采用拥有自主知识产权的国产数据库系统变得越来越重要。DM数据库作为一款国产数据库...

    数据库迁移之从oracle 到 MySQL

    3. **架构设计**:根据MySQL的特性优化表结构,例如考虑InnoDB存储引擎的主键策略,以及使用合适的数据类型来节省存储空间。 4. **数据导出**:使用Oracle的`expdp`工具导出数据,生成DMP文件。同时,可以使用SQL...

    驾驭数据流动:MySQL数据库迁移全攻略

    ### 驾驭数据流动:MySQL数据库迁移全攻略 #### 引言:数据库迁移的重要性 在信息技术领域,数据库迁移是一项非常关键的任务。随着业务的发展和技术的进步,企业常常需要升级系统、更换服务提供商或是优化现有的...

    SQL Server数据库迁移Mysql数据库工具

    首先,SQL Server和MySQL之间的主要差异在于它们的架构、语法和性能优化策略。SQL Server通常与Windows操作系统紧密集成,而MySQL则跨平台且开源,因此在迁移过程中需要考虑这些差异对应用程序的影响。 要进行SQL ...

    mysql_oracle_dm整库数据迁移相关资料.rar

    本资料包"mysql_oracle_dm整库数据迁移相关资料.rar"主要关注的是MySQL、Oracle以及国产的达梦数据库之间的数据迁移流程。以下将详细介绍这一过程中的关键知识点。 1. **数据库架构对比**:首先,了解这三种数据库...

    windows+apache+mysql+php架构部署

    - **跨平台兼容**:可以在多种操作系统上运行,便于迁移和维护。 - **社区支持广泛**:庞大的用户群和活跃的开发者社区确保了技术支持的及时性。 #### 四、WAMP架构部署步骤 1. **安装Apache Web服务器** - 下载...

    mysql数据库转换成sql server

    它能自动分析MySQL的架构,生成对应的SQL Server架构,并迁移数据。 2. **转换过程**: - **分析阶段**:SSMA会扫描MySQL数据库的所有对象(如表、视图、存储过程等),并创建一个映射到SQL Server的对应结构。 -...

Global site tag (gtag.js) - Google Analytics