PHP 怎么可配置的Sidecar

wen PHP项目 1

本文目录导读:

PHP 怎么可配置的Sidecar

  1. 基于配置文件的 Sidecar 管理器
  2. 配置文件示例
  3. 带健康检查和自动重启的增强版
  4. 使用示例
  5. PHP 配置文件示例 (sidecar-config.php)
  6. 使用环境变量配置
  7. 使用 Docker Compose 配合

在 PHP 中实现可配置的 Sidecar 模式,通常有以下几种方案:

基于配置文件的 Sidecar 管理器

<?php
// SidecarConfig.php
class SidecarConfig
{
    private array $config;
    public function __construct(string $configFile)
    {
        $this->config = $this->loadConfig($configFile);
    }
    private function loadConfig(string $file): array
    {
        if (!file_exists($file)) {
            throw new Exception("Config file not found: {$file}");
        }
        $ext = pathinfo($file, PATHINFO_EXTENSION);
        return match ($ext) {
            'yaml', 'yml' => yaml_parse_file($file),
            'json' => json_decode(file_get_contents($file), true),
            'php' => require $file,
            default => throw new Exception("Unsupported config format: {$ext}")
        };
    }
    public function getSidecars(): array
    {
        return $this->config['sidecars'] ?? [];
    }
}
// SidecarRunner.php
class SidecarRunner
{
    private SidecarConfig $config;
    private array $processes = [];
    public function __construct(SidecarConfig $config)
    {
        $this->config = $config;
    }
    public function startAll(): void
    {
        foreach ($this->config->getSidecars() as $name => $sidecar) {
            $this->start($name, $sidecar);
        }
    }
    private function start(string $name, array $sidecar): void
    {
        $command = $sidecar['command'] ?? '';
        $args = $sidecar['args'] ?? [];
        $env = $sidecar['env'] ?? [];
        $workingDir = $sidecar['working_dir'] ?? null;
        $fullCommand = $command . ' ' . implode(' ', $args);
        $process = proc_open(
            $fullCommand,
            [
                0 => ['file', '/dev/null', 'r'],
                1 => ['file', $sidecar['log_file'] ?? '/dev/null', 'a'],
                2 => ['file', $sidecar['error_log'] ?? '/dev/null', 'a']
            ],
            $pipes,
            $workingDir,
            $env
        );
        if (is_resource($process)) {
            $this->processes[$name] = $process;
            echo "Started sidecar: {$name}\n";
        } else {
            echo "Failed to start sidecar: {$name}\n";
        }
    }
    public function stopAll(): void
    {
        foreach ($this->processes as $name => $process) {
            proc_terminate($process);
            proc_close($process);
            echo "Stopped sidecar: {$name}\n";
        }
        $this->processes = [];
    }
    public function getStatus(): array
    {
        $status = [];
        foreach ($this->processes as $name => $process) {
            $status[$name] = proc_get_status($process);
        }
        return $status;
    }
}

配置文件示例

config.yaml

sidecars:
  redis:
    command: "redis-server"
    args: ["--port", "6379", "--daemonize", "no"]
    env:
      REDIS_PASSWORD: "secret"
    working_dir: "/tmp"
    log_file: "/var/log/sidecars/redis.log"
    error_log: "/var/log/sidecars/redis-error.log"
    timeout: 30
    health_check:
      command: "redis-cli ping"
      interval: 5
      retries: 3
  nginx:
    command: "nginx"
    args: ["-g", "daemon off;"]
    env: {}
    working_dir: "/etc/nginx"
    log_file: "/var/log/sidecars/nginx.log"
    error_log: "/var/log/sidecars/nginx-error.log"
    timeout: 30
    health_check:
      command: "curl -f http://localhost:8080/health"
      interval: 10
      retries: 5

带健康检查和自动重启的增强版

