本文目录导读:

- 基于配置文件的 Sidecar 管理器
- 配置文件示例
- 带健康检查和自动重启的增强版
- 使用示例
- PHP 配置文件示例 (sidecar-config.php)
- 使用环境变量配置
- 使用 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 流程中,并且方便在不同环境(开发、测试、生产)之间切换配置。