Create & Init Project...
This commit is contained in:
30
library/naming/BUILD
Normal file
30
library/naming/BUILD
Normal file
@ -0,0 +1,30 @@
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//library/naming/discovery:all-srcs",
|
||||
"//library/naming/livezk:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["naming.go"],
|
||||
importpath = "go-common/library/naming",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
13
library/naming/README.md
Normal file
13
library/naming/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
#### naming
|
||||
|
||||
##### 项目简介
|
||||
|
||||
服务发现、服务注册相关的SDK集合
|
||||
|
||||
##### 编译环境
|
||||
|
||||
- **请只用 Golang v1.8.x 以上版本编译执行**
|
||||
|
||||
##### 依赖包
|
||||
|
||||
|
69
library/naming/discovery/BUILD
Normal file
69
library/naming/discovery/BUILD
Normal file
@ -0,0 +1,69 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_test",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["discovery_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
rundir = ".",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//library/conf/env:go_default_library",
|
||||
"//library/ecode:go_default_library",
|
||||
"//library/exp/feature:go_default_library",
|
||||
"//library/naming:go_default_library",
|
||||
"//library/net/http/blademaster:go_default_library",
|
||||
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"discovery.go",
|
||||
"shuffle.go",
|
||||
],
|
||||
importpath = "go-common/library/naming/discovery",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//library/conf/env:go_default_library",
|
||||
"//library/ecode:go_default_library",
|
||||
"//library/exp/feature:go_default_library",
|
||||
"//library/log:go_default_library",
|
||||
"//library/naming:go_default_library",
|
||||
"//library/net/http/blademaster:go_default_library",
|
||||
"//library/net/netutil:go_default_library",
|
||||
"//library/net/netutil/breaker:go_default_library",
|
||||
"//library/time:go_default_library",
|
||||
"//library/xstr:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_xtest",
|
||||
srcs = ["example_test.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//library/naming:go_default_library",
|
||||
"//library/naming/discovery:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
32
library/naming/discovery/CHANGELOG.md
Normal file
32
library/naming/discovery/CHANGELOG.md
Normal file
@ -0,0 +1,32 @@
|
||||
### discovery
|
||||
|
||||
##### Version 1.2.3
|
||||
> 1.register add context
|
||||
|
||||
##### Version 1.2.2
|
||||
> 1.添加discovery域名
|
||||
|
||||
##### Version 1.2.1
|
||||
> 1.diff自发现节点
|
||||
|
||||
##### Version 1.2.0
|
||||
> 1.添加自发现feature
|
||||
|
||||
##### Version 1.1.4
|
||||
> 1.Watch之后会立即取消Polls
|
||||
|
||||
##### Version 1.1.3
|
||||
> 1.Fetch返回一个Map
|
||||
|
||||
##### Version 1.1.2
|
||||
> 1.初始化的时候新增同步阻塞的nodes更新
|
||||
|
||||
##### Version 1.1.1
|
||||
> 1.fix atomic store inconsistency
|
||||
|
||||
##### Version 1.1.0
|
||||
> 1.添加unwatch 事件
|
||||
|
||||
##### Version 1.0.0
|
||||
|
||||
1. 服务发现客户端基本功能完成
|
11
library/naming/discovery/CONTRIBUTORS.md
Normal file
11
library/naming/discovery/CONTRIBUTORS.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Owner
|
||||
lintanghui
|
||||
caoguoliang
|
||||
|
||||
|
||||
# Author
|
||||
lintanghui
|
||||
caoguoliang
|
||||
|
||||
# Reviewer
|
||||
maojian
|
9
library/naming/discovery/OWNERS
Normal file
9
library/naming/discovery/OWNERS
Normal file
@ -0,0 +1,9 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- caoguoliang
|
||||
- lintanghui
|
||||
reviewers:
|
||||
- caoguoliang
|
||||
- lintanghui
|
||||
- maojian
|
13
library/naming/discovery/README.md
Normal file
13
library/naming/discovery/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
#### discovery
|
||||
|
||||
##### 项目简介
|
||||
|
||||
discovery的客户端SDK,包括了服务发现和服务注册功能
|
||||
|
||||
##### 编译环境
|
||||
|
||||
- **请只用 Golang v1.8.x 以上版本编译执行**
|
||||
|
||||
##### 依赖包
|
||||
|
||||
> 1.公共包go-common
|
721
library/naming/discovery/discovery.go
Normal file
721
library/naming/discovery/discovery.go
Normal file
@ -0,0 +1,721 @@
|
||||
package discovery
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go-common/library/conf/env"
|
||||
"go-common/library/ecode"
|
||||
"go-common/library/exp/feature"
|
||||
"go-common/library/log"
|
||||
"go-common/library/naming"
|
||||
bm "go-common/library/net/http/blademaster"
|
||||
"go-common/library/net/netutil"
|
||||
"go-common/library/net/netutil/breaker"
|
||||
xtime "go-common/library/time"
|
||||
"go-common/library/xstr"
|
||||
)
|
||||
|
||||
const (
|
||||
_registerURL = "http://%s/discovery/register"
|
||||
_setURL = "http://%s/discovery/set"
|
||||
_cancelURL = "http://%s/discovery/cancel"
|
||||
_renewURL = "http://%s/discovery/renew"
|
||||
|
||||
_pollURL = "http://%s/discovery/polls"
|
||||
_nodesURL = "http://%s/discovery/nodes"
|
||||
|
||||
_registerGap = 30 * time.Second
|
||||
|
||||
_statusUP = "1"
|
||||
)
|
||||
|
||||
const (
|
||||
_appid = "infra.discovery"
|
||||
)
|
||||
|
||||
var (
|
||||
_ naming.Builder = &Discovery{}
|
||||
_ naming.Registry = &Discovery{}
|
||||
|
||||
_selfDiscoveryFeatrue feature.Feature = "discovery.self"
|
||||
_discoveryFeatures = map[feature.Feature]feature.Spec{
|
||||
_selfDiscoveryFeatrue: {Default: false},
|
||||
}
|
||||
|
||||
// ErrDuplication duplication treeid.
|
||||
ErrDuplication = errors.New("discovery: instance duplicate registration")
|
||||
)
|
||||
|
||||
func init() {
|
||||
feature.DefaultGate.Add(_discoveryFeatures)
|
||||
}
|
||||
|
||||
// Config discovery configures.
|
||||
type Config struct {
|
||||
Nodes []string
|
||||
Key string
|
||||
Secret string
|
||||
Region string
|
||||
Zone string
|
||||
Env string
|
||||
Host string
|
||||
}
|
||||
|
||||
type appData struct {
|
||||
ZoneInstances map[string][]*naming.Instance `json:"zone_instances"`
|
||||
LastTs int64 `json:"latest_timestamp"`
|
||||
}
|
||||
|
||||
// Discovery is discovery client.
|
||||
type Discovery struct {
|
||||
once sync.Once
|
||||
conf *Config
|
||||
ctx context.Context
|
||||
cancelFunc context.CancelFunc
|
||||
httpClient *bm.Client
|
||||
|
||||
mutex sync.RWMutex
|
||||
apps map[string]*appInfo
|
||||
registry map[string]struct{}
|
||||
lastHost string
|
||||
cancelPolls context.CancelFunc
|
||||
idx uint64
|
||||
node atomic.Value
|
||||
delete chan *appInfo
|
||||
}
|
||||
|
||||
type appInfo struct {
|
||||
zoneIns atomic.Value
|
||||
resolver map[*Resolver]struct{}
|
||||
lastTs int64 // latest timestamp
|
||||
}
|
||||
|
||||
func fixConfig(c *Config) {
|
||||
if len(c.Nodes) == 0 {
|
||||
c.Nodes = []string{"api.bilibili.co"}
|
||||
}
|
||||
if env.Region != "" {
|
||||
c.Region = env.Region
|
||||
}
|
||||
if env.Zone != "" {
|
||||
c.Zone = env.Zone
|
||||
}
|
||||
if env.DeployEnv != "" {
|
||||
c.Env = env.DeployEnv
|
||||
}
|
||||
if env.Hostname != "" {
|
||||
c.Host = env.Hostname
|
||||
} else {
|
||||
c.Host, _ = os.Hostname()
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
_defaultDiscovery *Discovery
|
||||
)
|
||||
|
||||
func initDefault() {
|
||||
once.Do(func() {
|
||||
_defaultDiscovery = New(nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Builder return default discvoery resolver builder.
|
||||
func Builder() naming.Builder {
|
||||
if _defaultDiscovery == nil {
|
||||
initDefault()
|
||||
}
|
||||
return _defaultDiscovery
|
||||
}
|
||||
|
||||
// Build register resolver into default discovery.
|
||||
func Build(id string) naming.Resolver {
|
||||
if _defaultDiscovery == nil {
|
||||
initDefault()
|
||||
}
|
||||
return _defaultDiscovery.Build(id)
|
||||
}
|
||||
|
||||
// New new a discovery client.
|
||||
func New(c *Config) (d *Discovery) {
|
||||
if c == nil {
|
||||
c = &Config{
|
||||
Nodes: []string{"discovery.bilibili.co", "api.bilibili.co"},
|
||||
Key: "discovery",
|
||||
Secret: "discovery",
|
||||
}
|
||||
}
|
||||
fixConfig(c)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
d = &Discovery{
|
||||
ctx: ctx,
|
||||
cancelFunc: cancel,
|
||||
conf: c,
|
||||
apps: map[string]*appInfo{},
|
||||
registry: map[string]struct{}{},
|
||||
delete: make(chan *appInfo, 10),
|
||||
}
|
||||
// httpClient
|
||||
cfg := &bm.ClientConfig{
|
||||
App: &bm.App{
|
||||
Key: c.Key,
|
||||
Secret: c.Secret,
|
||||
},
|
||||
Dial: xtime.Duration(3 * time.Second),
|
||||
Timeout: xtime.Duration(40 * time.Second),
|
||||
Breaker: &breaker.Config{
|
||||
Window: 100,
|
||||
Sleep: 3,
|
||||
Bucket: 10,
|
||||
Ratio: 0.5,
|
||||
Request: 100,
|
||||
},
|
||||
}
|
||||
d.httpClient = bm.NewClient(cfg)
|
||||
if feature.DefaultGate.Enabled(_selfDiscoveryFeatrue) {
|
||||
resolver := d.Build(_appid)
|
||||
event := resolver.Watch()
|
||||
_, ok := <-event
|
||||
if !ok {
|
||||
panic("discovery watch failed")
|
||||
}
|
||||
ins, ok := resolver.Fetch(context.Background())
|
||||
if ok {
|
||||
d.newSelf(ins)
|
||||
}
|
||||
go d.selfproc(resolver, event)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Discovery) selfproc(resolver naming.Resolver, event <-chan struct{}) {
|
||||
for {
|
||||
_, ok := <-event
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
zones, ok := resolver.Fetch(context.Background())
|
||||
if ok {
|
||||
d.newSelf(zones)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discovery) newSelf(zones map[string][]*naming.Instance) {
|
||||
ins, ok := zones[d.conf.Zone]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var nodes []string
|
||||
for _, in := range ins {
|
||||
for _, addr := range in.Addrs {
|
||||
u, err := url.Parse(addr)
|
||||
if err == nil && u.Scheme == "http" {
|
||||
nodes = append(nodes, u.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
// diff old nodes
|
||||
olds, ok := d.node.Load().([]string)
|
||||
if ok {
|
||||
var diff int
|
||||
for _, n := range nodes {
|
||||
for _, o := range olds {
|
||||
if o == n {
|
||||
diff++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(nodes) == diff {
|
||||
return
|
||||
}
|
||||
}
|
||||
// FIXME: we should use rand.Shuffle() in golang 1.10
|
||||
Shuffle(len(nodes), func(i, j int) {
|
||||
nodes[i], nodes[j] = nodes[j], nodes[i]
|
||||
})
|
||||
d.node.Store(nodes)
|
||||
}
|
||||
|
||||
// Build disovery resovler builder.
|
||||
func (d *Discovery) Build(appid string) naming.Resolver {
|
||||
r := &Resolver{
|
||||
id: appid,
|
||||
d: d,
|
||||
event: make(chan struct{}, 1),
|
||||
}
|
||||
d.mutex.Lock()
|
||||
app, ok := d.apps[appid]
|
||||
if !ok {
|
||||
app = &appInfo{
|
||||
resolver: make(map[*Resolver]struct{}),
|
||||
}
|
||||
d.apps[appid] = app
|
||||
cancel := d.cancelPolls
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
app.resolver[r] = struct{}{}
|
||||
d.mutex.Unlock()
|
||||
if ok {
|
||||
select {
|
||||
case r.event <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
log.Info("disocvery: AddWatch(%s) already watch(%v)", appid, ok)
|
||||
d.once.Do(func() {
|
||||
go d.serverproc()
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
// Scheme return discovery's scheme
|
||||
func (d *Discovery) Scheme() string {
|
||||
return "discovery"
|
||||
}
|
||||
|
||||
// Resolver discveory resolver.
|
||||
type Resolver struct {
|
||||
id string
|
||||
event chan struct{}
|
||||
d *Discovery
|
||||
}
|
||||
|
||||
// Watch watch instance.
|
||||
func (r *Resolver) Watch() <-chan struct{} {
|
||||
return r.event
|
||||
}
|
||||
|
||||
// Fetch fetch resolver instance.
|
||||
func (r *Resolver) Fetch(c context.Context) (ins map[string][]*naming.Instance, ok bool) {
|
||||
r.d.mutex.RLock()
|
||||
app, ok := r.d.apps[r.id]
|
||||
r.d.mutex.RUnlock()
|
||||
if ok {
|
||||
ins, ok = app.zoneIns.Load().(map[string][]*naming.Instance)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Close close resolver.
|
||||
func (r *Resolver) Close() error {
|
||||
r.d.mutex.Lock()
|
||||
if app, ok := r.d.apps[r.id]; ok && len(app.resolver) != 0 {
|
||||
delete(app.resolver, r)
|
||||
// TODO: delete app from builder
|
||||
}
|
||||
r.d.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Discovery) pickNode() string {
|
||||
nodes, ok := d.node.Load().([]string)
|
||||
if !ok || len(nodes) == 0 {
|
||||
return d.conf.Nodes[d.idx%uint64(len(d.conf.Nodes))]
|
||||
}
|
||||
return nodes[d.idx%uint64(len(nodes))]
|
||||
}
|
||||
|
||||
func (d *Discovery) switchNode() {
|
||||
atomic.AddUint64(&d.idx, 1)
|
||||
}
|
||||
|
||||
// Reload reload the config
|
||||
func (d *Discovery) Reload(c *Config) {
|
||||
fixConfig(c)
|
||||
d.mutex.Lock()
|
||||
d.conf = c
|
||||
d.mutex.Unlock()
|
||||
}
|
||||
|
||||
// Close stop all running process including discovery and register
|
||||
func (d *Discovery) Close() error {
|
||||
d.cancelFunc()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Register Register an instance with discovery and renew automatically
|
||||
func (d *Discovery) Register(c context.Context, ins *naming.Instance) (cancelFunc context.CancelFunc, err error) {
|
||||
d.mutex.Lock()
|
||||
if _, ok := d.registry[ins.AppID]; ok {
|
||||
err = ErrDuplication
|
||||
} else {
|
||||
d.registry[ins.AppID] = struct{}{}
|
||||
}
|
||||
d.mutex.Unlock()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = d.register(c, ins); err != nil {
|
||||
d.mutex.Lock()
|
||||
delete(d.registry, ins.AppID)
|
||||
d.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithCancel(d.ctx)
|
||||
ch := make(chan struct{}, 1)
|
||||
cancelFunc = context.CancelFunc(func() {
|
||||
cancel()
|
||||
<-ch
|
||||
})
|
||||
go func() {
|
||||
ticker := time.NewTicker(_registerGap)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := d.renew(ctx, ins); err != nil && ecode.NothingFound.Equal(err) {
|
||||
d.register(ctx, ins)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
d.cancel(ins)
|
||||
ch <- struct{}{}
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
// Set set ins status and metadata.
|
||||
func (d *Discovery) Set(ins *naming.Instance) error {
|
||||
return d.set(context.Background(), ins)
|
||||
}
|
||||
|
||||
// cancel Remove the registered instance from discovery
|
||||
func (d *Discovery) cancel(ins *naming.Instance) (err error) {
|
||||
d.mutex.RLock()
|
||||
conf := d.conf
|
||||
d.mutex.RUnlock()
|
||||
|
||||
res := new(struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
})
|
||||
uri := fmt.Sprintf(_cancelURL, d.pickNode())
|
||||
params := d.newParams(conf)
|
||||
params.Set("appid", ins.AppID)
|
||||
// request
|
||||
if err = d.httpClient.Post(context.Background(), uri, "", params, &res); err != nil {
|
||||
d.switchNode()
|
||||
log.Error("discovery cancel client.Get(%v) env(%s) appid(%s) hostname(%s) error(%v)",
|
||||
uri, conf.Env, ins.AppID, conf.Host, err)
|
||||
return
|
||||
}
|
||||
if ec := ecode.Int(res.Code); !ec.Equal(ecode.OK) {
|
||||
log.Warn("discovery cancel client.Get(%v) env(%s) appid(%s) hostname(%s) code(%v)",
|
||||
uri, conf.Env, ins.AppID, conf.Host, res.Code)
|
||||
err = ec
|
||||
return
|
||||
}
|
||||
log.Info("discovery cancel client.Get(%v) env(%s) appid(%s) hostname(%s) success",
|
||||
uri, conf.Env, ins.AppID, conf.Host)
|
||||
return
|
||||
}
|
||||
|
||||
// register Register an instance with discovery
|
||||
func (d *Discovery) register(ctx context.Context, ins *naming.Instance) (err error) {
|
||||
d.mutex.RLock()
|
||||
conf := d.conf
|
||||
d.mutex.RUnlock()
|
||||
|
||||
var metadata []byte
|
||||
if ins.Metadata != nil {
|
||||
if metadata, err = json.Marshal(ins.Metadata); err != nil {
|
||||
log.Error("discovery:register instance Marshal metadata(%v) failed!error(%v)", ins.Metadata, err)
|
||||
}
|
||||
}
|
||||
res := new(struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
})
|
||||
uri := fmt.Sprintf(_registerURL, d.pickNode())
|
||||
params := d.newParams(conf)
|
||||
params.Set("appid", ins.AppID)
|
||||
params.Set("addrs", strings.Join(ins.Addrs, ","))
|
||||
params.Set("version", ins.Version)
|
||||
params.Set("status", _statusUP)
|
||||
params.Set("metadata", string(metadata))
|
||||
if err = d.httpClient.Post(ctx, uri, "", params, &res); err != nil {
|
||||
d.switchNode()
|
||||
log.Error("discovery: register client.Get(%v) zone(%s) env(%s) appid(%s) addrs(%v) error(%v)",
|
||||
uri, conf.Zone, conf.Env, ins.AppID, ins.Addrs, err)
|
||||
return
|
||||
}
|
||||
if ec := ecode.Int(res.Code); !ec.Equal(ecode.OK) {
|
||||
log.Warn("discovery: register client.Get(%v) env(%s) appid(%s) addrs(%v) code(%v)",
|
||||
uri, conf.Env, ins.AppID, ins.Addrs, res.Code)
|
||||
err = ec
|
||||
return
|
||||
}
|
||||
log.Info("discovery: register client.Get(%v) env(%s) appid(%s) addrs(%s) success",
|
||||
uri, conf.Env, ins.AppID, ins.Addrs)
|
||||
return
|
||||
}
|
||||
|
||||
// rset set instance info with discovery
|
||||
func (d *Discovery) set(ctx context.Context, ins *naming.Instance) (err error) {
|
||||
d.mutex.RLock()
|
||||
conf := d.conf
|
||||
d.mutex.RUnlock()
|
||||
res := new(struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
})
|
||||
uri := fmt.Sprintf(_setURL, d.pickNode())
|
||||
params := d.newParams(conf)
|
||||
params.Set("appid", ins.AppID)
|
||||
params.Set("version", ins.Version)
|
||||
params.Set("status", strconv.FormatInt(ins.Status, 10))
|
||||
if ins.Metadata != nil {
|
||||
var metadata []byte
|
||||
if metadata, err = json.Marshal(ins.Metadata); err != nil {
|
||||
log.Error("discovery:set instance Marshal metadata(%v) failed!error(%v)", ins.Metadata, err)
|
||||
}
|
||||
params.Set("metadata", string(metadata))
|
||||
}
|
||||
if err = d.httpClient.Post(ctx, uri, "", params, &res); err != nil {
|
||||
d.switchNode()
|
||||
log.Error("discovery: set client.Get(%v) zone(%s) env(%s) appid(%s) addrs(%v) error(%v)",
|
||||
uri, conf.Zone, conf.Env, ins.AppID, ins.Addrs, err)
|
||||
return
|
||||
}
|
||||
if ec := ecode.Int(res.Code); !ec.Equal(ecode.OK) {
|
||||
log.Warn("discovery: set client.Get(%v) env(%s) appid(%s) addrs(%v) code(%v)",
|
||||
uri, conf.Env, ins.AppID, ins.Addrs, res.Code)
|
||||
err = ec
|
||||
return
|
||||
}
|
||||
log.Info("discovery: set client.Get(%v) env(%s) appid(%s) addrs(%s) success",
|
||||
uri+"?"+params.Encode(), conf.Env, ins.AppID, ins.Addrs)
|
||||
return
|
||||
}
|
||||
|
||||
// renew Renew an instance with discovery
|
||||
func (d *Discovery) renew(ctx context.Context, ins *naming.Instance) (err error) {
|
||||
d.mutex.RLock()
|
||||
conf := d.conf
|
||||
d.mutex.RUnlock()
|
||||
|
||||
res := new(struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
})
|
||||
uri := fmt.Sprintf(_renewURL, d.pickNode())
|
||||
params := d.newParams(conf)
|
||||
params.Set("appid", ins.AppID)
|
||||
if err = d.httpClient.Post(ctx, uri, "", params, &res); err != nil {
|
||||
d.switchNode()
|
||||
log.Error("discovery: renew client.Get(%v) env(%s) appid(%s) hostname(%s) error(%v)",
|
||||
uri, conf.Env, ins.AppID, conf.Host, err)
|
||||
return
|
||||
}
|
||||
if ec := ecode.Int(res.Code); !ec.Equal(ecode.OK) {
|
||||
err = ec
|
||||
if ec.Equal(ecode.NothingFound) {
|
||||
return
|
||||
}
|
||||
log.Error("discovery: renew client.Get(%v) env(%s) appid(%s) hostname(%s) code(%v)",
|
||||
uri, conf.Env, ins.AppID, conf.Host, res.Code)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Discovery) serverproc() {
|
||||
var (
|
||||
retry int
|
||||
update bool
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
)
|
||||
bc := netutil.DefaultBackoffConfig
|
||||
ticker := time.NewTicker(time.Minute * 30)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
if ctx == nil {
|
||||
ctx, cancel = context.WithCancel(d.ctx)
|
||||
d.mutex.Lock()
|
||||
d.cancelPolls = cancel
|
||||
d.mutex.Unlock()
|
||||
}
|
||||
select {
|
||||
case <-d.ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
update = true
|
||||
default:
|
||||
}
|
||||
if !feature.DefaultGate.Enabled(_selfDiscoveryFeatrue) {
|
||||
nodes, ok := d.node.Load().([]string)
|
||||
if !ok || len(nodes) == 0 || update {
|
||||
update = false
|
||||
tnodes := d.nodes()
|
||||
if len(tnodes) == 0 {
|
||||
time.Sleep(bc.Backoff(retry))
|
||||
retry++
|
||||
continue
|
||||
}
|
||||
retry = 0
|
||||
// FIXME: we should use rand.Shuffle() in golang 1.10
|
||||
Shuffle(len(tnodes), func(i, j int) {
|
||||
tnodes[i], tnodes[j] = tnodes[j], tnodes[i]
|
||||
})
|
||||
d.node.Store(tnodes)
|
||||
}
|
||||
}
|
||||
apps, err := d.polls(ctx, d.pickNode())
|
||||
if err != nil {
|
||||
d.switchNode()
|
||||
if ctx.Err() == context.Canceled {
|
||||
ctx = nil
|
||||
continue
|
||||
}
|
||||
time.Sleep(bc.Backoff(retry))
|
||||
retry++
|
||||
continue
|
||||
}
|
||||
retry = 0
|
||||
d.broadcast(apps)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discovery) nodes() (nodes []string) {
|
||||
res := new(struct {
|
||||
Code int `json:"code"`
|
||||
Data []struct {
|
||||
Addr string `json:"addr"`
|
||||
} `json:"data"`
|
||||
})
|
||||
uri := fmt.Sprintf(_nodesURL, d.pickNode())
|
||||
if err := d.httpClient.Get(d.ctx, uri, "", nil, res); err != nil {
|
||||
d.switchNode()
|
||||
log.Error("discovery: consumer client.Get(%v)error(%+v)", uri, err)
|
||||
return
|
||||
}
|
||||
if ec := ecode.Int(res.Code); !ec.Equal(ecode.OK) {
|
||||
log.Error("discovery: consumer client.Get(%v) error(%v)", uri, res.Code)
|
||||
return
|
||||
}
|
||||
if len(res.Data) == 0 {
|
||||
log.Warn("discovery: get nodes(%s) failed,no nodes found!", uri)
|
||||
return
|
||||
}
|
||||
nodes = make([]string, 0, len(res.Data))
|
||||
for i := range res.Data {
|
||||
nodes = append(nodes, res.Data[i].Addr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Discovery) polls(ctx context.Context, host string) (apps map[string]appData, err error) {
|
||||
var (
|
||||
lastTs []int64
|
||||
appid []string
|
||||
changed bool
|
||||
)
|
||||
if host != d.lastHost {
|
||||
d.lastHost = host
|
||||
changed = true
|
||||
}
|
||||
d.mutex.RLock()
|
||||
conf := d.conf
|
||||
for k, v := range d.apps {
|
||||
if changed {
|
||||
v.lastTs = 0
|
||||
}
|
||||
appid = append(appid, k)
|
||||
lastTs = append(lastTs, v.lastTs)
|
||||
}
|
||||
d.mutex.RUnlock()
|
||||
if len(appid) == 0 {
|
||||
return
|
||||
}
|
||||
uri := fmt.Sprintf(_pollURL, host)
|
||||
res := new(struct {
|
||||
Code int `json:"code"`
|
||||
Data map[string]appData `json:"data"`
|
||||
})
|
||||
params := url.Values{}
|
||||
params.Set("env", conf.Env)
|
||||
params.Set("hostname", conf.Host)
|
||||
params.Set("appid", strings.Join(appid, ","))
|
||||
params.Set("latest_timestamp", xstr.JoinInts(lastTs))
|
||||
if err = d.httpClient.Get(ctx, uri, "", params, res); err != nil {
|
||||
log.Error("discovery: client.Get(%s) error(%+v)", uri+"?"+params.Encode(), err)
|
||||
return
|
||||
}
|
||||
if ec := ecode.Int(res.Code); !ec.Equal(ecode.OK) {
|
||||
if !ec.Equal(ecode.NotModified) {
|
||||
log.Error("discovery: client.Get(%s) get error code(%d)", uri+"?"+params.Encode(), res.Code)
|
||||
err = ec
|
||||
}
|
||||
return
|
||||
}
|
||||
info, _ := json.Marshal(res.Data)
|
||||
for _, app := range res.Data {
|
||||
if app.LastTs == 0 {
|
||||
err = ecode.ServerErr
|
||||
log.Error("discovery: client.Get(%s) latest_timestamp is 0,instances:(%s)", uri+"?"+params.Encode(), info)
|
||||
return
|
||||
}
|
||||
}
|
||||
log.Info("discovery: polls uri(%s)", uri+"?"+params.Encode())
|
||||
log.Info("discovery: successfully polls(%s) instances (%s)", uri+"?"+params.Encode(), info)
|
||||
apps = res.Data
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Discovery) broadcast(apps map[string]appData) {
|
||||
for id, v := range apps {
|
||||
var count int
|
||||
for zone, ins := range v.ZoneInstances {
|
||||
if len(ins) == 0 {
|
||||
delete(v.ZoneInstances, zone)
|
||||
}
|
||||
count += len(ins)
|
||||
}
|
||||
if count == 0 {
|
||||
continue
|
||||
}
|
||||
d.mutex.RLock()
|
||||
app, ok := d.apps[id]
|
||||
d.mutex.RUnlock()
|
||||
if ok {
|
||||
app.lastTs = v.LastTs
|
||||
app.zoneIns.Store(v.ZoneInstances)
|
||||
d.mutex.RLock()
|
||||
for rs := range app.resolver {
|
||||
select {
|
||||
case rs.event <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
d.mutex.RUnlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discovery) newParams(conf *Config) url.Values {
|
||||
params := url.Values{}
|
||||
params.Set("region", conf.Region)
|
||||
params.Set("zone", conf.Zone)
|
||||
params.Set("env", conf.Env)
|
||||
params.Set("hostname", conf.Host)
|
||||
return params
|
||||
}
|
365
library/naming/discovery/discovery_test.go
Normal file
365
library/naming/discovery/discovery_test.go
Normal file
@ -0,0 +1,365 @@
|
||||
package discovery
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go-common/library/conf/env"
|
||||
"go-common/library/ecode"
|
||||
"go-common/library/exp/feature"
|
||||
"go-common/library/naming"
|
||||
"go-common/library/net/http/blademaster"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
var appdID = "main.arch.test66"
|
||||
var appID2 = "main.arch.test22"
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
feature.DefaultGate.AddFlag(flag.CommandLine)
|
||||
flag.Set("feature-gates", fmt.Sprintf("%s=true", _selfDiscoveryFeatrue))
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
var c = &Config{
|
||||
Nodes: []string{"172.18.33.51:7171"},
|
||||
Zone: "sh001",
|
||||
Env: "pre",
|
||||
Key: "0c4b8fe3ff35a4b6",
|
||||
Secret: "b370880d1aca7d3a289b9b9a7f4d6812",
|
||||
Host: "host_1",
|
||||
}
|
||||
var indis = &naming.Instance{
|
||||
AppID: appdID,
|
||||
Zone: env.Zone,
|
||||
Addrs: []string{
|
||||
"grpc://172.18.33.51:8080",
|
||||
"http://172.18.33.51:7171",
|
||||
},
|
||||
Version: "1",
|
||||
Metadata: map[string]string{
|
||||
"test": "1",
|
||||
"weight": "12",
|
||||
"color": "blue",
|
||||
},
|
||||
}
|
||||
|
||||
var in = &naming.Instance{
|
||||
AppID: appdID,
|
||||
Addrs: []string{
|
||||
"grpc://127.0.0.1:8080",
|
||||
},
|
||||
Version: "1",
|
||||
Metadata: map[string]string{
|
||||
"test": "1",
|
||||
"weight": "12",
|
||||
"color": "blue",
|
||||
},
|
||||
}
|
||||
|
||||
var intest2 = &naming.Instance{
|
||||
AppID: appID2,
|
||||
Addrs: []string{
|
||||
"grpc://127.0.0.1:8080",
|
||||
},
|
||||
Version: "1",
|
||||
Metadata: map[string]string{
|
||||
"test": "1",
|
||||
"weight": "12",
|
||||
"color": "blue",
|
||||
},
|
||||
}
|
||||
|
||||
var in2 = &naming.Instance{
|
||||
AppID: appdID,
|
||||
Addrs: []string{
|
||||
"grpc://127.0.0.1:8081",
|
||||
},
|
||||
Version: "1",
|
||||
Metadata: map[string]string{
|
||||
"test": "2",
|
||||
"weight": "6",
|
||||
"color": "red",
|
||||
},
|
||||
}
|
||||
|
||||
func TestDiscoverySelf(t *testing.T) {
|
||||
Convey("test TestDiscoverySelf ", t, func() {
|
||||
So(feature.DefaultGate.Enabled(_selfDiscoveryFeatrue), ShouldBeTrue)
|
||||
d := New(c)
|
||||
So(len(d.node.Load().([]string)), ShouldNotEqual, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
Convey("test register and cancel", t, func() {
|
||||
env.Hostname = "host_1"
|
||||
d := New(c)
|
||||
defer d.Close()
|
||||
ctx := context.TODO()
|
||||
cancel, err := d.Register(ctx, in)
|
||||
defer cancel()
|
||||
So(err, ShouldBeNil)
|
||||
rs := d.Build(appdID)
|
||||
ch := rs.Watch()
|
||||
So(ch, ShouldNotBeNil)
|
||||
<-ch
|
||||
ins, ok := rs.Fetch(ctx)
|
||||
So(ok, ShouldBeTrue)
|
||||
So(err, ShouldBeNil)
|
||||
var count int
|
||||
for _, data := range ins {
|
||||
count += len(data)
|
||||
}
|
||||
So(count, ShouldEqual, 1)
|
||||
c.Host = "host_2"
|
||||
env.Hostname = "host_2"
|
||||
|
||||
d2 := New(c)
|
||||
defer d2.Close()
|
||||
cancel2, err := d2.Register(ctx, in2)
|
||||
So(err, ShouldBeNil)
|
||||
<-ch
|
||||
ins, _ = rs.Fetch(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
count = 0
|
||||
for _, data := range ins {
|
||||
count += len(data)
|
||||
}
|
||||
So(count, ShouldEqual, 2)
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
cancel2()
|
||||
<-ch
|
||||
ins, _ = rs.Fetch(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
count = 0
|
||||
for _, data := range ins {
|
||||
count += len(data)
|
||||
}
|
||||
So(count, ShouldEqual, 1)
|
||||
Convey("test discovery set", func() {
|
||||
c.Host = "host_1"
|
||||
inSet := &naming.Instance{
|
||||
AppID: appdID,
|
||||
Addrs: []string{
|
||||
"grpc://127.0.0.1:8080",
|
||||
},
|
||||
Status: 1,
|
||||
Metadata: map[string]string{
|
||||
"test": "1",
|
||||
"weight": "111",
|
||||
"color": "blue",
|
||||
},
|
||||
}
|
||||
ins, _ = rs.Fetch(context.TODO())
|
||||
fmt.Println("ins", ins["sh001"][0])
|
||||
err = d2.Set(inSet)
|
||||
// So(err, ShouldBeNil)
|
||||
<-ch
|
||||
ins, _ = rs.Fetch(context.TODO())
|
||||
fmt.Println("ins1", ins["sh001"][0])
|
||||
So(ins["sh001"][0].Metadata["weight"], ShouldResemble, "111")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestMultiZone(t *testing.T) {
|
||||
Convey("test multi zone", t, func() {
|
||||
env.Hostname = "host_1"
|
||||
d := New(c)
|
||||
defer d.Close()
|
||||
ctx := context.TODO()
|
||||
cancel, err := d.Register(ctx, in)
|
||||
So(err, ShouldBeNil)
|
||||
defer cancel()
|
||||
rs := d.Build(appdID)
|
||||
ch := rs.Watch()
|
||||
So(ch, ShouldNotBeNil)
|
||||
<-ch
|
||||
ins, ok := rs.Fetch(ctx)
|
||||
So(ok, ShouldBeTrue)
|
||||
count := 0
|
||||
for _, data := range ins {
|
||||
count += len(data)
|
||||
}
|
||||
So(count, ShouldEqual, 1)
|
||||
env.Hostname = "host_2"
|
||||
env.Zone = "other_zone"
|
||||
d2 := New(c)
|
||||
defer d2.Close()
|
||||
cancel2, err := d2.Register(ctx, in2)
|
||||
So(err, ShouldBeNil)
|
||||
defer func() {
|
||||
cancel2()
|
||||
time.Sleep(time.Millisecond * 300)
|
||||
}()
|
||||
<-ch
|
||||
ins, ok = rs.Fetch(ctx)
|
||||
So(ok, ShouldBeTrue)
|
||||
count = 0
|
||||
zoneCount := 0
|
||||
for _, data := range ins {
|
||||
zoneCount++
|
||||
count += len(data)
|
||||
}
|
||||
So(count, ShouldEqual, 2)
|
||||
So(zoneCount, ShouldEqual, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDiscoveryFailOver(t *testing.T) {
|
||||
Convey("test failover", t, func() {
|
||||
var conf = &Config{
|
||||
Nodes: []string{"127.0.0.1:8080"},
|
||||
Zone: "sh001",
|
||||
Env: "pre",
|
||||
Key: "0c4b8fe3ff35a4b6",
|
||||
Secret: "b370880d1aca7d3a289b9b9a7f4d6812",
|
||||
Host: "host_1",
|
||||
}
|
||||
for _, a := range []string{":8080"} {
|
||||
go func(addr string) {
|
||||
e := blademaster.DefaultServer(nil)
|
||||
e.GET("/discovery/nodes", func(ctx *blademaster.Context) {
|
||||
type v struct {
|
||||
Addr string `json:"addr"`
|
||||
}
|
||||
ctx.JSON([]v{{Addr: "127.0.0.1:8080"}}, nil)
|
||||
})
|
||||
e.GET("/discovery/polls", func(ctx *blademaster.Context) {
|
||||
params := ctx.Request.Form
|
||||
ts := params.Get("latest_timestamp")
|
||||
if ts == "0" {
|
||||
ctx.JSON(map[string]appData{
|
||||
appdID: {LastTs: time.Now().UnixNano(), ZoneInstances: map[string][]*naming.Instance{"zone": {in}}},
|
||||
"infra.discovery": {LastTs: time.Now().UnixNano(), ZoneInstances: map[string][]*naming.Instance{conf.Zone: {indis}}},
|
||||
}, nil)
|
||||
} else {
|
||||
ctx.JSON(nil, ecode.ServerErr)
|
||||
}
|
||||
})
|
||||
e.Run(addr)
|
||||
}(a)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
d := New(conf)
|
||||
defer d.Close()
|
||||
rs := d.Build(appdID)
|
||||
ch := rs.Watch()
|
||||
<-ch
|
||||
ins, _ := rs.Fetch(context.TODO())
|
||||
count := 0
|
||||
zoneCount := 0
|
||||
for _, data := range ins {
|
||||
zoneCount++
|
||||
count += len(data)
|
||||
}
|
||||
So(count, ShouldEqual, 1)
|
||||
So(zoneCount, ShouldEqual, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWatchContinuosly(t *testing.T) {
|
||||
Convey("test TestWatchContinuosly ", t, func() {
|
||||
env.Hostname = "host_1"
|
||||
d := New(c)
|
||||
defer d.Close()
|
||||
in1 := *in
|
||||
in1.AppID = "test.test"
|
||||
ctx := context.TODO()
|
||||
cancel, err := d.Register(ctx, &in1)
|
||||
So(err, ShouldBeNil)
|
||||
defer cancel()
|
||||
in2 := *in
|
||||
in2.AppID = "test.test2"
|
||||
cancel2, err := d.Register(ctx, &in2)
|
||||
So(err, ShouldBeNil)
|
||||
defer cancel2()
|
||||
in3 := *in
|
||||
in3.AppID = "test.test3"
|
||||
cancel3, err := d.Register(ctx, &in3)
|
||||
So(err, ShouldBeNil)
|
||||
defer cancel3()
|
||||
rs := d.Build("test.test")
|
||||
ch := rs.Watch()
|
||||
<-ch
|
||||
ins, ok := rs.Fetch(ctx)
|
||||
So(ok, ShouldBeTrue)
|
||||
count := 0
|
||||
for _, data := range ins {
|
||||
count += len(data)
|
||||
}
|
||||
So(count, ShouldBeGreaterThanOrEqualTo, 1)
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
rs = d.Build("test.test2")
|
||||
ch2 := rs.Watch()
|
||||
<-ch2
|
||||
ins, ok = rs.Fetch(ctx)
|
||||
So(ok, ShouldBeTrue)
|
||||
count = 0
|
||||
for _, data := range ins {
|
||||
count += len(data)
|
||||
}
|
||||
So(count, ShouldBeGreaterThanOrEqualTo, 1)
|
||||
rs = d.Build("test.test3")
|
||||
ch3 := rs.Watch()
|
||||
<-ch3
|
||||
ins, ok = rs.Fetch(ctx)
|
||||
So(ok, ShouldBeTrue)
|
||||
count = 0
|
||||
for _, data := range ins {
|
||||
count += len(data)
|
||||
}
|
||||
So(count, ShouldBeGreaterThanOrEqualTo, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSameBuilder(t *testing.T) {
|
||||
Convey("test multi watch", t, func() {
|
||||
env.Hostname = "host_1"
|
||||
d := New(c)
|
||||
defer d.Close()
|
||||
ctx := context.TODO()
|
||||
cancel, err := d.Register(ctx, in)
|
||||
d.Register(ctx, intest2)
|
||||
defer cancel()
|
||||
So(err, ShouldBeNil)
|
||||
// first builder
|
||||
rs := d.Build(appdID)
|
||||
ch := rs.Watch()
|
||||
So(ch, ShouldNotBeNil)
|
||||
<-ch
|
||||
_, ok := rs.Fetch(ctx)
|
||||
So(ok, ShouldBeTrue)
|
||||
So(err, ShouldBeNil)
|
||||
var count int
|
||||
// for _, data := range ins {
|
||||
// count += len(data)
|
||||
// }
|
||||
// So(count, ShouldEqual, 1)
|
||||
// same appd builder
|
||||
rs2 := d.Build(appdID)
|
||||
ch2 := rs2.Watch()
|
||||
<-ch2
|
||||
_, ok = rs2.Fetch(ctx)
|
||||
So(ok, ShouldBeTrue)
|
||||
So(err, ShouldBeNil)
|
||||
rs3 := d.Build(appID2)
|
||||
ch3 := rs3.Watch()
|
||||
<-ch3
|
||||
ins, _ := rs3.Fetch(ctx)
|
||||
So(ok, ShouldBeTrue)
|
||||
So(err, ShouldBeNil)
|
||||
count = 0
|
||||
for _, data := range ins {
|
||||
count += len(data)
|
||||
}
|
||||
So(count, ShouldEqual, 1)
|
||||
})
|
||||
}
|
55
library/naming/discovery/example_test.go
Normal file
55
library/naming/discovery/example_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
package discovery_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go-common/library/naming"
|
||||
"go-common/library/naming/discovery"
|
||||
"time"
|
||||
)
|
||||
|
||||
// this example creates a registry service to register instance info
|
||||
// to discovery server.
|
||||
// when the program is about to exit,registry.Cancel should be called.
|
||||
func Example() {
|
||||
var c = &discovery.Config{
|
||||
Nodes: []string{"api.bilibili.co"},
|
||||
Zone: "sh001",
|
||||
Env: "pre",
|
||||
Key: "0c4b8fe3ff35a4b6",
|
||||
Secret: "b370880d1aca7d3a289b9b9a7f4d6812",
|
||||
}
|
||||
var ins = &naming.Instance{
|
||||
AppID: "main.arch.test2",
|
||||
Addrs: []string{
|
||||
"grpc://127.0.0.1:8080",
|
||||
},
|
||||
Version: "1",
|
||||
Metadata: map[string]string{
|
||||
"weight": "128",
|
||||
"color": "blue",
|
||||
},
|
||||
}
|
||||
|
||||
d := discovery.New(c)
|
||||
cacenl, err := d.Register(context.TODO(), ins)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer cacenl()
|
||||
//start to Serve
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
|
||||
// this example creates a discovery client to poll instances from discovery server.
|
||||
func ExampleDiscovery() {
|
||||
d := discovery.Build("1231234")
|
||||
ch := d.Watch()
|
||||
for {
|
||||
<-ch
|
||||
ins, ok := d.Fetch(context.TODO())
|
||||
if ok {
|
||||
fmt.Println("new instances found:", ins)
|
||||
}
|
||||
}
|
||||
}
|
33
library/naming/discovery/shuffle.go
Normal file
33
library/naming/discovery/shuffle.go
Normal file
@ -0,0 +1,33 @@
|
||||
package discovery
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
var r = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
// Shuffle pseudo-randomizes the order of elements.
|
||||
// n is the number of elements. Shuffle panics if n < 0.
|
||||
// swap swaps the elements with indexes i and j.
|
||||
func Shuffle(n int, swap func(i, j int)) {
|
||||
if n < 0 {
|
||||
panic("invalid argument to Shuffle")
|
||||
}
|
||||
|
||||
// Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
|
||||
// Shuffle really ought not be called with n that doesn't fit in 32 bits.
|
||||
// Not only will it take a very long time, but with 2³¹! possible permutations,
|
||||
// there's no way that any PRNG can have a big enough internal state to
|
||||
// generate even a minuscule percentage of the possible permutations.
|
||||
// Nevertheless, the right API signature accepts an int n, so handle it as best we can.
|
||||
i := n - 1
|
||||
for ; i > 1<<31-1-1; i-- {
|
||||
j := int(r.Int63n(int64(i + 1)))
|
||||
swap(i, j)
|
||||
}
|
||||
for ; i > 0; i-- {
|
||||
j := int(r.Int31n(int32(i + 1)))
|
||||
swap(i, j)
|
||||
}
|
||||
}
|
47
library/naming/livezk/BUILD
Normal file
47
library/naming/livezk/BUILD
Normal file
@ -0,0 +1,47 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_test",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["livezk_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
rundir = ".",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//library/naming:go_default_library",
|
||||
"//library/time:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["livezk.go"],
|
||||
importpath = "go-common/library/naming/livezk",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//library/log:go_default_library",
|
||||
"//library/naming:go_default_library",
|
||||
"//library/time:go_default_library",
|
||||
"//vendor/github.com/samuel/go-zookeeper/zk:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
137
library/naming/livezk/livezk.go
Normal file
137
library/naming/livezk/livezk.go
Normal file
@ -0,0 +1,137 @@
|
||||
package livezk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go-common/library/log"
|
||||
"go-common/library/naming"
|
||||
xtime "go-common/library/time"
|
||||
|
||||
"github.com/samuel/go-zookeeper/zk"
|
||||
)
|
||||
|
||||
const (
|
||||
basePath = "/live/service"
|
||||
scheme = "grpc"
|
||||
)
|
||||
|
||||
// Zookeeper Server&Client settings.
|
||||
type Zookeeper struct {
|
||||
Root string
|
||||
Addrs []string
|
||||
Timeout xtime.Duration
|
||||
}
|
||||
|
||||
// New new live zookeeper registry
|
||||
func New(config *Zookeeper) (naming.Registry, error) {
|
||||
lz := &livezk{
|
||||
zkConfig: config,
|
||||
}
|
||||
var err error
|
||||
lz.zkConn, lz.zkEvent, err = zk.Connect(config.Addrs, time.Duration(config.Timeout))
|
||||
if err != nil {
|
||||
go lz.eventproc()
|
||||
}
|
||||
return lz, err
|
||||
}
|
||||
|
||||
type zkIns struct {
|
||||
Group string `json:"group"`
|
||||
LibVersion string `json:"lib_version"`
|
||||
StartupTime string `json:"startup_time"`
|
||||
}
|
||||
|
||||
func newZkInsData(ins *naming.Instance) ([]byte, error) {
|
||||
zi := &zkIns{
|
||||
// TODO group support
|
||||
Group: "default",
|
||||
LibVersion: ins.Version,
|
||||
StartupTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
return json.Marshal(zi)
|
||||
}
|
||||
|
||||
// livezk live service zookeeper registry
|
||||
type livezk struct {
|
||||
zkConfig *Zookeeper
|
||||
zkConn *zk.Conn
|
||||
zkEvent <-chan zk.Event
|
||||
}
|
||||
|
||||
var _ naming.Registry = &livezk{}
|
||||
|
||||
func (l *livezk) Register(ctx context.Context, ins *naming.Instance) (cancel context.CancelFunc, err error) {
|
||||
nodePath := path.Join(l.zkConfig.Root, basePath, ins.AppID)
|
||||
if err = l.createAll(nodePath); err != nil {
|
||||
return
|
||||
}
|
||||
var rpc string
|
||||
for _, addr := range ins.Addrs {
|
||||
u, ue := url.Parse(addr)
|
||||
if ue == nil && u.Scheme == scheme {
|
||||
rpc = u.Host
|
||||
break
|
||||
}
|
||||
}
|
||||
if rpc == "" {
|
||||
err = errors.New("no GRPC addr")
|
||||
return
|
||||
}
|
||||
|
||||
dataPath := path.Join(nodePath, rpc)
|
||||
data, err := newZkInsData(ins)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = l.zkConn.Create(dataPath, data, zk.FlagEphemeral, zk.WorldACL(zk.PermAll))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func() {
|
||||
l.unregister(dataPath)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *livezk) Close() error {
|
||||
l.zkConn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *livezk) createAll(nodePath string) (err error) {
|
||||
seps := strings.Split(nodePath, "/")
|
||||
lastPath := "/"
|
||||
ok := false
|
||||
for _, part := range seps {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
lastPath = path.Join(lastPath, part)
|
||||
if ok, _, err = l.zkConn.Exists(lastPath); err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
continue
|
||||
}
|
||||
if _, err = l.zkConn.Create(lastPath, nil, 0, zk.WorldACL(zk.PermAll)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *livezk) eventproc() {
|
||||
for event := range l.zkEvent {
|
||||
// TODO handle zookeeper event
|
||||
log.Info("zk event: err: %s, path: %s, server: %s, state: %s, type: %s",
|
||||
event.Err, event.Path, event.Server, event.State, event.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *livezk) unregister(dataPath string) error {
|
||||
return l.zkConn.Delete(dataPath, -1)
|
||||
}
|
77
library/naming/livezk/livezk_test.go
Normal file
77
library/naming/livezk/livezk_test.go
Normal file
@ -0,0 +1,77 @@
|
||||
package livezk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go-common/library/naming"
|
||||
xtime "go-common/library/time"
|
||||
)
|
||||
|
||||
var appdID = "main.arch.test6"
|
||||
var addr = "127.0.0.1:8080"
|
||||
|
||||
var ins1 = &naming.Instance{
|
||||
AppID: appdID,
|
||||
Addrs: []string{"grpc://" + addr},
|
||||
Version: "1",
|
||||
Metadata: map[string]string{
|
||||
"test": "1",
|
||||
"color": "blue",
|
||||
},
|
||||
}
|
||||
|
||||
var addrs = []string{"172.18.33.131:2181", "172.18.33.168:2181", "172.18.33.169:2181"}
|
||||
var zkConfig = &Zookeeper{
|
||||
Addrs: addrs,
|
||||
Timeout: xtime.Duration(time.Second),
|
||||
}
|
||||
|
||||
func TestLiveZK(t *testing.T) {
|
||||
reg, err := New(zkConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cancel, err := reg.Register(context.TODO(), ins1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cancel()
|
||||
lzk := reg.(*livezk)
|
||||
nodePath := path.Join(basePath, appdID, addr)
|
||||
ok, _, err := lzk.zkConn.Exists(nodePath)
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
t.Errorf("path not exists %s", nodePath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLiveZKCancel(t *testing.T) {
|
||||
reg, err := New(zkConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cancel, err := reg.Register(context.TODO(), ins1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cancel()
|
||||
lzk := reg.(*livezk)
|
||||
nodePath := path.Join(basePath, fmt.Sprintf("b%s", ins1.AppID), addr)
|
||||
ok, _, err := lzk.zkConn.Exists(nodePath)
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
t.Errorf("path should not exists %s", nodePath)
|
||||
}
|
||||
}
|
58
library/naming/naming.go
Normal file
58
library/naming/naming.go
Normal file
@ -0,0 +1,58 @@
|
||||
package naming
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// metadata common key
|
||||
const (
|
||||
MetaColor = "color"
|
||||
MetaWeight = "weight"
|
||||
MetaCluster = "cluster"
|
||||
MetaZone = "zone"
|
||||
)
|
||||
|
||||
// Instance represents a server the client connects to.
|
||||
type Instance struct {
|
||||
// Region bj/sh/gz
|
||||
Region string `json:"region"`
|
||||
// Zone is IDC.
|
||||
Zone string `json:"zone"`
|
||||
// Env prod/pre、uat/fat1
|
||||
Env string `json:"env"`
|
||||
// AppID is mapping servicetree appid.
|
||||
AppID string `json:"appid"`
|
||||
// Hostname is hostname from docker.
|
||||
Hostname string `json:"hostname"`
|
||||
// Addrs is the adress of app instance
|
||||
// format: scheme://host
|
||||
Addrs []string `json:"addrs"`
|
||||
// Version is publishing version.
|
||||
Version string `json:"version"`
|
||||
// LastTs is instance latest updated timestamp
|
||||
LastTs int64 `json:"latest_timestamp"`
|
||||
// Metadata is the information associated with Addr, which may be used
|
||||
// to make load balancing decision.
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
Status int64
|
||||
}
|
||||
|
||||
// Resolver resolve naming service
|
||||
type Resolver interface {
|
||||
Fetch(context.Context) (map[string][]*Instance, bool)
|
||||
//Unwatch(id string)
|
||||
Watch() <-chan struct{}
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Registry Register an instance and renew automatically
|
||||
type Registry interface {
|
||||
Register(context.Context, *Instance) (context.CancelFunc, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Builder resolver builder.
|
||||
type Builder interface {
|
||||
Build(id string) Resolver
|
||||
Scheme() string
|
||||
}
|
Reference in New Issue
Block a user