<?php
class SidecarSupervisor
{
    private array $config;
    private array $processes = [];
    private array $healthChecks = [];
    public function __construct(array $config)
    {
        $this->config = $config;
    }
    public function start(): void
    {
        foreach ($this->config['sidecars'] as $name => $sidecar) {
            $this->startProcess($name, $sidecar);
            $this->startHealthCheck($name, $sidecar);
        }
    }
    private function startProcess(string $name, array $sidecar): void
    {
        $command = $this->buildCommand($sidecar['command'], $sidecar['args'] ?? []);
        $descriptors = [
            0 => ['pipe', 'r'],
            1 => ['file', $sidecar['log_file'] ?? '/dev/null', 'a'],
            2 => ['file', $sidecar['error_log'] ?? '/dev/null', 'a']
        ];
        $process = proc_open($command, $descriptors, $pipes, $sidecar['working_dir'] ?? null, $sidecar['env'] ?? []);
        if (is_resource($process)) {
            $this->processes[$name] = [
                'process' => $process,
                'pipes' => $pipes,
                'config' => $sidecar
            ];
            fclose($pipes[0]); // Close stdin
        }
    }
    private function startHealthCheck(string $name, array $sidecar): void
    {
        if (!isset($sidecar['health_check'])) {
            return;
        }
        $this->healthChecks[$name] = [
            'command' => $sidecar['health_check']['command'],
            'interval' => $sidecar['health_check']['interval'] ?? 5,
            'retries' => $sidecar['health_check']['retries'] ?? 3,
            'last_check' => 0,
            'failures' => 0
        ];
    }
    public function monitor(): void
    {
        while (true) {
            $this->checkAllHealth();
            $this->restartFailedProcesses();
            sleep(1);
        }
    }
    private function checkAllHealth(): void
    {
        $now = time();
        foreach ($this->healthChecks as $name => &$health) {
            if ($now - $health['last_check'] >= $health['interval']) {
                $health['last_check'] = $now;
                // Execute health check
                $result = shell_exec($health['command']);
                if ($result === false || empty(trim($result))) {
                    $health['failures']++;
                    $this->log("Health check failed for {$name} ({$health['failures']}/{$health['retries']})");
                    if ($health['failures'] >= $health['retries']) {
                        $this->restartProcess($name);
                        $health['failures'] = 0;
                    }
                } else {
                    $health['failures'] = 0;
                }
            }
        }
    }
    private function restartProcess(string $name): void
    {
        $this->log("Restarting sidecar: {$name}");
        if (isset($this->processes[$name])) {
            proc_terminate($this->processes[$name]['process']);
            proc_close($this->processes[$name]['process']);
        }
        $this->startProcess($name, $this->processes[$name]['config']);
    }
    private function buildCommand(string $command, array $args): string
    {
        return $command . ' ' . implode(' ', array_map('escapeshellarg', $args));
    }
    private function log(string $message): void
    {
        echo "[" . date('Y-m-d H:i:s') . "]" . " {$message}\n";
    }
}

使用示例

<?php
// main.php
require_once 'SidecarConfig.php';
require_once 'SidecarRunner.php';
// 方式1:使用配置文件
$config = new SidecarConfig('config.yaml');
$runner = new SidecarRunner($config);
$runner->startAll();
// 方式2:使用 Supervisor(推荐用于生产环境)
$config = require 'sidecar-config.php'; // PHP 配置文件
$supervisor = new SidecarSupervisor($config);
pcntl_signal(SIGTERM, function($signo) use ($supervisor) {
    $supervisor->shutdown();
});
$supervisor->start();
$supervisor->monitor();

PHP 配置文件示例 (sidecar-config.php)

<?php
return [
    'sidecars' => [
        'mysql' => [
            'command' => 'mysqld',
            'args' => ['--port=3306', '--socket=/tmp/mysql.sock'],
            'env' => [
                'MYSQL_ROOT_PASSWORD' => 'root',
                'MYSQL_DATABASE' => 'app_db'
            ],
            'working_dir' => '/var/lib/mysql',
            'log_file' => '/var/log/mysql/error.log',
            'error_log' => '/var/log/mysql/error.log',
            'health_check' => [
                'command' => 'mysqladmin ping -h localhost',
                'interval' => 5,
                'retries' => 3
            ]
        ],
        'redis-cache' => [
            'command' => 'redis-server',
            'args' => ['/etc/redis/redis.conf'],
            'env' => [],
            'working_dir' => null,
            'log_file' => '/var/log/redis/redis.log',
            'error_log' => '/var/log/redis/redis-error.log',
            'health_check' => [
                'command' => 'redis-cli ping',
                'interval' => 10,
                'retries' => 5
            ]
        ],
        'custom-worker' => [
            'command' => 'php',
            'args' => ['/app/worker.php'],
            'env' => [
                'QUEUE_CONNECTION' => 'redis',
                'CACHE_DRIVER' => 'redis'
            ],
            'working_dir' => '/app',
            'log_file' => '/var/log/app/worker.log',
            'error_log' => '/var/log/app/worker-error.log',
            'health_check' => [
                'command' => 'curl -f http://localhost:8080/health',
                'interval' => 15,
                'retries' => 3
            ]
        ]
    ],
    'global_settings' => [
        'log_level' => 'debug',
        'resource_limits' => [
            'memory_limit' => '256M',
            'timeout' => 60,
            'max_processes' => 10
        ],
        'restart_policy' => [
            'max_retries' => 3,
            'delay_seconds' => 5
        ]
    ]
];

使用环境变量配置

<?php
class EnvConfigSidecar
{
    public static function fromEnv(): array
    {
        $config = [];
        // 从环境变量读取 Sidecar 配置
        $sidecars = getenv('SIDECARS') ? json_decode(getenv('SIDECARS'), true) : [];
        foreach ($sidecars as $name => $sidecar) {
            $config['sidecars'][$name] = [
                'command' => $sidecar['command'] ?? getenv("SIDECAR_{$name}_COMMAND"),
                'args' => $sidecar['args'] ?? [],
                'env' => $sidecar['env'] ?? [],
                'log_file' => $sidecar['log_file'] ?? '/dev/null',
                'health_check' => $sidecar['health_check'] ?? null
            ];
        }
        return $config;
    }
}

使用 Docker Compose 配合

# docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    environment:
      - SIDECARS=${SIDECARS}
    volumes:
      - ./config:/app/config
    command: php /app/main.php
  sidecar-proxy:
    image: redis:alpine
    restart: always

这种配置方式非常灵活,支持多种配置格式(YAML、JSON、PHP 数组),可以轻松集成到 CI/CD 流程中,并且方便在不同环境(开发、测试、生产)之间切换配置。

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