PHP `MySQL` `Replication` (主从复制) 深度:异步、半同步与 GTID

各位观众,晚上好!我是今晚的主讲人,咱们今天来聊聊PHP开发中,MySQL主从复制那些事儿。别紧张,这玩意儿听起来高大上,其实也没那么玄乎。咱们用大白话,加上一些喜闻乐见的代码,保证你听完之后,也能在项目里玩转主从复制。

开场白:话说,为啥要有主从复制?

咱们先来唠唠嗑,设想一个场景:你运营着一个电商平台,每天都有大量的用户涌入,疯狂下单。数据库作为核心,压力山大啊!如果只有一个数据库服务器,万一它挂了,整个网站就瘫痪了,损失可就大了。

主从复制就像给数据库找了个“替身”,或者说“分身”。主数据库(Master)负责处理写操作,比如用户下单、修改商品信息等。从数据库(Slave)则从主数据库同步数据,主要负责读操作,比如用户浏览商品、查询订单信息等。

这样一来,就把读写压力分摊到不同的服务器上,提高了数据库的性能和可用性。即使主数据库挂了,从数据库也能顶上,保证网站还能继续运行。是不是很机智?

第一幕:异步复制 (Asynchronous Replication) – 随性的“老大哥”

最简单,也最常见的,就是异步复制。就像一个老大哥,告诉小弟们“我做了啥”,然后自己就去忙别的了,才不管小弟们有没有收到,有没有执行。

  • 原理: 主数据库执行完写操作后,立即返回给客户端,然后异步地将数据变更(以二进制日志的形式)发送给从数据库。从数据库接收到二进制日志后,再执行这些变更。

  • 优点: 性能高,对主数据库的影响最小。主数据库不用等待从数据库的响应。

  • 缺点: 数据一致性风险高。如果主数据库发生故障,而从数据库还没有完全同步主数据库的数据,那么就会发生数据丢失。

  • 代码示例(PHP): 虽然PHP代码本身不直接控制MySQL复制,但我们可以通过PHP来监控复制状态。

    <?php
    // 连接到主数据库
    $master_mysqli = new mysqli('master_host', 'user', 'password', 'database');
    if ($master_mysqli->connect_error) {
        die('Master connection failed: ' . $master_mysqli->connect_error);
    }
    
    // 连接到从数据库
    $slave_mysqli = new mysqli('slave_host', 'user', 'password', 'database');
    if ($slave_mysqli->connect_error) {
        die('Slave connection failed: ' . $slave_mysqli->connect_error);
    }
    
    // 获取主数据库的binlog位置
    $master_status_result = $master_mysqli->query("SHOW MASTER STATUS");
    $master_status = $master_status_result->fetch_assoc();
    $master_file = $master_status['File'];
    $master_position = $master_status['Position'];
    
    // 获取从数据库的复制状态
    $slave_status_result = $slave_mysqli->query("SHOW SLAVE STATUS");
    $slave_status = $slave_status_result->fetch_assoc();
    $slave_IO_Running = $slave_status['Slave_IO_Running'];
    $slave_SQL_Running = $slave_status['Slave_SQL_Running'];
    $slave_file = $slave_status['Relay_Master_Log_File'];
    $slave_position = $slave_status['Exec_Master_Log_Pos'];
    
    echo "Master Binlog File: " . $master_file . "n";
    echo "Master Binlog Position: " . $master_position . "n";
    
    echo "Slave IO Running: " . $slave_IO_Running . "n";
    echo "Slave SQL Running: " . $slave_SQL_Running . "n";
    echo "Slave Relay Log File: " . $slave_file . "n";
    echo "Slave Executed Position: " . $slave_position . "n";
    
    // 检查复制是否正常
    if ($slave_IO_Running == 'Yes' && $slave_SQL_Running == 'Yes') {
        echo "Replication is running.n";
    } else {
        echo "Replication is not running.n";
    }
    
    // 计算延迟 (粗略估计)
    $delay = $master_position - $slave_position;
    echo "Replication Delay (bytes): " . $delay . "n";
    
    $master_mysqli->close();
    $slave_mysqli->close();
    ?>

    这个例子展示了如何用PHP来获取主从服务器的复制状态。通过比较主从服务器的binlog位置,可以粗略估计复制的延迟。注意,这只是一个简单的监控示例,实际应用中需要更完善的监控和告警机制。

  • 适用场景: 对数据一致性要求不高,但对性能要求很高的场景。例如,日志服务器、备份服务器等。

第二幕:半同步复制 (Semi-Synchronous Replication) – 有点靠谱的“好基友”

