Initial commit

This commit is contained in:
Cruise Zhao
2020-04-06 19:57:19 +08:00
commit 2fc86fd0a1
42 changed files with 4056 additions and 0 deletions

79
gfetcd/README.md Normal file
View 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
View 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
View 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
View 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
View 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")
}