add etcd naming

pull/217/head
libi.chai 5 years ago
commit e19cc05abe
  1. 2
      go.mod
  2. 12
      go.sum
  3. 288
      pkg/naming/etcd/etcd.go
  4. 98
      pkg/naming/etcd/etcd_test.go

@ -6,6 +6,7 @@ require (
cloud.google.com/go v0.41.0 // indirect
github.com/BurntSushi/toml v0.3.1
github.com/aristanetworks/goarista v0.0.0-20190409234242-46f4bc7b73ef // indirect
github.com/coreos/etcd v3.3.13+incompatible
github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d // indirect
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect
@ -51,7 +52,6 @@ require (
gopkg.in/go-playground/validator.v9 v9.26.0
gopkg.in/yaml.v2 v2.2.2
honnef.co/go/tools v0.0.0-20190605142022-0a11fc526260 // indirect
)
replace (

@ -2,11 +2,16 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/aristanetworks/goarista v0.0.0-20190409234242-46f4bc7b73ef h1:ajsnF5qTstiBlP+V/mgh91zZfoKP477KfSmRoCoyYGU=
github.com/aristanetworks/goarista v0.0.0-20190409234242-46f4bc7b73ef/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d h1:SwD98825d6bdB+pEuTxWOXiSjBrHdOl/UVp75eI7JT8=
github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
@ -23,10 +28,13 @@ github.com/dgryski/go-farm v0.0.0-20190323231341-8198c7b169ec/go.mod h1:SqUrOPUn
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/etcd-io/etcd v3.3.10+incompatible/go.mod h1:cdZ77EstHBwVtD6iTgzgvogwcjo9m4iOqoijouPJ4bs=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
@ -44,6 +52,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/net v0.0.0-20190311183353-d8887717615a h1:4V+LPwzBFLRg7XSXZw133Jsur1mTVMY73hIv/FTdrbg=
github.com/golang/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -75,9 +84,11 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
@ -134,6 +145,7 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=

@ -0,0 +1,288 @@
package etcd
import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/bilibili/kratos/pkg/log"
"github.com/bilibili/kratos/pkg/naming"
"github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/mvcc/mvccpb"
)
const (
//Prefix is a etcd globe key prefix
Prefix = "kratos_etcd3"
RegisterTTL = 30
)
var (
_once sync.Once
_builder naming.Builder
//ErrDuplication is a register duplication err
ErrDuplication = errors.New("etcd: instance duplicate registration")
)
// Builder return default etcd resolver builder.
func Builder(c *clientv3.Config) naming.Builder {
_once.Do(func() {
_builder,_ = New(c)
})
return _builder
}
// Build register resolver into default etcd.
func Build(c *clientv3.Config, id string) naming.Resolver {
return Builder(c).Build(id)
}
//EtcdBuilder is a etcd clientv3 EtcdBuilder
type EtcdBuilder struct {
cli *clientv3.Client
ctx context.Context
cancelFunc context.CancelFunc
mutex sync.RWMutex
apps map[string]*appInfo
registry map[string]struct{}
}
type appInfo struct {
resolver map[*Resolve]struct{}
ins atomic.Value
e *EtcdBuilder
once sync.Once
}
// Resolve etch resolver.
type Resolve struct {
id string
event chan struct{}
e *EtcdBuilder
}
//New is new a etcdbuilder
func New(c *clientv3.Config) (e *EtcdBuilder,err error) {
cli, err := clientv3.New(*c)
if err != nil {
return nil,err
}
ctx, cancel := context.WithCancel(context.Background())
e = &EtcdBuilder{
cli: cli,
ctx: ctx,
cancelFunc: cancel,
apps: map[string]*appInfo{},
registry: map[string]struct{}{},
}
return
}
// Build disovery resovler builder.
func (e *EtcdBuilder) Build(appid string) naming.Resolver {
r := &Resolve{
id: appid,
e: e,
event: make(chan struct{}, 1),
}
e.mutex.Lock()
app, ok := e.apps[appid]
if !ok {
app = &appInfo{
resolver: make(map[*Resolve]struct{}),
e: e,
}
e.apps[appid] = app
}
app.resolver[r] = struct{}{}
e.mutex.Unlock()
if ok {
select {
case r.event <- struct{}{}:
default:
}
}
app.once.Do(func() {
go app.watch(appid)
log.Info("etcd: AddWatch(%s) already watch(%v)", appid, ok)
})
return r
}
// Scheme return etcd's scheme
func (e *EtcdBuilder) Scheme() string {
return "etcd"
}
//Register is register instance
func (e *EtcdBuilder) Register(ctx context.Context, ins *naming.Instance) (cancelFunc context.CancelFunc, err error) {
e.mutex.Lock()
if _, ok := e.registry[ins.AppID]; ok {
err = ErrDuplication
} else {
e.registry[ins.AppID] = struct{}{}
}
e.mutex.Unlock()
if err != nil {
return
}
ctx, cancel := context.WithCancel(e.ctx)
if err = e.register(ctx, ins); err != nil {
e.mutex.Lock()
delete(e.registry, ins.AppID)
e.mutex.Unlock()
cancel()
return
}
ch := make(chan struct{}, 1)
cancelFunc = context.CancelFunc(func() {
cancel()
<-ch
})
go func() {
//提前2秒续约 避免续约操作缓慢时租约过期
ticker := time.NewTicker((RegisterTTL - 2) * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
_ = e.register(ctx, ins)
case <-ctx.Done():
_ = e.unregister(ins)
ch <- struct{}{}
return
}
}
}()
return
}
//注册和续约公用一个操作
func (e *EtcdBuilder) register(ctx context.Context, ins *naming.Instance) (err error) {
prefix := e.getprefix(ins)
val, _ := json.Marshal(ins)
ttlResp, err := e.cli.Grant(context.TODO(), int64(RegisterTTL))
if err != nil {
log.Error("etcd: register client.Grant(%v) error(%v)", RegisterTTL, err)
return err
}
_, err = e.cli.Put(ctx, prefix, string(val), clientv3.WithLease(ttlResp.ID))
if err != nil {
log.Error("etcd: register client.Put(%v) appid(%s) hostname(%s) error(%v)",
prefix, ins.AppID, ins.Hostname, err)
return err
}
return nil
}
func (e *EtcdBuilder) unregister(ins *naming.Instance) (err error) {
prefix := e.getprefix(ins)
if _, err = e.cli.Delete(context.TODO(), prefix); err != nil {
log.Error("etcd: unregister client.Delete(%v) appid(%s) hostname(%s) error(%v)",
prefix, ins.AppID, ins.Hostname, err)
}
log.Info("etcd: unregister client.Delete(%v) appid(%s) hostname(%s) success",
prefix, ins.AppID, ins.Hostname)
return
}
func (e *EtcdBuilder) getprefix(ins *naming.Instance) string {
return fmt.Sprintf("/%s/%s/%s", Prefix, ins.AppID, ins.Hostname)
}
// Close stop all running process including etcdfetch and register
func (e *EtcdBuilder) Close() error {
e.cancelFunc()
return nil
}
func (a *appInfo) watch(appID string) {
_ = a.fetchstore(appID)
prefix := fmt.Sprintf("/%s/%s/", Prefix, appID)
rch := a.e.cli.Watch(a.e.ctx, prefix, clientv3.WithPrefix())
for wresp := range rch {
for _, ev := range wresp.Events {
if ev.Type == mvccpb.PUT || ev.Type == mvccpb.DELETE {
_ = a.fetchstore(appID)
}
}
}
}
func (a *appInfo) fetchstore(appID string) (err error) {
prefix := fmt.Sprintf("/%s/%s/", Prefix, appID)
resp, err := a.e.cli.Get(a.e.ctx, prefix, clientv3.WithPrefix())
if err != nil {
log.Error("etcd: fetch client.Get(%s) error(%+v)", prefix, err)
return err
}
ins, err := a.paserIns(resp)
if err != nil {
return err
}
a.store(ins)
return nil
}
func (a *appInfo) store(ins *naming.InstancesInfo) {
a.ins.Store(ins)
a.e.mutex.RLock()
for rs := range a.resolver {
select {
case rs.event <- struct{}{}:
default:
}
}
a.e.mutex.RUnlock()
}
func (a *appInfo) paserIns(resp *clientv3.GetResponse) (ins *naming.InstancesInfo, err error) {
ins = &naming.InstancesInfo{
Instances: make(map[string][]*naming.Instance, 0),
}
for _, ev := range resp.Kvs {
in := new(naming.Instance)
err := json.Unmarshal(ev.Value, in)
if err != nil {
return nil, err
}
ins.Instances[in.Zone] = append(ins.Instances[in.Zone], in)
}
return ins, nil
}
// Watch watch instance.
func (r *Resolve) Watch() <-chan struct{} {
return r.event
}
// Fetch fetch resolver instance.
func (r *Resolve) Fetch(ctx context.Context) (ins *naming.InstancesInfo, ok bool) {
r.e.mutex.RLock()
app, ok := r.e.apps[r.id]
r.e.mutex.RUnlock()
if ok {
ins, ok = app.ins.Load().(*naming.InstancesInfo)
return
}
return
}
// Close close resolver.
func (r *Resolve) Close() error {
r.e.mutex.Lock()
if app, ok := r.e.apps[r.id]; ok && len(app.resolver) != 0 {
delete(app.resolver, r)
}
r.e.mutex.Unlock()
return nil
}

