Initial commit
This commit is contained in:
79
gfetcd/README.md
Normal file
79
gfetcd/README.md
Normal file
@@ -0,0 +1,79 @@
|
||||
## 代码结构
|
||||
|
||||
``` shell
|
||||
├── README.md
|
||||
├── client.go # grpc客户端调用
|
||||
├── config.go # 配置结构
|
||||
├── resolver.go # 地址解析
|
||||
└── server.go # grpc服务端调用
|
||||
```
|
||||
|
||||
## 数据结构
|
||||
|
||||
### 基本结构
|
||||
|
||||
``` golang
|
||||
type BasicConfig struct {
|
||||
DialTimeout int `toml:"dial_timeout"` // 连接超时时间,默认5秒
|
||||
EndPoints []string `toml:"endpoints"` // etcd的地址
|
||||
Prefix string `toml:"prefix"` // etcd服务的路径前缀,区分环境用
|
||||
Debug bool `toml:"debug"` // 使用debug模式,打印详细的etcd交互日志,很多,非调试不建议打开
|
||||
}
|
||||
```
|
||||
|
||||
### grpc服务端用的配置
|
||||
|
||||
``` golang
|
||||
type NameConfig struct {
|
||||
BasicConfig
|
||||
Servers []string `toml:"servers"` // 注册的服务名称,可以是多个
|
||||
Addr string `toml:"addr"` // 服务地址,可以是IP:PORT或者:PORT格式
|
||||
TTL int64 `toml:"ttl"` // 租约超时时间,默认5秒
|
||||
}
|
||||
```
|
||||
|
||||
### grpc客户端用的配置
|
||||
|
||||
``` golang
|
||||
type ResolverConfig struct {
|
||||
BasicConfig
|
||||
WatchServers []string `toml:"watch_servers"` // 依赖的服务名称
|
||||
}
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### grpc服务端
|
||||
|
||||
``` golang
|
||||
// demo
|
||||
&config.EtcdName = {
|
||||
EndPoints:[]string{"127.0.0.1:2379"},
|
||||
Prefix: "/ng/test/okash",
|
||||
Servers: []string{"orders.Orders", "product.Product"},
|
||||
Addr: ":8701",
|
||||
}
|
||||
etcdName, err := gfetcd.NameRegist(&config.EtcdName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer etcdName.Unregister()
|
||||
```
|
||||
|
||||
### grpc客户端
|
||||
|
||||
```golang
|
||||
// demo
|
||||
&config.EtcdReslv{
|
||||
EndPoints:[]string{"127.0.0.1:2379"},
|
||||
Prefix: "/ng/test/okash",
|
||||
WatchServers: []string{"product.Product"},
|
||||
}
|
||||
|
||||
// 连接etcd
|
||||
EtcdReslv, err = gfetcd.NewResolver(&config.EtcdReslv)
|
||||
|
||||
// 调用方法
|
||||
err := EtcdReslv.Call(context.Background(), &XXXReq{}, &XXXResp{})
|
||||
```
|
||||
|
||||
91
gfetcd/client.go
Normal file
91
gfetcd/client.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package gfetcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"globalfintech/golib/tlog"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
// 客户端配置
|
||||
type ResolverConfig struct {
|
||||
BasicConfig
|
||||
WatchServers []string `toml:"watch_servers"` // 依赖的服务名称
|
||||
}
|
||||
|
||||
type Resolver struct {
|
||||
userCfg *ResolverConfig
|
||||
etcdCli *clientv3.Client
|
||||
rpcServers map[string]*grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewResolver(cfg *ResolverConfig) (resolver *Resolver, err error) {
|
||||
|
||||
if cfg.Debug {
|
||||
clientv3.SetLogger(grpclog.NewLoggerV2WithVerbosity(os.Stderr, os.Stderr, os.Stderr, 4))
|
||||
}
|
||||
|
||||
cli, err := etcdDial(&cfg.BasicConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolver = &Resolver{
|
||||
userCfg: cfg,
|
||||
etcdCli: cli,
|
||||
rpcServers: make(map[string]*grpc.ClientConn),
|
||||
}
|
||||
|
||||
for _, serviceName := range cfg.WatchServers {
|
||||
|
||||
service := ServiceKey(cfg.Prefix, serviceName)
|
||||
tlog.Debugf("ETCD RESOLVER||service: %s", service)
|
||||
|
||||
//r := &etcdnaming.GRPCResolver{Client: cli} // todo: 全部更新为新版后,改为这种方式
|
||||
r := &GFgrpcResolver{Client: cli}
|
||||
b := grpc.RoundRobin(r)
|
||||
|
||||
resolver.rpcServers[service], err = grpc.Dial(service, grpc.WithInsecure(),
|
||||
grpc.WithTimeout(time.Second*5),
|
||||
grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: time.Duration(2) * time.Second}),
|
||||
grpc.WithBalancer(b),
|
||||
grpc.WithBlock())
|
||||
if err != nil {
|
||||
tlog.Errorf("ETCD RESOLVER||service: %s ||grpc dial err:%s", service, err)
|
||||
return nil, fmt.Errorf("grpc dial err")
|
||||
}
|
||||
}
|
||||
|
||||
return resolver, nil
|
||||
}
|
||||
|
||||
func (self *Resolver) Call(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error {
|
||||
// method format: /<serviceName>/<methodName>
|
||||
// eg. "/case_expire.CaseExpire/ExpireCase"
|
||||
|
||||
t := strings.Split(method, "/")
|
||||
if len(t) < 3 {
|
||||
tlog.Errorf("ETCD RESOLVER||method name: %s incorrect format", method)
|
||||
return fmt.Errorf("invalid method format")
|
||||
}
|
||||
|
||||
serviceKey := ServiceKey(self.userCfg.Prefix, t[1], "")
|
||||
|
||||
tlog.Debugf("ETCD RESOLVER||Call(serviceKey: %s, method: %s)",
|
||||
serviceKey, method)
|
||||
|
||||
conn, ok := self.rpcServers[serviceKey]
|
||||
if !ok {
|
||||
tlog.Errorf("ETCD RESOLVER||method: %s||no grpc client available", method)
|
||||
return fmt.Errorf("no grpc client available")
|
||||
}
|
||||
|
||||
return grpc.Invoke(ctx, method, args, reply, conn, opts...)
|
||||
}
|
||||
39
gfetcd/config.go
Normal file
39
gfetcd/config.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package gfetcd
|
||||
|
||||
import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_DIAL_TIMEOUT = time.Second * 5
|
||||
DEFAULT_LEASE_TTL = 5
|
||||
)
|
||||
|
||||
// 基础配置,公共字段
|
||||
type BasicConfig struct {
|
||||
DialTimeout int `toml:"dial_timeout"` // 连接超时时间
|
||||
EndPoints []string `toml:"endpoints"` // etcd的地址
|
||||
Prefix string `toml:"prefix"` // etcd服务的路径前缀,区分环境用
|
||||
Debug bool `toml:"debug"` // 使用debug模式
|
||||
}
|
||||
|
||||
func etcdDial(cfg *BasicConfig) (*clientv3.Client, error) {
|
||||
dialTimeout := DEFAULT_DIAL_TIMEOUT
|
||||
if cfg.DialTimeout > 0 {
|
||||
dialTimeout = time.Duration(cfg.DialTimeout) * time.Second
|
||||
}
|
||||
|
||||
return clientv3.New(clientv3.Config{
|
||||
Endpoints: cfg.EndPoints,
|
||||
DialTimeout: dialTimeout,
|
||||
DialKeepAliveTime: 2 * time.Second,
|
||||
DialKeepAliveTimeout: 2 * time.Second,
|
||||
})
|
||||
}
|
||||
|
||||
func ServiceKey(opts ...string) string {
|
||||
return path.Join(opts...)
|
||||
}
|
||||
159
gfetcd/resolver.go
Normal file
159
gfetcd/resolver.go
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gfetcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
etcd "github.com/coreos/etcd/clientv3"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/naming"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var ErrWatcherClosed = fmt.Errorf("naming: watch closed")
|
||||
|
||||
// GRPCResolver creates a grpc.Watcher for a target to track its resolution changes.
|
||||
type GFgrpcResolver struct {
|
||||
// Client is an initialized etcd client.
|
||||
Client *etcd.Client
|
||||
}
|
||||
|
||||
/*
|
||||
func (gr *GRPCResolver) Update(ctx context.Context, target string, nm naming.Update, opts ...etcd.OpOption) (err error) {
|
||||
switch nm.Op {
|
||||
case naming.Add:
|
||||
var v []byte
|
||||
if v, err = json.Marshal(nm); err != nil {
|
||||
return status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
_, err = gr.Client.KV.Put(ctx, target+"/"+nm.Addr, string(v), opts...)
|
||||
case naming.Delete:
|
||||
_, err = gr.Client.Delete(ctx, target+"/"+nm.Addr, opts...)
|
||||
default:
|
||||
return status.Error(codes.InvalidArgument, "naming: bad naming op")
|
||||
}
|
||||
return err
|
||||
}
|
||||
*/
|
||||
|
||||
func (gr *GFgrpcResolver) Resolve(target string) (naming.Watcher, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
w := &GFgRPCWatcher{c: gr.Client, target: target + "/", ctx: ctx, cancel: cancel}
|
||||
return w, nil
|
||||
}
|
||||
|
||||
type GFgRPCWatcher struct {
|
||||
c *etcd.Client
|
||||
target string
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
wch etcd.WatchChan
|
||||
err error
|
||||
}
|
||||
|
||||
// Next gets the next set of updates from the etcd resolver.
|
||||
// Calls to Next should be serialized; concurrent calls are not safe since
|
||||
// there is no way to reconcile the update ordering.
|
||||
func (gw *GFgRPCWatcher) Next() ([]*naming.Update, error) {
|
||||
if gw.wch == nil {
|
||||
// first Next() returns all addresses
|
||||
return gw.firstNext()
|
||||
}
|
||||
if gw.err != nil {
|
||||
return nil, gw.err
|
||||
}
|
||||
|
||||
// process new events on target/*
|
||||
wr, ok := <-gw.wch
|
||||
if !ok {
|
||||
gw.err = status.Error(codes.Unavailable, ErrWatcherClosed.Error())
|
||||
return nil, gw.err
|
||||
}
|
||||
if gw.err = wr.Err(); gw.err != nil {
|
||||
return nil, gw.err
|
||||
}
|
||||
|
||||
updates := make([]*naming.Update, 0, len(wr.Events))
|
||||
for _, e := range wr.Events {
|
||||
var jupdate naming.Update
|
||||
var err error
|
||||
var jsonVal bool
|
||||
|
||||
switch e.Type {
|
||||
case etcd.EventTypePut:
|
||||
if strings.HasPrefix(string(e.Kv.Value), "{") {
|
||||
err = json.Unmarshal(e.Kv.Value, &jupdate)
|
||||
jsonVal = true
|
||||
} else {
|
||||
jupdate.Addr = string(e.Kv.Value)
|
||||
}
|
||||
jupdate.Op = naming.Add
|
||||
|
||||
case etcd.EventTypeDelete:
|
||||
if strings.HasPrefix(string(e.PrevKv.Value), "{") {
|
||||
err = json.Unmarshal(e.PrevKv.Value, &jupdate)
|
||||
} else {
|
||||
jupdate.Addr = string(e.PrevKv.Value)
|
||||
}
|
||||
jupdate.Op = naming.Delete
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if jsonVal {
|
||||
if err == nil {
|
||||
updates = append(updates, &jupdate)
|
||||
}
|
||||
} else {
|
||||
updates = append(updates, &jupdate)
|
||||
}
|
||||
}
|
||||
return updates, nil
|
||||
}
|
||||
|
||||
func (gw *GFgRPCWatcher) firstNext() ([]*naming.Update, error) {
|
||||
// Use serialized request so resolution still works if the target etcd
|
||||
// server is partitioned away from the quorum.
|
||||
resp, err := gw.c.Get(gw.ctx, gw.target, etcd.WithPrefix(), etcd.WithSerializable())
|
||||
if gw.err = err; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updates := make([]*naming.Update, 0, len(resp.Kvs))
|
||||
for _, kv := range resp.Kvs {
|
||||
var jupdate naming.Update
|
||||
|
||||
if strings.HasPrefix(string(kv.Value), "{") {
|
||||
if err := json.Unmarshal(kv.Value, &jupdate); err != nil {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
jupdate.Addr = string(kv.Value)
|
||||
}
|
||||
updates = append(updates, &jupdate)
|
||||
}
|
||||
|
||||
opts := []etcd.OpOption{etcd.WithRev(resp.Header.Revision + 1), etcd.WithPrefix(), etcd.WithPrevKV()}
|
||||
gw.wch = gw.c.Watch(gw.ctx, gw.target, opts...)
|
||||
return updates, nil
|
||||
}
|
||||
|
||||
func (gw *GFgRPCWatcher) Close() { gw.cancel() }
|
||||
142
gfetcd/server.go
Normal file
142
gfetcd/server.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package gfetcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"globalfintech/golib/tlog"
|
||||
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
etcdnaming "github.com/coreos/etcd/clientv3/naming"
|
||||
"github.com/siddontang/go/log"
|
||||
"google.golang.org/grpc/naming"
|
||||
)
|
||||
|
||||
// 服务端配置
|
||||
type NameConfig struct {
|
||||
BasicConfig
|
||||
Servers []string `toml:"servers"` // 注册的服务名称,可以是多个
|
||||
Addr string `toml:"addr"` // 服务地址
|
||||
TTL int64 `toml:"ttl"` // lease, default 5
|
||||
UseNew bool `toml:"use_new"` // 使用新版地址格式
|
||||
}
|
||||
|
||||
func (self *NameConfig) GetAddr() string {
|
||||
if strings.HasPrefix(self.Addr, ":") {
|
||||
return fmt.Sprintf("%s%s", getLocalIP(), self.Addr)
|
||||
}
|
||||
return self.Addr
|
||||
}
|
||||
|
||||
//获取本机的内网Ip, 如果发现对方的ip 和自己的ip 相同,用127.0.0.1 替代
|
||||
func getLocalIP() string {
|
||||
ifaces, _ := net.Interfaces()
|
||||
for _, i := range ifaces {
|
||||
addrs, _ := i.Addrs()
|
||||
for _, addr := range addrs {
|
||||
var ip net.IP
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
ipAddr := ip.String()
|
||||
if strings.HasPrefix(ipAddr, "172.") || strings.HasPrefix(ipAddr, "192.") || strings.HasPrefix(ipAddr, "10.") {
|
||||
return ipAddr
|
||||
}
|
||||
}
|
||||
}
|
||||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
type Name struct {
|
||||
userCfg *NameConfig
|
||||
etcdCli *clientv3.Client
|
||||
stopSignal chan bool
|
||||
}
|
||||
|
||||
func NameRegist(cfg *NameConfig) (name *Name, err error) {
|
||||
|
||||
cli, err := etcdDial(&cfg.BasicConfig)
|
||||
if err != nil {
|
||||
tlog.Errorf("ETCD NAME||endpoints: %#v||err: %s", cfg.EndPoints, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name = &Name{
|
||||
userCfg: cfg,
|
||||
etcdCli: cli,
|
||||
stopSignal: make(chan bool, 1),
|
||||
}
|
||||
|
||||
if cfg.TTL == 0 {
|
||||
cfg.TTL = DEFAULT_LEASE_TTL
|
||||
}
|
||||
|
||||
go func() {
|
||||
|
||||
ticker := time.NewTicker(time.Second * time.Duration(cfg.TTL/2))
|
||||
serviceAddr := cfg.GetAddr()
|
||||
resolver := &etcdnaming.GRPCResolver{Client: cli}
|
||||
|
||||
for {
|
||||
|
||||
lease, err := cli.Grant(context.Background(), cfg.TTL)
|
||||
if err != nil {
|
||||
tlog.Errorf("ETCD NAME||endpoints: %#v||grant err: %s",
|
||||
cfg.EndPoints, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, serviceName := range cfg.Servers {
|
||||
|
||||
serviceKey := ServiceKey("/new", cfg.Prefix, serviceName)
|
||||
|
||||
if err = resolver.Update(context.Background(), serviceKey,
|
||||
naming.Update{Op: naming.Add, Addr: serviceAddr},
|
||||
clientv3.WithLease(lease.ID)); err != nil {
|
||||
|
||||
tlog.Errorf("ETCD NAME||endpoints: %#v service: %s addr: %s||update err: %s",
|
||||
cfg.EndPoints, serviceKey, serviceAddr, err)
|
||||
}
|
||||
|
||||
if !cfg.UseNew {
|
||||
serviceKey = ServiceKey(cfg.Prefix, serviceName, serviceAddr)
|
||||
if _, err = cli.Put(context.Background(), serviceKey, serviceAddr, clientv3.WithLease(lease.ID)); err != nil {
|
||||
tlog.Errorf("ETCD NAME||endpoints: %#v put key: %s value: %s||err: %s",
|
||||
cfg.EndPoints, serviceKey, serviceAddr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-name.stopSignal:
|
||||
tlog.Info("ETCD NAME||received stop signal")
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func (self *Name) Unregister() {
|
||||
self.stopSignal <- true
|
||||
//self.stopSignal = make(chan bool, 1) // just a hack to avoid multi unregister deadlock
|
||||
|
||||
serviceAddr := self.userCfg.GetAddr()
|
||||
for _, serviceName := range self.userCfg.Servers {
|
||||
serviceKey := ServiceKey(self.userCfg.Prefix, serviceName, serviceAddr)
|
||||
|
||||
if _, err := self.etcdCli.Delete(context.Background(), serviceKey); err != nil {
|
||||
tlog.Errorf("ETCD NAME||unregister key: %s||err: %s", serviceKey, err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("ETCD SERVER||unregister done")
|
||||
}
|
||||
Reference in New Issue
Block a user