主从切换实例(传统复制)

本文介绍了一种通过制造异常来实现数据库主从切换的方法,并提供了一个具体的脚本实例。该方法适用于需要快速切换主从库场景。

主从切换案例

M为主库,read write
S* 为从库,read only
现在要对M进行硬件维护,提升S1为主库,接管业务读写. M维护完成后作为从库,如下图
这里写图片描述

首先可以进行如下切换 (A)
这里写图片描述
再进行如下切换(B)
这里写图片描述

A切换步骤

首先在S1制造”错误”

set session sql_log_bin=0;
create table t_error_maker(id int);
set session sql_log_bin=1;
drop table t_error_maker;

通过在session级别关闭写入binlog,建表,开启写入binlog,删表 制造异常, 当S11 S12 S13都执行到drop语句时,会报错停止sql_thread.
通过这种方式,可以让它们停止在同一个位置.
S12

show master status 获取File Position

S11 S13

stop slave;
change master到S12上
start slave;

S12

set global sql_slave_skip_counter=1;
start slave sql_thread;
B切换步骤

M 停止业务写操作

set global read_only=on; 此时只有super权限用户能写入
set global super_read_only=on;禁止super权限用户写
#这里没有通过修改用户密码的方式是因为修改用户密码对已经连接上来的用户无效

等M S1 S12 跑一致后(File Position相同) 停S12 sql_thread, 将业务写入操作接入S1
最后M

set global read_only=off;
set global super_read_only=off;
change master 到S12
start slave

S12

start slave sql_thread;

以上步骤通过脚本完成的话,可以做到对业务造成很小的影响
脚本实例如下,注释掉了B步骤,因为需要配合切换业务写入操作

# -*- coding: utf-8 -*-  
#coding=utf-8
import pymysql
import time
from warnings import filterwarnings
from progressive.bar import Bar
error_message=u"Error 'Unknown table 'fandb.t_error_maker'' on query. Default database: 'fandb'. Query: 'DROP TABLE `t_error_maker` /* generated by server */'"

def dec_progressive(func):
    def progess(*args, **kwargs):
        global i
        i += 100/9
        bar.cursor.restore()
        bar.draw(value=i)
        return func(*args, **kwargs)
    return progess

#创建数据库连接函数
@dec_progressive
def get_conn(host,port,user,password,db='performance_schema',charset='utf8'):
    return pymysql.connect(host=host, port=int(port), user=user,password=password,db=db,charset=charset)


#制造复制异常函数
@dec_progressive
def error_maker(host,port,user,password,db,charset):
    conn=get_conn(host=host, port=int(port), user=user,password=password,db=db,charset=charset)
    cursor = conn.cursor()
    cursor.execute("set session sql_log_bin=0;")
    cursor.execute("create table t_error_maker(id int)")
    cursor.execute("set session sql_log_bin=1")
    cursor.execute("drop table t_error_maker")
    cursor.close()
    conn.close()



#获取slave status
def get_slave_statue(conn,sql):
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    cursor.execute("show slave status;")
    result = cursor.fetchone()
    return result
    cursor.close()



#获取master status
@dec_progressive
def get_master_status(conn):
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    cursor.execute("show master status;")
    result = cursor.fetchone()
    return result
    cursor.close()


#执行change master语句
@dec_progressive
def change_master(conn,change_string):
    cursor = conn.cursor()
    cursor.execute("stop slave;")
    cursor.execute(change_string)
    cursor.execute("start slave;")
    cursor.close()


#修复slave错误
@dec_progressive
def repair_slave(conn):
    cursor = conn.cursor()
    cursor.execute("set global sql_slave_skip_counter=1;")
    cursor.execute("start slave sql_thread;")
    cursor.close()  

#设置read only
@dec_progressive
def set_read_only(conn,switch):
    cursor = conn.cursor()
    if switch == 'on':
        cursor.execute("set global read_only=on;")
    elif switch == 'off':
        cursor.execute("set global read_only=off;")
    cursor.close()

#启停 sql_thread
@dec_progressive
def set_sql_thread(conn,switch):
    cursor = conn.cursor()
    if switch == 'off':
        cursor.execute("stop slave sql_thread;")
    elif switch == 'on':
        cursor.execute("start slave sql_thread;")
    cursor.close()

#判断 39 40 41是否都因为drop t_error_maker停止
@dec_progressive
def get_error_status():
    while True:
        Last_SQL_Error_39 = get_slave_statue(conn39,'show slave status;')['Last_SQL_Error']
        Last_SQL_Error_40 = get_slave_statue(conn40,'show slave status;')['Last_SQL_Error']
        Last_SQL_Error_41 = get_slave_statue(conn41,'show slave status;')['Last_SQL_Error']
        if Last_SQL_Error_39 == Last_SQL_Error_40 == Last_SQL_Error_41 == error_message:
            break
        else:
            time.sleep(1)

