本文目录导读:

在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 + 服务网格 | 秒级 | 高 |
关键注意事项:
- 会话保持:PHP Session 需使用 Redis/Cookie 共享,避免切换后登录失效
- 缓存预热:主备切换后,新主库可能需要重建缓存
- 数据一致性:主从延迟可能导致读写冲突,建议强制读主库场景
- 监控告警:对接 Prometheus + Grafana 实时监控健康状态
- 人工干预:保留手动切换接口,防止自动化误判
通过以上方案,可实现 PHP 项目 99.9% 以上的可用性,具体选择需根据业务重要性、预算和技术栈灵活调整。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播