本文目录导读:

我来详细介绍如何在PHP项目中集成Consul和Etcd服务发现与配置管理。
基本概念
Consul
- 服务发现
- 健康检查
- KV存储
- 多数据中心
Etcd
- 分布式KV存储
- 服务发现
- 配置管理
- 分布式锁
Consul集成
1 安装Consul客户端库
composer require sensiolabs/consul-php-sdk # 或 composer require dcarbone/phpconsul-api
2 基础连接配置
<?php
namespace App\Service;
use SensioLabs\Consul\ConsulResponse;
use SensioLabs\Consul\ServiceFactory;
class ConsulService
{
private $consul;
public function __construct()
{
$serviceFactory = new ServiceFactory([
'base_uri' => 'http://127.0.0.1:8500'
]);
$this->consul = $serviceFactory;
}
// 服务注册
public function registerService(array $config)
{
$agent = $this->consul->get('agent');
$serviceConfig = [
'ID' => $config['id'] ?? uniqid(),
'Name' => $config['name'],
'Tags' => $config['tags'] ?? [],
'Port' => $config['port'],
'Address' => $config['address'] ?? '127.0.0.1',
'Check' => [
'HTTP' => sprintf('http://%s:%d/health',
$config['address'] ?? '127.0.0.1',
$config['port']
),
'Interval' => $config['check_interval'] ?? '10s',
'Timeout' => $config['check_timeout'] ?? '5s',
'DeregisterCriticalServiceAfter' => '30s'
]
];
return $agent->registerService($serviceConfig);
}
// 发现服务
public function discoverService(string $serviceName): array
{
$catalog = $this->consul->get('catalog');
$services = $catalog->service($serviceName)->json();
$healthyServices = [];
foreach ($services as $service) {
if ($this->checkServiceHealth($service['ServiceID'])) {
$healthyServices[] = [
'id' => $service['ServiceID'],
'address' => $service['ServiceAddress'],
'port' => $service['ServicePort'],
'tags' => $service['ServiceTags'] ?? []
];
}
}
return $healthyServices;
}
// 检查服务健康状态
private function checkServiceHealth(string $serviceId): bool
{
$health = $this->consul->get('health');
$checks = $health->checks($serviceId)->json();
foreach ($checks as $check) {
if ($check['Status'] !== 'passing') {
return false;
}
}
return true;
}
// KV存储操作
public function getConfig(string $key): ?string
{
$kv = $this->consul->get('kv');
$response = $kv->get($key);
if ($response->getStatusCode() === 200) {
$data = $response->json();
return base64_decode($data[0]['Value']);
}
return null;
}
public function setConfig(string $key, string $value): bool
{
$kv = $this->consul->get('kv');
$response = $kv->put($key, $value);
return $response->getStatusCode() === 200;
}
// 监听配置变化
public function watchConfig(string $key, callable $callback)
{
$lastIndex = 0;
while (true) {
$kv = $this->consul->get('kv');
$response = $kv->get($key, [
'index' => $lastIndex,
'wait' => '5s'
]);
$headers = $response->getHeaders();
$newIndex = (int)$headers['X-Consul-Index'][0];
if ($newIndex > $lastIndex) {
$data = $response->json();
$value = base64_decode($data[0]['Value']);
$callback($value);
$lastIndex = $newIndex;
}
}
}
}
3 健康检查端点
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
class HealthController
{
#[Route('/health', name: 'health_check')]
public function check(): JsonResponse
{
$healthStatus = [
'status' => 'UP',
'timestamp' => time(),
'checks' => [
'database' => $this->checkDatabase(),
'cache' => $this->checkCache(),
'disk' => $this->checkDiskSpace()
]
];
$isHealthy = !in_array(false, array_column($healthStatus['checks'], 'healthy'));
return new JsonResponse(
$healthStatus,
$isHealthy ? 200 : 503
);
}
private function checkDatabase(): array
{
try {
// 数据库检查逻辑
return [
'healthy' => true,
'message' => 'Database connection OK'
];
} catch (\Exception $e) {
return [
'healthy' => false,
'message' => $e->getMessage()
];
}
}
private function checkCache(): array
{
try {
// 缓存检查逻辑
return [
'healthy' => true,
'message' => 'Cache connection OK'
];
} catch (\Exception $e) {
return [
'healthy' => false,
'message' => $e->getMessage()
];
}
}
private function checkDiskSpace(): array
{
$freeSpace = disk_free_space('/');
$totalSpace = disk_total_space('/');
$usedPercent = (1 - $freeSpace / $totalSpace) * 100;
return [
'healthy' => $usedPercent < 90,
'message' => sprintf('Disk usage: %.2f%%', $usedPercent),
'freeSpace' => $freeSpace,
'totalSpace' => $totalSpace
];
}
}
Etcd集成
1 安装Etcd客户端
composer require linkorb/etcd-php # 或 composer require etcd-php/etcd
2 Etcd服务类
<?php
namespace App\Service;
use Etcd\Client;
use Etcd\Response;
class EtcdService
{
private $client;
private $config;
public function __construct(array $config = [])
{
$this->config = array_merge([
'host' => '127.0.0.1',
'port' => 2379,
'version' => 'v3',
'timeout' => 5
], $config);
$this->client = new Client(
$this->config['host'],
$this->config['port'],
$this->config['version']
);
}
// 服务注册
public function registerService(array $serviceConfig)
{
$key = sprintf('/services/%s/%s',
$serviceConfig['name'],
$serviceConfig['id'] ?? uniqid()
);
$value = json_encode([
'name' => $serviceConfig['name'],
'address' => $serviceConfig['address'],
'port' => $serviceConfig['port'],
'tags' => $serviceConfig['tags'] ?? [],
'registered_at' => time(),
'ttl' => $serviceConfig['ttl'] ?? 30
]);
// 注册服务(带TTL)
$this->client->put($key, $value, [
'lease' => $this->createLease($serviceConfig['ttl'] ?? 30)
]);
return $key;
}
// 创建租约
private function createLease(int $ttl): string
{
$response = $this->client->grant($ttl);
return $response['ID'];
}
// 续约
public function keepAlive(string $leaseId): bool
{
try {
$this->client->keepAlive($leaseId);
return true;
} catch (\Exception $e) {
return false;
}
}
// 发现服务
public function discoverService(string $serviceName): array
{
$key = sprintf('/services/%s', $serviceName);
$response = $this->client->get($key, ['prefix' => true]);
$services = [];
foreach ($response['kvs'] ?? [] as $kv) {
$service = json_decode($kv['value'], true);
if ($this->isServiceHealthy($service)) {
$services[] = $service;
}
}
return $services;
}
// 检查服务健康状态
private function isServiceHealthy(array $service): bool
{
if (!isset($service['address'], $service['port'])) {
return false;
}
$url = sprintf('http://%s:%d/health',
$service['address'],
$service['port']
);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_CONNECTTIMEOUT => 3
]);
curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode === 200;
}
// KV存储操作
public function put(string $key, $value): bool
{
try {
$this->client->put($key, is_array($value) ? json_encode($value) : $value);
return true;
} catch (\Exception $e) {
return false;
}
}
public function get(string $key, bool $prefix = false): ?array
{
try {
$options = $prefix ? ['prefix' => true] : [];
$response = $this->client->get($key, $options);
if (!empty($response['kvs'])) {
$result = [];
foreach ($response['kvs'] as $kv) {
$result[] = [
'key' => $kv['key'],
'value' => json_decode($kv['value'], true) ?? $kv['value'],
'create_revision' => $kv['create_revision'],
'mod_revision' => $kv['mod_revision']
];
}
return $result;
}
return null;
} catch (\Exception $e) {
return null;
}
}
// 删除
public function delete(string $key, bool $prefix = false): bool
{
try {
$options = $prefix ? ['prefix' => true] : [];
$this->client->del($key, $options);
return true;
} catch (\Exception $e) {
return false;
}
}
// 监听变化
public function watch(string $key, callable $callback, bool $prefix = false)
{
$options = [
'prefix' => $prefix,
'progress_notify' => true
];
$watch = $this->client->watch($key, $options);
foreach ($watch as $response) {
foreach ($response['events'] ?? [] as $event) {
$type = $event['type'];
$kv = $event['kv'];
$callback([
'type' => $type,
'key' => $kv['key'],
'value' => json_decode($kv['value'], true) ?? $kv['value'],
'create_revision' => $kv['create_revision'],
'mod_revision' => $kv['mod_revision']
]);
}
}
}
// 分布式锁
public function acquireLock(string $lockKey, int $ttl = 10): bool
{
$lockPath = sprintf('/locks/%s', $lockKey);
$lockValue = uniqid('lock_', true);
try {
$this->client->put($lockPath, $lockValue, [
'lease' => $this->createLease($ttl),
'prev_kv' => false
]);
return true;
} catch (\Exception $e) {
return false;
}
}
public function releaseLock(string $lockKey): bool
{
$lockPath = sprintf('/locks/%s', $lockKey);
return $this->delete($lockPath);
}
}
配置管理类
<?php
namespace App\Config;
class DynamicConfig
{
private $consul;
private $etcd;
private $cache = [];
private $backend;
public function __construct($consul, $etcd, string $backend = 'consul')
{
$this->consul = $consul;
$this->etcd = $etcd;
$this->backend = $backend;
}
// 获取配置
public function get(string $key, $default = null)
{
// 优先从缓存获取
if (isset($this->cache[$key])) {
return $this->cache[$key];
}
$value = $this->backend === 'consul'
? $this->consul->getConfig($key)
: $this->etcd->get($key);
if ($value !== null) {
$this->cache[$key] = $value;
return $value;
}
return $default;
}
// 设置配置
public function set(string $key, $value): bool
{
$result = $this->backend === 'consul'
? $this->consul->setConfig($key, $value)
: $this->etcd->put($key, $value);
if ($result) {
$this->cache[$key] = $value;
}
return $result;
}
// 批量获取配置
public function getMultiple(array $keys): array
{
$result = [];
foreach ($keys as $key) {
$result[$key] = $this->get($key);
}
return $result;
}
// 配置热更新
public function watchConfig(string $key, callable $callback)
{
$this->backend === 'consul'
? $this->consul->watchConfig($key, $callback)
: $this->etcd->watch($key, $callback);
}
}
服务注册器
<?php
namespace App\Service;
class ServiceRegistrar
{
private $consulService;
private $etcdService;
private $config;
private $leaseId;
public function __construct($consulService, $etcdService, array $config)
{
$this->consulService = $consulService;
$this->etcdService = $etcdService;
$this->config = $config;
}
// 注册服务
public function register(): bool
{
$serviceConfig = [
'name' => $this->config['service_name'],
'id' => $this->config['service_id'] ?? null,
'address' => $this->config['service_address'],
'port' => $this->config['service_port'],
'tags' => $this->config['service_tags'] ?? [],
'check_interval' => $this->config['check_interval'] ?? '10s',
'health_endpoint' => $this->config['health_endpoint'] ?? '/health'
];
try {
// 注册到Consul
$this->consulService->registerService($serviceConfig);
// 注册到Etcd
$this->etcdService->registerService($serviceConfig);
return true;
} catch (\Exception $e) {
throw $e;
}
}
// 注销服务
public function deregister(): bool
{
try {
// 注销Consul服务
$this->consulService->get('agent')
->deregisterService($this->config['service_id']);
// 删除Etcd服务键
$key = sprintf('/services/%s/%s',
$this->config['service_name'],
$this->config['service_id']
);
$this->etcdService->delete($key);
return true;
} catch (\Exception $e) {
return false;
}
}
// 心跳维护
public function heartbeat()
{
while (true) {
try {
$this->consulService->get('agent')
->passCheck('service:' . $this->config['service_id']);
if ($this->leaseId) {
$this->etcdService->keepAlive($this->leaseId);
}
sleep($this->config['heartbeat_interval'] ?? 10);
} catch (\Exception $e) {
// 重新注册
$this->register();
}
}
}
}
使用示例
<?php
// 1. 服务注册
$registrar = new ServiceRegistrar($consulService, $etcdService, [
'service_name' => 'user-service',
'service_id' => 'user-service-1',
'service_address' => '192.168.1.100',
'service_port' => 8080,
'service_tags' => ['api', 'v1'],
'check_interval' => '10s',
'health_endpoint' => '/health'
]);
$registrar->register();
// 2. 服务发现
$consulService = new ConsulService();
$services = $consulService->discoverService('user-service');
foreach ($services as $service) {
echo sprintf(
"Service: %s at %s:%d\n",
$service['id'],
$service['address'],
$service['port']
);
}
// 3. 配置管理
$config = new DynamicConfig($consulService, $etcdService, 'consul');
// 读取配置
$dbConfig = $config->getMultiple([
'config/database/host',
'config/database/port',
'config/database/name',
'config/database/user',
'config/database/password'
]);
// 写入配置
$config->set('config/feature/new-feature', json_encode([
'enabled' => true,
'percentage' => 50,
'start_date' => '2024-01-01'
]));
// 4. 配置监听
$config->watchConfig('config/feature/new-feature', function($value) {
$featureConfig = json_decode($value, true);
if ($featureConfig['enabled']) {
// 启用新功能
}
});
// 5. 分布式锁使用
$lockKey = 'user-service-1-migration';
if ($etcdService->acquireLock($lockKey, 30)) {
try {
// 执行需要同步的操作
$this->performMigration();
} finally {
$etcdService->releaseLock($lockKey);
}
}
Docker配置
# docker-compose.yml
version: '3.8'
services:
consul:
image: consul:1.15
ports:
- "8500:8500"
- "8600:8600/udp"
command: agent -server -bootstrap-expect=1 -ui -client=0.0.0.0
volumes:
- consul-data:/consul/data
networks:
- service-mesh
etcd:
image: bitnami/etcd:3.5
ports:
- "2379:2379"
- "2380:2380"
environment:
- ALLOW_NONE_AUTHENTICATION=yes
- ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379
volumes:
- etcd-data:/bitnami/etcd
networks:
- service-mesh
app:
build: .
ports:
- "8080:8080"
environment:
- CONSUL_HOST=consul
- CONSUL_PORT=8500
- ETCD_HOST=etcd
- ETCD_PORT=2379
depends_on:
- consul
- etcd
networks:
- service-mesh
volumes:
consul-data:
etcd-data:
networks:
service-mesh:
driver: bridge
这个集成方案提供了完整的服务发现、配置管理和健康检查功能,可以根据实际需求选择使用Consul或Etcd,或者同时使用两者作为备份方案。