PHP etcd v3 client with gRPC + HTTP dual transport. Supports Laravel, Hyperf, ThinkPHP, Webman.

Maintainers

Package info

github.com/erikwang2013/etcd

Homepage

pkg:composer/erikwang2013/etcd

Statistics

Installs: 10

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.0.2 2026-05-14 08:45 UTC

This package is auto-updated.

Last update: 2026-05-14 08:48:12 UTC


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.userauth.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 模式检测逻辑:

  1. extension_loaded('grpc') — C 扩展已加载?
  2. class_exists('Grpc\BaseStub')grpc/grpc composer 包已安装?

两者都满足才走 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

  1. 安装后,在 app/service.php 中注册:
return [
    Erikwang2013\Etcd\Adapter\ThinkPHP\Service::class,
];
  1. 创建 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