if __name__ == '__main__':
    MAX_VALUE = 100
    bar = Bar(max_value=MAX_VALUE, fallback=True)
    bar.cursor.clear_lines(2)
    bar.cursor.save()
    i=0

    #不显示MySQL的warning
    filterwarnings('ignore',category=pymysql.Warning)
    #连接3306 制造复制异常函数
    print(u"连接3306 制造复制异常函数")
    error_maker(host='172.16.65.36', port=3306, user='root',password='mysql',db='fandb',charset='utf8')
    conn39 = get_conn('10.0.1.39',3306,'root','mysql')
    conn40 = get_conn('10.0.1.40',3306,'root','mysql')
    conn41 = get_conn('10.0.1.41',3306,'root','mysql')

    #判断 39 40 41是否都因为drop t_error_maker停止
    print(u"判断 39 40 41是否都因为drop t_error_maker停止")
    get_error_status()

    #获取40 master status 以供39 41切换
    print(u"获取40 master status 以供39 41切换")
    master_status_40 = get_master_status(conn40)
    File_40,Position_40 = master_status_40['File'],master_status_40['Position']

    change_string = """
    change master to 
    master_host='10.0.1.40',
    master_port=3306,
    master_user='repl',
    master_password='repl',
    master_log_file='%s',
    master_log_pos=%d;
    """ % (File_40,Position_40)

    #39 41切换到40
    print(u"39,41切换到40")
    change_master(conn39,change_string)
    print(u"39切换到40成功")
    change_master(conn41,change_string)
    print(u"41切换到40成功")
    #修复40 slave
    print(u"修复40 slave")
    repair_slave(conn40)

    # conn35 = get_conn('172.16.65.35',3306,'root','mysql')
    # conn36 = get_conn('172.16.65.36',3306,'root','mysql')

    # #35 设置read only
    # set_read_only(conn35,switch='on')

    # #判断35 36 40 是否同步
    # while True:
    #   res35 = get_slave_statue(conn35,'show master status;')
    #   res36 = get_slave_statue(conn36,'show slave status;')
    #   res40 = get_slave_statue(conn40,'show slave status;')
    #   File_35,Position_35 = res35['File'],res35['Position']
    #   File_36,Position_36 = res36['Relay_Master_Log_File'],res36['Exec_Master_Log_Pos']
    #   File_40,Position_40 = res40['Relay_Master_Log_File'],res40['Exec_Master_Log_Pos']
    #   if File_35 == File_36 == File_40 and Position_35 == Position_36 == Position_40:
    #       break
    #   else:
    #       time.sleep(1)

    # #停40 sql_thread
    # set_sql_thread(conn40,switch='off')

    # #写接入36

    # #35 read_only=off
    # set_read_only(conn35,switch='off')

    # master_status_40 = get_master_status(conn40)
    # File_40,Position_40 = master_status_40['File'],master_status_40['Position']

    # change_string = """
    # change master to 
    # master_host='10.0.1.40',
    # master_port=3306,
    # master_user='repl',
    # master_password='repl',
    # master_log_file='%s',
    # master_log_pos=%d;
    # """ % (File_40,Position_40)

    # change_master(conn35,change_string)

    # #起40 sql_thread
    # set_sql_thread(conn40,switch='on')

    bar.cursor.restore()
    bar.draw(value=100)
