WebMam AI 代理故障自动转移的方案

mliev

先说明新建的三个文件都是干嘛的
GlobalData.php 使用了 GlobalData变量共享组件
HealthCheck.php 按照设定的时间做健康检查
Proxy.php 代理设置,需要将后台的代理改成这里的设置

  1. 使用Composer添加一些依赖

    composer require workerman/globaldata
    composer require guzzlehttp/guzzle
  2. process 目录新建 GlobalData.php 并添加代码

<?php

namespace process;

class GlobalData extends \GlobalData\Server
{

}
  1. process 目录新建 HealthCheck.php 并添加代码
<?php

namespace process;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise\Utils;
use Psr\Http\Message\ResponseInterface;
use Workerman\Worker;

class HealthCheck
{

    //没五分钟做一次健康检查
    protected bool $quitSignal = false;

    //这里填你需要监控的代理列表
    protected array $list = [
        ['protocol' => 'https', 'domain' => 'api.xxx.ink'],
        ['protocol' => 'https', 'domain' => 'api.xxxx.fun'],
    ];

    protected \GlobalData\Client $GlobalData;

    protected string $GlobalKey = 'serviceList';

    public function onWorkerStart(Worker $worker)
    {
        $this->GlobalData = new \GlobalData\Client('127.0.0.1:2207');
        $this->quitSignal = false;

        $this->check();
    }

    public function onWorkerStop(Worker $worker)
    {
        $this->quitSignal = true;
    }

    public function check(): void
    {
        $client = new Client([
            \GuzzleHttp\RequestOptions::CONNECT_TIMEOUT => 30,
            \GuzzleHttp\RequestOptions::TIMEOUT => 30,
            \GuzzleHttp\RequestOptions::VERIFY => false,
            \GuzzleHttp\RequestOptions::HTTP_ERRORS => false,
        ]);
        $promises  = [];

        foreach ($this->list as $item) {
            $now = intval(microtime(true) * 1000);
            $protocol = $item['protocol'];
            $domain   = $item['domain'];
            try {
                $client->get("{$protocol}://$domain");
                $newTime = intval(microtime(true) * 1000) - $now;
                //echo "请求域名:{$domain} 响应:{$newTime}ms\n";
                $this->setKeyGlobalData($domain, $newTime);
            } catch (GuzzleException $e) {
                //echo "请求{$domain}失败:{$e->getMessage()}\n";
                if (!empty($this->GlobalData->serviceList) && isset($this->GlobalData->serviceList[$domain])) {
                    //echo "域名下线:{$domain}\n";
                    $this->delKeyGlobalData($domain);
                }
            }
        }

        if (!$this->quitSignal){
            \Workerman\Timer::add(300, [$this, 'check'], [], false);
        }
    }

    protected function setKeyGlobalData($key, $value): void
    {
        $serviceList = $this->GlobalData->serviceList;
        if (empty($serviceList)) $serviceList = [];
        $serviceList[$key] = $value;
        $this->GlobalData->serviceList = $serviceList;
    }

    protected function delKeyGlobalData($key): void
    {
        $serviceList = $this->GlobalData->serviceList;
        if (empty($serviceList)) $serviceList = [];
        if (isset($serviceList[$key])){
            unset($serviceList[$key]);
        }
        $this->GlobalData->serviceList = $serviceList;
    }
}
  1. process 目录新建 Proxy.php 并添加代码

<?php

namespace process;

use Workerman\Connection\AsyncTcpConnection;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http\Request;
use Workerman\Worker;

class Proxy
{
    //这里填和HealthCheck.php配置一样的代理列表
    protected array $defaultHostList = [
        'api.xxx.ink',
        'api.xxx.fun'
    ];

    protected $selectChannel = [];

    //这里填非官方的代理地址和key,我用了一个第三方的GTP4的代理(密钥=》代理地址)
    protected array $tokenHostList = [
        'sk-xxxxxxxxxxxxx' => ['api.xxx.ltd'],
    ];

    protected \GlobalData\Client $GlobalData;

    public function onWorkerStart(Worker $worker): void
    {
        $this->GlobalData = new \GlobalData\Client('127.0.0.1:2207');
    }

    public function onMessage(TcpConnection $connection, Request $request): void
    {
        $this->cluster($connection, $request);
    }

    /**
     * 集群调用
     * @param TcpConnection $connection
     * @param Request $request
     * @return void
     */
    public function cluster(TcpConnection $connection, Request $request): void
    {
        $authorization = $request->header('Authorization','');
        list(, $token) = explode(' ', $authorization);

        $hostList = $this->defaultHostList;

        if (isset($this->tokenHostList[$token])){
            $hostList = $this->tokenHostList[$token];
        }

        $hostList = $this->getHostList($hostList);

        foreach ($hostList as $host) {
            try {
                $this->proxy($connection, $request, $host);
                $this->selectChannel[$host] = true;
                return;
            } catch (\Exception $e) {
                continue;
            }
        }

        //如果全部都失败则直接返回超时提示
        $connection->send('{"error":{"code":503,"message":"Proxy Service Timeout.","param":null,"type":"cf_service_unavailable"}}');

    }

    /**
     * @param $hostList
     * @return string[]
     */
    public function getHostList($hostList): array
    {
        $serviceList = $this->GlobalData->serviceList;

        $list_a = [];
        $list_b = [];

        foreach ($hostList as $domain) {
            if (isset($serviceList[$domain])){
                if (isset($this->selectChannel[$domain])){
                    $serviceList[$domain] = $serviceList[$domain] - 120;
                }
                $list_a[$domain] = $serviceList[$domain];
            }else{
                $list_b[] = $domain;
            }
        }
        $list_a = array_flip($list_a);
        ksort($list_a);

        $retList = [];

        foreach ($list_a as $domain) {
            $retList[] = $domain;
        }

        foreach ($list_b as $domain) {
            $retList[] = $domain;
        }

        return $retList;
    }

    /**
     * @throws \Exception
     */
    public function proxy(TcpConnection $connection, Request $request, string $host): void
    {
        $buffer = (string)$request;
        $con = new AsyncTcpConnection("tcp://$host:443", ['ssl' =>['verify_peer' => false]]);
        $buffer = preg_replace("/Host: ?(.*?)\r\n/", "Host: $host\r\n", $buffer);
        $con->transport = 'ssl';
        $connection->protocol = null;
        $con->send($buffer);
        $con->pipe($connection);
        $connection->pipe($con);
        $con->connect();
    }
}
  1. 设置配置文件

config/process.php里面增加下面的配置

'Proxy' => [
        'handler' => process\Proxy::class,
        'listen' => 'http://0.0.0.0:8988',//本地代理地址,可以自己更换
        'count' => cpu_count(),
        'reloadable' => false,
    ],
    'GlobalData' => [
        'handler' => process\GlobalData::class,
        'listen' => 'frame://127.0.0.1:2207',
        'count' => 1,
        'constructor' => [
            'ip' => '127.0.0.1',
            'port' => 2207
        ],
    ],
    'HealthCheck' => [
        'handler' => process\HealthCheck::class,
        'count' => 1,
    ],
349 1 1
1个评论

walkor

  • 暂无评论

mliev

376
积分
0
获赞数
0
粉丝数
2018-12-21 加入
🔝