PHP项目Balancer与加权池

wen PHP项目 2

本文目录导读:

PHP项目Balancer与加权池

  1. 核心设计思路
  2. 代码实现
  3. 进阶优化

针对你提到的 “PHP项目Balancer与加权池”,这通常指的是在 PHP 应用中实现负载均衡连接池/资源池的调度算法,特别是加权轮询(Weighted Round Robin)加权随机(Weighted Random)

由于 PHP 本身是单线程(通过 FPM 处理请求),这类“Balancer”通常用于以下场景:

  1. 数据库读写分离(多个 MySQL 从库,不同权重)。
  2. 第三方 API 网关(多个服务端,权重不同)。
  3. Redis 或 Memcached 集群(分片或主从)。
  4. 任务队列消费者(多个 worker 进程)。

以下是一个 PHP 实现的加权池(Weighted Pool)与 Balancer 的完整示例,包含加权轮询和加权随机两种常见算法。


核心设计思路

  1. 池(Pool):存储服务器列表及权重,权重越高,被选中的概率越高(5 表示可以连续被选中 5 次)。
  2. Balancer:负责从池中选择一个目标,状态(如当前轮询位置)通常存储在内存中(如 $_SESSIONAPCu、Redis 或进程内单例)。

代码实现

定义服务器节点与池

<?php
class ServerNode
{
    public string $host;
    public int $port;
    public int $weight; // 权重,1-10
    public function __construct(string $host, int $port, int $weight = 1)
    {
        $this->host = $host;
        $this->port = $port;
        $this->weight = max(1, $weight); // 至少为1
    }
    public function getEndpoint(): string
    {
        return "{$this->host}:{$this->port}";
    }
}

加权轮询 Balancer(平滑算法)

为了解决普通轮询在权重变化时的“颠簸”,使用 平滑加权轮询(Nginx 使用的版本)。

<?php
class WeightedRoundRobinBalancer
{
    private array $nodes;
    private array $currentWeights;
    private int $totalWeight;
    public function __construct(array $nodes)
    {
        // 确保节点有唯一标识(这里用端点字符串)
        $this->nodes = [];
        foreach ($nodes as $node) {
            $key = $node->getEndpoint();
            $this->nodes[$key] = $node;
            $this->currentWeights[$key] = 0; // 初始化当前权重
        }
        $this->totalWeight = array_sum(array_map(fn($n) => $n->weight, $this->nodes));
    }
    /**
     * 选择下一个节点(平滑加权轮询)
     */
    public function next(): ?ServerNode
    {
        if (empty($this->nodes)) {
            return null;
        }
        $bestKey = null;
        $maxCurrent = -1;
        // 1. 为每个节点增加其权重
        foreach ($this->nodes as $key => $node) {
            $this->currentWeights[$key] += $node->weight;
            // 2. 找到当前权重最大的节点
            if ($this->currentWeights[$key] > $maxCurrent) {
                $maxCurrent = $this->currentWeights[$key];
                $bestKey = $key;
            }
        }
        if ($bestKey === null) {
            return null;
        }
        // 3. 选中节点,减去总权重
        $this->currentWeights[$bestKey] -= $this->totalWeight;
        return $this->nodes[$bestKey];
    }
    /**
     * 获取当前所有节点状态(用于调试)
     */
    public function getState(): array
    {
        return [
            'nodes' => $this->nodes,
            'currentWeights' => $this->currentWeights,
            'totalWeight' => $this->totalWeight,
        ];
    }
}

加权随机 Balancer

<?php
class WeightedRandomBalancer
{
    private array $nodes;
    private array $weightMap; // 累积权重数组
    private int $totalWeight;
    public function __construct(array $nodes)
    {
        $this->nodes = [];
        $this->weightMap = [];
        $cumulative = 0;
        foreach ($nodes as $node) {
            $key = $node->getEndpoint();
            $this->nodes[$key] = $node;
            $cumulative += $node->weight;
            $this->weightMap[$key] = $cumulative;
        }
        $this->totalWeight = $cumulative;
    }
    /**
     * 随机选择一个节点
     */
    public function next(): ?ServerNode
    {
        if (empty($this->nodes) || $this->totalWeight <= 0) {
            return null;
        }
        $rand = mt_rand(1, $this->totalWeight);
        foreach ($this->weightMap as $key => $cumulativeWeight) {
            if ($rand <= $cumulativeWeight) {
                return $this->nodes[$key];
            }
        }
        return null; // 理论上不会发生
    }
}

使用示例

<?php
// 创建服务器节点池
$pool = [
    new ServerNode('192.168.1.10', 3306, 5), // 高权重 MySQL
    new ServerNode('192.168.1.11', 3306, 3),
    new ServerNode('192.168.1.12', 3306, 1),
];
// 选择均衡器策略
// $balancer = new WeightedRoundRobinBalancer($pool);
// $balancer = new WeightedRandomBalancer($pool);
// 模拟请求 10 次
$balancer = new WeightedRoundRobinBalancer($pool);
echo "加权轮询结果:\n";
for ($i = 1; $i <= 10; $i++) {
    $node = $balancer->next();
    if ($node) {
        echo "Request {$i}: -> {$node->getEndpoint()} (weight: {$node->weight})\n";
    }
}
echo "\n--- 状态 ---\n";
print_r($balancer->getState());

预期输出(平滑加权轮询)

加权轮询结果:
Request 1: -> 192.168.1.10:3306 (weight: 5)
Request 2: -> 192.168.1.10:3306 (weight: 5)
Request 3: -> 192.168.1.11:3306 (weight: 3)
Request 4: -> 192.168.1.10:3306 (weight: 5)
Request 5: -> 192.168.1.11:3306 (weight: 3)
Request 6: -> 192.168.1.10:3306 (weight: 5)
Request 7: -> 192.168.1.10:3306 (weight: 5)
Request 8: -> 192.168.1.12:3306 (weight: 1)
Request 9: -> 192.168.1.11:3306 (weight: 3)
Request 10: -> 192.168.1.10:3306 (weight: 5)

可以看出权重高的 10 被选中次数最多(5 次),权重低的 12 仅出现一次。


进阶优化

连接池的健康检查

在实际生产环境中,需要定期移除故障节点,可以在节点中加入 isAlive() 方法,Balancer 在选择时跳过不可用节点。

持久化状态

如果需要在多进程(FPM)间共享轮询状态,建议使用 RedisAPCu 存储 currentWeights

class RedisRoundRobinBalancer
{
    private \Redis $redis;
    private string $prefix = 'balancer:';
    public function next(array $nodes): ?ServerNode
    {
        // 使用 Redis 的 INCR 或 EVAL 脚本实现原子化轮询
        // ...
    }
}

组合模式

你可以创建一个 PoolManager 类,包含多个 Balancer(如主库用随机,从库用轮询),然后按需切换。


  • 加权轮询:适用于请求处理时间相对均衡的场景(如数据库查询)。
  • 加权随机:适用于处理时间波动大的场景(如第三方 API 调用),可避免流量集中。
  • PHP 实现:建议使用平滑加权轮询避免短时间内的请求突刺。
  • 生产环境:务必处理节点故障、动态配置更新以及多进程共享状态。

如果你有具体的应用场景(如 MySQL 读写分离、gRPC 客户端均衡),可以进一步调整 Balancer 的实现。

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