<think> 我们之前讨论的是VIP(虚拟IP)以及高可用性(HA)的实现。现在用户的问题转向了使用Pacemaker/Corosync(PCS)实现MySQL主从切换后的验证方法。用户引用的配置片段是关于MariaDB Galera集群的配置,但用户明确提到的是“MySQL主从切换”,因此我们应关注传统的异步复制主从架构下的切换验证,而不是Galera集群。 用户需求:使用pcs实现MySQL主从切换后进行验证的方法。 分析: 1. PCS是Pacemaker/Corosync集群管理工具的命令行接口,用于管理高可用集群。 2. MySQL主从切换通常是指当主库故障时,Pacemaker集群将VIP和MySQL服务资源从主节点转移到备节点(提升从库为主库)的过程。 3. 切换后的验证需要确保: a. VIP已经成功漂移到新的主库节点。 b. MySQL服务在新的主库节点上正常运行。 c. 应用能够通过VIP连接到新的主库,并且可以正常读写。 d. 复制关系是否已经正确重建(如果原主库恢复后作为新的从库)。 验证步骤: 步骤1:检查集群状态 使用`pcs status`命令查看集群资源状态,确认VIP和MySQL服务资源是否已经在新的节点上运行。 步骤2:检查VIP绑定 在新的主库节点上,使用`ip addr show`命令检查VIP是否已经绑定到该节点的网络接口上。 步骤3:检查MySQL服务状态 在新的主库节点上,检查MySQL服务是否正在运行: ```bash systemctl status mysqld ``` 步骤4:验证MySQL实例角色 连接到新的主库(通过VIP或新主库的物理IP),执行SQL语句确认其角色为主库: ```sql SHOW MASTER STATUS; -- 或者查看复制状态 SHOW SLAVE STATUS\G ``` 注意:如果切换后,原从库被提升为新主库,那么在新主库上执行`SHOW SLAVE STATUS`应该显示为空(因为不再作为从库)。 步骤5:应用连接测试 配置应用连接字符串使用VIP,然后执行简单的数据库读写操作,验证应用是否可以正常工作。 步骤6:检查复制状态(如果配置了新的复制) 如果原主库已经恢复并作为新的从库,那么在新的从库(原主库)上检查复制状态: ```sql SHOW SLAVE STATUS\G ``` 确保`Slave_IO_Running`和`Slave_SQL_Running`都为Yes,并且没有延迟。 步骤7:监控日志 检查MySQL错误日志和Pacemaker日志,确保切换过程中没有异常: - MySQL日志:通常位于`/var/log/mysqld.log`或`/var/log/mysql/error.log` - Pacemaker日志:通过`journalctl -u corosync -u pacemaker`查看 注意事项: - 在切换后,原主库恢复后需要重新配置为从库并指向新的主库(除非使用自动重建复制的脚本)。 - 验证过程中应避免写操作到旧主库(如果它意外启动),防止脑裂。 生成相关问题: 根据用户当前问题,生成的相关问题应围绕Pacemaker管理MySQL主从切换的验证和后续操作。 注意:用户引用的配置是Galera的,但用户明确要求的是主从切换(异步复制)的验证。因此,回答中不涉及Galera的验证。 最终回答结构: 1. 步骤说明 2. 关键命令和操作 3. 注意事项 4. 相关问题 </think> 使用 PCS (Pacemaker/Corosync) 实现 MySQL 主从切换后,需要通过以下步骤进行验证,确保高可用集群正常工作: ### 一、基础状态验证 1. **检查集群状态** ```bash pcs status ``` - 确认所有节点在线 (`Online: [node1 node2]`) - 检查 VIP 资源运行节点: ```bash pcs resource show ``` - 输出示例:`vip (ocf::heartbeat:IPaddr2): Started node2` [^1] 2. **验证 VIP 绑定** 在新主库节点执行: ```bash ip addr show ``` - 确认 VIP(如 `192.168.10.100`)出现在目标节点的网卡上(如 `eth0:1`) --- ### 二、MySQL 服务验证 1. **检查 MySQL 进程状态** 在新主库节点执行: ```bash systemctl status mysqld ``` - 确保状态为 `active (running)` 2. **验证主库角色** 连接到 MySQL(通过 VIP 或物理 IP): ```sql SHOW MASTER STATUS\G ``` - 输出应包含 `File` 和 `Position`(非空值) - 确认读写权限: ```sql CREATE DATABASE verify_ha; DROP DATABASE verify_ha; ``` 3. **检查复制状态** 在从库节点执行: ```sql SHOW SLAVE STATUS\G ``` - 关键字段: ``` Slave_IO_Running: Yes Slave_SQL_Running: Yes Master_Host: <新主库的VIP或IP> # 确认指向新主库 ``` --- ### 三、故障切换模拟验证 1. **手动触发切换** ```bash pcs resource move mysql-master <目标节点> ``` 2. **观察切换过程** ```bash pcs status --full ``` - 检查资源迁移日志,确保无 `FAILED` 状态 - 切换时间应 ≤ 30秒(默认超时时间) 3. **应用层连接测试** 使用应用连接字符串配置 VIP,执行: ```bash mysql -uapp_user -p -h <VIP> -e "SELECT @@hostname" ``` - 预期返回新主库的主机名 --- ### 四、日志与监控检查 1. **Pacemaker 日志** ```bash journalctl -u pacemaker -n 100 ``` - 搜索 `promote`/`demote` 关键字确认角色切换 2. **MySQL 错误日志** ```bash tail -f /var/log/mysqld.log ``` - 检查是否有复制中断或权限错误 3. **资源代理调试** ```bash pcs resource debug-start mysql-master ``` --- ### 关键配置注意事项 1. **资源代理配置** 在 `pcs` 资源定义中需明确指定主从切换逻辑: ```bash pcs resource create mysql-master ocf:heartbeat:mysql \ master_slave=true \ replication_user="repl" replication_passwd="password" \ binary_log="/var/log/mysql/binlog" \ op monitor interval=5s ``` 2. **脑裂防护** 确保配置 `no-quorum-policy`: ```bash pcs property set no-quorum-policy=freeze ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值