本文目录导读:

在PHP项目中实现根据节点负载动态调整权重,通常用于反向代理负载均衡或服务注册与发现场景,以下是几种主流实现方案:
基于反向代理(Nginx + Lua)
利用 OpenResty 或 Nginx Lua 模块实现动态权重调整:
-- nginx.conf
upstream backend {
hash $remote_addr consistent;
server 192.168.1.1 weight=1;
server 192.168.1.2 weight=1;
}
-- lua脚本:根据节点负载动态调整权重
local function get_node_load(ip)
-- 从监控系统获取CPU、内存、连接数等指标
local cpu_usage = get_node_cpu(ip)
local mem_usage = get_node_memory(ip)
local conn_count = get_node_connections(ip)
-- 计算综合负载分数 (0-100)
local load_score = (cpu_usage * 0.4 + mem_usage * 0.3 + conn_count * 1000 * 0.3)
return load_score
end
local function adjust_weight()
local upstream = require("ngx.upstream").get_upstreams("backend")
for _, server in ipairs(upstream:servers()) do
local load = get_node_load(server.ip)
-- 负载越低,权重越高
local new_weight = math.max(1, 100 - math.floor(load))
server:set_weight(new_weight)
end
end
-- 每隔5秒调整一次权重
ngx.timer.every(5, adjust_weight)
PHP 服务注册与发现 (Consul + PHP)
使用 Consul 作为注册中心,PHP 客户端动态获取节点权重:
class DynamicWeightBalancer
{
private $consul;
private $services = [];
public function __construct()
{
$this->consul = new ConsulClient('http://consul:8500');
}
public function getOptimalNode($serviceName)
{
// 获取所有健康节点
$nodes = $this->consul->getServiceNodes($serviceName);
// 计算每个节点的动态权重
$weightedNodes = [];
foreach ($nodes as $node) {
$load = $this->getNodeLoad($node);
$weight = $this->calculateWeight($node, $load);
$weightedNodes[] = [
'node' => $node,
'weight' => $weight
];
}
// 根据权重选择节点
return $this->weightedRandomSelect($weightedNodes);
}
private function getNodeLoad($node)
{
try {
// 从节点获取负载信息
$url = "http://{$node['Address']}:{$node['Port']}/health";
$response = file_get_contents($url);
$health = json_decode($response, true);
return [
'cpu' => $health['cpu_usage'] ?? 0,
'memory' => $health['memory_usage'] ?? 0,
'connections' => $health['active_connections'] ?? 0,
'response_time' => $health['avg_response_time'] ?? 0
];
} catch (Exception $e) {
return ['cpu' => 100, 'memory' => 100, 'connections' => 999];
}
}
private function calculateWeight($node, $load)
{
// 基础权重
$baseWeight = $node['Meta']['initial_weight'] ?? 10;
// 负载因子计算(越低越好)
$cpuFactor = max(0, 100 - $load['cpu']) / 100;
$memFactor = max(0, 100 - $load['memory']) / 100;
$connFactor = max(0, 100 - min($load['connections'], 100)) / 100;
$respFactor = max(0, 100 - min($load['response_time'] * 10, 100)) / 100;
// 综合权重 = 基础权重 × 各因子乘积
$dynamicWeight = $baseWeight * $cpuFactor * $memFactor * $connFactor * $respFactor;
return max(1, round($dynamicWeight));
}
private function weightedRandomSelect($nodes)
{
$totalWeight = array_sum(array_column($nodes, 'weight'));
$random = mt_rand(1, $totalWeight);
foreach ($nodes as $node) {
$random -= $node['weight'];
if ($random <= 0) {
return $node['node'];
}
}
return $nodes[0]['node'];
}
}
// 使用示例
$balancer = new DynamicWeightBalancer();
$node = $balancer->getOptimalNode('api-service');
$url = "http://{$node['Address']}:{$node['Port']}/api/endpoint";
节点健康检查 + 权重更新
在每个PHP节点上暴露健康检查接口:
// health.php - 每个PHP节点部署
class NodeHealthController
{
public function check()
{
// 获取系统负载
$cpu = sys_getloadavg()[0]; // 1分钟平均负载
$memory = $this->getMemoryUsage();
$connections = $this->getActiveConnections();
$responseTime = $this->getAvgResponseTime();
return json_encode([
'status' => 'healthy',
'cpu_usage' => min(100, $cpu * 20), // 转换为百分比
'memory_usage' => $memory,
'active_connections' => $connections,
'avg_response_time' => $responseTime,
'timestamp' => time()
]);
}
private function getMemoryUsage()
{
$memInfo = file_get_contents('/proc/meminfo');
preg_match('/MemTotal:\s+(\d+)/', $memInfo, $total);
preg_match('/MemAvailable:\s+(\d+)/', $memInfo, $available);
if (!empty($total[1]) && !empty($available[1])) {
return round((1 - $available[1] / $total[1]) * 100);
}
return 50;
}
private function getActiveConnections()
{
// 检测当前PHP-FPM进程数
$processes = shell_exec("ps aux | grep 'php-fpm' | grep -v grep | wc -l");
return (int)$processes;
}
private function getAvgResponseTime()
{
// 从apcu或redis获取最近100次请求的平均响应时间
return apcu_fetch('avg_response_time') ?: 0.2; // 单位秒
}
}
权重调整策略算法
class LoadWeightAdjuster
{
// 自适应权重调整
public function adaptiveWeight($node, $currentWeight, $metrics)
{
// 1. 基础负载权重
$loadWeight = 100 - ($metrics['cpu'] * 0.4 + $metrics['memory'] * 0.3 +
$metrics['io'] * 0.2 + $metrics['network'] * 0.1);
// 2. 响应时间惩罚
$responsePenalty = 0;
if ($metrics['response_time'] > 1.0) {
$responsePenalty = min(50, ($metrics['response_time'] - 1.0) * 20);
}
// 3. 错误率惩罚
$errorPenalty = $metrics['error_rate'] * 100;
// 4. 历史表现平滑
$historyWeight = $currentWeight * 0.7;
$instantWeight = ($loadWeight - $responsePenalty - $errorPenalty) * 0.3;
// 5. 最终权重
$newWeight = $historyWeight + $instantWeight;
return max(1, min(100, round($newWeight)));
}
// 平滑权重变化(防止抖动)
public function smoothTransition($oldWeight, $newWeight)
{
$maxStep = 5; // 每次最大调整步长
$difference = $newWeight - $oldWeight;
if (abs($difference) > $maxStep) {
return $oldWeight + ($difference > 0 ? $maxStep : -$maxStep);
}
return $newWeight;
}
}
Redis 分布式权重管理
class RedisWeightManager
{
private $redis;
private $prefix = 'load_balancer:';
public function __construct()
{
$this->redis = new Redis();
$this->redis->connect('redis', 6379);
}
// 更新节点权重
public function updateWeight($nodeId, $weight)
{
$key = $this->prefix . $nodeId;
$this->redis->hMSet($key, [
'weight' => $weight,
'updated_at' => time()
]);
// 设置过期时间,防止脏数据
$this->redis->expire($key, 30);
// 更新总权重
$this->redis->zAdd($this->prefix . 'weights', $weight, $nodeId);
}
// 获取最优节点
public function getBestNode()
{
// 获取所有活跃节点
$nodes = $this->redis->zRevRange($this->prefix . 'weights', 0, -1, true);
if (empty($nodes)) {
return null;
}
// 加权随机选择
$total = array_sum($nodes);
$rand = mt_rand(1, $total);
foreach ($nodes as $nodeId => $weight) {
$rand -= $weight;
if ($rand <= 0) {
return $nodeId;
}
}
return array_key_first($nodes);
}
// 批量获取节点权重
public function getAllWeights()
{
return $this->redis->zRevRange($this->prefix . 'weights', 0, -1, true);
}
}
监控与告警集成
class WeightMonitor
{
public function monitorAndAdjust()
{
$nodes = $this->getAllNodes();
$adjustments = [];
foreach ($nodes as $node) {
$metrics = $this->getNodeMetrics($node);
// 检测异常节点
if ($metrics['error_rate'] > 0.1) { // 错误率超过10%
$adjustments[] = [
'node' => $node,
'action' => 'drain', // 排空节点
'reason' => 'High error rate'
];
} elseif ($metrics['response_time'] > 5) { // 响应时间超过5秒
$adjustments[] = [
'node' => $node,
'action' => 'reduce_weight',
'amount' => 50,
'reason' => 'Slow response'
];
} elseif ($metrics['cpu'] > 80) { // CPU超过80%
$adjustments[] = [
'node' => $node,
'action' => 'reduce_weight',
'amount' => 30,
'reason' => 'High CPU usage'
];
}
// 记录调整日志
$this->logAdjustment($adjustments);
}
return $adjustments;
}
}
最佳实践建议
- 避免频繁调整:设置最小调整间隔(如10秒),防止权重频繁波动
- 使用指数平滑:
current_weight = previous_weight * 0.7 + new_weight * 0.3 - 设置权重范围:限制在1-100之间,防止极端值
- 健康检查优先:如果节点不可用,直接设置权重为0
- 监控可视化:使用 Prometheus + Grafana 监控权重变化
根据实际业务场景选择合适的方案,对于简单场景推荐使用 Nginx + Lua,复杂场景推荐使用 Consul 或 etcd 作为注册中心。