为了解决异步复制的数据一致性问题,就有了半同步复制。这就像一对好基友,老大哥做完事,至少要等一个小弟确认收到,才算完事。

  • 原理: 主数据库执行完写操作后,必须等待至少一个从数据库接收到数据变更,并写入到 relay log 中,才返回给客户端。

  • 优点: 提高了数据一致性,在主数据库发生故障时,可以减少数据丢失的风险。

  • 缺点: 性能比异步复制略低,因为主数据库需要等待从数据库的响应。

  • 配置示例(MySQL):

    首先,你需要安装半同步复制插件。

    -- 在主数据库上
    INSTALL PLUGIN rpl_semi_sync_master SONAME 'rpl_semi_sync_master.so';
    INSTALL PLUGIN rpl_semi_sync_slave SONAME 'rpl_semi_sync_slave.so';
    
    -- 检查插件是否安装成功
    SHOW PLUGINS;
    
    -- 开启半同步复制
    SET GLOBAL rpl_semi_sync_master_enabled = 1;
    SET GLOBAL rpl_semi_sync_slave_enabled = 1;
    
    -- 至少需要多少个从库确认才算成功
    SET GLOBAL rpl_semi_sync_master_wait_for_slave_count = 1;
    
    -- 提交后多少毫秒超时
    SET GLOBAL rpl_semi_sync_master_timeout = 10;
    
    -- 在从数据库上
    INSTALL PLUGIN rpl_semi_sync_slave SONAME 'rpl_semi_sync_slave.so';
    
    -- 开启半同步复制
    SET GLOBAL rpl_semi_sync_slave_enabled = 1;
    
    -- 配置主服务器信息,并启动复制
    CHANGE MASTER TO
        MASTER_HOST='master_host',
        MASTER_USER='replication_user',
        MASTER_PASSWORD='password',
        MASTER_LOG_FILE='mysql-bin.000001', -- 根据你的实际情况修改
        MASTER_LOG_POS=4;                -- 根据你的实际情况修改
    
    START SLAVE;

    注意:MASTER_LOG_FILEMASTER_LOG_POS 需要根据主数据库的 SHOW MASTER STATUS 命令的输出来设置。

  • PHP 代码示例(监控): 和异步复制类似,只是监控的指标略有不同。

    <?php
    // 连接到主数据库
    $master_mysqli = new mysqli('master_host', 'user', 'password', 'database');
    if ($master_mysqli->connect_error) {
        die('Master connection failed: ' . $master_mysqli->connect_error);
    }
    
    // 连接到从数据库
    $slave_mysqli = new mysqli('slave_host', 'user', 'password', 'database');
    if ($slave_mysqli->connect_error) {
        die('Slave connection failed: ' . $slave_mysqli->connect_error);
    }
    
    // 获取主数据库的半同步状态
    $master_status_result = $master_mysqli->query("SHOW GLOBAL STATUS LIKE 'Rpl_semi_sync_%'");
    $master_status = array();
    while ($row = $master_status_result->fetch_assoc()) {
        $master_status[$row['Variable_name']] = $row['Value'];
    }
    
    // 获取从数据库的复制状态
    $slave_status_result = $slave_mysqli->query("SHOW SLAVE STATUS");
    $slave_status = $slave_status_result->fetch_assoc();
    $slave_IO_Running = $slave_status['Slave_IO_Running'];
    $slave_SQL_Running = $slave_status['Slave_SQL_Running'];
    $slave_file = $slave_status['Relay_Master_Log_File'];
    $slave_position = $slave_status['Exec_Master_Log_Pos'];
    
    echo "Master Semi-Sync Status:n";
    print_r($master_status);
    
    echo "Slave IO Running: " . $slave_IO_Running . "n";
    echo "Slave SQL Running: " . $slave_SQL_Running . "n";
    echo "Slave Relay Log File: " . $slave_file . "n";
    echo "Slave Executed Position: " . $slave_position . "n";
    
    // 检查复制是否正常
    if ($slave_IO_Running == 'Yes' && $slave_SQL_Running == 'Yes') {
        echo "Replication is running.n";
    } else {
        echo "Replication is not running.n";
    }
    
    $master_mysqli->close();
    $slave_mysqli->close();
    ?>

    这个例子展示了如何获取主数据库的半同步状态,例如 Rpl_semi_sync_master_clients (连接的从库数量) 和 Rpl_semi_sync_master_no_tx (没有等待从库确认的事务数量)。

  • 适用场景: 对数据一致性有一定要求,但又不能承受完全同步复制带来的性能损失的场景。例如,金融系统、电商平台的订单系统等。

第三幕:GTID (Global Transaction Identifier) – 复制界的“身份证”

