etcd v3服务注册与发现

参考博客:https://www.cnblogs.com/sevenPP/p/8149890.html

ETCD服务注册分为以下几个步骤:
  • 服务向ETCD注册自己的信息,即在ETCD的某个目录下创建Key以及填写Value;
  • 服务可能异常退出,所以需要维护一个TTL(V3使用lease实现),当服务异常退出时,监听程序可以监听到;
  • 监听程序,可以根据自己的需求监听服务的事件;(添加、修改、删除)
注册服务

注册的时候,需要提供key,以及注册的信息info
start启动后,执行keeplive(),维护心跳,挂掉时revoke()
同时监听stop chan,相当于unregistered

package discovery

import (
   "context"
   "encoding/json"
   "fmt"
   "go.etcd.io/etcd/clientv3"
   "time"
)

type EtcdServiceInfo struct {
   Info string
}

type EtcdService struct {
   Cluster string  // 集群名称
   Name   string // 服务名称
   Info   EtcdServiceInfo // 节点信息
   stop   chan error
   leaseid clientv3.LeaseID
   client  *clientv3.Client
}

// 注册ETCD服务
func NewService (cluster, name string, info EtcdServiceInfo, hosts[] string) (*EtcdService, error){
   cli, err := clientv3.New(clientv3.Config{
      Endpoints: hosts,
      DialTimeout: 2 * time.Second,
   })

   if nil != err {
      fmt.Println(err.Error())
      return nil, err
   }
   // 返回服务对象
   return &EtcdService{
      Cluster:cluster,
      Name:  name,
      Info:  info,
      stop:  make (chan error),
      client: cli,
   }, err

}

// 启动
func (s *EtcdService) Start() error {
   // 获取心跳的通道
   ch, err := s.keepLive()
   if nil != err {
      fmt.Println(err.Error())
      return  err
   }
   go func() {
      // 死循环
      for {
         select {
         case <- s.stop:
            s.revoke()
            return
         case <- s.client.Ctx().Done():
            fmt.Println("server closed")
            return
         case /*ka*/_, ok := <-ch:
            if !ok {
               fmt.Println("keep live channel closed")
               s.revoke()
               return
            } else {
               //fmt.Printf("recv reply frem service:%s, ttl:%d\n", s.Name, ka.TTL)
            }
         }
      }
   }()
   return nil
}

// 保持心跳
func (s *EtcdService) keepLive() (<-chan *clientv3.LeaseKeepAliveResponse, error) {
   key := s.Cluster + "/" + s.Name
   value, _ := json.Marshal(s.Info)

   // minimum lease TTL is 5-second
   resp, err := s.client.Grant(context.TODO(), 5)
   if nil != err {
      fmt.Println(err.Error())
      return nil, err
   }

   fmt.Printf("Register, Key:%s, Value:%s\n", key, string(value))

   _, err = s.client.Put(context.TODO(), key, string(value), clientv3.WithLease(resp.ID))
   if nil != err {
      fmt.Println(err.Error())
      return nil, err
   }
   s.leaseid = resp.ID

   return s.client.KeepAlive(context.TODO(), resp.ID)
}

// 设置节点信息
func (s *EtcdService) SetValue(info EtcdServiceInfo) {
   s.Info = info
   tmp, _ := json.Marshal(info)
   key := s.Cluster + "/" + s.Name
   if _, err := s.client.Put(context.TODO(), key, string(tmp), clientv3.WithLease(s.leaseid)); nil != err {
      fmt.Printf("etcd set value failed! key:%s;value:%s", key, info)
   }

}

// 停止
func (s *EtcdService) Stop() {
   s.stop <- nil
}

// 撤销
func (s *EtcdService) revoke() error {
   _, err :=  s.client.Revoke(context.TODO(), s.leaseid)
   if nil != err {
      fmt.Println(err.Error())
   }
   fmt.Printf("service:%s stop\n", s.Name)
   return nil
}
监听程序Master

提供监听程序的路径PATH,启动监听程序的时候,当PUT和DELETE事件发发生的时候,监听程序都可以监听到;
但是,有的服务在监听程序启动之前就存在了,所以在在监听之前记得获取已经存在的注册的服务;

package discovery

import (
   "context"
   "encoding/json"
   "fmt"
   "go.etcd.io/etcd/clientv3"
   "math/rand"
   "time"
)

type EtcdMaster struct {
   Cluster string // 集群
   Path   string // 路径
   Nodes  map[string]*EtcdNode
   Client  *clientv3.Client
}

