PHP项目主备切换与故障转移

wen PHP项目 4

本文目录导读:

PHP项目主备切换与故障转移

  1. 数据库主备切换
  2. 应用层主备切换
  3. 缓存主备切换(Redis)
  4. 全面的故障转移方案
  5. 部署架构建议
  6. 最佳实践总结

在PHP项目中实现主备切换与故障转移,通常涉及数据库缓存服务节点等不同层面的高可用设计,以下是针对不同场景的详细方案和最佳实践。


数据库主备切换

1 读写分离 + 自动故障转移

使用 MySQL + ProxySQL / MaxScale / HAProxy 实现透明切换。

// 配置示例(使用 PDO + 读写分离)
$config = [
    'write' => [
        'host' => '192.168.1.10',  // 主库
        'port' => 3306,
    ],
    'read' => [
        ['host' => '192.168.1.11', 'port' => 3306],
        ['host' => '192.168.1.12', 'port' => 3306],  // 备库
    ],
    'user' => 'app_user',
    'password' => 'secret',
    'database' => 'myapp',
];
// 连接池管理(简化版)
class DBConnectionManager {
    private static $master = null;
    private static $slaves = [];
    public static function getWriteConnection() {
        if (self::$master === null) {
            self::$master = self::connect($config['write']);
        }
        return self::$master;
    }
    public static function getReadConnection() {
        // 随机选择从库
        $slave = $config['read'][array_rand($config['read'])];
        $key = $slave['host'];
        if (!isset(self::$slaves[$key])) {
            self::$slaves[$key] = self::connect($slave);
        }
        return self::$slaves[$key];
    }
}

2 自动故障检测与切换

使用 Keepalived + MySQL MHA / Orchestrator 实现VIP漂移。

# keepalived配置示例
vrrp_instance VI_1 {
    state MASTER          # 备库改为 BACKUP
    interface eth0
    virtual_router_id 51
    priority 100          # 备库设为 90
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1234
    }
    virtual_ipaddress {
        192.168.1.100/24   # VIP地址
    }
    track_script {
        chk_mysql
    }
}

PHP 应用只需连接 VIP(192.168.1.100),切换过程对应用透明。


应用层主备切换

1 PHP-FPM 多实例 + Nginx 负载均衡

# nginx upstream 配置
upstream php_backend {
    server 10.0.0.1:9000 weight=5;   # 主节点
    server 10.0.0.2:9000 weight=5;   # 备节点
    server 10.0.0.3:9000 backup;      # 备用节点(故障时启用)
    # 健康检查(需要 nginx-plus 或第三方模块)
    health_check interval=5s fails=3 passes=2;
}
server {
    location ~ \.php$ {
        fastcgi_pass php_backend;
        include fastcgi_params;
    }
}

2 应用级故障转移(代码层面)

// 多节点调用示例
class FailoverClient {
    private $servers = [
        'http://primary.app.local',
        'http://secondary.app.local',
    ];
    public function call($endpoint, $data) {
        foreach ($this->servers as $server) {
            try {
                $response = $this->doRequest($server . $endpoint, $data);
                if ($response['status'] === 200) {
                    return $response['body'];
                }
            } catch (Exception $e) {
                // 记录日志,尝试下一个节点
                error_log("Failover: {$server} failed - " . $e->getMessage());
                continue;
            }
        }
        throw new Exception("All servers unavailable");
    }
}

缓存主备切换(Redis)

1 Redis Sentinel 自动故障转移

// 使用 Predis 或 phpredis 连接 Sentinel
$sentinel = new Predis\Client([
    'tcp://192.168.1.10:26379',
    'tcp://192.168.1.11:26379',
    'tcp://192.168.1.12:26379',
]);
// 获取当前主节点
$master = $sentinel->getMaster('mymaster');
echo "Current master: {$master[0]}:{$master[1]}\n";
// 连接主节点写入
$redis = new Predis\Client("tcp://{$master[0]}:{$master[1]}");
$redis->set('key', 'value');
// 连接从节点读取(可选)
$slaves = $sentinel->getSlaves('mymaster');
$slave = $slaves[array_rand($slaves)];
$redisSlave = new Predis\Client("tcp://{$slave[0]}:{$slave[1]}");
$value = $redisSlave->get('key');

