参考博客:https://www.cnblogs.com/sevenPP/p/8149890.html
注册的时候,需要提供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
}
提供监听程序的路径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)
}
}