// Etcd注册的节点,一个节点代表一个client
type EtcdNode struct {
   State  bool
   Cluster string              // 集群
   Key       string          // key
   Info   EtcdServiceInfo       // 节点信息
}

func NewMaster(host[] string, cluster string, watchPath string) (*EtcdMaster, error) {
   cli, err := clientv3.New(clientv3.Config{
      Endpoints:host,
      DialTimeout:time.Second,
   })

   if nil != err {
      fmt.Println(err.Error())
      return nil, err
   }

   master := &EtcdMaster{
      Cluster:cluster,
      Path:watchPath,
      Nodes:make(map[string]*EtcdNode),
      Client: cli,
      }

   // 监听观察节点
   go master.WatchNodes()

   return master, err
}

func NewEtcdNode(ev *clientv3.Event) *EtcdServiceInfo {
   info := &EtcdServiceInfo{}
   err := json.Unmarshal([]byte(ev.Kv.Value), info)
   if nil != err {
      fmt.Println(err.Error())
   }
   return info
}

// 监听观察节点
func (m* EtcdMaster) WatchNodes() {
   // 查看之前存在的节点
   resp, err := m.Client.Get(context.Background(), m.Cluster + "/" + m.Path, clientv3.WithPrefix())
   if nil != err {
      fmt.Println(err.Error())
   } else {
      for _, ev := range resp.Kvs {
         fmt.Printf("add dir:%q, value:%q\n", ev.Key, ev.Value)
         info := &EtcdServiceInfo{}
         json.Unmarshal([]byte(ev.Value), info)
         m.addNode(string(ev.Key), info)
      }
   }

   rch := m.Client.Watch(context.Background(), m.Cluster + "/" + m.Path, clientv3.WithPrefix(), clientv3.WithPrevKV())
   for wresp := range rch {
      for _, ev := range wresp.Events {
         switch ev.Type {
         case clientv3.EventTypePut:
            fmt.Printf("[%s] dir:%q, value:%q\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
            info := NewEtcdNode(ev)
            m.addNode(string(ev.Kv.Key), info)
         case clientv3.EventTypeDelete:
            fmt.Printf("[%s] dir:%q, value:%q\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
            k :=  ev.Kv.Key
            if len( ev.Kv.Key) > (len(m.Cluster) + 1) {
               k =  ev.Kv.Key[len(m.Cluster) + 1:]
            }
            delete(m.Nodes, string(k))
         default:
            fmt.Printf("[%s] dir:%q, value:%q\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
         }
      }
   }
}

// 添加节点
func (m *EtcdMaster) addNode(key string, info* EtcdServiceInfo) {
   k := key
   if len(key) > (len(m.Cluster) + 1) {
      k = key[len(m.Cluster) + 1:]
   }

   node := &EtcdNode{
      State:true,
      Cluster:m.Cluster,
      Key:k,
      Info:*info,
   }

   m.Nodes[node.Key] = node
}

// 获取该集群下所有的节点
func (m* EtcdMaster) GetAllNodes() []EtcdNode{
   var temp []EtcdNode
   for _, v := range m.Nodes {
      if nil != v {
         temp = append(temp, *v)
      }
   }
   return temp
}

func (m* EtcdMaster) GetNodeRandom() (EtcdNode, bool){
   count := len(m.Nodes)
   // 该集群不存在节点时,直接返回false
   if 0 == count {
      return EtcdNode{}, false
   }
   idx := rand.Intn(count)
   for _, v := range m.Nodes {
      if idx == 0 {
         return *v, true
      }
      idx = idx - 1
   }
   return EtcdNode{}, false
}
同时注册、监听多个服务

工程里面有时候需要监听和注册的服务不只是一个,可能同时监听多个服务和注册多个服务,这就需要进一步的封装以便能够注册、监听多个服务。

package discovery

import (
   "fmt"
   "strings"
)

type EtcdDis struct {
   Cluster string
   // 注册;key:服务名称
   MapRegister map[string]*EtcdService
   // 监听相关的服务
   MapWatch   map[string]*EtcdMaster
}

//----------------------------------------------------------------------------------
// 注册相关的函数
//----------------------------------------------------------------------------------
// 注册
func (d *EtcdDis) Register(host []string, service, key string, value EtcdServiceInfo) {
   name := service + "/" + key
   var s *EtcdService
   var e error
   if s, e = NewService(d.Cluster,name, value, host); nil != e {
      fmt.Printf("Register service:%s error:", service, e.Error())
      return
   }

   if nil == d.MapRegister {
      d.MapRegister = make(map[string]*EtcdService)
   }

   if _, ok := d.MapRegister[name]; ok {
      fmt.Printf("Service:%s Have Registered!", name)
      return
   }

   d.MapRegister[name] = s
   // w维持心跳
   s.Start()
}

// 更新
func (d *EtcdDis) UpdateInfo(service, key string, info EtcdServiceInfo) {
   name := service + "/" + key
   if sri, ok := d.MapRegister[name]; ok {
      if nil != sri {
         sri.SetValue(info)
      }
   }
}

//----------------------------------------------------------------------------------
// 监听相关的函数
//----------------------------------------------------------------------------------
func (d *EtcdDis) Watch(host []string, service string) {
   var w *EtcdMaster
   var e error
   if w, e = NewMaster(host, d.Cluster, service); nil != e {
      fmt.Printf("Watch Service:%s Failed!Error:%s", service, e.Error())
      return
   }

   if nil == d.MapWatch {
      d.MapWatch = make(map[string]*EtcdMaster)
   }

   if _, ok := d.MapWatch[service]; ok {
      fmt.Printf("Service:%s Have Watch!\n", service)
      return
   }

   d.MapWatch[service] = w
}

// 获取服务的节点信息-随机获取
func (d *EtcdDis) GetServiceInfoRandom(service string) (EtcdNode, bool) {
   if nil == d.MapWatch {
      fmt.Println("MapWatch is nil")
      return EtcdNode{}, false
   }

   if v, ok := d.MapWatch[service]; ok {
      if nil != v {
         if n, ok1 := v.GetNodeRandom(); ok1 {
            return n, true
         }
      }
   } else {
      fmt.Printf("Service:%s Not Be Watched!\n", service)
   }

   return EtcdNode{}, false
}

// 获取服务的节点信息-全部获取
func (d *EtcdDis) GetServiceInfoAllNode(service string)([]EtcdNode, bool) {
   if nil == d.MapWatch {
      fmt.Println("MapWatch is nil")
      return []EtcdNode{}, false
   }

   if v, ok := d.MapWatch[service]; ok {
      if nil != v {
         return v.GetAllNodes(),true
      }
   } else {
      fmt.Printf("Service:%s Not Be Watched!\n", service)
   }

   return []EtcdNode{}, false
}

//----------------------------------------------------------------------------------
// 工具相关函数
//----------------------------------------------------------------------------------
// 拆分service name、key;返回bool true表示成功;false表示失败
func SplitServiceNameKey(dir string) (string, string, bool) {
   if idx := strings.Index(dir,"/"); -1 != idx {
      name := dir[ : idx]
      key := dir[idx+1 :]
      return name, key, true
   }
   return "", "", false
}
使用方法-注册服务
func main() {
   info := dis.EtcdServiceInfo{Info: "123453434324"}
   e := dis.EtcdDis{Cluster:"tc"}
   e.Register([]string{"http://127.0.0.1:2379"}, "s-test", "192.168.21.35",info)
   e.Register([]string{"http://127.0.0.1:2379"}, "s-xxxx", "127.0.0.1",info)
   time.Sleep(time.Second * 10)
   info.Info = "xxxxxxxxxxx"
   e.UpdateInfo("s-test", "192.168.21.35",info)
   time.Sleep(time.Second * 10)
}
使用方法-监听服务
func main() {
   m := dis.EtcdDis{Cluster:"tc"}
   m.Watch([]string{
      "http://127.0.0.1:2379",
   }, "s-test")
   m.Watch([]string{
      "http://127.0.0.1:2379",
   }, "s-xxxx")
   for {
      if e, ok := m.GetServiceInfoAllNode("s-test"); ok {
         tmp, _ := json.Marshal(e)
         fmt.Println(string(tmp))
         if len(e) > 0 {
            name, key, _ := dis.SplitServiceNameKey(e[0].Key)
            fmt.Printf("name:%s, key:%s\n", name, key)
         }
      }

      if e, ok := m.GetServiceInfoAllNode("s-xxxx"); ok {
         tmp, _ := json.Marshal(e)
         fmt.Println(string(tmp))
      }

      //fmt.Printf("nodes num = %d\n", len(m.Nodes))
      fmt.Printf("nodes num = %d\n", 0)
      time.Sleep(time.Second * 2)
   }
}
运行截图

在这里插入图片描述在这里插入图片描述

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值