fox/etcd/etcdImpl.go
2025-06-17 18:24:33 +08:00

183 lines
4.3 KiB
Go

package etcd
import (
"context"
"fmt"
"github.com/fox/fox/ksync"
"github.com/fox/fox/log"
"sync"
"time"
clientv3 "go.etcd.io/etcd/client/v3"
)
const (
DefaultDialTimeout = 3 * time.Second // 默认拨号超时时间
DefaultLeaseTTL = int64(10) // 默认租约TTL为60秒
DefaultKeepAliveInterval = 5 * time.Second // 默认续租间隔为30秒
KeepAliveFailCount = 1
)
type etcdRegistryImpl struct {
cli *clientv3.Client
leaseID clientv3.LeaseID
meNodeKey string // 自身节点key
meNodeValue string // 自身节点json
cancelFunc context.CancelFunc
rootKey string
nodeOperator iNodeOperator
}
// 创建服务注册中心
func newServiceRegistryImpl(endpoints []string, rootKey, username, password string, operator iNodeOperator, meKey, meValue string) (*etcdRegistryImpl, error) {
cli, err := clientv3.New(clientv3.Config{
Endpoints: endpoints,
DialTimeout: DefaultDialTimeout,
Username: username,
Password: password,
})
if err != nil {
return nil, err
}
impl := &etcdRegistryImpl{
cli: cli,
rootKey: rootKey,
nodeOperator: operator,
meNodeKey: meKey,
meNodeValue: meValue,
}
if err = impl.register(); err != nil {
return nil, err
}
impl.watchServices()
return impl, nil
}
// 注册服务 RegisterService
func (sr *etcdRegistryImpl) register() error {
// 生成唯一服务key
// /services/serviceType/serviceName
log.DebugF("register key:%s value:%v to etcd", sr.meNodeKey, sr.meNodeValue)
// 创建租约
ctx, cancel := context.WithCancel(context.Background())
sr.cancelFunc = cancel
// 申请租约
resp, err := sr.cli.Grant(ctx, DefaultLeaseTTL)
if err != nil {
return err
}
sr.leaseID = resp.ID
// 序列化服务信息
// data, err := json.Marshal(node)
// if err != nil {
// return err
// }
// 写入ETCD
_, err = sr.cli.Put(ctx, sr.meNodeKey, sr.meNodeValue, clientv3.WithLease(sr.leaseID))
if err != nil {
return err
}
// 启动自动续租
ksync.GoSafe(func() { sr.keepAlive(ctx) }, nil)
if err = sr.discoverServices(); err == nil {
// ss := sr.GetService()
// for _, s := range ss {
// log.Debug(fmt.Sprintf("Discovered services: %+v\n", s))
// }
}
return nil
}
// 自动续租逻辑
func (sr *etcdRegistryImpl) keepAlive(ctx context.Context) {
retryCount := 0
ticker := time.NewTicker(DefaultKeepAliveInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
_, err := sr.cli.KeepAliveOnce(ctx, sr.leaseID)
if err != nil {
if retryCount > KeepAliveFailCount {
log.DebugF("KeepAlive failed after %d retries: %v", KeepAliveFailCount, err)
sr.UnregisterService()
if err = sr.register(); err != nil {
log.ErrorF("Reconnect failed: %v", err)
} else {
retryCount = 0
}
return
}
retryCount++
// log.DebugF("KeepAlive error (retry %d/%d): %v", retryCount, KeepAliveFailCount, err)
} else {
retryCount = 0
}
case <-ctx.Done():
return
}
}
}
// 反注册服务
func (sr *etcdRegistryImpl) UnregisterService() {
if sr.cancelFunc != nil {
sr.cancelFunc()
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
if _, err := sr.cli.Delete(ctx, sr.meNodeKey); err != nil {
log.ErrorF("unregister:%v failed:%v from etcd", sr.meNodeKey, err)
} else {
log.DebugF("unregister:%v from etcd", sr.meNodeKey)
}
}
// 服务发现
func (sr *etcdRegistryImpl) discoverServices() error {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
srv := fmt.Sprintf("/%s/", sr.rootKey)
resp, err := sr.cli.Get(ctx, srv, clientv3.WithPrefix())
if err != nil {
return err
}
//log.Debug(fmt.Sprintf("discoverServices srv:%s", srv))
newNodes := &sync.Map{}
for _, kv := range resp.Kvs {
sr.nodeOperator.saveNode(newNodes, kv.Value)
}
sr.nodeOperator.replace(newNodes)
return nil
}
// 监控服务变化
func (sr *etcdRegistryImpl) watchServices() {
watchKey := fmt.Sprintf("/%s/", sr.rootKey)
ksync.GoSafe(func() {
rch := sr.cli.Watch(context.Background(), watchKey, clientv3.WithPrefix())
for resp := range rch {
for range resp.Events {
// 当有变化时获取最新服务列表
if err := sr.discoverServices(); err != nil {
log.Error(err.Error())
}
}
}
}, nil)
}