前面两种复制方式,都需要手动指定主数据库的二进制日志文件和位置,这在主从切换或者增加从数据库的时候,很容易出错。GTID就像给每个事务分配了一个唯一的“身份证”,让复制更加简单可靠。

  • 原理: GTID是一个全局唯一的事务标识符,由服务器UUID和事务序列号组成。主数据库上的每个事务都会被分配一个GTID,然后从数据库通过GTID来跟踪复制进度,而不是依赖二进制日志文件和位置。

  • 优点:

    • 简化了主从切换和故障恢复。
    • 方便了增加从数据库。
    • 提高了复制的可靠性。
  • 配置示例(MySQL):

    -- 在所有数据库服务器上
    -- 开启 GTID
    SET GLOBAL gtid_mode = ON;
    SET GLOBAL enforce_gtid_consistency = ON;
    
    -- 重启MySQL服务器
    
    -- 查看GTID是否开启
    SHOW GLOBAL VARIABLES LIKE '%gtid%';
    
    -- 配置主服务器信息,并启动复制
    CHANGE MASTER TO
        MASTER_HOST='master_host',
        MASTER_USER='replication_user',
        MASTER_PASSWORD='password',
        MASTER_AUTO_POSITION = 1;  -- 使用GTID自动定位
    
    START SLAVE;

    关键在于 MASTER_AUTO_POSITION = 1,它告诉从数据库使用 GTID 自动定位复制位置,不再需要手动指定二进制日志文件和位置。

  • PHP 代码示例(监控): 监控 GTID 复制状态。

    <?php
    // 连接到主数据库
    $master_mysqli = new mysqli('master_host', 'user', 'password', 'database');
    if ($master_mysqli->connect_error) {
        die('Master connection failed: ' . $master_mysqli->connect_error);
    }
    
    // 连接到从数据库
    $slave_mysqli = new mysqli('slave_host', 'user', 'password', 'database');
    if ($slave_mysqli->connect_error) {
        die('Slave connection failed: ' . $slave_mysqli->connect_error);
    }
    
    // 获取主数据库的已提交GTID集合
    $master_gtid_executed_result = $master_mysqli->query("SELECT @@global.gtid_executed");
    $master_gtid_executed = $master_gtid_executed_result->fetch_row()[0];
    
    // 获取从数据库的复制状态
    $slave_status_result = $slave_mysqli->query("SHOW SLAVE STATUS");
    $slave_status = $slave_status_result->fetch_assoc();
    $slave_IO_Running = $slave_status['Slave_IO_Running'];
    $slave_SQL_Running = $slave_status['Slave_SQL_Running'];
    $slave_gtid_executed = $slave_status['Executed_Gtid_Set'];
    
    echo "Master GTID Executed: " . $master_gtid_executed . "n";
    echo "Slave IO Running: " . $slave_IO_Running . "n";
    echo "Slave SQL Running: " . $slave_SQL_Running . "n";
    echo "Slave GTID Executed: " . $slave_gtid_executed . "n";
    
    // 检查复制是否正常
    if ($slave_IO_Running == 'Yes' && $slave_SQL_Running == 'Yes') {
        echo "Replication is running.n";
    } else {
        echo "Replication is not running.n";
    }
    
    // 可以比较 master_gtid_executed 和 slave_gtid_executed 来判断复制延迟
    
    $master_mysqli->close();
    $slave_mysqli->close();
    ?>

    这个例子展示了如何获取主从数据库的 GTID 执行集合,通过比较这两个集合,可以更精确地判断复制延迟。

  • 适用场景: 强烈推荐所有新的MySQL复制集群都使用GTID,尤其是需要频繁进行主从切换或者需要高可用性的场景。

总结:选择哪个“替身”?

说了这么多,到底该选择哪种复制方式呢?咱们来总结一下:

特性 异步复制 半同步复制 GTID复制
数据一致性 最低 较高 较高 (结合半同步复制可进一步提高)
性能 最高 较低 中等 (取决于是否结合半同步复制)
配置复杂度 最低 中等 较高 (但长期来看,简化了管理)
适用场景 日志备份 金融系统 强烈推荐所有新集群,尤其需要高可用性的场景

进阶:多源复制和 Group Replication

除了上面三种基本的复制方式,MySQL 还有一些更高级的复制技术,例如:

  • 多源复制 (Multi-Source Replication): 一个从数据库可以从多个主数据库同步数据,适用于数据聚合等场景。

  • Group Replication: 一种基于 Paxos 协议的分布式复制方案,提供更高的数据一致性和可用性,但配置和维护也更加复杂。

这些高级技术,咱们以后有机会再深入探讨。

结尾:复制不是银弹

最后,要强调一点,主从复制并不是解决所有问题的“银弹”。它只能提高数据库的读性能和可用性,并不能解决所有的数据一致性问题。在实际应用中,还需要结合其他的技术,例如缓存、分布式事务等,才能构建一个高性能、高可用的系统。

好了,今天的讲座就到这里。希望大家有所收获,能够在自己的项目中灵活运用MySQL主从复制技术。谢谢大家!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注