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

44
.gitignore vendored Normal file
View File

@@ -0,0 +1,44 @@
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
.DS_Store
*~
*.swp
*.swo
*.dSYM
*.bak
# IDE project
*.project
*.buildpath
.idea/*
.idea
# Xcode
## gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
## Other
*.xccheckout
*.moved-aside
*.xcuserstate
*.xcscmblueprint
## Playgrounds
timeline.xctimeline
playground.xcworkspace
.build/

24
Readme.md Normal file
View File

@@ -0,0 +1,24 @@
#Go lib
## tlog
## trace
## metrics
## cache
## lrucache
## db
mysql
## kv
redis
## kafka
## aws
## geo
maxmind geoip2
## qgrpc

126
aws/aws.go Normal file
View File

@@ -0,0 +1,126 @@
package aws
import (
"io"
"io/ioutil"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
type Config struct {
AwsAccessKeyId string `toml:"access_key_id" json:"access_key_id"`
AwsSecretAccessKey string `toml:"secret_access_key" json:"secret_access_id"`
AwsBuckets string `toml:"buckets" json:"buckets"`
AwsRegion string `toml:"region" json:"region"`
}
func NewS3Session(c *Config) *session.Session {
cfg := aws.NewConfig().WithRegion(c.AwsRegion).WithCredentials(
credentials.NewStaticCredentials(
c.AwsAccessKeyId,
c.AwsSecretAccessKey,
""),
)
s, _ := session.NewSession(cfg)
return s
}
func Put(c Config, key, contentType string, input io.ReadSeeker) error {
s := NewS3Session(&c)
client := s3.New(s)
_, err := client.PutObject(
&s3.PutObjectInput{
Bucket: aws.String(c.AwsBuckets),
Key: aws.String(key),
ContentType: aws.String(contentType),
Body: input,
CacheControl: aws.String("max-age=2592000"),
},
)
return err
}
func Del(c Config, key string) error {
s := NewS3Session(&c)
client := s3.New(s)
arg := &s3.DeleteObjectInput{
Bucket: aws.String(c.AwsBuckets),
Key: aws.String(key),
}
_, err := client.DeleteObject(arg)
return err
}
func Get(c Config, key string) (string, []byte, error) {
s := NewS3Session(&c)
client := s3.New(s)
arg := &s3.GetObjectInput{
Bucket: aws.String(c.AwsBuckets),
Key: aws.String(key),
}
ret, err := client.GetObject(arg)
if err != nil {
return "", nil, err
}
if ret != nil {
defer ret.Body.Close()
}
contentType := ""
if ret != nil && ret.ContentType != nil {
contentType = *ret.ContentType
}
b, err := ioutil.ReadAll(ret.Body)
return contentType, b, err
}
func List(c Config, prefix string, file string, count int64) ([]string, error) {
s := NewS3Session(&c)
client := s3.New(s)
input := &s3.ListObjectsInput{
Bucket: aws.String(c.AwsBuckets),
Prefix: aws.String(prefix),
Marker: aws.String(file),
MaxKeys: aws.Int64(count),
}
ret, err := client.ListObjects(input)
if err != nil {
return nil, err
}
keys := []string{}
for _, obj := range ret.Contents {
if strings.HasSuffix(*obj.Key, "/") {
continue
}
keys = append(keys, *obj.Key)
}
return keys, nil
}
func Copy(c Config, source, key string) error {
s := NewS3Session(&c)
client := s3.New(s)
_, err := client.CopyObject(&s3.CopyObjectInput{
Bucket: aws.String(c.AwsBuckets),
CopySource: aws.String(source),
Key: aws.String(key),
})
return err
}

122
cache/cache.go vendored Normal file
View File

@@ -0,0 +1,122 @@
package cache
import (
"errors"
"github.com/creachadair/cityhash"
"globalfintech/golib/tlog"
"sync"
"time"
)
//API
var Nil error = errors.New("cache: nil")
func Set(key string, value interface{}) error {
return SetEx(key, value, 86400)
}
func SetEx(key string, value interface{}, exp int) error {
return setEx(key, value, exp)
}
func Get(key string) (interface{}, error) {
return get(key)
}
func Del(key string) error {
return del(key)
}
//internal
var dbs map[byte]*db
type db struct {
m map[string]*item
t time.Duration
sync.RWMutex
}
type item struct {
t int
d interface{}
}
func init() {
if dbs == nil {
dbs = make(map[byte]*db, 256)
for i := 0; i <= 255; i++ {
dbs[byte(i)] = newdb(time.Duration(3600+i) * time.Second)
}
}
}
func newdb(t time.Duration) *db {
ret := &db{
t: t,
m: make(map[string]*item),
}
go ret.flush()
return ret
}
//定期扫描过期的key
func (this *db) flush() {
for range time.NewTicker(this.t).C {
this.doflush()
}
}
func (this *db) doflush() {
start := time.Now()
tt := int(start.Unix())
var i, j int
this.Lock()
for key, item := range this.m {
i++
if item.t != 0 && item.t < tt {
j++
delete(this.m, key)
}
}
this.Unlock()
tlog.Infof("FlushCacheKey||Totoal=%d||Expired=%d||t=%s", i, j, time.Since(start))
}
func setEx(key string, value interface{}, exp int) error {
if exp <= 0 {
return errors.New("args error, expTime must be > 0")
}
db, _ := dbs[hash(key)]
db.Lock()
db.m[key] = &item{
d: value,
t: int(time.Now().Unix()) + exp}
db.Unlock()
return nil
}
func get(key string) (interface{}, error) {
db, _ := dbs[hash(key)]
db.RLock()
item, ok := db.m[key]
db.RUnlock()
if ok {
if item.t >= int(time.Now().Unix()) {
return item.d, nil
}
del(key)
}
return nil, Nil
}
func del(key string) error {
db, _ := dbs[hash(key)]
db.Lock()
delete(db.m, key)
db.Unlock()
return nil
}
func hash(key string) byte {
return byte(cityhash.Hash32([]byte(key)))
}

88
cache/cache_test.go vendored Normal file
View File

@@ -0,0 +1,88 @@
package cache
import (
"fmt"
"testing"
"time"
)
type T struct {
A int
B string
}
func TestSet(t *testing.T) {
err := Set("test", "test")
if err != nil {
fmt.Println(err)
return
}
value, err := Get("test")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(value.(string))
}
func TestStruct(t *testing.T) {
err := Set("struct", T{A: 1, B: "testtest"})
if err != nil {
fmt.Println(err)
return
}
value1, err := Get("struct")
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v\n", value1.(T))
}
func TestStruct1(t *testing.T) {
err := Set("struct", &T{A: 1, B: "testtest"})
if err != nil {
fmt.Println(err)
return
}
value1, err := Get("struct")
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v\n", value1.(*T))
}
func TestDel(t *testing.T) {
err := Del("test")
if err != nil {
fmt.Println(err)
return
}
value, err := Get("test")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(value.(string))
}
func TestSetEx(t *testing.T) {
err := SetEx("testex", []byte("testex"), 2)
if err != nil {
fmt.Println(err)
}
valueEx, err := Get("testex")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(valueEx.([]byte))
time.Sleep(3 * time.Second)
valueEx, err = Get("testex")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(valueEx.([]byte))
}

67
db/db.go Normal file
View File

@@ -0,0 +1,67 @@
package db
import (
"database/sql"
"errors"
"fmt"
"time"
_ "github.com/go-sql-driver/mysql"
)
var x map[string]*sql.DB = make(map[string]*sql.DB)
var ErrNoDb = errors.New("no db")
type Config struct {
Addr string `toml:"addr" json:"addr"`
User string `toml:"user" json:"user"`
Pwd string `toml:"pwd" json:"pwd"`
Db string `toml:"db" json:"db"`
Options string `toml:"options" json:"options"`
MaxOpenConns int `toml:"max_open_conns" json:"max_open_conns"`
MaxIdleConns int `toml:"max_idle_conns" json:"max_idle_conns"`
MaxConnLifeTime int `toml:"max_conn_life_time" json:"max_conn_life_time"`
}
func (c Config) Dsn() string {
if c.Options == "" {
format := "%s:%s@tcp(%s)/%s?charset=utf8mb4&timeout=5s"
return fmt.Sprintf(format, c.User, c.Pwd, c.Addr, c.Db)
} else {
format := "%s:%s@tcp(%s)/%s?charset=utf8mb4&timeout=5s&%s"
return fmt.Sprintf(format, c.User, c.Pwd, c.Addr, c.Db, c.Options)
}
}
func Add(name string, c Config) error {
db, err := sql.Open("mysql", c.Dsn())
if err != nil {
return err
}
if err := db.Ping(); err != nil {
return err
}
db.SetMaxIdleConns(c.MaxIdleConns)
db.SetMaxOpenConns(c.MaxOpenConns)
if c.MaxConnLifeTime <= 0 {
c.MaxConnLifeTime = 600 // Default 10 minutes
}
db.SetConnMaxLifetime(time.Duration(c.MaxConnLifeTime) * time.Second)
_, ok := x[name]
if ok {
return errors.New("db exists")
}
x[name] = db
return nil
}
func GetDB(name string) (*sql.DB, error) {
db, ok := x[name]
if ok {
return db, nil
}
return nil, ErrNoDb
}

92
geo/geo.go Normal file
View File

@@ -0,0 +1,92 @@
package geo
import (
"net"
iplib "github.com/oschwald/geoip2-golang"
)
var db *iplib.Reader
func Init(file string) (err error) {
db, err = iplib.Open(file)
return
}
func Close() {
if db != nil {
db.Close()
}
}
func City(ip string) (*iplib.City, error) {
return db.City(net.ParseIP(ip))
}
func GetCity(ip string) (string, error) {
r, err := db.City(net.ParseIP(ip))
if err != nil {
return "", err
}
return r.City.Names["en"], nil
}
func GetCountry(ip string) (string, error) {
r, err := db.City(net.ParseIP(ip))
if err != nil {
return "", err
}
return r.Country.Names["en"], nil
}
func GetProvince(ip string) (string, error) {
defer func() { recover() }()
r, err := db.City(net.ParseIP(ip))
if err != nil {
return "", err
}
return r.Subdivisions[0].Names["en"], nil
}
//同时获取国家,省份,简写, 城市名称
func GetLoc(ip string) (string, string, string, error) {
defer func() { recover() }()
r, err := db.City(net.ParseIP(ip))
if err != nil {
return "", "", "", err
}
return r.Country.IsoCode, r.Subdivisions[0].IsoCode, r.City.Names["en"], nil
}
// 获取 iso_code, name
func GetContinent(ip string) (string, string, error) {
defer func() { recover() }()
r, err := db.City(net.ParseIP(ip))
if err != nil {
return "", "", err
}
return r.Continent.Code, r.Continent.Names["en"], nil
}
// Return latitude and longitude
func GetCoordinate(ip string) (float64, float64, error) {
defer func() { recover() }()
r, err := db.City(net.ParseIP(ip))
if err != nil {
return 0, 0, err
}
return r.Location.Latitude, r.Location.Longitude, nil
}
func GetTimezone(ip string) (string, error) {
defer func() { recover() }()
r, err := db.City(net.ParseIP(ip))
if err != nil {
return "", err
}
return r.Location.TimeZone, nil
}

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")
}

52
go.mod Normal file
View File

@@ -0,0 +1,52 @@
module globalfintech/golib
go 1.12
require (
github.com/Shopify/sarama v1.22.0
github.com/aws/aws-sdk-go v1.19.15
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668
github.com/bsm/sarama-cluster v2.1.15+incompatible
github.com/coreos/bbolt v1.3.2 // indirect
github.com/coreos/etcd v3.3.15+incompatible
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
github.com/creachadair/cityhash v0.1.0
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/go-redis/redis v6.15.2+incompatible
github.com/go-sql-driver/mysql v1.4.1
github.com/gogo/protobuf v1.2.1 // indirect
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
github.com/golang/protobuf v1.3.1
github.com/google/btree v1.0.0 // indirect
github.com/google/uuid v1.1.1 // indirect
github.com/gorilla/websocket v1.4.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.8.5 // indirect
github.com/hashicorp/consul/api v1.2.0
github.com/hashicorp/golang-lru v0.5.1
github.com/jonboulle/clockwork v0.1.0 // indirect
github.com/json-iterator/go v1.1.7 // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/onsi/ginkgo v1.8.0 // indirect
github.com/onsi/gomega v1.5.0 // indirect
github.com/oschwald/geoip2-golang v1.2.1
github.com/oschwald/maxminddb-golang v1.3.0 // indirect
github.com/prometheus/client_golang v0.9.2
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726
github.com/sirupsen/logrus v1.4.1 // indirect
github.com/soheilhy/cmux v0.1.4 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
go.etcd.io/bbolt v1.3.2 // indirect
go.uber.org/atomic v1.3.2 // indirect
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.9.1 // indirect
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
google.golang.org/grpc v1.20.1
sigs.k8s.io/yaml v1.1.0 // indirect
)

265
go.sum Normal file
View File

@@ -0,0 +1,265 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DataDog/zstd v1.3.5 h1:DtpNbljikUepEPD16hD4LvIcmhnhdLTiW/5pHgbmp14=
github.com/DataDog/zstd v1.3.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Shopify/sarama v1.22.0 h1:rtiODsvY4jW6nUV6n3K+0gx/8WlAwVt+Ixt6RIvpYyo=
github.com/Shopify/sarama v1.22.0/go.mod h1:lm3THZ8reqBDBQKQyb5HB3sY1lKp3grEbQ81aWSgPp4=
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go v1.19.15 h1:Ov8zxPunHxZFxrXBarqzkZBSZugJRzPgb4mfq/SyVgA=
github.com/aws/aws-sdk-go v1.19.15/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA=
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/bsm/sarama-cluster v2.1.15+incompatible h1:RkV6WiNRnqEEbp81druK8zYhmnIgdOjqSVi0+9Cnl2A=
github.com/bsm/sarama-cluster v2.1.15+incompatible/go.mod h1:r7ao+4tTNXvWm+VRpRJchr2kQhqxgmAp2iEX5W96gMM=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.15+incompatible h1:+9RjdC18gMxNQVvSiXvObLu29mOFmkgdsB4cRTlV+EE=
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/creachadair/cityhash v0.1.0 h1:rzSfISre5xT/EgbVvXBmsUJ58Hsrp8ccYMZVvc5uBmk=
github.com/creachadair/cityhash v0.1.0/go.mod h1:bYNmpea/cMZgL9Rjt+ZfDfjaT0hScMO6SSGV1DN5lEM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
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/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJlb8Kqsd41CTE=
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.2.0 h1:oPsuzLp2uk7I7rojPKuncWbZ+m5TMoD4Ivs+2Rkeh4Y=
github.com/hashicorp/consul/api v1.2.0/go.mod h1:1SIkFYi2ZTXUE5Kgt179+4hH33djo11+0Eo2XgTAtkw=
github.com/hashicorp/consul/sdk v0.2.0 h1:GWFYFmry/k4b1hEoy7kSkmU8e30GAyI4VZHk0fRxeL4=
github.com/hashicorp/consul/sdk v0.2.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU=
github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE=
github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE=
github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41 h1:GeinFsrjWz97fAxVUEd748aV0cYL+I6k44gFJTCVvpU=
github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 h1:bselrhR0Or1vomJZC8ZIjWtbDmn9OYFLX5Ik9alpJpE=
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

55
kafka/README.md Normal file
View File

@@ -0,0 +1,55 @@
# kafka cluster封装
> 目前只封装了consumer单个consumer支持同时消费一个topic
> 如果指定了group且group名称开头不为'test_consumer_'则认为是正式订阅,会在处理完成后标记消息已经被消费
> 否则会生成随机的group名称用于测试
## 示例代码如下
```go
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"golib/kafka"
)
// 回调的消息处理函数,用户自己实现的消息处理逻辑
func processor(msg []byte) {
fmt.Println("recv msg:", string(msg))
}
func main() {
// 配置,可以从文件加载
cfg := kafka.Config{
Brokers: []string{"test.kafka.o-kash.com:9092"},
Topic: "charge_success",
Workers: 5, // 并发的回调函数数量不是partition的数量
}
if consumer := kafka.NewConsumer(&cfg, processor); consumer != nil {
consumer.Run()
// 监控退出信号通知consumer退出
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
consumer.Stop()
}
}
```
## 产生测试用的消息
使用官方工具
```shell
./kafka-console-producer.sh --broker-list test.kafka.o-kash.com:9092 --topic charge_success
```

123
kafka/kafka.go Normal file
View File

@@ -0,0 +1,123 @@
package kafka
import (
"fmt"
"math/rand"
"strings"
"sync"
"globalfintech/golib/tlog"
"github.com/Shopify/sarama"
cluster "github.com/bsm/sarama-cluster"
)
type Config struct {
Brokers []string `toml:"brokers"`
Topic string `toml:"topic"`
Group string `toml:"group"`
Workers int `toml:"workers"`
Oldest bool `toml:"oldest"`
}
type Consumer struct {
test bool
consumer *cluster.Consumer
workers int
handler func([]byte)
stop chan bool
wg *sync.WaitGroup
}
const (
TEST_PREFIX = "test_consumer_"
)
func NewConsumer(cfg *Config, hdl func([]byte)) *Consumer {
var (
csm *cluster.Consumer
err error
group string
)
config := cluster.NewConfig()
config.Consumer.Return.Errors = true
config.Group.Return.Notifications = true
if cfg.Oldest {
config.Consumer.Offsets.Initial = sarama.OffsetOldest
} else {
config.Consumer.Offsets.Initial = sarama.OffsetNewest
}
group = cfg.Group
if group == "" {
group = fmt.Sprintf("%s%.8x", TEST_PREFIX, rand.Int63())
}
tlog.Infof("NewConsumer() brokers=%#v, topic=%s, workers=%d, group=%s", cfg.Brokers, cfg.Topic, cfg.Workers, group)
if csm, err = cluster.NewConsumer(cfg.Brokers, group, []string{cfg.Topic}, config); err != nil {
tlog.Errorf("NewConsumer() create fail: %s", err)
return nil
}
tlog.Infof("NewConsumer() Created OK! %#v", csm)
go func() {
for err := range csm.Errors() {
tlog.Errorf("KafkaConsumer<brokers=%#v, group=%s> Error: %s", cfg.Brokers, group, err.Error())
}
}()
go func() {
for ntf := range csm.Notifications() {
tlog.Infof("KafkaConsumer<brokers=%#v, group=%s> Notice: %#v", cfg.Brokers, group, ntf)
}
}()
return &Consumer{
test: strings.HasPrefix(group, TEST_PREFIX),
consumer: csm,
workers: cfg.Workers,
handler: hdl,
stop: make(chan bool),
wg: &sync.WaitGroup{},
}
}
func (self *Consumer) Run() {
for i := 0; i < self.workers; i++ {
go self.Worker()
}
}
func (self *Consumer) Stop() {
close(self.stop)
self.wg.Wait()
self.consumer.Close()
}
func (self *Consumer) Worker() {
self.wg.Add(1)
defer self.wg.Done()
for {
select {
case msg, ok := <-self.consumer.Messages():
if ok {
//tlog.Debugf("topic: %s, partition: %d, offset: %d, msg: %s", msg.Topic, msg.Partition, msg.Offset, string(msg.Value))
self.handler(msg.Value)
// 测试用的group不标记处理完毕防止破坏集群数据
if !self.test {
self.consumer.MarkOffset(msg, "done")
}
}
case <-self.stop:
tlog.Info("KafkaConsumer stop signal received!")
return
}
}
}

98
kv/kv.go Normal file
View File

@@ -0,0 +1,98 @@
package kv
import (
"errors"
"github.com/go-redis/redis"
"net"
"time"
)
var x map[string]*redis.Client = make(map[string]*redis.Client)
type Config struct {
Addrs []string `toml:"addrs" json:"addrs"`
Pwd string `toml:"pwd" json:"pwd"`
PoolSize int `toml:"pool_size" json:"pool_size"`
ReadTimeout int `toml:"read_timeout" json:"read_timeout"`
}
type UnixConfig struct {
Addr string `toml:"addr" json:"addr"`
PoolSize int `toml:"pool_size" json:"pool_size"`
}
func AddByUnix(name string, c *UnixConfig) error {
p := redis.NewClient(&redis.Options{
Network: "unix",
Addr: c.Addr,
PoolSize: c.PoolSize,
})
x[name] = p
return nil
}
func Add(name string, c *Config) error {
p, err := NewRedisClientWithConfig(c)
if err != nil {
return err
}
x[name] = p
return nil
}
func GetRedisConn(name string) (*redis.Client, error) {
conn, ok := x[name]
if ok {
return conn, nil
}
return nil, errors.New("no redis conn")
}
func (c *Config) Parse() (*redis.Options, error) {
redisNum := len(c.Addrs)
if redisNum < 1 {
return nil, errors.New("redis addrs is empty")
}
ch := make(chan []string, redisNum)
for i := 0; i < redisNum; i++ {
list := make([]string, redisNum)
for j := 0; j < redisNum; j++ {
list[j] = c.Addrs[(i+j)%redisNum]
}
ch <- list
}
if c.ReadTimeout < 1 {
c.ReadTimeout = 1
}
options := &redis.Options{
Password: c.Pwd,
PoolSize: c.PoolSize,
ReadTimeout: time.Duration(c.ReadTimeout) * time.Second,
WriteTimeout: time.Second,
PoolTimeout: time.Second,
IdleTimeout: 60 * time.Second,
Dialer: func() (net.Conn, error) {
list := <-ch
ch <- list
for _, addr := range list {
c, err := net.DialTimeout("tcp", addr, 1000*time.Millisecond)
if err == nil {
return c, nil
}
}
return nil, errors.New("all redis down")
},
}
return options, nil
}
func NewRedisClientWithConfig(c *Config) (*redis.Client, error) {
options, err := c.Parse()
if err != nil {
return nil, err
}
return redis.NewClient(options), nil
}

61
kv/mkv.go Normal file
View File

@@ -0,0 +1,61 @@
package kv
import (
"errors"
"github.com/bradfitz/gomemcache/memcache"
)
var ErrNoClient error = errors.New("no memcache client")
var mClient *memcache.Client
//memcache 的配置
type MConfig struct {
Addrs []string `toml:"addrs" json:"addrs"`
PoolSize int `toml:"pool_size" json:"pool_size"`
}
func (c *MConfig) GetAddrs() []string {
if c != nil {
return c.Addrs
}
return []string{}
}
func (c *MConfig) GetPoolSize() int {
if c != nil {
return c.PoolSize
}
return 5
}
func InitMcClient(c *MConfig) {
mClient = memcache.New(c.GetAddrs()...)
mClient.MaxIdleConns = c.GetPoolSize()
return
}
func GetMcClient() *memcache.Client {
return mClient
}
func Get(key string) ([]byte, error) {
client := GetMcClient()
if client == nil {
return nil, ErrNoClient
}
item, err := client.Get(key)
if err != nil {
return nil, err
}
return item.Value, nil
}
func Del(key string) error {
if client := GetMcClient(); client == nil {
return ErrNoClient
} else {
return client.Delete(key)
}
return nil
}

108
lrucache/lrucache.go Normal file
View File

@@ -0,0 +1,108 @@
package lrucache
import (
"errors"
"github.com/creachadair/cityhash"
"github.com/hashicorp/golang-lru"
"globalfintech/golib/tlog"
"time"
)
// API
var (
Nil error = errors.New("cache: nil")
ErrExpTime error = errors.New("args error, expTime must be > 0")
dbs map[int]*lru.Cache
)
func Set(key string, value interface{}) error {
return SetEx(key, value, 86400)
}
func SetEx(key string, value interface{}, exp int) error {
return setEx(key, value, exp)
}
func Get(key string) (interface{}, error) {
return get(key)
}
func Del(key string) error {
return del(key)
}
// internal
func init() {
if dbs == nil {
dbs = make(map[int]*lru.Cache, 1024)
for i := 0; i < 1024; i++ {
dbs[i], _ = lru.New(3000)
}
}
go flush()
}
func flush() {
i := 0
for range time.NewTicker(time.Minute).C {
start := time.Now()
shard := i % 1024
db, _ := dbs[shard]
keys := db.Keys()
for _, key := range keys {
if keyS, ok := key.(string); ok {
getByDb(keyS, db)
}
}
i++
tlog.Infof("FlushKey||time=%s||shard=%d||old=%d||new=%d", time.Since(start), shard, len(keys), db.Len())
}
}
type item struct {
d interface{}
t int
}
func setEx(key string, value interface{}, exp int) error {
if exp < 1 {
return ErrExpTime
}
db, _ := dbs[hash(key)]
db.Add(key, &item{d: value, t: int(time.Now().Unix()) + exp})
return nil
}
func get(key string) (interface{}, error) {
db, _ := dbs[hash(key)]
return getByDb(key, db)
}
func getByDb(key string, db *lru.Cache) (interface{}, error) {
if value, ok := db.Get(key); ok {
if item, ok := value.(*item); ok {
if item.t >= int(time.Now().Unix()) {
return item.d, nil
} else {
delByDb(key, db)
}
}
}
return nil, Nil
}
func del(key string) error {
db, _ := dbs[hash(key)]
return delByDb(key, db)
}
func delByDb(key string, db *lru.Cache) error {
db.Remove(key)
return nil
}
func hash(key string) int {
return int(cityhash.Hash32([]byte(key)) % 1024)
}

14
metric/alert.go Normal file
View File

@@ -0,0 +1,14 @@
package metric
import (
"sync"
"github.com/prometheus/client_golang/prometheus"
)
var singletonCounter prometheus.Counter
var once sync.Once
func SingletonCounter() {
}

View File

@@ -0,0 +1,25 @@
package main
import (
"fmt"
"globalfintech/golib/metric"
)
func main() {
cfg := metric.ExporterCfg{
Listener: nil,
RegisterConsul: false,
ProductName: "ProductName_001",
ServiceRegion: "ServiceRegion_001",
AppName: "AppName_001",
}
go func() {
if err := metric.InitExporter(cfg); err != nil {
fmt.Println("prometheus exporter stopped,err: ", err)
}
}()
fmt.Println("prometheus exporter started")
select {}
}

46
metric/examples/main.go Normal file
View File

@@ -0,0 +1,46 @@
package main
import (
"fmt"
"github.com/prometheus/client_golang/prometheus"
"globalfintech/golib/metric"
"time"
)
var callCounter = metric.NewCounterVec(
prometheus.CounterOpts{
Namespace: "crm",
Name: "call_total_count2",
Help: "Total Request",
},
[]string{"privilege", "uri"},
)
func test2() {
pusher := metric.InitPusher("http://ops-dev.a.mobimagic.com:9091", "MetricsExample3", prometheus.Labels{})
pusher.Collector(callCounter).Add()
pusher.Push()
//pusher.Push(callCounter)
}
func main() {
//test2()
var labels = prometheus.Labels{"app": "app2",
"country": "INDIA",
"service": "test2",
}
var pushGateway, job = "http://ops-dev.a.mobimagic.com:9091", "app2"
//fmt.Println("labels:", labels)
metric.InitDefaultPusher(pushGateway, job, labels)
for {
callCounter.With(prometheus.Labels{"privilege": "test", "uri": "xxxxx/sswwe"}).Inc()
time.Sleep(time.Second * 1)
fmt.Println("Inc 1")
}
}

113
metric/exporter.go Normal file
View File

@@ -0,0 +1,113 @@
package metric
import (
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"strconv"
"time"
consulapi "github.com/hashicorp/consul/api"
"github.com/prometheus/client_golang/prometheus/promhttp"
"globalfintech/golib/tlog"
)
const (
DefaultListenerAddr = "0.0.0.0:9166"
)
type ExporterCfg struct {
Listener net.Listener
RegisterConsul bool
ProductName string
ServiceRegion string
AppName string
}
// Exporter init function must be executed asynchronously
func InitExporter(cfg ExporterCfg) (err error) {
lis := cfg.Listener
if cfg.Listener == nil {
if lis, err = net.Listen("tcp", DefaultListenerAddr); err != nil {
tlog.Error("fail to create listener with default address")
return err
}
}
_, port, err := net.SplitHostPort(lis.Addr().String())
if err != nil {
tlog.Error("fail to get port from listener, err: ", err)
return err
}
h := http.NewServeMux()
h.Handle("/metrics", promhttp.Handler())
h.HandleFunc("/health_checker", healthChecker)
server := &http.Server{
Handler: h,
ReadTimeout: 30 * time.Second,
ReadHeaderTimeout: 30 * time.Second,
IdleTimeout: 30 * time.Second,
WriteTimeout: 10 * time.Second,
}
if cfg.RegisterConsul {
var tags []string
tags = append(tags, fmt.Sprintf("app=%s", cfg.ProductName))
tags = append(tags, fmt.Sprintf("country=%s", cfg.ServiceRegion))
err = consulRegister(port, "", tags, cfg.AppName)
if err != nil {
tlog.Error("consul register failed, err: ", err)
return err
}
}
if err = server.Serve(lis); err != nil {
tlog.Error("fail to init prometheus exporter, err: ", err)
return err
}
return nil
}
func consulRegister(checkPort string, regAddr string, tags []string, name string) (err error) {
config := consulapi.DefaultConfig()
if regAddr != "" {
config.Address = regAddr
}
client, err := consulapi.NewClient(config)
if err != nil {
tlog.Error("fail to new consul client, err: ", err)
return err
}
intPort, _ := strconv.Atoi(checkPort)
hostName, _ := os.Hostname()
registration := &consulapi.AgentServiceRegistration{
Port: intPort,
Name: name,
ID: name + ":" + hostName,
Tags: append(tags, "lang=go"),
Check: &consulapi.AgentServiceCheck{
HTTP: fmt.Sprintf("http://%s:%s%s", "127.0.0.1", checkPort, "/health_checker"),
Interval: "10s",
Timeout: "10s",
},
}
return client.Agent().ServiceRegister(registration)
}
func healthChecker(resp http.ResponseWriter, req *http.Request) {
data := map[string]interface{}{
"code": 0,
"message": "ok",
"server_time": time.Now().Unix(),
}
res, _ := json.Marshal(data)
resp.Write(res)
}

160
metric/metric.go Normal file
View File

@@ -0,0 +1,160 @@
package metric
import (
"github.com/prometheus/client_golang/prometheus"
"sync"
)
const missVal = "MissLabel"
// CounterVecDecorator Add safe decorator
type CounterVecDecorator struct {
*prometheus.CounterVec
secureLabels []string
once *sync.Once
}
func NewCounterVec(opts prometheus.CounterOpts, labelNames []string) *CounterVecDecorator {
v := &CounterVecDecorator{prometheus.NewCounterVec(opts, labelNames), labelNames, &sync.Once{}}
return v
}
// With is secure wrap for with of prometheus.CounterVec
// there is no panic, and always calculate to metrics , if miss values, will calculate to MissLabel
// It's secure
func (v *CounterVecDecorator) With(labels prometheus.Labels) prometheus.Counter {
// To avoid initialization sequence, so must delay register to with func
v.once.Do(func() {
Register(v)
})
safeLabels := labelAlignment(v.secureLabels, labels)
//
c, err := v.GetMetricWith(safeLabels)
if err != nil {
// should never happen , but should still add handler to record alert
// TODO singleton alert Gauge and return an alert counter
}
return c
}
// GaugeVecDecorator Add safe decorator
type GaugeVecDecorator struct {
*prometheus.GaugeVec
secureLabels []string
once *sync.Once
}
func NewGaugeVec(opts prometheus.GaugeOpts, labelNames []string) *GaugeVecDecorator {
v := &GaugeVecDecorator{prometheus.NewGaugeVec(opts, labelNames), labelNames, &sync.Once{}}
return v
}
// With is secure wrap for with of prometheus.GaugeVec
// there is no panic, and always calculate to metrics , if miss values, will calculate to MissLabel
// It's secure
func (v *GaugeVecDecorator) With(labels prometheus.Labels) prometheus.Gauge {
// To avoid initialization sequence, so must delay register to with func
v.once.Do(func() {
Register(v)
})
safeLabels := labelAlignment(v.secureLabels, labels)
//
c, err := v.GetMetricWith(safeLabels)
if err != nil {
// should never happen , but should still add handler to record alert
// TODO singleton alert Gauge and return
}
return c
}
// HistogramVecDecorator Add safe
type HistogramVecDecorator struct {
*prometheus.HistogramVec
secureLabels []string
once *sync.Once
}
func NewHistogramVec(opts prometheus.HistogramOpts, labelNames []string) *HistogramVecDecorator {
v := &HistogramVecDecorator{prometheus.NewHistogramVec(opts, labelNames), labelNames, &sync.Once{}}
return v
}
// With is secure wrap for with of prometheus.HistogramVec
// there is no panic, and always calculate to metrics , if miss values, will calculate to MissLabel
// It's secure
func (v *HistogramVecDecorator) With(labels prometheus.Labels) prometheus.Observer {
// To avoid initialization sequence, so must delay register to with func
v.once.Do(func() {
Register(v)
})
safeLabels := labelAlignment(v.secureLabels, labels)
//
c, err := v.GetMetricWith(safeLabels)
if err != nil {
// should never happen , but should still add handler to record alert
// TODO singleton alert Histogram and return
}
return c
}
// SummaryVecDecorator Add safe
type SummaryVecDecorator struct {
*prometheus.SummaryVec
secureLabels []string
once *sync.Once
}
func NewSummaryVec(opts prometheus.SummaryOpts, labelNames []string) *SummaryVecDecorator {
v := &SummaryVecDecorator{prometheus.NewSummaryVec(opts, labelNames), labelNames, &sync.Once{}}
//if len(autoReg) == 0 || autoReg[0] == true {
// Register(v)
//}
return v
}
// With is secure wrap for with of prometheus.SummaryVec
// there is no panic, and always calculate to metrics , if miss values, will calculate to MissLabel
// It's secure
func (v *SummaryVecDecorator) With(labels prometheus.Labels) prometheus.Observer {
// To avoid initialization sequence, so must delay register to with func
v.once.Do(func() {
Register(v)
})
safeLabels := labelAlignment(v.secureLabels, labels)
//
c, err := v.GetMetricWith(safeLabels)
if err != nil {
// should never happen , but should still add handler to record alert
// TODO singleton alert Gauge and return
}
//prometheus.NewCounter(v.SummaryVec.)
return c
}
// 标签对齐
func labelAlignment(labelNames []string, labels prometheus.Labels) prometheus.Labels {
newLabels := make(prometheus.Labels, len(labelNames))
for _, val := range labelNames {
if v, ok := labels[val]; ok {
newLabels[val] = v
} else {
newLabels[val] = missVal
}
}
return newLabels
}

48
metric/metric_test.go Normal file
View File

@@ -0,0 +1,48 @@
package metric
import (
"github.com/prometheus/client_golang/prometheus"
"reflect"
"testing"
)
func Test_labelAlignment(t *testing.T) {
tds := []struct {
in1 []string
in2 prometheus.Labels
in2New prometheus.Labels
}{
{
[]string{"label1", "label2"},
map[string]string{"label1": "1"},
map[string]string{"label1": "1", "label2": missVal},
},
{
[]string{"label1", "label3"},
map[string]string{"label1": "1", "label2": "2"},
map[string]string{"label1": "1", "label3": missVal},
},
{
[]string{},
map[string]string{"label1": "1", "label2": "2"},
map[string]string{},
},
{
[]string{"label1", "label3", "label4"},
map[string]string{"label1": "1", "label2": "2"},
map[string]string{"label1": "1", "label3": missVal, "label4": missVal},
},
}
for _, d := range tds {
//var org map[string]string
//copy(org, d.in2)
result := labelAlignment(d.in1, d.in2)
t.Log(result, d.in2New)
if !reflect.DeepEqual(result, d.in2New) {
t.Errorf("Input: %#v, Expected is not same as , get ", d)
}
}
}

69
metric/push.go Normal file
View File

@@ -0,0 +1,69 @@
package metric
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/push"
"os"
"time"
)
var defaultPusher *push.Pusher
const (
defaultPushIntervalSecond = 5
)
func InitDefaultPusher(gateway string, job string, labels prometheus.Labels, pushIntervalSecond ...int) {
defaultPusher = InitPusher(gateway, job, labels)
// for default pusher , we should add process collector and go collector
// then we can monitor all base monitor on our job
defaultPusher.
Collector(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{})).
Collector(prometheus.NewGoCollector()).
Add()
// TODO implement init
// and push
//defaultPusher = InitPusher("")
go func() {
var pushInterval = defaultPushIntervalSecond * time.Second
if len(pushIntervalSecond) > 0 {
pushInterval = time.Second * time.Duration(pushIntervalSecond[0])
}
for {
defaultPusher.Push()
time.Sleep(pushInterval)
}
}()
}
// ExitTriggerLastPush
// TODO
func ExitTriggerLastPush() {
defaultPusher.Push()
}
//var MustLabel prometheus.Labels
//
//type RequireLabelConf struct {
// Service string `json:"service";yaml:`
// App string ``
// Country string
//}
// Init Pusher
// return prometheus pusher
// TODO add labels
// need lan ,app,country, service labels to implement
func InitPusher(gateway string, job string, labels prometheus.Labels) *push.Pusher {
host, _ := os.Hostname()
p := push.New(gateway, job).Grouping("instance", host).Grouping("lang","go")
for name, value := range labels {
p.Grouping(name, value)
}
return p
}

27
metric/register.go Normal file
View File

@@ -0,0 +1,27 @@
package metric
import (
"github.com/prometheus/client_golang/prometheus"
)
// Register Here, we must decide pusher/exporter which we use
// it's very important, note: var/init/func init sort
// keep, when a git rep, not only a service or job
// collector will mixed with Push And Registered for pull
// So register with pull and add for push collector must control in one place
// then we can use conf to switch
// avoid collector duplicate
// we don't use must register, to avoid panic
func Register(cs ...prometheus.Collector) {
if defaultPusher == nil {
// register by exporter
for _, mv := range cs {
prometheus.Register(mv)
}
} else {
// register by pusher
for _, mv := range cs {
defaultPusher.Collector(mv).Add()
}
}
}

View File

@@ -0,0 +1,197 @@
package main
import (
"flag"
"globalfintech/golib/metrics"
"globalfintech/golib/metrics/prometheus"
"globalfintech/golib/tlog"
"math/rand"
"os"
"os/signal"
"strconv"
"sync"
"syscall"
"time"
stdprometheus "github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
)
func main() {
// parse the flags
flag.Parse()
processMetrics, err := metrics.NewProcessMetrics("test", "INDIA", "testProject")
if err != nil {
tlog.Error(err)
return
}
workPort := processMetrics.GetMetricsPort()
// init Exporter
tlog.Info("Beginning to serve on port :", workPort)
processMetrics.InitExporter()
// You can registry metrics any where as you want
// Now we registry a local gauge in function main
workersGauge := prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{
Namespace: "worker",
Subsystem: "jobs",
Name: "worker_number",
Help: "Number of workers.",
}, []string{"server"})
host, _ := os.Hostname()
workersGauge.With("server", host).Set(float64(workers))
// create a channel with a 1000 Job buffer
jobChannel := make(chan *Job, 1000)
// start the job processor
go startJobProcessor(jobChannel)
// start a Goroutine to create some mock jobs
go createJobs(jobChannel)
exit := make(chan os.Signal, 1)
// kill (no param) default send syscanll.SIGTERM
// kill -2 is syscall.SIGINT
// kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
signal.Notify(exit, syscall.SIGTERM, syscall.SIGINT)
<-exit
tlog.Info("Graceful shutdown Server in 10 seconds...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// set workers to 0 , then stop all workers and job creator
workers = 0
select {
case <-ctx.Done():
tlog.Info("timeout of 10 seconds.")
}
// init pusher
// pusher usually uses at DAG/Cron job processor
pusher := metrics.InitPusher("http://ops-dev.a.mobimagic.com:9091", "MetricsExample1")
if err := pusher.Collector(workerTotalCounter.Cv).Add(); err != nil {
tlog.Error("Could not push to Pushgateway:", err)
}
// time is up
close(jobChannel)
tlog.Infof("Jobs left: %d", len(jobChannel))
for leftJob := range jobChannel {
tlog.Info(leftJob)
}
tlog.Info("Server exit")
}
var (
modes = []string{"SMS", "WeChat", "WhatsApp", "GooglePush", "ApplePush"}
workers = 0
// Two ways to define name
// Name : fullname
// or Namespace / Subsystem / Name to combine a full name
workerTotalCounter = prometheus.NewCounterFrom(stdprometheus.CounterOpts{
Name: "worker_jobs_processed_total",
Help: "Total number of jobs processed by the workers.",
}, []string{"worker_id", "mode"})
inqueueGauge = prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{
Namespace: "worker",
Subsystem: "jobs",
Name: "inqueue",
Help: "Number of jobs in queue.",
}, []string{"mode"})
processingTimeHistogram = prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{
Name: "worker_jobs_process_time_seconds",
Help: "Amount of time spent processing jobs",
Buckets: []float64{1, 2, 3, 4, 5}, //if no set, then use DefBuckets : []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
}, []string{"worker_id", "mode"})
)
type Job struct {
Mode string
Sleep time.Duration
}
func init() {
flag.IntVar(&workers, "workers", 10, "Number of workers to use")
}
func getMode() string {
return modes[rand.Int()%len(modes)]
}
// makeJob creates a new job with a random sleep time between 0.5s and 5s
func makeJob() *Job {
return &Job{
Mode: getMode(),
Sleep: time.Duration(rand.Int()%4500+500) * time.Millisecond,
}
}
func createJobs(jobs chan<- *Job) {
for {
if workers == 0 {
tlog.Info("Stop create jobs.")
return
}
// create a random job
job := makeJob()
// track the job in the inqueue tracker
inqueueGauge.With("mode", job.Mode).Add(1)
// send the job down the channel
jobs <- job
// don't pile up too quickly
time.Sleep(200 * time.Millisecond)
}
}
// creates a worker that pulls jobs from the job channel
func startWorker(workerID int, jobs <-chan *Job) {
for {
select {
// read from the job channel
case job := <-jobs:
startTime := time.Now()
// decrement the in queue tracker
inqueueGauge.With("mode", job.Mode).Sub(1)
// fake processing the request
time.Sleep(job.Sleep)
tlog.Infof("[%d][%s] Processed job in %0.3f seconds", workerID, job.Mode, time.Now().Sub(startTime).Seconds())
// track the total number of jobs processed by the worker
workerTotalCounter.With("worker_id", strconv.FormatInt(int64(workerID), 10), "mode", job.Mode).Inc()
processingTimeHistogram.With("worker_id", strconv.FormatInt(int64(workerID), 10), "mode", job.Mode).Observe(time.Now().Sub(startTime).Seconds())
if workers == 0 {
tlog.Infof("worker[%d] quit", workerID)
return
}
}
}
}
func startJobProcessor(jobs <-chan *Job) {
tlog.Infof("[INFO] starting %d workers\n", workers)
wait := sync.WaitGroup{}
// notify the sync group we need to wait for 10 goroutines
wait.Add(workers)
// start 10 works
for i := 0; i < workers; i++ {
go func(workerID int) {
// start the worker
startWorker(workerID, jobs)
wait.Done()
}(i)
}
wait.Wait()
}

190
metrics/metrics.go Normal file
View File

@@ -0,0 +1,190 @@
package metrics
import (
"encoding/json"
"errors"
"fmt"
"globalfintech/golib/tlog"
"net/http"
"os"
"strconv"
"time"
consulapi "github.com/hashicorp/consul/api"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/client_golang/prometheus/push"
)
const (
defaultAddr string = "9166"
)
// Counter describes a metric that accumulates values monotonically.
// An example of a counter is the number of received HTTP requests.
type Counter interface {
With(labelValues ...string) Counter
Add(delta float64)
Inc()
}
// Gauge describes a metric that takes specific values over time.
// An example of a gauge is the current depth of a job queue.
type Gauge interface {
With(labelValues ...string) Gauge
Set(value float64)
Add(delta float64)
Sub(delta float64)
}
// Histogram describes a metric that takes repeated observations of the same
// kind of thing, and produces a statistical summary of those observations,
// typically expressed as quantiles or buckets. An example of a histogram is
// HTTP request latencies.
type Histogram interface {
With(labelValues ...string) Histogram
Observe(value float64)
}
// LabelValues is a type alias that provides validation on its With method.
// Metrics may include it as a member to help them satisfy With semantics and
// save some code duplication.
type LabelValues []string
// With validates the input, and returns a new aggregate labelValues.
func (lvs LabelValues) With(labelValues ...string) LabelValues {
if len(labelValues)%2 != 0 {
labelValues = append(labelValues, "unknown")
}
return append(lvs, labelValues...)
}
// new a *ProcessMetrics
// productName,serviceRegion,appName all those params must be passed
// port if not be pass,will use defaultAddr 9166
func NewProcessMetrics(productName, serviceRegion, appName string, port ...string) (*ProcessMetrics, error) {
if len(productName) == 0 || len(serviceRegion) == 0 || len(appName) == 0 {
err := errors.New("productName,serviceRegion,appName all those params must be passed")
tlog.Error(err)
return nil, err
}
addr := defaultAddr
if len(port) > 0 {
addr = port[0]
}
var processMetrics ProcessMetrics
processMetrics.Port = addr
processMetrics.ProductName = productName
processMetrics.ServiceRegion = serviceRegion
processMetrics.Appname = appName
return &processMetrics, nil
}
type ProcessMetrics struct {
Port string // port default 9166
ProductName string
ServiceRegion string
Appname string
}
// get real work port
func (s *ProcessMetrics) GetMetricsPort() string {
if len(s.Port) > 0 {
return s.Port
}
return defaultAddr
}
// Init Export on GF default port 9166
// If you want custom listen port, just pass your port to MetricsParams struct
// return exporter http server
func (s *ProcessMetrics) InitExporter() *http.Server {
h := http.NewServeMux()
h.Handle("/metrics", promhttp.Handler())
h.HandleFunc("/health_checker", s.healthChecker)
addr := defaultAddr
if len(s.Port) > 0 {
addr = s.Port
} else {
s.Port = defaultAddr
}
server := &http.Server{
Addr: ":" + addr,
Handler: h,
ReadTimeout: 30 * time.Second,
ReadHeaderTimeout: 30 * time.Second,
IdleTimeout: 30 * time.Second,
WriteTimeout: 10 * time.Second,
}
{
var tags []string
tags = append(tags, fmt.Sprintf("app=%s", s.ProductName))
tags = append(tags, fmt.Sprintf("country=%s", s.ServiceRegion))
s.initExporterOnExistHttpServer(addr, "/health_checker", tags, s.Appname)
}
go func() {
if err := server.ListenAndServe(); err != nil {
tlog.Fatal(err)
}
}()
return server
}
func (s *ProcessMetrics) healthChecker(resp http.ResponseWriter, req *http.Request) {
data := map[string]interface{}{
"code": 0,
"message": "ok",
"server_time": time.Now().Unix(),
}
res, _ := json.Marshal(data)
resp.Write(res)
}
// Init Pusher
// return prometheus pusher
func InitPusher(gateway string, job string) *push.Pusher {
host, _ := os.Hostname()
return push.New(gateway, job).Grouping("instance", host)
}
// InitExporterOnExistHttpServer Init http exporter
// Why we init exporter on exist http server:
// to avoid new custom port
// which should add healthCheckRouter - like /healthChecker func
// must export a route: /metrics router for prometheus
// promhttp.Handler().ServeHTTP(c.Ctx.ResponseWriter, c.Ctx.Request)
func (s *ProcessMetrics) initExporterOnExistHttpServer(port string, healthCheckRouter string, tags []string, name string, id ...string) {
config := consulapi.DefaultConfig()
client, err := consulapi.NewClient(config)
if err != nil {
tlog.Error("consul client error : ", err)
return
}
registration := new(consulapi.AgentServiceRegistration)
registration.Name = name
intPort, _ := strconv.Atoi(port)
registration.Port = intPort
hostName, _ := os.Hostname()
if len(id) > 0 {
registration.ID = registration.Name + ":" + id[0]
} else {
registration.ID = registration.Name + ":" + hostName
}
registration.Tags = append(tags, "lang=go")
registration.Check = new(consulapi.AgentServiceCheck)
registration.Check.HTTP = fmt.Sprintf("http://%s:%s%s",
"127.0.0.1", port, healthCheckRouter)
registration.Check.Interval = "5s"
registration.Check.Timeout = "3s"
// 为兼容现有的 平滑逻辑
//err = client.Agent().ServiceDeregister(registration.ID)
err = client.Agent().ServiceRegister(registration)
if err != nil {
tlog.Error(err)
}
}

View File

@@ -0,0 +1,169 @@
package prometheus
import (
"github.com/prometheus/client_golang/prometheus"
"globalfintech/golib/metrics"
)
// Counter implements Counter, via a Prometheus CounterVec.
type Counter struct {
Cv *prometheus.CounterVec
lvs metrics.LabelValues
}
// NewCounterFrom constructs and registers a Prometheus CounterVec,
// and returns a usable Counter object.
func NewCounterFrom(opts prometheus.CounterOpts, labelNames []string) *Counter {
cv := prometheus.NewCounterVec(opts, labelNames)
prometheus.MustRegister(cv)
return NewCounter(cv)
}
// NewCounter wraps the CounterVec and returns a usable Counter object.
func NewCounter(cv *prometheus.CounterVec) *Counter {
return &Counter{
Cv: cv,
}
}
// With implements Counter.
func (c *Counter) With(labelValues ...string) metrics.Counter {
return &Counter{
Cv: c.Cv,
lvs: c.lvs.With(labelValues...),
}
}
// Add implements Counter.
func (c *Counter) Add(delta float64) {
c.Cv.With(makeLabels(c.lvs...)).Add(delta)
}
// Inc implement Counter.
func (c *Counter) Inc() {
c.Cv.With(makeLabels(c.lvs...)).Inc()
}
// Gauge implements Gauge, via a Prometheus GaugeVec.
type Gauge struct {
gv *prometheus.GaugeVec
lvs metrics.LabelValues
}
// NewGaugeFrom construts and registers a Prometheus GaugeVec,
// and returns a usable Gauge object.
func NewGaugeFrom(opts prometheus.GaugeOpts, labelNames []string) *Gauge {
gv := prometheus.NewGaugeVec(opts, labelNames)
prometheus.MustRegister(gv)
return NewGauge(gv)
}
// NewGauge wraps the GaugeVec and returns a usable Gauge object.
func NewGauge(gv *prometheus.GaugeVec) *Gauge {
return &Gauge{
gv: gv,
}
}
// With implements Gauge.
func (g *Gauge) With(labelValues ...string) metrics.Gauge {
return &Gauge{
gv: g.gv,
lvs: g.lvs.With(labelValues...),
}
}
// Set implements Gauge.
func (g *Gauge) Set(value float64) {
g.gv.With(makeLabels(g.lvs...)).Set(value)
}
// Add is supported by Prometheus GaugeVecs.
func (g *Gauge) Add(delta float64) {
g.gv.With(makeLabels(g.lvs...)).Add(delta)
}
// Sub is supported by Prometheus GaugeVecs.
func (g *Gauge) Sub(delta float64) {
g.gv.With(makeLabels(g.lvs...)).Sub(delta)
}
// Summary implements Histogram, via a Prometheus SummaryVec. The difference
// between a Summary and a Histogram is that Summaries don't require predefined
// quantile buckets, but cannot be statistically aggregated.
type Summary struct {
sv *prometheus.SummaryVec
lvs metrics.LabelValues
}
// NewSummaryFrom constructs and registers a Prometheus SummaryVec,
// and returns a usable Summary object.
func NewSummaryFrom(opts prometheus.SummaryOpts, labelNames []string) *Summary {
sv := prometheus.NewSummaryVec(opts, labelNames)
prometheus.MustRegister(sv)
return NewSummary(sv)
}
// NewSummary wraps the SummaryVec and returns a usable Summary object.
func NewSummary(sv *prometheus.SummaryVec) *Summary {
return &Summary{
sv: sv,
}
}
// With implements Histogram.
func (s *Summary) With(labelValues ...string) metrics.Histogram {
return &Summary{
sv: s.sv,
lvs: s.lvs.With(labelValues...),
}
}
// Observe implements Histogram.
func (s *Summary) Observe(value float64) {
s.sv.With(makeLabels(s.lvs...)).Observe(value)
}
// Histogram implements Histogram via a Prometheus HistogramVec. The difference
// between a Histogram and a Summary is that Histograms require predefined
// quantile buckets, and can be statistically aggregated.
type Histogram struct {
hv *prometheus.HistogramVec
lvs metrics.LabelValues
}
// NewHistogramFrom constructs and registers a Prometheus HistogramVec,
// and returns a usable Histogram object.
func NewHistogramFrom(opts prometheus.HistogramOpts, labelNames []string) *Histogram {
hv := prometheus.NewHistogramVec(opts, labelNames)
prometheus.MustRegister(hv)
return NewHistogram(hv)
}
// NewHistogram wraps the HistogramVec and returns a usable Histogram object.
func NewHistogram(hv *prometheus.HistogramVec) *Histogram {
return &Histogram{
hv: hv,
}
}
// With implements Histogram.
func (h *Histogram) With(labelValues ...string) metrics.Histogram {
return &Histogram{
hv: h.hv,
lvs: h.lvs.With(labelValues...),
}
}
// Observe implements Histogram.
func (h *Histogram) Observe(value float64) {
h.hv.With(makeLabels(h.lvs...)).Observe(value)
}
func makeLabels(labelValues ...string) prometheus.Labels {
labels := prometheus.Labels{}
for i := 0; i < len(labelValues); i += 2 {
labels[labelValues[i]] = labelValues[i+1]
}
return labels
}

View File

@@ -0,0 +1,61 @@
package prometheus
import (
"fmt"
stdprometheus "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"globalfintech/golib/metrics"
"io/ioutil"
"math/rand"
"net/http"
"net/http/httptest"
"regexp"
"strconv"
"testing"
)
func TestCounter(t *testing.T) {
s := httptest.NewServer(promhttp.Handler())
defer s.Close()
scrape := func() string {
resp, _ := http.Get(s.URL)
buf, _ := ioutil.ReadAll(resp.Body)
return string(buf)
}
namespace, subsystem, name := "ns", "ss", "foo"
re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{alpha="alpha-value",beta="beta-value"} ([0-9\.]+)`)
counter := NewCounterFrom(stdprometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: name,
Help: "This is the help string.",
}, []string{"alpha", "beta"}).With("beta", "beta-value", "alpha", "alpha-value") // order shouldn't matter
value := func() float64 {
matches := re.FindStringSubmatch(scrape())
f, _ := strconv.ParseFloat(matches[1], 64)
return f
}
want := FillCounter(counter)
if have := value(); want != have {
t.Fatal(fmt.Errorf("want %f, have %f", want, have))
}
}
// FillCounter puts some deltas through the counter and returns the total value.
func FillCounter(counter metrics.Counter) float64 {
a := rand.Perm(100)
n := rand.Intn(len(a))
var want float64
for i := 0; i < n; i++ {
f := float64(a[i])
counter.Add(f)
want += f
}
return want
}

66
qgrpc/config.go Normal file
View File

@@ -0,0 +1,66 @@
package qgrpc
import (
"fmt"
"net"
"os"
"strings"
)
// 基础配置,公共字段
type BasicConfig struct {
DialTimeout int `toml:"dial_timeout"` // 连接超时时间
KeepAlive int `toml:"keep_alive"` // 保活时间
EndPoints []string `toml:"endpoints"` // etcd的地址
Prefix string `toml:"prefix"` // etcd服务的路径前缀区分环境用
}
// 服务端配置
type ServerConfig struct {
BasicConfig
Servers []string `toml:"servers"` // 注册的服务名称,可以是多个
Addr string `toml:"addr"` // 服务地址
}
func (self *ServerConfig) GetAddr() string {
if strings.HasPrefix(self.Addr, ":") {
localIP := getLocalIP()
return fmt.Sprintf("%s%s", localIP, self.Addr)
}
return self.Addr
}
// 客户端配置
type ClientConfig struct {
BasicConfig
WatchServers []string `toml:"watch_servers"` // 依赖的服务名称,多个服务用','分割;每个服务名后增加'|ip:port'表示默认连接的服务地址
}
func getHostName() string {
if h, err := os.Hostname(); err == nil {
return h
}
return getLocalIP()
}
//获取本机的内网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"
}

141
qgrpc/op.go Normal file
View File

@@ -0,0 +1,141 @@
package qgrpc
import (
"context"
"fmt"
"github.com/coreos/etcd/clientv3"
"globalfintech/golib/qgrpc/ping"
"globalfintech/golib/tlog"
"google.golang.org/grpc"
"google.golang.org/grpc/keepalive"
"strings"
"time"
)
type Server struct {
userCfg *ServerConfig
etcdCli *clientv3.Client
etcdId clientv3.LeaseID
}
type Client struct {
userCfg *ClientConfig
etcdCli *clientv3.Client
etcdId clientv3.LeaseID
rpcServers map[string]*grpc.ClientConn
}
type RpcServer struct {
Name string //服务的名字
Addrs []string //默认的地址
}
const (
DFT_DIAL_TIMEOUT = 3
)
func etcdDialWrapper(cfg *BasicConfig) (*clientv3.Client, error) {
dialTimeout := DFT_DIAL_TIMEOUT
if cfg.DialTimeout > 0 {
dialTimeout = cfg.DialTimeout
}
return clientv3.New(clientv3.Config{
Endpoints: cfg.EndPoints,
DialTimeout: time.Duration(dialTimeout) * time.Second,
})
}
func ServerRegist(cfg *ServerConfig) (server *Server, err error) {
server = &Server{userCfg: cfg}
if server.etcdCli, err = etcdDialWrapper(&cfg.BasicConfig); err != nil {
return nil, err
}
server.register()
return server, nil
}
func NewClient(cfg *ClientConfig) (client *Client, err error) {
client = &Client{userCfg: cfg}
if client.etcdCli, err = etcdDialWrapper(&cfg.BasicConfig); err != nil {
return nil, err
}
client.rpcServers = make(map[string]*grpc.ClientConn)
keepAlive := 3
if cfg.KeepAlive > 0 {
keepAlive = cfg.KeepAlive
}
for _, watchServer := range cfg.WatchServers {
rpcServer := ParseServerName(watchServer)
resolver := newresolver(client, rpcServer.Name, rpcServer.Addrs)
rrBalancer := grpc.RoundRobin(resolver)
client.rpcServers[rpcServer.Name], err = grpc.Dial("", grpc.WithInsecure(),
grpc.WithTimeout(time.Second),
grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: time.Duration(keepAlive) * time.Second}),
grpc.WithBalancer(rrBalancer))
}
go client.ping()
return
}
//user.User|127.0.0.1:9011 -> Server{Name: user.User Addrs: []string{127.0.0.1:9011}}
func ParseServerName(serverName string) *RpcServer {
ss := strings.Split(serverName, "|")
s := &RpcServer{Name: ss[0]}
//没有配置默认地址
if len(ss) < 2 {
return s
}
s.Addrs = strings.Split(ss[1], ",")
return s
}
//根据server-name 获得对应的连接池
func (self *Client) GetConn(serverName string) *grpc.ClientConn {
return self.rpcServers[serverName]
}
//从etcd注销
func (self *Server) Revoke() error {
_, err := self.etcdCli.Revoke(context.Background(), self.etcdId)
return err
}
func (self *Client) Call(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) (err error) {
ss := strings.Split(method, "/")
fmt.Println("ss:", ss, len(ss))
if len(ss) < 3 {
tlog.Errorf("no grpc client available:%s", method)
return fmt.Errorf("no grpc client available:%s", method)
}
conn, ok := self.rpcServers[ss[1]]
fmt.Println("conn:", conn)
if !ok {
tlog.Errorf("no grpc client available:%s", method)
return fmt.Errorf("no grpc client available:%s", method)
}
err = grpc.Invoke(ctx, method, args, reply, conn, opts...)
return
}
func (self *Client) ping() {
for range time.NewTicker(time.Second).C {
for server, conn := range self.rpcServers {
req, resp := &ping.NoArgs{}, &ping.NoArgs{}
if err := grpc.Invoke(context.Background(), strings.Join([]string{server, "Ping"}, "/"), req, resp, conn); err != nil {
tlog.Errorf("qgrpc ping||server=%s||err=%s", server, err)
}
}
}
}

51
qgrpc/ping/ping.pb.go Normal file
View File

@@ -0,0 +1,51 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: ping.proto
/*
Package ping is a generated protocol buffer package.
It is generated from these files:
ping.proto
It has these top-level messages:
NoArgs
*/
package ping
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type NoArgs struct {
}
func (m *NoArgs) Reset() { *m = NoArgs{} }
func (m *NoArgs) String() string { return proto.CompactTextString(m) }
func (*NoArgs) ProtoMessage() {}
func (*NoArgs) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func init() {
proto.RegisterType((*NoArgs)(nil), "ping.NoArgs")
}
func init() { proto.RegisterFile("ping.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 66 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0xc8, 0xcc, 0x4b,
0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0xb1, 0x95, 0xd8, 0xb9, 0x58, 0x5d, 0x73,
0x0b, 0x4a, 0x2a, 0x9d, 0x04, 0xa2, 0xf8, 0x72, 0x32, 0x93, 0xf4, 0xc1, 0x72, 0xfa, 0x20, 0xa9,
0x24, 0x36, 0x30, 0xdb, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0xb5, 0x65, 0x5a, 0x5a, 0x35, 0x00,
0x00, 0x00,
}

60
qgrpc/register.go Normal file
View File

@@ -0,0 +1,60 @@
package qgrpc
import (
"context"
"github.com/coreos/etcd/clientv3"
"globalfintech/golib/tlog"
"path"
"time"
)
func etcdGrantWrapper(cli *clientv3.Client) (clientv3.LeaseID, error) {
resp, err := cli.Grant(context.Background(), 5)
if err != nil {
return 0, err
}
return resp.ID, nil
}
func (self *Server) register() (err error) {
if len(self.userCfg.Prefix) < 1 || len(self.userCfg.Addr) < 1 {
tlog.Info("qgrpc server||register||prefix or addr less than 1 char")
return nil
}
if self.etcdId, err = etcdGrantWrapper(self.etcdCli); err != nil {
return err
}
for _, server := range self.userCfg.Servers {
addr := self.userCfg.GetAddr()
etcdKey := path.Join(self.userCfg.Prefix, server, addr)
if _, err = self.etcdCli.Put(context.Background(), etcdKey, addr, clientv3.WithLease(self.etcdId)); err != nil {
return err
}
}
ch, err := self.etcdCli.KeepAlive(context.Background(), self.etcdId)
if err != nil {
tlog.Error(err)
}
go self.keepalive(ch)
return err
}
func (self *Server) keepalive(ch <-chan *clientv3.LeaseKeepAliveResponse) {
for {
resp := <-ch
if resp == nil {
tlog.Error("qgrpc server||keepalive||nil response")
self.Revoke()
time.Sleep(time.Second)
self.register()
return
}
}
}

148
qgrpc/watch.go Normal file
View File

@@ -0,0 +1,148 @@
package qgrpc
import (
"context"
"fmt"
"github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/mvcc/mvccpb"
"google.golang.org/grpc/naming"
"path"
"strings"
"time"
)
type watcher struct {
prefix string
addrs []string
c *clientv3.Client
hasStart bool
}
type resolver struct {
client *Client
name string
addrs []string
}
func newresolver(client *Client, name string, addrs []string) *resolver {
return &resolver{client: client, name: name, addrs: addrs}
}
func (self *resolver) Resolve(name string) (naming.Watcher, error) {
return self.client.newwatcher(self.name, self.addrs), nil
}
func (self *Client) newwatcher(name string, addrs []string) naming.Watcher {
prefix := path.Join(self.userCfg.Prefix, name)
return &watcher{prefix: prefix, addrs: addrs, c: self.etcdCli}
}
func (w *watcher) Close() {}
func (w *watcher) Next() ([]*naming.Update, error) {
//第一次启动从etcd上获取所有的地址同时加上默认的地址
if !w.hasStart {
updates := []*naming.Update{}
//etcd 的连接正常
if w.c != nil {
resp, err := w.c.Get(context.Background(), w.prefix, clientv3.WithPrefix())
if err == nil {
addrs := extractAddrs(resp)
for _, addr := range addrs {
updates = append(updates, &naming.Update{
Op: naming.Add,
Addr: addr})
updates = append(updates, &naming.Update{
Op: naming.Add,
Addr: strings.Replace(addr, fmt.Sprintf("%s:", getLocalIP()), "127.0.0.1:", -1)})
updates = append(updates, &naming.Update{
Op: naming.Add,
Addr: strings.Replace(addr, fmt.Sprintf("%s:", getLocalIP()), "localhost:", -1)})
updates = append(updates, &naming.Update{
Op: naming.Add,
Addr: strings.Replace(addr, fmt.Sprintf("%s:", getLocalIP()), getHostName()+":", -1)})
}
}
}
//将默认的地址加进入
for _, addr := range w.addrs {
updates = append(updates, &naming.Update{
Op: naming.Add,
Addr: addr})
updates = append(updates, &naming.Update{
Op: naming.Add,
Addr: strings.Replace(addr, fmt.Sprintf("%s:", getLocalIP()), "127.0.0.1:", -1)})
updates = append(updates, &naming.Update{
Op: naming.Add,
Addr: strings.Replace(addr, fmt.Sprintf("%s:", getLocalIP()), "localhost:", -1)})
updates = append(updates, &naming.Update{
Op: naming.Add,
Addr: strings.Replace(addr, fmt.Sprintf("%s:", getLocalIP()), getHostName()+":", -1)})
}
w.hasStart = true
return updates, nil
}
if w.c == nil {
time.Sleep(time.Second)
return nil, nil
}
rch := w.c.Watch(context.Background(), w.prefix, clientv3.WithPrefix())
for wresp := range rch {
for _, ev := range wresp.Events {
switch ev.Type {
case mvccpb.PUT:
_, value := string(ev.Kv.Key), string(ev.Kv.Value)
//fmt.Println(time.Now(), "[PUT]", key, value)
return []*naming.Update{
&naming.Update{
Op: naming.Add,
Addr: value},
&naming.Update{
Op: naming.Add,
Addr: strings.Replace(value, fmt.Sprintf("%s:", getLocalIP()), "127.0.0.1:", -1)},
&naming.Update{
Op: naming.Add,
Addr: strings.Replace(value, fmt.Sprintf("%s:", getLocalIP()), "localhost:", -1)},
&naming.Update{
Op: naming.Add,
Addr: strings.Replace(value, fmt.Sprintf("%s:", getLocalIP()), getHostName()+":", -1)},
}, nil
case mvccpb.DELETE:
_, value := string(ev.Kv.Key), string(ev.Kv.Value)
//fmt.Println(time.Now(), "[DELETE]", key)
return []*naming.Update{
&naming.Update{
Op: naming.Delete,
Addr: value,
},
}, nil
}
}
}
return nil, nil
}
func extractAddrs(resp *clientv3.GetResponse) []string {
addrs := []string{}
if resp == nil || resp.Kvs == nil {
return addrs
}
for _, v := range resp.Kvs {
if v.Value != nil {
addrs = append(addrs, string(v.Value))
}
}
return addrs
}

81
tlog/clog.go Normal file
View File

@@ -0,0 +1,81 @@
package tlog
import (
"context"
"fmt"
"google.golang.org/grpc/metadata"
)
func Cdebug(ctx context.Context, args ...interface{}) {
id := fmt.Sprintf("trace_id=%s", getTraceIdFromCtx(ctx))
data := append([]interface{}{id}, args...)
l.p(DEBUG, data...)
}
func Cdebugf(ctx context.Context, format string, args ...interface{}) {
f := "trace_id=%s||" + format
data := append([]interface{}{getTraceIdFromCtx(ctx)}, args...)
l.pf(DEBUG, f, data...)
}
func Cinfo(ctx context.Context, args ...interface{}) {
id := fmt.Sprintf("trace_id=%s", getTraceIdFromCtx(ctx))
data := append([]interface{}{id}, args...)
l.p(INFO, data...)
}
func Cinfof(ctx context.Context, format string, args ...interface{}) {
f := "trace_id=%s||" + format
data := append([]interface{}{getTraceIdFromCtx(ctx)}, args...)
l.pf(INFO, f, data...)
}
func Cerror(ctx context.Context, args ...interface{}) {
id := fmt.Sprintf("trace_id=%s", getTraceIdFromCtx(ctx))
data := append([]interface{}{id}, args...)
l.p(ERROR, data...)
}
func Cerrorf(ctx context.Context, format string, args ...interface{}) {
f := "trace_id=%s||" + format
data := append([]interface{}{getTraceIdFromCtx(ctx)}, args...)
l.pf(ERROR, f, data...)
}
func Cwarning(ctx context.Context, args ...interface{}) {
id := fmt.Sprintf("trace_id=%s", getTraceIdFromCtx(ctx))
data := append([]interface{}{id}, args...)
l.p(WARNING, data...)
}
func Cwarningf(ctx context.Context, format string, args ...interface{}) {
f := "trace_id=%s||" + format
data := append([]interface{}{getTraceIdFromCtx(ctx)}, args...)
l.pf(WARNING, f, data...)
}
func Cfatal(ctx context.Context, args ...interface{}) {
id := fmt.Sprintf("trace_id=%s", getTraceIdFromCtx(ctx))
data := append([]interface{}{id}, args...)
l.p(FATAL, data...)
}
func Cfatalf(ctx context.Context, format string, args ...interface{}) {
f := "trace_id=%s||" + format
data := append([]interface{}{getTraceIdFromCtx(ctx)}, args...)
l.pf(FATAL, f, data...)
}
func getTraceIdFromCtx(ctx context.Context) string {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return "0"
}
v, ok := md["trace_id"]
if !ok || len(v) < 1 {
return "0"
}
return v[0]
}

