erikwang2013 / etcd
PHP etcd v3 client with gRPC + HTTP dual transport. Supports Laravel, Hyperf, ThinkPHP, Webman.
v1.0.2
2026-05-14 08:45 UTC
Requires
- php: >=8.1
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
Suggests
- google/protobuf: For protobuf message support (required by gRPC transport)
- grpc/grpc: For native gRPC transport (better performance, full watch streaming)
README
PHP etcd v3 客户端 — gRPC + HTTP 双模传输,全功能 API,支持 Laravel / Hyperf / ThinkPHP / Webman。
要求
- PHP >= 8.1
- etcd v3.x 服务端
- PSR-18 + PSR-17 HTTP 客户端(HTTP 传输必需,各框架通常自带)
安装
composer require erikwang2013/etcd
快速开始
use Erikwang2013\Etcd\EtcdClient; $etcd = new EtcdClient(['endpoints' => ['127.0.0.1:2379']]); // 写入 $etcd->kv()->put('/app/config', '{"debug":true}'); // 读取 $result = $etcd->kv()->get('/app/config'); print_r($result['kvs'][0]); // ['key' => '/app/config', 'value' => '{"debug":true}', ...] // 找不到时抛异常 $kv = $etcd->kv()->getOrFail('/app/config'); // 前缀扫描 $all = $etcd->kv()->getByPrefix('/app/'); echo "共 {$all['count']} 条\n"; // 删除 $etcd->kv()->delete('/app/config'); $etcd->kv()->deleteByPrefix('/cache/'); // 带租约写入(60 秒后自动删除) $lease = $etcd->lease()->grant(60); $etcd->kv()->put('/session/123', 'active', ['lease' => $lease['ID']]); // 续约 $etcd->lease()->keepAlive($lease['ID']);
配置
$etcd = new EtcdClient([ 'endpoints' => ['192.168.1.10:2379', '192.168.1.11:2379'], // 多节点 'transport' => 'auto', // auto(默认)| http | grpc 'timeout' => 5.0, // 秒 'retry' => 3, // 连接失败重试次数 'auth' => [ // 可选,Basic Auth 'user' => 'root', 'password' => 'secret', ], ]);
环境变量
不传配置时自动读取环境变量:
| 变量 | 默认值 | 说明 |
|---|---|---|
ETCD_ENDPOINTS |
127.0.0.1:2379 |
逗号分隔的多节点地址 |
ETCD_TRANSPORT |
auto |
auto / http / grpc |
ETCD_TIMEOUT |
5.0 |
请求超时(秒) |
ETCD_RETRY |
2 |
连接重试次数 |
ETCD_USER |
— | etcd 用户名 |
ETCD_PASSWORD |
— | etcd 密码 |
API 参考
KV — 键值操作
// 写入 $etcd->kv()->put('key', 'value', [ 'lease' => 12345, // 绑定租约 ID 'prevKv' => true, // 返回写入前的旧值 'ignoreValue' => false, 'ignoreLease' => false, ]); // 读取单键 $etcd->kv()->get('/exact/key'); // 找不到即抛异常 $kv = $etcd->kv()->getOrFail('/exact/key'); // 前缀扫描 $etcd->kv()->getByPrefix('/prefix/'); // 范围查询(完整参数) $etcd->kv()->get('/start', [ 'rangeEnd' => '/startz', // 范围结束 key 'limit' => 100, // 最大返回条数 'revision' => 42, // 快照版本号 'sortOrder' => 'ascend', // none | ascend | descend 'sortTarget' => 'key', // key | version | create | mod | value 'serializable'=> true, // 跳过 Raft 共识(更快,可能过期) 'keysOnly' => true, // 只返回 key,不返回 value 'countOnly' => false, // 只返回计数 ]); // 删除 $etcd->kv()->delete('/key'); $etcd->kv()->deleteByPrefix('/prefix/'); $etcd->kv()->delete('/key', ['prevKv' => true]); // 同时返回被删的值 // 事务(原子 CAS) $etcd->kv()->txn( compare: [ ['result' => 0, 'target' => 3, 'key' => '/counter', 'value' => '100'] ], success: [ ['request_put' => ['key' => '/counter', 'value' => '101']] ], failure: [ ['request_put' => ['key' => '/counter', 'value' => '1']] ] ); // 压缩历史版本(释放存储空间) $etcd->kv()->compact(1000);
比较目标(target)常量: 0=VERSION, 1=CREATE, 2=MOD, 3=VALUE, 4=LEASE
比较结果(result)常量: 0=EQUAL, 1=GREATER, 2=LESS, 3=NOT_EQUAL
Watch — 变更监听
// 监听单个 key(阻塞模式,建议在协程/独立进程中运行) $etcd->watch()->watch('/config/key', function (array $events) { foreach ($events as $event) { // $event: ['type' => 'PUT'|'DELETE', 'kv' => [...], 'prev_kv' => [...]|null] echo "{$event['type']} {$event['kv']['key']} = {$event['kv']['value']}\n"; } }); // 监听前缀下所有 key 的变更 $etcd->watch()->watchPrefix('/config/', $callback, [ 'startRevision' => 100, // 从指定版本开始 'prevKv' => true, // DELETE 事件返回原值 'progressNotify'=> true, // 定期发送空事件(心跳) ]);
断线重连: Watch 连接断开时自动从上一次收到的 revision 续订,不会丢失事件。
Lease — 租约
// 创建租约 $lease = $etcd->lease()->grant(300); // 300 秒 TTL $lease = $etcd->lease()->grant(300, 99999); // 指定租约 ID // 续约(单次) $result = $etcd->lease()->keepAlive($lease['ID']); echo "TTL 剩余: {$result['TTL']} 秒"; // 查看租约状态 $info = $etcd->lease()->timeToLive($lease['ID']); $info = $etcd->lease()->timeToLive($lease['ID'], true); // 含绑定的 key 列表 // 列出所有活跃租约 $leases = $etcd->lease()->list(); // 撤销租约(绑定的所有 key 立即删除) $etcd->lease()->revoke($lease['ID']);
典型场景: 服务注册时创建租约 + 写入 key,定时调用 keepAlive() 心跳续约;服务停止后租约到期自动清理。
Auth — 认证与权限
$auth = $etcd->auth(); // === 用户管理 === $auth->user()->add('alice', 'password123'); // 创建用户 $auth->user()->get('alice'); // 查看用户及其角色 $auth->user()->list(); // 列出所有用户 $auth->user()->changePassword('alice', 'newpass'); // 修改密码 $auth->user()->grantRole('alice', 'admin'); // 授权角色 $auth->user()->revokeRole('alice', 'admin'); // 撤销角色 $auth->user()->delete('alice'); // 删除用户 // === 角色管理 === $auth->role()->add('reader'); // 创建角色 $auth->role()->get('reader'); // 查看角色权限 $auth->role()->list(); // 列出所有角色 // 授予权限(permType: 0=READ, 1=WRITE, 2=READWRITE) $auth->role()->grantPermission('reader', 0, '/data/', "\0"); // 对 /data/ 前缀的读权限 $auth->role()->grantPermission('writer', 2, '/data/', "\0"); // 读写权限 $auth->role()->revokePermission('reader', '/data/', "\0"); // 撤销权限 $auth->role()->delete('reader'); // === 认证开关 === $auth->enable(); // 开启认证 $auth->disable(); // 关闭认证 $status = $auth->status(); // ['enabled' => true, 'authRevision' => 5]
注意: 开启认证后,客户端必须配置 auth.user 和 auth.password 才能继续操作。
Cluster — 集群管理
// 查看集群成员 $members = $etcd->cluster()->memberList(); // 添加成员 $etcd->cluster()->memberAdd(['http://node3:2380']); // 添加 Voting 成员 $etcd->cluster()->memberAdd(['http://node4:2380'], true); // 添加 Learner 成员 // 修改成员 peer URL $etcd->cluster()->memberUpdate(123456, ['http://newnode:2380']); // Learner 提升为 Voter $etcd->cluster()->memberPromote(789012); // 移除成员 $etcd->cluster()->memberRemove(345678);
Maintenance — 运维
// 查看节点状态 $status = $etcd->maintenance()->status(); // ['version' => '3.5.0', 'dbSize' => 24576, 'leader' => 123, 'raftIndex' => 1000, ...] // 告警管理 $alarms = $etcd->maintenance()->alarm(); // 查看告警 $etcd->maintenance()->alarm(action: 2, alarm: 1); // 清除 NOSPACE 告警 // 碎片整理(回收存储空间) $etcd->maintenance()->defragment(); // KV 哈希校验 $hash = $etcd->maintenance()->hash(); // 获取快照(返回二进制数据,写入文件即可) $snapshot = $etcd->maintenance()->snapshot(); file_put_contents('/backup/etcd-snapshot.db', $snapshot);
传输模式
| 模式 | 状态 | 依赖 | 适用场景 |
|---|---|---|---|
| HTTP | 可用 | PSR-18 + PSR-17 | 零扩展依赖,即刻可用 |
| gRPC | 骨架 | ext-grpc + grpc/grpc + google/protobuf | 高性能、原生流式 |
| auto | 默认 | 自动检测 | 有 gRPC 则用 gRPC,否则 HTTP |
auto 模式检测逻辑:
extension_loaded('grpc')— C 扩展已加载?class_exists('Grpc\BaseStub')—grpc/grpccomposer 包已安装?
两者都满足才走 gRPC,否则回退 HTTP。
手动配置 PSR-18 HTTP 客户端
use Erikwang2013\Etcd\Transport\HttpTransport; use GuzzleHttp\Client; use GuzzleHttp\Psr7\HttpFactory; $transport = new HttpTransport(['127.0.0.1:2379'], ['timeout' => 3.0]); $transport->setHttpClient( new Client(['timeout' => 3]), new HttpFactory(), new HttpFactory() );
框架集成
Laravel
安装即用。composer.json 的 extra.laravel 自动发现 ServiceProvider 和 Facade。
// Facade 方式 use Etcd; Etcd::kv()->put('/foo', 'bar'); $val = Etcd::kv()->get('/foo'); // 依赖注入方式 use Erikwang2013\Etcd\EtcdClient; class MyService { public function __construct(private EtcdClient $etcd) {} public function work(): void { $this->etcd->kv()->put('/key', 'value'); } }
发布配置文件:
php artisan vendor:publish --tag=etcd-config
# → config/etcd.php
.env 配置:
ETCD_ENDPOINTS=10.0.0.1:2379,10.0.0.2:2379 ETCD_USER=root ETCD_PASSWORD=secret
Hyperf
安装即用。Hyperf 自动发现 ConfigProvider。
use Erikwang2013\Etcd\EtcdClient; use Hyperf\Di\Annotation\Inject; class MyService { #[Inject] private EtcdClient $etcd; public function work(): void { $this->etcd->kv()->put('/key', 'value'); } } // 或者直接 make $etcd = make(EtcdClient::class);
发布配置:
php bin/hyperf.php vendor:publish erikwang2013/etcd
# → config/autoload/etcd.php
ThinkPHP
- 安装后,在
app/service.php中注册:
return [ Erikwang2013\Etcd\Adapter\ThinkPHP\Service::class, ];
- 创建
config/etcd.php配置文件。
使用:
// Facade 方式 use think\facade\Etcd; Etcd::kv()->put('/key', 'value'); // 容器方式 app('etcd')->kv()->get('/key');
Webman
安装即用,无需额外配置。
use Erikwang2013\Etcd\EtcdClient; $etcd = EtcdClient::instance(); $etcd->kv()->put('/key', 'value');
如需自定义配置,编辑 plugin/erikwang2013/etcd/config/etcd.php。
异常处理
use Erikwang2013\Etcd\Exception\{ EtcdException, ConnectionException, AuthException, KeyNotFoundException, }; try { $etcd->kv()->put('/key', 'value'); } catch (ConnectionException $e) { // etcd 节点无法连接(网络故障、宕机) } catch (AuthException $e) { // 认证失败(用户名密码错误) } catch (KeyNotFoundException $e) { // getOrFail() 时 key 不存在 } catch (EtcdException $e) { // 其他 etcd 服务端错误 }
目录结构
src/
├── EtcdClient.php # 顶层门面
├── Transport/ # 传输层
│ ├── TransportInterface.php # 传输抽象接口
│ ├── TransportSelector.php # 自动选择逻辑
│ ├── HttpTransport.php # HTTP JSON 传输(完整可用)
│ └── GrpcTransport.php # gRPC 传输(骨架)
├── Kv/KvClient.php # KV 键值操作
├── Watch/WatchClient.php # Watch 变更监听
├── Lease/LeaseClient.php # Lease 租约管理
├── Auth/ # Auth 认证授权
│ ├── AuthClient.php # 开关/状态
│ ├── UserClient.php # 用户 CRUD
│ └── RoleClient.php # 角色 CRUD + 权限
├── Cluster/ClusterClient.php # Cluster 集群管理
├── Maintenance/ # Maintenance 运维操作
├── Exception/ # 异常层次
├── Protobuf/ # 消息桩(60+ 类)
└── Adapter/ # 框架适配器
├── Laravel/ # ServiceProvider + Facade
├── Hyperf/ # ConfigProvider
├── ThinkPHP/ # Service + Facade
└── Webman/ # Plugin
License
MIT — Copyright (c) 2026 erik erik@erik.xyz