這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
本文整理一下思路,編寫樣本(golang),以便加深etcd的理解
大致如下,監聽程式為master,服務為service
1 service 啟動時向etcd註冊自己的資訊,即註冊到services/ 這個目錄
2 service 可能異常推出,需要維護一個TTL(V3 使用 lease實現),類似於心跳,掛掉了,master可以監聽到
3 master監聽 services/ 目錄下的所有服務,根據不同action(V3有put/delete),進行處理
service註冊
提供 key(service name), value(serviceInfo)進行registered
start 啟動後,執行keeplive(), 維護心跳,掛掉時revoke()
同時監聽 stop chan, 相當於unregistered
package discoveryimport ( "github.com/coreos/etcd/clientv3" "context" "time" "log" "encoding/json" "errors")//the detail of service type ServiceInfo struct{ IP string}type Service struct { Name string Info ServiceInfo stop chan error leaseid clientv3.LeaseID client *clientv3.Client}func NewService(name string, info ServiceInfo,endpoints []string) (*Service, error) { cli, err := clientv3.New(clientv3.Config{ Endpoints: endpoints, DialTimeout: 2*time.Second, }) if err != nil { log.Fatal(err) return nil, err } return &Service { Name: name, Info: info, stop: make (chan error), client: cli, },err}func (s *Service) Start() error { ch, err := s.keepAlive() if err != nil { log.Fatal(err) return err } for { select { case err := <- s.stop: s.revoke() return err case <- s.client.Ctx().Done(): return errors.New("server closed") case ka, ok := <-ch: if !ok { log.Println("keep alive channel closed") s.revoke() return nil } else { log.Printf("Recv reply from service: %s, ttl:%d", s.Name, ka.TTL) } } }}func (s *Service) Stop() { s.stop <- nil }func (s *Service) keepAlive() (<-chan *clientv3.LeaseKeepAliveResponse, error){ info := &s.Info key := "services/" + s.Name value, _ := json.Marshal(info) // minimum lease TTL is 5-second resp, err := s.client.Grant(context.TODO(), 5) if err != nil { log.Fatal(err) return nil, err } _, err = s.client.Put(context.TODO(), key, string(value), clientv3.WithLease(resp.ID)) if err != nil { log.Fatal(err) return nil, err } s.leaseid = resp.ID return s.client.KeepAlive(context.TODO(), resp.ID)}func (s *Service) revoke() error { _, err := s.client.Revoke(context.TODO(), s.leaseid) if err != nil { log.Fatal(err) } log.Printf("servide:%s stop\n", s.Name) return err}
監聽程式Master
提供監聽路徑path,啟動master,當put時加入 map, delete時 從map去掉
package discoveryimport ( "github.com/coreos/etcd/clientv3" "context" "log" "time" "encoding/json" "fmt")type Master struct { Path string Nodes map[string] *Node Client *clientv3.Client}//node is a client type Node struct { State bool Key string Info ServiceInfo}func NewMaster(endpoints []string, watchPath string) (*Master,error) { cli, err := clientv3.New(clientv3.Config{ Endpoints: endpoints, DialTimeout: time.Second, }) if err != nil { log.Fatal(err) return nil,err } master := &Master { Path: watchPath, Nodes: make(map[string]*Node), Client: cli, } go master.WatchNodes() return master,err}func (m *Master) AddNode(key string,info *ServiceInfo) { node := &Node{ State: true, Key: key, Info: *info, } m.Nodes[node.Key] = node}func GetServiceInfo(ev *clientv3.Event) *ServiceInfo { info := &ServiceInfo{} err := json.Unmarshal([]byte(ev.Kv.Value), info) if err != nil { log.Println(err) } return info}func (m *Master) WatchNodes() { rch := m.Client.Watch(context.Background(), m.Path, clientv3.WithPrefix()) for wresp := range rch { for _, ev := range wresp.Events { switch ev.Type { case clientv3.EventTypePut: fmt.Printf("[%s] %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) info := GetServiceInfo(ev) m.AddNode(string(ev.Kv.Key),info) case clientv3.EventTypeDelete: fmt.Printf("[%s] %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) delete(m.Nodes, string(ev.Kv.Key)) } } }}
service使用樣本,20s後斷開
package mainimport ("fmt"dis "discovery""log""time")func main() {serviceName := "s-test"serviceInfo := dis.ServiceInfo{IP:"192.168.1.26"}s, err := dis.NewService(serviceName, serviceInfo,[]string {"http://192.168.1.17:2379", "http://192.168.1.17:2479", "http://192.168.1.17:2579",})if err != nil {log.Fatal(err)}fmt.Printf("name:%s, ip:%s\n", s.Name, s.Info.IP)go func() {time.Sleep(time.Second*20)s.Stop()}()s.Start()}
master使用樣本
package mainimport ("log""time""fmt"dis "discovery")func main() {m, err := dis.NewMaster([]string{"http://192.168.1.17:2379","http://192.168.1.17:2479","http://192.168.1.17:2579",}, "services/")if err != nil {log.Fatal(err)}for {for k, v := range m.Nodes {fmt.Printf("node:%s, ip=%s\n", k, v.Info.IP)}fmt.Printf("nodes num = %d\n",len(m.Nodes))time.Sleep(time.Second * 5)}}
執行結果(需要提前搭建 etcd伺服器,可以到github下載,文末提供簡單啟動指令碼)
go run master
go run service
etcd伺服器啟動指令碼(執行環境 CentOS6.5)
#!/bin/shwork_path=$(dirname $0)cd ./${work_path}#echo $(pwd)#echo `date`case $1 in1)echo -e "[1]start first server\n"./etcd --name cd0 --initial-advertise-peer-urls http://127.0.0.1:2380 \ --listen-peer-urls http://127.0.0.1:2380 \ --listen-client-urls http://192.168.1.17:2379,http://127.0.0.1:2379 \ --advertise-client-urls http://192.168.1.17:2379,http://127.0.0.1:2379 \ --initial-cluster-token etcd-cluster-1 \ --initial-cluster cd0=http://127.0.0.1:2380,cd1=http://127.0.0.1:2480,cd2=http://127.0.0.1:2580 \ --initial-cluster-state new ;;2)echo -e "[2]start second server\n"./etcd --name cd1 --initial-advertise-peer-urls http://127.0.0.1:2480 \ --listen-peer-urls http://127.0.0.1:2480 \ --listen-client-urls http://192.168.1.17:2479,http://127.0.0.1:2479 \ --advertise-client-urls http://192.168.1.17:2479,http://127.0.0.1:2479 \ --initial-cluster-token etcd-cluster-1 \ --initial-cluster cd0=http://127.0.0.1:2380,cd1=http://127.0.0.1:2480,cd2=http://127.0.0.1:2580 \ --initial-cluster-state new ;;3)echo -e "[3]start third server\n"./etcd --name cd2 --initial-advertise-peer-urls http://127.0.0.1:2580 \ --listen-peer-urls http://127.0.0.1:2580 \ --listen-client-urls http://192.168.1.17:2579,http://127.0.0.1:2579 \ --advertise-client-urls http://192.168.1.17:2579,http://127.0.0.1:2579 \ --initial-cluster-token etcd-cluster-1 \ --initial-cluster cd0=http://127.0.0.1:2380,cd1=http://127.0.0.1:2480,cd2=http://127.0.0.1:2580 \ --initial-cluster-state new ;;*)echo "error paramater";;esac
View Code
完整代碼,請移步 git@github.com:moonlong/etcd-discovery.git
歡迎斧正
完!