38
tlog/level.go Normal file
View File

@@ -0,0 +1,38 @@
package tlog
type LEVEL byte
const (
ALL LEVEL = iota
DEBUG
INFO
WARNING
ERROR
FATAL
)
var levelText = map[LEVEL]string{
ALL: "ALL",
DEBUG: "DEBUG",
INFO: "INFO",
WARNING: "WARNING",
ERROR: "ERROR",
FATAL: "FATAL",
}
func getLevel(level string) LEVEL {
switch level {
case "DEBUG":
return DEBUG
case "INFO":
return INFO
case "WARNING":
return WARNING
case "ERROR":
return ERROR
case "FATAL":
return FATAL
default:
return ALL
}
}

334
tlog/logger.go Normal file
View File

@@ -0,0 +1,334 @@
package tlog
import (
"bufio"
"bytes"
//"encoding/json"
"fmt"
//"github.com/getsentry/raven-go"
"log/syslog"
"os"
"os/exec"
"path"
"runtime"
"strings"
"sync"
"time"
)
var l *Logger
var mu sync.Mutex
type Logger struct {
fileSize int64
fileNum int
fileName string
host string
debug bool
tag string
level LEVEL
dir string
ch chan *Atom
f *os.File
w *bufio.Writer
useSyslog bool
bytePool *sync.Pool
syslogW *syslog.Writer
//jsonEncoder *json.Encoder
//sentryCh chan *raven.Packet
//client *raven.Client
}
type Atom struct {
//isJson bool
line int
file string
format string
level LEVEL
args []interface{}
data map[string]interface{}
}
func newLogger(config Config) {
l = &Logger{
dir: config.Dir,
fileSize: int64(config.FileSize * 1024 * 1024),
fileNum: config.FileNum,
fileName: path.Join(config.Dir, config.FileName+".log"),
tag: config.Tag,
debug: config.Debug,
level: getLevel(config.Level),
ch: make(chan *Atom, 102400),
bytePool: &sync.Pool{New: func() interface{} { return new(bytes.Buffer) }},
}
host, _ := os.Hostname()
ss := strings.Split(host, "-")
if len(ss) < 2 {
l.host = host
} else {
l.host = ss[len(ss)-2] + ss[len(ss)-1]
}
if l.debug {
return
}
os.MkdirAll(l.dir, 0755)
l.f, _ = os.OpenFile(l.fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
l.w = bufio.NewWriterSize(l.f, 1024*1024)
/*
l.jsonEncoder = json.NewEncoder(l.w)
if config.SentryUrl != "" {
l.sentryCh = make(chan *raven.Packet, 1024)
l.client, _ = raven.New(config.SentryUrl)
}
*/
if config.UseSyslog {
var err error
l.syslogW, err = syslog.New(syslog.LOG_LOCAL3|syslog.LOG_INFO, config.SyslogTag)
if err == nil {
l.useSyslog = true
}
}
}
func (l *Logger) start() {
/*
if l.client != nil && l.sentryCh != nil {
go l.sentry()
}
*/
for {
a := <-l.ch
if a == nil {
l.w.Flush()
fileInfo, err := os.Stat(l.fileName)
if err != nil && os.IsNotExist(err) {
l.f.Close()
l.f, _ = os.OpenFile(l.fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
l.w.Reset(l.f)
}
if fileInfo.Size() > l.fileSize {
l.f.Close()
os.Rename(l.fileName, l.logname())
l.f, _ = os.OpenFile(l.fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
l.w.Reset(l.f)
l.rm()
}
continue
}
b := l.bytes(a)
l.w.Write(b)
if l.useSyslog {
l.syslogW.Write(b)
}
//发送sentry
//l.toSentryCh(a)
}
}
/*
func (l *Logger) sentry() {
for {
p := <-l.sentryCh
_, ch := l.client.Capture(p, nil)
if ch != nil {
if err := <-ch; err != nil {
}
close(ch)
}
}
}
*/
/*
func (l *Logger) toSentryCh(a *Atom) {
if a.level >= ERROR {
packet := l.formatSentryPacket(a)
select {
case l.sentryCh <- packet:
default:
}
}
}
func (l *Logger) formatSentryPacket(a *Atom) *raven.Packet {
packet := &raven.Packet{Message: l.fileName}
if a.level == ERROR {
packet.Level = raven.ERROR
}
if a.level == FATAL {
packet.Level = raven.FATAL
}
if a.isJson {
packet.Extra = a.data
packet.Extra["fileloc"] = fmt.Sprintf("%s:%d", a.file, a.line)
return packet
}
packet.Extra = map[string]interface{}{"fileloc": fmt.Sprintf("%s:%d", a.file, a.line)}
if a.format == "" {
packet.Culprit = fmt.Sprint(a.args...)
} else {
packet.Culprit = fmt.Sprintf(a.format, a.args...)
}
return packet
}
*/
func (l *Logger) run() {
if l.debug {
return
}
go l.flush()
go l.start()
}
func (l *Logger) stop() {
if l != nil && l.w != nil {
l.w.Flush()
}
}
var bytePool *sync.Pool = &sync.Pool{New: func() interface{} { return new(bytes.Buffer) }}
func (l *Logger) bytes(a *Atom) []byte {
w := l.bytePool.Get().(*bytes.Buffer)
defer func() {
recover()
w.Reset()
l.bytePool.Put(w)
}()
w.Write(l.genTime())
fmt.Fprintf(w, "%s %s %s %s:%d ", l.host, l.tag, levelText[a.level], a.file, a.line)
if len(a.format) < 1 {
for _, arg := range a.args {
w.WriteByte(' ')
fmt.Fprint(w, arg)
}
} else {
fmt.Fprintf(w, a.format, a.args...)
}
w.WriteByte(10)
b := make([]byte, w.Len())
copy(b, w.Bytes())
return b
}
func (l *Logger) rm() {
if out, err := exec.Command("ls", l.dir).Output(); err == nil {
files := bytes.Split(out, []byte("\n"))
totol, idx := len(files)-1, 0
for i := totol; i >= 0; i-- {
file := path.Join(l.dir, string(files[i]))
if strings.HasPrefix(file, l.fileName) && file != l.fileName {
idx++
if idx > l.fileNum {
os.Remove(file)
}
}
}
}
}
func (l *Logger) flush() {
for range time.NewTicker(time.Second).C {
l.ch <- nil
}
}
func (l *Logger) logname() string {
t := fmt.Sprintf("%s", time.Now())[:19]
tt := strings.Replace(
strings.Replace(
strings.Replace(t, "-", "", -1),
" ", "", -1),
":", "", -1)
return fmt.Sprintf("%s.%s", l.fileName, tt)
}
func (l *Logger) genTime() []byte {
now := time.Now()
_, month, day := now.Date()
hour, minute, second := now.Clock()
return []byte{byte(month/10) + 48, byte(month%10) + 48, '-', byte(day/10) + 48, byte(day%10) + 48, ' ',
byte(hour/10) + 48, byte(hour%10) + 48, ':', byte(minute/10) + 48, byte(minute%10) + 48, ':',
byte(second/10) + 48, byte(second%10) + 48, ' '}
}
func (l *Logger) getTag() string {
if l != nil {
return l.tag
}
return ""
}
func (l *Logger) p(level LEVEL, args ...interface{}) {
file, line := l.getFileNameAndLine()
if l == nil || l.debug {
mu.Lock()
defer mu.Unlock()
fmt.Printf("%s%s %s %s:%d ", l.genTime(), l.getTag(), levelText[level], file, line)
fmt.Println(args...)
return
}
if level >= l.level {
select {
case l.ch <- &Atom{file: file, line: line, level: level, args: args}:
default:
}
}
}
func (l *Logger) pf(level LEVEL, format string, args ...interface{}) {
file, line := l.getFileNameAndLine()
if l == nil || l.debug {
mu.Lock()
defer mu.Unlock()
fmt.Printf("%s%s %s %s:%d ", l.genTime(), l.getTag(), levelText[level], file, line)
fmt.Printf(format, args...)
fmt.Println()
return
}
if level >= l.level {
select {
case l.ch <- &Atom{file: file, line: line, format: format, level: level, args: args}:
default:
}
}
}
//暂不支持json
func (l *Logger) pj(level LEVEL, m map[string]interface{}) {
return
}
/*
file, line := l.getFileNameAndLine()
if l == nil || l.debug {
mu.Lock()
defer mu.Unlock()
m["time"] = string(l.genTime())
m["fileloc"] = fmt.Sprintf("%s:%d", file, line)
m["level"] = levelText[level]
json.NewEncoder(os.Stdout).Encode(m)
return
}
if level >= l.level {
select {
case l.ch <- &Atom{file: file, line: line, data: m, level: level, isJson: true}:
default:
}
}
*/
func (l *Logger) getFileNameAndLine() (string, int) {
_, file, line, ok := runtime.Caller(3)
if !ok {
return "???", 1
}
dirs := strings.Split(file, "/")
if len(dirs) >= 2 {
return dirs[len(dirs)-2] + "/" + dirs[len(dirs)-1], line
}
return file, line
}

101
tlog/tlog.go Normal file
View File

@@ -0,0 +1,101 @@
package tlog
type Config struct {
FileSize int `toml:"filesize" json:"filesize"`
FileNum int `toml:"filenum" json:"filenum"`
FileName string `toml:"filename" json:"filename"`
Tag string `toml:"tag" json:"tag"`
Level string `toml:"level" json:"level"`
Debug bool `toml:"debug" json:"debug"`
Dir string `toml:"dir" json:"dir"`
SentryUrl string `toml:"sentry_url" json:"sentry_url"`
UseSyslog bool `toml:"use_syslog" json:"use_syslog"`
SyslogTag string `toml:"syslog_tag" json:"syslog_tag"`
}
func (c *Config) check() {
if c.FileSize == 0 {
c.FileSize = 128
}
if c.FileNum == 0 {
c.FileNum = 10
}
if c.FileName == "" {
c.FileName = "INFO"
}
if c.Dir == "" {
c.Dir = "./logs"
}
if c.Level == "" {
c.Level = "DEBUG"
}
}
func Init(c Config) {
c.check()
newLogger(c)
l.run()
}
func Close() {
l.stop()
}
func Debug(args ...interface{}) {
l.p(DEBUG, args...)
}
func Debugf(format string, args ...interface{}) {
l.pf(DEBUG, format, args...)
}
func DebugJson(m map[string]interface{}) {
l.pj(DEBUG, m)
}
func Info(args ...interface{}) {
l.p(INFO, args...)
}
func Infof(format string, args ...interface{}) {
l.pf(INFO, format, args...)
}
func InfoJson(m map[string]interface{}) {
l.pj(INFO, m)
}
func Warning(args ...interface{}) {
l.p(WARNING, args...)
}
func Warningf(format string, args ...interface{}) {
l.pf(WARNING, format, args...)
}
func WarningJson(m map[string]interface{}) {
l.pj(WARNING, m)
}
func Error(args ...interface{}) {
l.p(ERROR, args...)
}
func Errorf(format string, args ...interface{}) {
l.pf(ERROR, format, args...)
}
func ErrorJson(m map[string]interface{}) {
l.pj(ERROR, m)
}
func Fatal(args ...interface{}) {
l.p(FATAL, args...)
}
func Fatalf(format string, args ...interface{}) {
l.pf(FATAL, format, args...)
}
func FatalJson(m map[string]interface{}) {
l.pj(FATAL, m)
}

30
tlog/tlog_test.go Normal file
View File

@@ -0,0 +1,30 @@
package tlog
import (
"testing"
"time"
)
func TestTlog(t *testing.T) {
Init(Config{
FileSize: 128,
FileNum: 5,
FileName: "test",
Level: "DEBUG",
Dir: "./logs",
Tag: "test",
//Debug: true,
//SentryUrl: "http://d25c96785a184d6f8d051712a209a3a8:7f4a22c48897423caa9a0aec6bb93416@172.20.0.84:9000/5",
UseSyslog: true,
SyslogTag: "test",
})
for i := 0; i < 10; i++ {
Info("xxxxxxxxxxasfsadjflasjfdlasjdfsajdfsadfjasjfdafjsfsa")
Infof("%s", "xxxxxxxxxxasfsadjflasjfdlasjdfsajdfsadfjasjfdafjsfsa")
InfoJson(map[string]interface{}{"test": time.Now().UnixNano()})
Error("xxxxxxxxxxasfsadjflasjfdlasjdfsajdfsadfjasjfdafjsfsa")
Errorf("%s", "xxxxxxxxxxasfsadjflasjfdlasjdfsajdfsadfjasjfdafjsfsa")
ErrorJson(map[string]interface{}{"test": time.Now().UnixNano()})
}
time.Sleep(1 * time.Second)
}

52
trace/trace.go Normal file
View File

@@ -0,0 +1,52 @@
package trace
import "time"
var t *Trace
type Trace struct {
queue chan int64
tick <-chan time.Time
ts int64
serverId int64
}
func NewTrace(serverId int64) {
if t != nil {
return
}
t = &Trace{
queue: make(chan int64, 1000),
tick: time.Tick(time.Second),
serverId: serverId % 65536,
}
t.ts = t.serverId<<47 | time.Now().Unix()<<16
go func() {
var i int64 = 0
for {
t.queue <- i % 65536
i++
}
}()
go func() {
for now := range t.tick {
t.ts = t.serverId<<47 | now.Unix()<<16
}
}()
}
func GenId() int64 {
if t == nil {
return 0
}
select {
case seq := <-t.queue:
return seq | t.ts
default:
return 0
}
}