服务发现与 etcd
etcd 在 Vistack 中承担三件事:服务注册与发现、领导选举、元数据协调。
1. 服务注册(transcoder / auth)
transcoder 与 auth 启动时向 etcd 注册自身地址,并通过租约保活:
go
// internal/transcoder/registry/etcd.go
const (
leaseTTL = 10 // 租约 TTL 10s
keepAliveInterval = 3 * time.Second // 每 3s 保活一次
)
// key: /vistack/transcoders/{id} value: {addr}
func Register(ctx context.Context, client *clientv3.Client, prefix, id, addr string) (*Registrar, error) {
lease, _ := client.Grant(ctx, leaseTTL)
client.Put(ctx, key, addr, clientv3.WithLease(lease.ID))
// 后台 keepAlive:失败时重新 Grant + Put
}注册 key:
| 服务 | 前缀 | 值 |
|---|---|---|
| transcoder | /vistack/transcoders | gRPC 地址(如 transcoder:50051) |
| auth | /vistack/auth | gRPC 地址(如 host:50052) |
保活策略:KeepAliveOnce 每 3s 续约,失败则重新 Grant 租约并重新 Put。实例宕机后租约在 10s 内过期,自动从注册表摘除。
2. 服务发现(worker → transcoder)
worker 通过自定义 gRPC resolver(scheme = etcd)动态发现 transcoder:
go
// internal/discovery/etcd.go
type EtcdBuilder struct { client *clientv3.Client; prefix string }
func (b *EtcdBuilder) Scheme() string { return "etcd" }
func (b *EtcdBuilder) Build(target, cc, opts) (resolver.Resolver, error) {
r := &etcdResolver{...}
r.update() // 先拉取当前注册表
go r.watch() // 再 Watch 变更
return r, nil
}update():Get(prefix, WithPrefix())拉取全部实例地址,UpdateState推给 gRPC;watch():Watch 前缀,任何注册/注销事件触发update(),实现动态上下线;- 配合 gRPC
round_robin负载均衡,新扩容的 transcoder 自动被 worker 使用,宕机的自动摘除。
3. 领导选举(单例任务)
retry dispatcher 与 watchdog 必须在全局唯一运行,否则多 worker 会重复投递重试消息。通过 etcd 领导选举解决:
go
// internal/core/leader/leader.go
key := "/vistack/leaders/worker-singleton" // DefaultLeaderKey
e := concurrency.NewElection(sess, key)
e.Campaign(ctx, instanceID) // 竞选 leader
onElected(leadCtx) // 只有 leader 执行单例循环
<-e.Done() // 失去领导权 → 重新竞选工作机制:
- 同一 key 下任意时刻只有一个 leader,其余实例阻塞等待;
- leader 租约过期(崩溃)→ 其他实例自动接任,无主窗口 ≈ 租约 TTL(10s);
- worker 启动时若 etcd 不可用,降级为直接运行(仅适合单实例,多实例有重复风险,日志告警)。
4. etcd 在 compose / K8s 中的位置
- compose:单节点
quay.io/coreos/etcd:v3.5.16,端口 2379; - K8s:
deploy/k8s/etcd.yaml,带 Service 供集群内访问。
演进方向(roadmap)
| 方向 | 说明 |
|---|---|
| 注册值升级 JSON 元数据 | {addr, role, version, region, capacity},支持容量感知调度 |
| 配置中心 | 动态配置写入 etcd + Watch 热更新(限流阈值、功能开关) |
| 分布式锁 | concurrency.NewMutex 用于低频全局操作(缓存重建、ref_count 修复) |
| Snowflake node_id 分配 | etcd CAS 事务分配,消除哈希碰撞 |
| etcd 集群高可用 | 3~5 节点 + TLS + 认证 |