2 Redis Cluster 自动分片

// 连接 Redis 集群(自动处理节点切换)
$client = new Predis\Client([
    'tcp://192.168.1.10:7000',
    'tcp://192.168.1.11:7000',
    'tcp://192.168.1.12:7000',
], ['cluster' => 'redis']);

全面的故障转移方案

1 健康检查 + 降级策略

class ServiceMonitor {
    private $healthChecks = [];
    private $serviceStatus = [];
    public function registerCheck($name, callable $check) {
        $this->healthChecks[$name] = $check;
        $this->serviceStatus[$name] = true;
    }
    public function runChecks() {
        foreach ($this->healthChecks as $name => $check) {
            try {
                $result = $check();
                $this->serviceStatus[$name] = $result;
            } catch (Exception $e) {
                $this->serviceStatus[$name] = false;
                // 触发告警
                $this->alert("Service {$name} failed");
            }
        }
    }
    public function isServiceAvailable($name) {
        return $this->serviceStatus[$name] ?? false;
    }
}
// 使用示例
$monitor = new ServiceMonitor();
$monitor->registerCheck('database', function() {
    $db = DB::connection();
    return $db->select('SELECT 1')->count() > 0;
});
// 降级逻辑
if (!$monitor->isServiceAvailable('database')) {
    // 切换到本地缓存或静态数据
    return Cache::getFallback('users');
}

2 断路器模式(Circuit Breaker)

使用 PHP Circuit Breaker 库(如 guzzlehttp/circuit-breaker):

use GuzzleHttp\Client;
use GuzzleHttp\Subscriber\CircuitBreaker\CircuitBreaker;
$client = new Client([
    'base_uri' => 'http://api.example.com',
    'timeout' => 5.0,
]);
$circuitBreaker = new CircuitBreaker($client, [
    'failures' => 3,
    'success_threshold' => 2,
    'timeout' => 30000,  // 30秒后重试
]);
try {
    $response = $circuitBreaker->get('/users');
} catch (Exception $e) {
    // 断路器打开,返回降级数据
    return ['error' => 'Service unavailable', 'data' => $cachedData];
}

部署架构建议

                  +-------------+
                  |  负载均衡器   |
                  | (VIP/HAProxy)|
                  +------+------+
                         |
        +----------------+----------------+
        |                |                |
+-------+------+ +-------+------+ +-------+------+
| Nginx + PHP  | | Nginx + PHP  | | Nginx + PHP  |
| (主节点)      | | (备节点)      | | (备节点)      |
+-------+------+ +-------+------+ +-------+------+
        |                |                |
        +----------------+----------------+
                         |
              +----------+----------+
              |       MySQL        |
              |  (主-主 同步或主从) |
              +-------------------+
              |     Redis         |
              |  (Sentinel/集群)   |
              +-------------------+

最佳实践总结

场景 推荐方案 切换时间 复杂度
MySQL 主备 ProxySQL + Orchestrator < 10秒
Redis 故障 Redis Sentinel < 30秒
PHP 应用节点 Nginx + 健康检查 < 5秒
全流程自动切换 Kubernetes + 服务网格 秒级

关键注意事项:

  1. 会话保持:PHP Session 需使用 Redis/Cookie 共享,避免切换后登录失效
  2. 缓存预热:主备切换后,新主库可能需要重建缓存
  3. 数据一致性:主从延迟可能导致读写冲突,建议强制读主库场景
  4. 监控告警:对接 Prometheus + Grafana 实时监控健康状态
  5. 人工干预:保留手动切换接口,防止自动化误判

通过以上方案,可实现 PHP 项目 99.9% 以上的可用性,具体选择需根据业务重要性、预算和技术栈灵活调整。

抱歉,评论功能暂时关闭!