基于 Etcd v3 HTTP API 的 PHP 客户端,提供服务注册、发现、KV 存储等功能

| 特性 |
说明 |
| 服务注册 |
自动租约续期 |
| 服务发现 |
随机负载均衡 |
| KV 存储 |
完整的 CRUD 操作 |
| 心跳保活 |
定时刷新租约 |
| 健康检查 |
Etcd 集群状态监控 |
composer require guzzlehttp/guzzle
或直接引入:
require_once __DIR__ . '/extra/Etcd.php';
use extra\Etcd;
$etcd = new Etcd();
// 注册服务
$etcd->register('my-service', '127.0.0.1', 8080);
// 发现服务
$services = $etcd->discover('my-service');
// 注销服务
$etcd->deregister('my-service', '127.0.0.1', 8080);
$etcd = new Etcd(
'127.0.0.1', // host - Etcd 服务器地址
2379, // port - Etcd 端口
'/services/', // prefix - 服务注册 key 前缀
30 // ttl - 租约 TTL(秒)
);
构造函数参数支持环境变量覆盖
$etcd = new Etcd();
// 或通过环境变量: ETCD_HOST, ETCD_PORT, ETCD_PREFIX, ETCD_TTL
$etcd = new Etcd();
// 基础注册
$etcd->register('user-api', '127.0.0.1', 8080);
// 带元数据
$etcd->register('user-api', '127.0.0.1', 8080, [
'version' => '1.0.0',
'region' => 'cn-beijing'
]);
// 带回调
$etcd->registerWithCallback(
'user-api', '127.0.0.1', 8080, [],
fn() => print "OK\n",
fn($e) => print $e->getMessage() . "\n"
);
// 发现指定服务
$services = $etcd->discover('user-api');
/*
返回:
[
['host' => '127.0.0.1', 'port' => 8080, 'metadata' => [...], 'registered_at' => '...'],
['host' => '127.0.0.1', 'port' => 8081, ...]
]
*/
// 发现所有服务
$allServices = $etcd->discoverAll();
/*
返回:
[
'user-api' => [...],
'order-api' => [...]
]
*/
$etcd->deregister('user-api', '127.0.0.1', 8080);
$services = [
['name' => 'user-api', 'host' => '127.0.0.1', 'port' => 8080],
['name' => 'order-api', 'host' => '127.0.0.1', 'port' => 8081],
];
// 每 25 秒重新注册
$etcd->heartbeat($services, 25, fn(string $name) => print "{$name} OK\n");
// 刷新单个租约
$etcd->refreshLease($leaseId);
// 刷新所有服务租约(每 60 秒)
$etcd->refreshAllServicesLease($services, 60);
// 存储
$etcd->kvPut('/my/key', 'hello world');
$etcd->kvPut('/config/app', json_encode(['debug' => true]));
$etcd->kvPut('/temp/key', 'value', ['lease' => $leaseId]);
// 获取
$value = $etcd->get('/my/key');
$value = $etcd->get('/my/key', ['revision' => 10]);
// 删除
$etcd->del('/my/key');
$etcd->del('/prefix/', ['range_end' => '/prefix0']);
// 获取所有键
$keys = $etcd->getAllKeys();
// 前缀匹配
$keys = $etcd->getKeysWithPrefix('/services/');
// 压缩存储
$etcd->compaction(100);
// 字符串路径
$result = $etcd->call('user-api', '/api/user/1');
// 带参数
$result = $etcd->call('user-api', '/api/user', ['id' => 1]);
// Request 对象
$result = $etcd->call('user-api', $request, ['extra' => 'param']);
/*
成功返回:
{
"success": true,
"data": {
"service": "user-api",
"target": "127.0.0.1:8080",
"response": {...}
}
}
失败返回:
{
"success": false,
"error": "错误信息",
"target": "127.0.0.1:8080"
}
*/
// 从 Request 对象解析
$parsed = Etcd::parseRequest($request);
// 返回 ['path' => '/xxx', 'data' => [...]]
// 从数组解析
$parsed = Etcd::parseRequest(['path' => '/api/user', 'data' => ['id' => 1]]);
if ($etcd->getServiceHealth()) {
echo "Etcd 服务正常";
}
<?php
namespace app\controller;
use support\Request;
use extra\Etcd;
class IndexController
{
private static ?Etcd $etcd = null;
private static function getEtcd(): Etcd
{
self::$etcd ??= new Etcd();
return self::$etcd;
}
public function index(Request $request)
{
$etcd = self::getEtcd();
$name = $request->get('service');
if ($name) {
return json(['code' => 0, 'data' => ['instances' => $etcd->discover($name)]]);
}
return json(['code' => 0, 'data' => $etcd->discoverAll()]);
}
public function call(Request $request)
{
$etcd = self::getEtcd();
$result = $etcd->call($request->get('service', 'api'), $request);
return $result['success']
? json(['code' => 0, 'data' => $result['data']])
: json(['code' => 404, 'msg' => $result['error']]);
}
}
<?php
namespace app\process;
use extra\Etcd;
class EtcdRegister
{
private static ?Etcd $etcd = null;
public static function init(array $config): void
{
$cfg = $config['etcd'] ?? [];
self::$etcd = new Etcd(
$cfg['host'] ?? '127.0.0.1',
$cfg['port'] ?? 2379,
$cfg['prefix'] ?? '/services/',
$cfg['ttl'] ?? 30
);
foreach ($config['services'] ?? [] as $s) {
self::$etcd->register($s['name'], $s['host'], $s['port']);
}
self::$etcd->heartbeat($config['services'] ?? []);
}
public static function getEtcd(): Etcd
{
return self::$etcd;
}
}
| 方法 |
参数 |
返回 |
说明 |
__construct() |
string $host = '127.0.0.1'
int $port = 2379
string $prefix = '/services/'
int $ttl = 30 |
void |
创建 Etcd 实例 |
| 方法 |
参数 |
返回 |
说明 |
register() |
string $serviceName
string $host
int $port
array $metadata = [] |
bool |
注册服务到 Etcd |
registerWithCallback() |
string $serviceName
string $host
int $port
array $metadata = []
?callable $onSuccess = null
?callable $onError = null |
bool |
带回调注册服务 |
deregister() |
string $serviceName
string $host
int $port |
bool |
注销服务 |
| 方法 |
参数 |
返回 |
说明 |
discover() |
string $serviceName |
array |
发现指定服务的所有实例 |
discoverAll() |
- |
array |
发现所有已注册服务,按服务名分组 |
call() |
string $serviceName
$request = ''
array $extraParams = [] |
array |
调用远程服务,随机负载均衡 |
| 方法 |
参数 |
返回 |
说明 |
heartbeat() |
array $services
int $interval = 25
?callable $onBeat = null |
void |
启动定时心跳 |
refreshLease() |
int $leaseId |
bool |
刷新单个租约 |
refreshAllServicesLease() |
array $services
int $interval = 60 |
void |
刷新所有服务租约 |
| 方法 |
参数 |
返回 |
说明 |
kvPut() |
string $key
string $value
array $options = [] |
bool |
存储键值对
options 支持: lease |
get() |
string $key
array $options = [] |
?string |
获取值
options 支持: revision |
getAllKeys() |
- |
array |
获取所有键 |
getKeysWithPrefix() |
string $prefix |
array |
获取前缀匹配的键 |
del() |
string $key
array $options = [] |
bool |
删除键
options 支持: range_end |
compaction() |
int $revision |
bool |
压缩存储,清理历史版本 |
| 方法 |
参数 |
返回 |
说明 |
parseRequest() |
$request |
array |
解析请求参数 返回 ['path' => string, 'data' => array] |
getServiceHealth() |
- |
bool |
检查 Etcd 健康状态 |
MIT License