@ -0,0 +1,98 @@
package etcd
import (
"context"
"fmt"
"github.com/bilibili/kratos/pkg/naming"
"github.com/coreos/etcd/clientv3"
"testing"
"time"
)
func TestNew(t *testing.T) {
config := &clientv3.Config{
Endpoints: []string{"127.0.0.1:2379"},
DialTimeout:time.Second*3,
}
builder,err := New(config)
if(err != nil){
fmt.Println("etcd 连接失败")
return
}
app1 := builder.Build("app1")
go func() {
fmt.Printf("Watch \n")
for {
select {
case <-app1.Watch():
fmt.Printf("app1 节点发生变化 \n")
}
}
}()
time.Sleep(time.Second)
app1Cancel, err := builder.Register(context.Background(), &naming.Instance{
AppID: "app1",
Hostname: "h1",
Zone: "z1",
})
fmt.Println(2222)
app2Cancel, err := builder.Register(context.Background(), &naming.Instance{
AppID: "app2",
Hostname: "h5",
Zone: "z3",
})
if err != nil {
fmt.Println(err)
}
fmt.Println(1111)
app2 := builder.Build("app2")
fmt.Println(1111)
go func() {
fmt.Println("节点列表")
for {
fmt.Printf("app1: ")
r1, _ := app1.Fetch(context.Background())
if r1 != nil {
for z, ins := range r1.Instances {
fmt.Printf("zone: %s :", z)
for _, in := range ins {
fmt.Printf("app: %s host %s \n", in.AppID, in.Hostname)
}
}
}
fmt.Printf("app2: ")
r2, _ := app2.Fetch(context.Background())
if r2 != nil {
for z, ins := range r2.Instances {
fmt.Printf("zone: %s :", z)
for _, in := range ins {
fmt.Printf("app: %s host %s \n", in.AppID, in.Hostname)
}
}
}
time.Sleep(time.Second)
}
}()
fmt.Println(1111)
time.Sleep(time.Second * 5)
fmt.Println("取消app1")
app1Cancel()
time.Sleep(time.Second * 10)
fmt.Println("取消app2")
app2Cancel()
fmt.Println(1111)
time.Sleep(30*time.Second)
}
Loading…
Cancel
Save