diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e54ae66 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +# 使用官方的 Go 镜像作为基础镜像 +FROM golang:1.20 AS builder + +# 设置工作目录 +WORKDIR /app + +# 拷贝 Go 项目代码到容器中 +COPY . . + +# 为 Linux 构建 plato 可执行文件 +# 为了在mac上能够执行,指定了 GOOS=linux GOARCH=amd64 +# 其他linux环境编译时不需要添加该命令,直接 go build -o plato . 即可 +RUN GOOS=linux GOARCH=amd64 go build -o plato . + + +# 创建最终的生产环境镜像 +FROM alpine:latest + +# 设置工作目录 +WORKDIR /app + +# 拷贝构建好的可执行文件到生产镜像中 +COPY --from=builder /app/plato . + +# 赋予 plato-gateway 和 plato-state 可执行权限 +RUN #chmod +x ./plato-gateway && chmod +x ./plato-state +RUN chmod +x ./plato + +# 拷贝项目的配置文件到容器中 +COPY ./plato.yaml . + +# 声明 gateway 服务运行时监听的端口 +EXPOSE 8900 +EXPOSE 8901 + +# 声明 state 服务运行时监听的端口 +EXPOSE 8902 + +# 启动应用程序 +#CMD ["./plato gateway"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9033293 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +all:build + +build: + @echo "go build plato begin" + go build -o plato . + @echo "go build plato success" diff --git a/chat.log b/chat.log index fc32448..87ec344 100644 --- a/chat.log +++ b/chat.log @@ -1,5 +1,42 @@ -me: hello -logic: hello -me: 你好! plato -logic: 你好! plato +me-clientID:0: 123 +logic: 123 +me-clientID:0: 345566 +me-clientID:1: sadklasjdl +logic: sadklasjdl +me-clientID:2: sdksajdlsa +logic: sdksajdlsa +me-clientID:3: djsalkjdlsa +logic: djsalkjdlsa +me-clientID:4: dhaskljdlks +logic: dhaskljdlks +me-clientID:5: dhjakshdks +logic: dhjakshdks +me-clientID:6: dhjksahdks +logic: dhjksahdks +me-clientID:7: 123daksjdklas +logic: 123daksjdklas +me-clientID:8: dmaklsdklsa +logic: dmaklsdklsa +me-clientID:9: dmksamdlsa +logic: dmksamdlsa +me-clientID:10: dals;dk;asd +logic: dals;dk;asd +me-clientID:11: dhaskjhdkasdk +logic: dhaskjhdkasdk +me-clientID:12: sdklajdklsad +logic: sdklajdklsad +me-clientID:13: djaslkdlsa +logic: djaslkdlsa +me-clientID:14: asdkljsaljdslajd +logic: asdkljsaljdslajd +me-clientID:15: djskahdkhsakd +logic: djskahdkhsakd +me-clientID:16: dklasjdklsajkld +logic: dklasjdklsajkld +me-clientID:17: djklsajdlsajdl +logic: djklsajdlsajdl +me-clientID:18: djaksljdlasdjl +logic: djaksljdlasdjl +me-clientID:19: djaslkjdlas +logic: djaslkjdlas diff --git a/client/cui.go b/client/cui.go index 5129dab..e7e09bc 100644 --- a/client/cui.go +++ b/client/cui.go @@ -2,13 +2,14 @@ package client import ( "fmt" + "github.com/hardcore-os/plato/common/sdk" "io/ioutil" "log" "math/rand" + "net" "time" "github.com/gookit/color" - "github.com/hardcore-os/plato/client/sdk" "github.com/rocket049/gocui" ) @@ -59,16 +60,19 @@ func viewPrint(g *gocui.Gui, name, msg string, newline bool) { g.Update(out.Show) } -//doRecv work in goroutine +// doRecv work in goroutine func doRecv(g *gocui.Gui) { recvChannel := chat.Recv() for msg := range recvChannel { - switch msg.Type { - case sdk.MsgTypeText: - viewPrint(g, msg.Name, msg.Content, false) + if msg != nil { + switch msg.Type { + case sdk.MsgTypeText: + viewPrint(g, msg.Name, msg.Content, false) + case sdk.MsgTypeAck: + //TODO 默认不处理 + } } } - g.Close() } func quit(g *gocui.Gui, v *gocui.View) error { @@ -91,7 +95,8 @@ func doSay(g *gocui.Gui, cv *gocui.View) { ToUserID: "222222", Content: string(p)} // 先把自己说的话显示到消息流中 - viewPrint(g, "me", msg.Content, false) + idKey := fmt.Sprintf("%d", chat.GetCurClientID()) + viewPrint(g, "me-clientID:"+idKey, msg.Content, false) chat.Send(msg) } v.Autoscroll = true @@ -168,7 +173,7 @@ func viewHead(g *gocui.Gui, x0, y0, x1, y1 int) error { } v.Wrap = false v.Overwrite = true - msg := "开始聊天了!" + msg := "Plato: IM系统聊天对话框" setHeadText(g, msg) } return nil @@ -223,7 +228,7 @@ func pasteDown(g *gocui.Gui, cv *gocui.View) error { func RunMain() { // step1 创建caht的核心对象 - chat = sdk.NewChat("127.0.0.1:8080", "logic", "12312321", "2131") + chat = sdk.NewChat(net.ParseIP("0.0.0.0"), 8900, "logic", "12312321", "2131") // step2 创建 GUI 图层对象并进行参与与回调函数的配置 g, err := gocui.NewGui(gocui.OutputNormal) if err != nil { @@ -256,6 +261,15 @@ func RunMain() { if err := g.SetKeybinding("main", gocui.KeyArrowUp, gocui.ModNone, pasteUP); err != nil { log.Panicln(err) } + + // emit disconnection + //go func() { + // time.Sleep(10 * time.Second) + // // reconnection + // chat.ReConn() + // go doRecv(g) + //}() + // 启动消费函数 go doRecv(g) if err := g.MainLoop(); err != nil { diff --git a/client/sdk/api.go b/client/sdk/api.go deleted file mode 100644 index a456157..0000000 --- a/client/sdk/api.go +++ /dev/null @@ -1,43 +0,0 @@ -package sdk - -const ( - MsgTypeText = "text" -) - -type Chat struct { - Nick string - UserID string - SessionID string - conn *connect -} - -type Message struct { - Type string - Name string - FormUserID string - ToUserID string - Content string - Session string -} - -func NewChat(serverAddr, nick, userID, sessionID string) *Chat { - return &Chat{ - Nick: nick, - UserID: userID, - SessionID: sessionID, - conn: newConnet(serverAddr), - } -} -func (chat *Chat) Send(msg *Message) { - chat.conn.send(msg) -} - -//Close close chat -func (chat *Chat) Close() { - chat.conn.close() -} - -//Recv receive message -func (chat *Chat) Recv() <-chan *Message { - return chat.conn.recv() -} diff --git a/client/sdk/logic.go b/client/sdk/logic.go deleted file mode 100644 index ab63730..0000000 --- a/client/sdk/logic.go +++ /dev/null @@ -1 +0,0 @@ -package sdk \ No newline at end of file diff --git a/client/sdk/net.go b/client/sdk/net.go deleted file mode 100644 index 05e97d8..0000000 --- a/client/sdk/net.go +++ /dev/null @@ -1,27 +0,0 @@ -package sdk - -type connect struct { - serverAddr string - sendChan, recvChan chan *Message -} - -func newConnet(serverAddr string) *connect { - return &connect{ - serverAddr: serverAddr, - sendChan: make(chan *Message), - recvChan: make(chan *Message), - } -} - -func (c *connect) send(data *Message) { - // 直接发送给接收方 - c.recvChan <- data -} - -func (c *connect) recv() <- chan *Message { - return c.recvChan -} - -func (c *connect) close() { - // 目前没啥值得回收的 -} diff --git a/cmd/gateway.go b/cmd/gateway.go new file mode 100644 index 0000000..c5182b5 --- /dev/null +++ b/cmd/gateway.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/hardcore-os/plato/gateway" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(gatewayCmd) +} + +var gatewayCmd = &cobra.Command{ + Use: "gateway", + Run: GatewayHandle, +} + +func GatewayHandle(cmd *cobra.Command, args []string) { + gateway.RunMain(ConfigPath) +} diff --git a/cmd/ipconf.go b/cmd/ipconf.go new file mode 100644 index 0000000..961d6e3 --- /dev/null +++ b/cmd/ipconf.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/hardcore-os/plato/ipconf" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(ipConfCmd) +} + +var ipConfCmd = &cobra.Command{ + Use: "ipconf", + Run: IpConfHandle, +} + +func IpConfHandle(cmd *cobra.Command, args []string) { + ipconf.RunMain(ConfigPath) +} diff --git a/cmd/perf.go b/cmd/perf.go new file mode 100644 index 0000000..0ea5f08 --- /dev/null +++ b/cmd/perf.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "github.com/hardcore-os/plato/perf" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(perfCmd) + perfCmd.PersistentFlags().Int32Var(&perf.TcpConnNum, "tcp_conn_num", 10000, "tcp 连接的数量,默认10000") +} + +var perfCmd = &cobra.Command{ + Use: "perf", + Run: PerfHandle, +} + +func PerfHandle(cmd *cobra.Command, args []string) { + perf.RunMain() +} diff --git a/cmd/plato.go b/cmd/plato.go index 411bbb2..0bb6a17 100644 --- a/cmd/plato.go +++ b/cmd/plato.go @@ -7,8 +7,18 @@ import ( "github.com/spf13/cobra" ) +var ( + ConfigPath string +) + func init() { cobra.OnInitialize(initConfig) + rootCmd.PersistentFlags().StringVar( + &ConfigPath, + "config", + "./plato.yaml", + "config file (default is ./plato.yaml)", + ) } var rootCmd = &cobra.Command{ diff --git a/cmd/state.go b/cmd/state.go new file mode 100644 index 0000000..bf2c5aa --- /dev/null +++ b/cmd/state.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/hardcore-os/plato/state" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(stateCmd) +} + +var stateCmd = &cobra.Command{ + Use: "state", + Run: StateHandle, +} + +func StateHandle(cmd *cobra.Command, args []string) { + state.RunMain(ConfigPath) +} diff --git a/common/cache/const.go b/common/cache/const.go new file mode 100644 index 0000000..3a14f69 --- /dev/null +++ b/common/cache/const.go @@ -0,0 +1,10 @@ +package cache + +import "time" + +const ( + MaxClientIDKey = "max_client_id_{%d}_%d" // slot_connID + LastMsgKey = "last_msg_{%d}_%d" // slot_connID + LoginSlotSetKey = "login_slot_set_{%d}" // 通过 hash tag保证在cluster模式上 key都在一个shard上 // slot + TTL7D = 7 * 24 * time.Hour +) diff --git a/common/cache/lua.go b/common/cache/lua.go new file mode 100644 index 0000000..2abe38a --- /dev/null +++ b/common/cache/lua.go @@ -0,0 +1,34 @@ +package cache + +import ( + "context" + "fmt" +) + +const ( + LuaCompareAndIncrClientID = "LuaCompareAndIncrClientID" +) + +type luaPart struct { + LuaScript string + Sha string +} + +var luaScriptTable map[string]*luaPart = map[string]*luaPart{ + LuaCompareAndIncrClientID: &luaPart{ + LuaScript: "if redis.call('exists', KEYS[1]) == 0 then redis.call('set', KEYS[1], 0) end; if redis.call('get', KEYS[1]) == ARGV[1] then redis.call('incr', KEYS[1]);redis.call('expire', KEYS[1], ARGV[2]);return 1 else return -1 end", + }, +} + +func initLuaScript(ctx context.Context) { + for name, part := range luaScriptTable { + cmd := rdb.ScriptLoad(ctx, part.LuaScript) + if cmd == nil { + panic(fmt.Sprintf("lua init failed, lua=%v", name)) + } + if cmd.Err() != nil { + panic(cmd.Err()) + } + part.Sha = cmd.Val() + } +} diff --git a/common/cache/redis.go b/common/cache/redis.go new file mode 100644 index 0000000..2e441e8 --- /dev/null +++ b/common/cache/redis.go @@ -0,0 +1,125 @@ +package cache + +import ( + "context" + "errors" + "github.com/bytedance/gopkg/util/logger" + "time" + + "github.com/hardcore-os/plato/common/config" + redis "github.com/redis/go-redis/v9" +) + +// 声明一个全局的rdb变量,这是一个单机client +var rdb *redis.Client + +func InitRedis(ctx context.Context) { + if rdb != nil { + return + } + endpoints := config.GetCacheRedisEndpointList() + opt := &redis.Options{Addr: endpoints[0], PoolSize: 10000} + rdb = redis.NewClient(opt) + if _, err := rdb.Ping(ctx).Result(); err != nil { + panic(err) + } + logger.CtxInfof(ctx, "init redis success") + initLuaScript(ctx) +} +func GetBytes(ctx context.Context, key string) ([]byte, error) { + cmd := rdb.Conn().Get(ctx, key) + if cmd == nil { + return nil, errors.New("redis GetBytes cmd is nil") + } + data, err := cmd.Bytes() + if redis.Nil == err { + return nil, nil + } + return data, err +} + +func GetUInt64(ctx context.Context, key string) (uint64, error) { + cmd := rdb.Conn().Get(ctx, key) + if cmd == nil { + return 0, errors.New("redis GetUInt64 cmd is nil") + } + return cmd.Uint64() +} + +func SetBytes(ctx context.Context, key string, value []byte, ttl time.Duration) error { + cmd := rdb.Set(ctx, key, value, ttl) + if cmd == nil { + return errors.New("redis SetBytes cmd is nil") + } + return cmd.Err() +} + +func Del(ctx context.Context, key string) error { + cmd := rdb.Conn().Del(ctx, key) + if cmd == nil { + return errors.New("redis Del cmd is nil") + } + return cmd.Err() +} + +func SADD(ctx context.Context, key string, member interface{}) error { + cmd := rdb.SAdd(ctx, key, member) + if cmd == nil { + return errors.New("redis SADD cmd is nil") + } + return cmd.Err() +} + +func SREM(ctx context.Context, key string, members ...interface{}) error { + cmd := rdb.Conn().SRem(ctx, key, members...) + if cmd == nil { + return errors.New("redis SREM cmd is nil") + } + return cmd.Err() +} + +func SmembersStrSlice(ctx context.Context, key string) ([]string, error) { + cmd := rdb.Conn().SMembers(ctx, key) + if cmd == nil { + return nil, errors.New("redis SmembersUint64StructMap cmd is nil") + } + return cmd.Result() +} + +func Incr(ctx context.Context, key string, ttl time.Duration) error { + _, err := rdb.Conn().Pipelined(ctx, func(p redis.Pipeliner) error { + p.Incr(ctx, key) + p.Expire(ctx, key, ttl) + return nil + }) + return err +} + +func SetString(ctx context.Context, key string, value string, ttl time.Duration) error { + cmd := rdb.Set(ctx, key, value, ttl) + if cmd == nil { + return errors.New("redis SetString cmd is nil") + } + return cmd.Err() +} + +func GetString(ctx context.Context, key string) (string, error) { + cmd := rdb.Get(ctx, key) + if cmd == nil { + return "", errors.New("redis GetString cmd is nil") + } + return cmd.String(), cmd.Err() +} + +func RunLuaInt(ctx context.Context, name string, keys []string, args ...interface{}) (int, error) { + if part, ok := luaScriptTable[name]; !ok { + return -1, errors.New("lua not registered") + } else { + cmd := rdb.EvalSha(ctx, part.Sha, keys, args...) + if cmd == nil { + return -1, errors.New("redis RunLua cmd is nil") + } + + return cmd.Int() + } +} diff --git a/common/config/config.go b/common/config/config.go new file mode 100644 index 0000000..2363285 --- /dev/null +++ b/common/config/config.go @@ -0,0 +1,38 @@ +package config + +import ( + "github.com/spf13/viper" + "time" +) + +func Init(path string) { + viper.SetConfigFile(path) + viper.SetConfigType("yaml") + if err := viper.ReadInConfig(); err != nil { + panic(err) + } +} + +// GetEndpointsForDiscovery 获取服务发现地 址 +func GetEndpointsForDiscovery() []string { + return viper.GetStringSlice("discovery.endpoints") +} + +// GetTimeoutForDiscovery 获取连接服务发现集群的超时时间 单位微秒 +func GetTimeoutForDiscovery() time.Duration { + return viper.GetDuration("discovery.timeout") * time.Second +} + +func GetServicePathForIPConf() string { + return viper.GetString("ip_conf.service_path") +} + +func GetCacheRedisEndpointList() []string { + return viper.GetStringSlice("cache.redis.endpoints") +} + +// IsDebug 判断是不是debug环境 +func IsDebug() bool { + env := viper.GetString("global.env") + return env == "debug" +} diff --git a/common/config/gateway.go b/common/config/gateway.go new file mode 100644 index 0000000..0f5f623 --- /dev/null +++ b/common/config/gateway.go @@ -0,0 +1,53 @@ +package config + +import ( + "github.com/spf13/viper" +) + +func GetGatewayMaxTcpNum() int32 { + return viper.GetInt32("gateway.tcp_max_num") +} + +func GetGatewayEpollerChanNum() int { + return viper.GetInt("gateway.epoll_channel_size") +} + +func GetGatewayEpollerNum() int { + return viper.GetInt("gateway.epoll_num") +} + +func GetGatewayEpollWaitQueueSize() int { + return viper.GetInt("gateway.epoll_wait_queue_size") +} + +func GetGatewayTCPServerPort() int { + return viper.GetInt("gateway.tcp_server_port") +} + +func GetGatewayRPCServerPort() int { + return viper.GetInt("gateway.rpc_server_port") +} + +func GetGatewayWorkerPoolNum() int { + return viper.GetInt("gateway.worker_pool_num") +} + +func GetGatewayCmdChannelNum() int { + return viper.GetInt("gateway.cmd_channel_num") +} + +func GetGatewayServiceAddr() string { + return viper.GetString("gateway.service_addr") +} + +func GetGatewayServiceName() string { + return viper.GetString("gateway.service_name") +} + +func GetGatewayRPCWeight() int { + return viper.GetInt("gateway.weight") +} + +func GetGatewayStateServerEndPoint() string { + return viper.GetString("gateway.state_server_endpoint") +} diff --git a/common/config/state.go b/common/config/state.go new file mode 100644 index 0000000..0cacfb7 --- /dev/null +++ b/common/config/state.go @@ -0,0 +1,55 @@ +package config + +import ( + "github.com/spf13/viper" + "strconv" + "strings" +) + +func GetStateCmdChannelNum() int { + return viper.GetInt("state.cmd_channel_num") +} + +func GetStateServiceAddr() string { + return viper.GetString("state.servide_addr") +} + +func GetStateServiceName() string { + return viper.GetString("state.service_name") +} + +func GetStateServerPort() int { + return viper.GetInt("state.server_port") +} + +func GetStateRPCWeight() int { + return viper.GetInt("state.weight") +} + +var connStateSlotList []int + +func GetStateServerLoginSlotRange() []int { + if len(connStateSlotList) != 0 { + return connStateSlotList + } + slotRnageStr := viper.GetString("state.conn_state_slot_range") + slotRnage := strings.Split(slotRnageStr, ",") + left, err := strconv.Atoi(slotRnage[0]) + if err != nil { + panic(err) + } + right, err := strconv.Atoi(slotRnage[1]) + if err != nil { + panic(err) + } + res := make([]int, right-left+1) + for i := left; i <= right; i++ { + res[i] = i + } + connStateSlotList = res + return connStateSlotList +} + +func GetStateServerGatewayServerEndpoint() string { + return viper.GetString("state.gateway_server_endpoint") +} diff --git a/common/discovery/discovery.go b/common/discovery/discovery.go new file mode 100644 index 0000000..2e9a810 --- /dev/null +++ b/common/discovery/discovery.go @@ -0,0 +1,67 @@ +package discovery + +import ( + "context" + "github.com/bytedance/gopkg/util/logger" + "github.com/hardcore-os/plato/common/config" + "go.etcd.io/etcd/api/v3/mvccpb" + clientv3 "go.etcd.io/etcd/client/v3" + "sync" +) + +// ServiceDiscovery 服务发现 +type ServiceDiscovery struct { + cli *clientv3.Client //etcd client + lock sync.Mutex + ctx *context.Context +} + +// NewServiceDiscovery 新建发现服务 +func NewServiceDiscovery(ctx *context.Context) *ServiceDiscovery { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: config.GetEndpointsForDiscovery(), + DialTimeout: config.GetTimeoutForDiscovery(), + }) + if err != nil { + // log.error and then exist with 1 + logger.Fatal(err) + } + return &ServiceDiscovery{ + cli: cli, + ctx: ctx, + } +} + +func (s *ServiceDiscovery) WatchService(prefix string, set, del func(key, value string)) error { + // 根据前缀获取现有的key + resp, err := s.cli.Get(*s.ctx, prefix, clientv3.WithPrefix()) + if err != nil { + return err + } + + for _, kv := range resp.Kvs { + set(string(kv.Key), string(kv.Value)) + } + // 监视前缀,修改变更的server + s.watcher(prefix, resp.Header.Revision+1, set, del) + return nil +} + +func (s *ServiceDiscovery) watcher(prefix string, rev int64, set, del func(key, value string)) { + rch := s.cli.Watch(*s.ctx, prefix, clientv3.WithPrefix(), clientv3.WithRev(rev)) + logger.CtxInfof(*s.ctx, "watching prefix:%s now...", prefix) + for wresp := range rch { + for _, ev := range wresp.Events { + switch ev.Type { + case mvccpb.PUT: //修改或者新增 + set(string(ev.Kv.Key), string(ev.Kv.Value)) + case mvccpb.DELETE: //删除 + del(string(ev.Kv.Key), string(ev.Kv.Value)) + } + } + } +} + +func (s *ServiceDiscovery) Close() error { + return s.cli.Close() +} diff --git a/common/discovery/discovery_test.go b/common/discovery/discovery_test.go new file mode 100644 index 0000000..7cb5487 --- /dev/null +++ b/common/discovery/discovery_test.go @@ -0,0 +1,20 @@ +package discovery + +import ( + "context" + "testing" + "time" +) + +func TestServiceDiscovery(t *testing.T) { + ctx := context.Background() + ser := NewServiceDiscovery(&ctx) + defer ser.Close() + ser.WatchService("/web/", func(key, value string) {}, func(key, value string) {}) + ser.WatchService("/gRPC/", func(key, value string) {}, func(key, value string) {}) + for { + select { + case <-time.Tick(10 * time.Second): + } + } +} diff --git a/common/discovery/model.go b/common/discovery/model.go new file mode 100644 index 0000000..3539236 --- /dev/null +++ b/common/discovery/model.go @@ -0,0 +1,27 @@ +package discovery + +import "encoding/json" + +type EndpointInfo struct { + IP string `json:"ip"` + Port string `json:"port"` + MetaData map[string]interface{} `json:"meta"` +} + +func UnMarshal(data []byte) (*EndpointInfo, error) { + var ed EndpointInfo + err := json.Unmarshal(data, &ed) + if err != nil { + return nil, err + } + return &ed, nil +} + +func (ed *EndpointInfo) Marshal() string { + data, err := json.Marshal(ed) + if err != nil { + // TODO zt 为什么不是返回""? + panic(err) + } + return string(data) +} diff --git a/common/discovery/register.go b/common/discovery/register.go new file mode 100644 index 0000000..8abf9c1 --- /dev/null +++ b/common/discovery/register.go @@ -0,0 +1,94 @@ +package discovery + +import ( + "context" + "github.com/bytedance/gopkg/util/logger" + "github.com/hardcore-os/plato/common/config" + clientv3 "go.etcd.io/etcd/client/v3" +) + +type ServiceRegister struct { + cli *clientv3.Client + leaseID clientv3.LeaseID + // 租约keepAlive响应chan + KeepAliveChan <-chan *clientv3.LeaseKeepAliveResponse + Key string + Value string + ctx *context.Context +} + +func NewServiceRegister(ctx *context.Context, key string, info *EndpointInfo, lease int64) (*ServiceRegister, error) { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: config.GetEndpointsForDiscovery(), + DialTimeout: config.GetTimeoutForDiscovery(), + }) + if err != nil { + logger.Fatal(err) + } + + ser := &ServiceRegister{ + cli: cli, + Key: key, + Value: info.Marshal(), + ctx: ctx, + } + + // 申请租约设置时间keepalive + if err := ser.putKeyWithLease(lease); err != nil { + return nil, err + } + + return ser, nil +} + +// putKeyWithLease 设置族月 +func (ser *ServiceRegister) putKeyWithLease(lease int64) error { + // 设置租约时间 + resp, err := ser.cli.Grant(*ser.ctx, lease) + if err != nil { + return err + } + // 注册服务并绑定租约 + _, err = ser.cli.Put(*ser.ctx, ser.Key, ser.Value, clientv3.WithLease(resp.ID)) + if err != nil { + return err + } + // 设置续租 定期发送需求请求 + leaseRespChan, err := ser.cli.KeepAlive(*ser.ctx, resp.ID) + if err != nil { + return err + } + ser.leaseID = resp.ID + ser.KeepAliveChan = leaseRespChan + return nil +} + +func (ser *ServiceRegister) UpdateValue(value *EndpointInfo) error { + val := value.Marshal() + _, err := ser.cli.Put(*ser.ctx, ser.Key, val, clientv3.WithLease(ser.leaseID)) + if err != nil { + return err + } + ser.Value = val + logger.CtxInfof(*ser.ctx, "ServiceRegister.updateValue leaseID=%d Put key=%s,val=%s, success!", ser.leaseID, ser.Key, ser.Value) + return nil +} + +// ListenLeaseRespChan 监听 续租情况 +func (ser *ServiceRegister) ListenLeaseRespChan() { + for leaseKeepResp := range ser.KeepAliveChan { + logger.CtxInfof(*ser.ctx, "lease success leaseID:%d, Put key:%s,val:%s reps:+%v", + ser.leaseID, ser.Key, ser.Value, leaseKeepResp) + } + logger.CtxInfof(*ser.ctx, "lease failed !!! leaseID:%d, Put key:%s,val:%s", ser.leaseID, ser.Key, ser.Value) +} + +// Close 注销服务 +func (ser *ServiceRegister) Close() error { + // 撤销租约 + if _, err := ser.cli.Revoke(*ser.ctx, ser.leaseID); err != nil { + return err + } + logger.CtxInfof(*ser.ctx, "lease close !!! leaseID:%d, Put key:%s,val:%s success!", ser.leaseID, ser.Key, ser.Value) + return ser.cli.Close() +} diff --git a/common/discovery/register_test.go b/common/discovery/register_test.go new file mode 100644 index 0000000..d563c69 --- /dev/null +++ b/common/discovery/register_test.go @@ -0,0 +1,25 @@ +package discovery + +import ( + "context" + "log" + "testing" + "time" +) + +func TestServiceRegiste(t *testing.T) { + ctx := context.Background() + ser, err := NewServiceRegister(&ctx, "/web/node1", &EndpointInfo{ + IP: "127.0.0.1", + Port: "9999", + }, 5) + if err != nil { + log.Fatalln(err) + } + //监听续租相应chan + go ser.ListenLeaseRespChan() + select { + case <-time.After(20 * time.Second): + ser.Close() + } +} diff --git a/common/idl/Makefile b/common/idl/Makefile new file mode 100644 index 0000000..6cf3c4a --- /dev/null +++ b/common/idl/Makefile @@ -0,0 +1,7 @@ +all: idl + +idl: + @echo "begin" + protoc -I message --go_out=message message/*.proto + @echo "*.pb.go success" + @echo "end" \ No newline at end of file diff --git a/common/idl/message/message.pb.go b/common/idl/message/message.pb.go new file mode 100644 index 0000000..86a1ae6 --- /dev/null +++ b/common/idl/message/message.pb.go @@ -0,0 +1,979 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v3.19.4 +// source: message.proto + +package message + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// cd common/idl; protoc -I message --go_out=plugins=grpc:message message/message.proto +type CmdType int32 + +const ( + CmdType_Login CmdType = 0 + CmdType_Heartbeat CmdType = 1 + CmdType_ReConn CmdType = 2 + CmdType_ACK CmdType = 3 + CmdType_UP CmdType = 4 // 上行消息 + CmdType_Push CmdType = 5 // 下行 推送消息 +) + +// Enum value maps for CmdType. +var ( + CmdType_name = map[int32]string{ + 0: "Login", + 1: "Heartbeat", + 2: "ReConn", + 3: "ACK", + 4: "UP", + 5: "Push", + } + CmdType_value = map[string]int32{ + "Login": 0, + "Heartbeat": 1, + "ReConn": 2, + "ACK": 3, + "UP": 4, + "Push": 5, + } +) + +func (x CmdType) Enum() *CmdType { + p := new(CmdType) + *p = x + return p +} + +func (x CmdType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CmdType) Descriptor() protoreflect.EnumDescriptor { + return file_message_proto_enumTypes[0].Descriptor() +} + +func (CmdType) Type() protoreflect.EnumType { + return &file_message_proto_enumTypes[0] +} + +func (x CmdType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CmdType.Descriptor instead. +func (CmdType) EnumDescriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{0} +} + +// 顶层cmd pb结构 +type MsgCmd struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type CmdType `protobuf:"varint,1,opt,name=Type,proto3,enum=message.CmdType" json:"Type,omitempty"` + Payload []byte `protobuf:"bytes,2,opt,name=Payload,proto3" json:"Payload,omitempty"` +} + +func (x *MsgCmd) Reset() { + *x = MsgCmd{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MsgCmd) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MsgCmd) ProtoMessage() {} + +func (x *MsgCmd) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MsgCmd.ProtoReflect.Descriptor instead. +func (*MsgCmd) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{0} +} + +func (x *MsgCmd) GetType() CmdType { + if x != nil { + return x.Type + } + return CmdType_Login +} + +func (x *MsgCmd) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +// 上行消息 pb结构 +type UPMsg struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Head *UPMsgHead `protobuf:"bytes,1,opt,name=Head,proto3" json:"Head,omitempty"` + UPMsgBody []byte `protobuf:"bytes,2,opt,name=UPMsgBody,proto3" json:"UPMsgBody,omitempty"` +} + +func (x *UPMsg) Reset() { + *x = UPMsg{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UPMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UPMsg) ProtoMessage() {} + +func (x *UPMsg) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UPMsg.ProtoReflect.Descriptor instead. +func (*UPMsg) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{1} +} + +func (x *UPMsg) GetHead() *UPMsgHead { + if x != nil { + return x.Head + } + return nil +} + +func (x *UPMsg) GetUPMsgBody() []byte { + if x != nil { + return x.UPMsgBody + } + return nil +} + +// 上行消息头 pb结构 +type UPMsgHead struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClientID uint64 `protobuf:"varint,1,opt,name=ClientID,proto3" json:"ClientID,omitempty"` + ConnID uint64 `protobuf:"varint,2,opt,name=ConnID,proto3" json:"ConnID,omitempty"` +} + +func (x *UPMsgHead) Reset() { + *x = UPMsgHead{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UPMsgHead) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UPMsgHead) ProtoMessage() {} + +func (x *UPMsgHead) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UPMsgHead.ProtoReflect.Descriptor instead. +func (*UPMsgHead) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{2} +} + +func (x *UPMsgHead) GetClientID() uint64 { + if x != nil { + return x.ClientID + } + return 0 +} + +func (x *UPMsgHead) GetConnID() uint64 { + if x != nil { + return x.ConnID + } + return 0 +} + +// 下行消息 pb结构 +type PushMsg struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MsgID uint64 `protobuf:"varint,1,opt,name=MsgID,proto3" json:"MsgID,omitempty"` + SessionID uint64 `protobuf:"varint,2,opt,name=SessionID,proto3" json:"SessionID,omitempty"` + Content []byte `protobuf:"bytes,3,opt,name=Content,proto3" json:"Content,omitempty"` +} + +func (x *PushMsg) Reset() { + *x = PushMsg{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushMsg) ProtoMessage() {} + +func (x *PushMsg) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushMsg.ProtoReflect.Descriptor instead. +func (*PushMsg) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{3} +} + +func (x *PushMsg) GetMsgID() uint64 { + if x != nil { + return x.MsgID + } + return 0 +} + +func (x *PushMsg) GetSessionID() uint64 { + if x != nil { + return x.SessionID + } + return 0 +} + +func (x *PushMsg) GetContent() []byte { + if x != nil { + return x.Content + } + return nil +} + +// ACK 消息 +type ACKMsg struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Code uint32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"` + Msg string `protobuf:"bytes,2,opt,name=Msg,proto3" json:"Msg,omitempty"` + Type CmdType `protobuf:"varint,3,opt,name=Type,proto3,enum=message.CmdType" json:"Type,omitempty"` + ConnID uint64 `protobuf:"varint,4,opt,name=ConnID,proto3" json:"ConnID,omitempty"` + ClientID uint64 `protobuf:"varint,5,opt,name=ClientID,proto3" json:"ClientID,omitempty"` // client msg id -> cid + SessionID uint64 `protobuf:"varint,6,opt,name=SessionID,proto3" json:"SessionID,omitempty"` + MsgID uint64 `protobuf:"varint,7,opt,name=MsgID,proto3" json:"MsgID,omitempty"` +} + +func (x *ACKMsg) Reset() { + *x = ACKMsg{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ACKMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ACKMsg) ProtoMessage() {} + +func (x *ACKMsg) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ACKMsg.ProtoReflect.Descriptor instead. +func (*ACKMsg) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{4} +} + +func (x *ACKMsg) GetCode() uint32 { + if x != nil { + return x.Code + } + return 0 +} + +func (x *ACKMsg) GetMsg() string { + if x != nil { + return x.Msg + } + return "" +} + +func (x *ACKMsg) GetType() CmdType { + if x != nil { + return x.Type + } + return CmdType_Login +} + +func (x *ACKMsg) GetConnID() uint64 { + if x != nil { + return x.ConnID + } + return 0 +} + +func (x *ACKMsg) GetClientID() uint64 { + if x != nil { + return x.ClientID + } + return 0 +} + +func (x *ACKMsg) GetSessionID() uint64 { + if x != nil { + return x.SessionID + } + return 0 +} + +func (x *ACKMsg) GetMsgID() uint64 { + if x != nil { + return x.MsgID + } + return 0 +} + +// 登陆消息 +type LoginMsgHead struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DeviceID uint64 `protobuf:"varint,1,opt,name=DeviceID,proto3" json:"DeviceID,omitempty"` +} + +func (x *LoginMsgHead) Reset() { + *x = LoginMsgHead{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoginMsgHead) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginMsgHead) ProtoMessage() {} + +func (x *LoginMsgHead) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginMsgHead.ProtoReflect.Descriptor instead. +func (*LoginMsgHead) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{5} +} + +func (x *LoginMsgHead) GetDeviceID() uint64 { + if x != nil { + return x.DeviceID + } + return 0 +} + +type LoginMsg struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Head *LoginMsgHead `protobuf:"bytes,1,opt,name=Head,proto3" json:"Head,omitempty"` + LoginMsgBody []byte `protobuf:"bytes,2,opt,name=LoginMsgBody,proto3" json:"LoginMsgBody,omitempty"` +} + +func (x *LoginMsg) Reset() { + *x = LoginMsg{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoginMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginMsg) ProtoMessage() {} + +func (x *LoginMsg) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginMsg.ProtoReflect.Descriptor instead. +func (*LoginMsg) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{6} +} + +func (x *LoginMsg) GetHead() *LoginMsgHead { + if x != nil { + return x.Head + } + return nil +} + +func (x *LoginMsg) GetLoginMsgBody() []byte { + if x != nil { + return x.LoginMsgBody + } + return nil +} + +// 心跳消息 +type HeartbeatMsgHead struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *HeartbeatMsgHead) Reset() { + *x = HeartbeatMsgHead{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HeartbeatMsgHead) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HeartbeatMsgHead) ProtoMessage() {} + +func (x *HeartbeatMsgHead) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HeartbeatMsgHead.ProtoReflect.Descriptor instead. +func (*HeartbeatMsgHead) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{7} +} + +type HeartbeatMsg struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Head *HeartbeatMsgHead `protobuf:"bytes,1,opt,name=Head,proto3" json:"Head,omitempty"` + HeartbeatMsgBody []byte `protobuf:"bytes,2,opt,name=HeartbeatMsgBody,proto3" json:"HeartbeatMsgBody,omitempty"` +} + +func (x *HeartbeatMsg) Reset() { + *x = HeartbeatMsg{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HeartbeatMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HeartbeatMsg) ProtoMessage() {} + +func (x *HeartbeatMsg) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HeartbeatMsg.ProtoReflect.Descriptor instead. +func (*HeartbeatMsg) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{8} +} + +func (x *HeartbeatMsg) GetHead() *HeartbeatMsgHead { + if x != nil { + return x.Head + } + return nil +} + +func (x *HeartbeatMsg) GetHeartbeatMsgBody() []byte { + if x != nil { + return x.HeartbeatMsgBody + } + return nil +} + +// 重连消息 +type ReConnMsgHead struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ConnID uint64 `protobuf:"varint,1,opt,name=ConnID,proto3" json:"ConnID,omitempty"` +} + +func (x *ReConnMsgHead) Reset() { + *x = ReConnMsgHead{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReConnMsgHead) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReConnMsgHead) ProtoMessage() {} + +func (x *ReConnMsgHead) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReConnMsgHead.ProtoReflect.Descriptor instead. +func (*ReConnMsgHead) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{9} +} + +func (x *ReConnMsgHead) GetConnID() uint64 { + if x != nil { + return x.ConnID + } + return 0 +} + +type ReConnMsg struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Head *ReConnMsgHead `protobuf:"bytes,1,opt,name=Head,proto3" json:"Head,omitempty"` + ReConnMsgBody []byte `protobuf:"bytes,2,opt,name=ReConnMsgBody,proto3" json:"ReConnMsgBody,omitempty"` +} + +func (x *ReConnMsg) Reset() { + *x = ReConnMsg{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReConnMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReConnMsg) ProtoMessage() {} + +func (x *ReConnMsg) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReConnMsg.ProtoReflect.Descriptor instead. +func (*ReConnMsg) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{10} +} + +func (x *ReConnMsg) GetHead() *ReConnMsgHead { + if x != nil { + return x.Head + } + return nil +} + +func (x *ReConnMsg) GetReConnMsgBody() []byte { + if x != nil { + return x.ReConnMsgBody + } + return nil +} + +var File_message_proto protoreflect.FileDescriptor + +var file_message_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x48, 0x0a, 0x06, 0x4d, 0x73, 0x67, 0x43, + 0x6d, 0x64, 0x12, 0x24, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x10, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x43, 0x6d, 0x64, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x61, 0x79, 0x6c, + 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x50, 0x61, 0x79, 0x6c, 0x6f, + 0x61, 0x64, 0x22, 0x4d, 0x0a, 0x05, 0x55, 0x50, 0x4d, 0x73, 0x67, 0x12, 0x26, 0x0a, 0x04, 0x48, + 0x65, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x2e, 0x55, 0x50, 0x4d, 0x73, 0x67, 0x48, 0x65, 0x61, 0x64, 0x52, 0x04, 0x48, + 0x65, 0x61, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x55, 0x50, 0x4d, 0x73, 0x67, 0x42, 0x6f, 0x64, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x55, 0x50, 0x4d, 0x73, 0x67, 0x42, 0x6f, 0x64, + 0x79, 0x22, 0x3f, 0x0a, 0x09, 0x55, 0x50, 0x4d, 0x73, 0x67, 0x48, 0x65, 0x61, 0x64, 0x12, 0x1a, + 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x43, 0x6f, + 0x6e, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x43, 0x6f, 0x6e, 0x6e, + 0x49, 0x44, 0x22, 0x57, 0x0a, 0x07, 0x50, 0x75, 0x73, 0x68, 0x4d, 0x73, 0x67, 0x12, 0x14, 0x0a, + 0x05, 0x4d, 0x73, 0x67, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x4d, 0x73, + 0x67, 0x49, 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, + 0x44, 0x12, 0x18, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0xbc, 0x01, 0x0a, 0x06, + 0x41, 0x43, 0x4b, 0x4d, 0x73, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x4d, 0x73, + 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x4d, 0x73, 0x67, 0x12, 0x24, 0x0a, 0x04, + 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x2e, 0x43, 0x6d, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x6e, 0x49, 0x44, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x06, 0x43, 0x6f, 0x6e, 0x6e, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x49, 0x44, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x4d, 0x73, 0x67, 0x49, 0x44, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x05, 0x4d, 0x73, 0x67, 0x49, 0x44, 0x22, 0x2a, 0x0a, 0x0c, 0x4c, 0x6f, + 0x67, 0x69, 0x6e, 0x4d, 0x73, 0x67, 0x48, 0x65, 0x61, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x49, 0x44, 0x22, 0x59, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4d, + 0x73, 0x67, 0x12, 0x29, 0x0a, 0x04, 0x48, 0x65, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x15, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x4d, 0x73, 0x67, 0x48, 0x65, 0x61, 0x64, 0x52, 0x04, 0x48, 0x65, 0x61, 0x64, 0x12, 0x22, 0x0a, + 0x0c, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4d, 0x73, 0x67, 0x42, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4d, 0x73, 0x67, 0x42, 0x6f, 0x64, + 0x79, 0x22, 0x12, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x4d, 0x73, + 0x67, 0x48, 0x65, 0x61, 0x64, 0x22, 0x69, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, + 0x61, 0x74, 0x4d, 0x73, 0x67, 0x12, 0x2d, 0x0a, 0x04, 0x48, 0x65, 0x61, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x48, 0x65, + 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x4d, 0x73, 0x67, 0x48, 0x65, 0x61, 0x64, 0x52, 0x04, + 0x48, 0x65, 0x61, 0x64, 0x12, 0x2a, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, + 0x74, 0x4d, 0x73, 0x67, 0x42, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, + 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x4d, 0x73, 0x67, 0x42, 0x6f, 0x64, 0x79, + 0x22, 0x27, 0x0a, 0x0d, 0x52, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x4d, 0x73, 0x67, 0x48, 0x65, 0x61, + 0x64, 0x12, 0x16, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x06, 0x43, 0x6f, 0x6e, 0x6e, 0x49, 0x44, 0x22, 0x5d, 0x0a, 0x09, 0x52, 0x65, 0x43, + 0x6f, 0x6e, 0x6e, 0x4d, 0x73, 0x67, 0x12, 0x2a, 0x0a, 0x04, 0x48, 0x65, 0x61, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x52, + 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x4d, 0x73, 0x67, 0x48, 0x65, 0x61, 0x64, 0x52, 0x04, 0x48, 0x65, + 0x61, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x52, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x4d, 0x73, 0x67, 0x42, + 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x52, 0x65, 0x43, 0x6f, 0x6e, + 0x6e, 0x4d, 0x73, 0x67, 0x42, 0x6f, 0x64, 0x79, 0x2a, 0x4a, 0x0a, 0x07, 0x43, 0x6d, 0x64, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x10, 0x00, 0x12, 0x0d, + 0x0a, 0x09, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x10, 0x01, 0x12, 0x0a, 0x0a, + 0x06, 0x52, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x43, 0x4b, + 0x10, 0x03, 0x12, 0x06, 0x0a, 0x02, 0x55, 0x50, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x75, + 0x73, 0x68, 0x10, 0x05, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x3b, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_message_proto_rawDescOnce sync.Once + file_message_proto_rawDescData = file_message_proto_rawDesc +) + +func file_message_proto_rawDescGZIP() []byte { + file_message_proto_rawDescOnce.Do(func() { + file_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_message_proto_rawDescData) + }) + return file_message_proto_rawDescData +} + +var file_message_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_message_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_message_proto_goTypes = []interface{}{ + (CmdType)(0), // 0: message.CmdType + (*MsgCmd)(nil), // 1: message.MsgCmd + (*UPMsg)(nil), // 2: message.UPMsg + (*UPMsgHead)(nil), // 3: message.UPMsgHead + (*PushMsg)(nil), // 4: message.PushMsg + (*ACKMsg)(nil), // 5: message.ACKMsg + (*LoginMsgHead)(nil), // 6: message.LoginMsgHead + (*LoginMsg)(nil), // 7: message.LoginMsg + (*HeartbeatMsgHead)(nil), // 8: message.HeartbeatMsgHead + (*HeartbeatMsg)(nil), // 9: message.HeartbeatMsg + (*ReConnMsgHead)(nil), // 10: message.ReConnMsgHead + (*ReConnMsg)(nil), // 11: message.ReConnMsg +} +var file_message_proto_depIdxs = []int32{ + 0, // 0: message.MsgCmd.Type:type_name -> message.CmdType + 3, // 1: message.UPMsg.Head:type_name -> message.UPMsgHead + 0, // 2: message.ACKMsg.Type:type_name -> message.CmdType + 6, // 3: message.LoginMsg.Head:type_name -> message.LoginMsgHead + 8, // 4: message.HeartbeatMsg.Head:type_name -> message.HeartbeatMsgHead + 10, // 5: message.ReConnMsg.Head:type_name -> message.ReConnMsgHead + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_message_proto_init() } +func file_message_proto_init() { + if File_message_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MsgCmd); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UPMsg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UPMsgHead); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PushMsg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ACKMsg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LoginMsgHead); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LoginMsg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HeartbeatMsgHead); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HeartbeatMsg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReConnMsgHead); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReConnMsg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_message_proto_rawDesc, + NumEnums: 1, + NumMessages: 11, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_message_proto_goTypes, + DependencyIndexes: file_message_proto_depIdxs, + EnumInfos: file_message_proto_enumTypes, + MessageInfos: file_message_proto_msgTypes, + }.Build() + File_message_proto = out.File + file_message_proto_rawDesc = nil + file_message_proto_goTypes = nil + file_message_proto_depIdxs = nil +} diff --git a/common/idl/message/message.proto b/common/idl/message/message.proto new file mode 100644 index 0000000..fcd1133 --- /dev/null +++ b/common/idl/message/message.proto @@ -0,0 +1,79 @@ +syntax = "proto3"; + +option go_package = "./;message"; + +package message; +// cd common/idl; protoc -I message --go_out=plugins=grpc:message message/message.proto +enum CmdType { //枚举消息类型 + Login = 0; + Heartbeat = 1; + ReConn = 2; + ACK = 3; + UP = 4; // 上行消息 + Push = 5; // 下行 推送消息 +} + +// 顶层cmd pb结构 +message MsgCmd{ + CmdType Type = 1; + bytes Payload = 2; +} + +// 上行消息 pb结构 +message UPMsg { + UPMsgHead Head = 1; + bytes UPMsgBody = 2; +} + +// 上行消息头 pb结构 +message UPMsgHead { + uint64 ClientID = 1; + uint64 ConnID = 2; +} + +// 下行消息 pb结构 +message PushMsg { + uint64 MsgID = 1; + uint64 SessionID = 2; + bytes Content = 3; +} + +// ACK 消息 +message ACKMsg { + uint32 Code = 1; + string Msg = 2; + CmdType Type = 3; + uint64 ConnID = 4; + uint64 ClientID = 5; // client msg id -> cid + uint64 SessionID = 6; + uint64 MsgID = 7; +} + +// 登陆消息 +message LoginMsgHead { + uint64 DeviceID = 1; +} + +message LoginMsg { + LoginMsgHead Head = 1; + bytes LoginMsgBody = 2; +} + +// 心跳消息 +message HeartbeatMsgHead { +} + +message HeartbeatMsg { + HeartbeatMsgHead Head = 1; + bytes HeartbeatMsgBody = 2; +} + +// 重连消息 +message ReConnMsgHead { + uint64 ConnID = 1; +} + +message ReConnMsg { + ReConnMsgHead Head = 1; + bytes ReConnMsgBody = 2; +} diff --git a/common/logger/log.go b/common/logger/log.go new file mode 100644 index 0000000..2643e85 --- /dev/null +++ b/common/logger/log.go @@ -0,0 +1,92 @@ +package logger + +import ( + "context" + "fmt" + "github.com/hardcore-os/plato/common/config" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "os" + + "gopkg.in/natefinch/lumberjack.v2" +) + +var ( + logger = &Logger{} +) + +type Logger struct { + Options + + logger *zap.Logger +} + +func NewLogger(opts ...Option) { + logger.Options = defaultOptions + for _, o := range opts { + o.apply(&logger.Options) + } + + fileWriteSyncer := logger.getFileLogWriter() + var core zapcore.Core + encoderConfig := zap.NewProductionEncoderConfig() + encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + encoder := zapcore.NewJSONEncoder(encoderConfig) + if config.IsDebug() { + core = zapcore.NewTee( + zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zapcore.DebugLevel), + zapcore.NewCore(encoder, fileWriteSyncer, zap.DebugLevel), + ) + } else { + core = zapcore.NewTee( + zapcore.NewCore(encoder, fileWriteSyncer, zapcore.InfoLevel), + ) + } + logger.logger = zap.New(core, zap.WithCaller(true), zap.AddCallerSkip(logger.callerSkip)) +} + +func (l *Logger) getFileLogWriter() (writeSyncer zapcore.WriteSyncer) { + lumberJackLogger := &lumberjack.Logger{ + Filename: fmt.Sprintf("%s/%s", l.logDir, l.filename), + MaxSize: l.maxSize, + MaxAge: l.maxAge, + MaxBackups: l.maxBackups, + } + + return zapcore.AddSync(lumberJackLogger) +} + +// DebugCtx ... +func DebugCtx(ctx context.Context, message string, fields ...zap.Field) { + logger.logger.With(zap.String(traceID, GetTraceID(ctx))).Debug(message, fields...) +} + +// InfoCtx ... +func InfoCtx(ctx context.Context, message string, fields ...zap.Field) { + logger.logger.With(zap.String(traceID, GetTraceID(ctx))).Info(message, fields...) +} + +// WarnCtx ... +func WarnCtx(ctx context.Context, message string, fields ...zap.Field) { + logger.logger.With(zap.String(traceID, GetTraceID(ctx))).Warn(message, fields...) +} + +// ErrorCtx ... +func ErrorCtx(ctx context.Context, message string, fields ...zap.Field) { + logger.logger.With(zap.String(traceID, GetTraceID(ctx))).Error(message, fields...) +} + +// DPanicCtx ... +func DPanicCtx(ctx context.Context, message string, fields ...zap.Field) { + logger.logger.With(zap.String(traceID, GetTraceID(ctx))).DPanic(message, fields...) +} + +// PanicCtx ... +func PanicCtx(ctx context.Context, message string, fields ...zap.Field) { + logger.logger.With(zap.String(traceID, GetTraceID(ctx))).Panic(message, fields...) +} + +// FatalCtx ... +func FatalCtx(ctx context.Context, message string, fields ...zap.Field) { + logger.logger.With(zap.String(traceID, GetTraceID(ctx))).Fatal(message, fields...) +} diff --git a/common/logger/log_test.go b/common/logger/log_test.go new file mode 100644 index 0000000..290dfd6 --- /dev/null +++ b/common/logger/log_test.go @@ -0,0 +1,34 @@ +package logger + +import ( + "context" + "github.com/hardcore-os/plato/common/config" + ptrace "github.com/hardcore-os/plato/common/prpc/trace" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" + "testing" + "time" +) + +func TestLogger(t *testing.T) { + config.Init("../../plato.yaml") + NewLogger() + InfoCtx(context.Background(), "info test") + DebugCtx(context.Background(), "debug test") + WarnCtx(context.Background(), "warn test") + ErrorCtx(context.Background(), "error test") + time.Sleep(1 * time.Second) +} + +func TestTraceLog(t *testing.T) { + config.Init("../../plato.yaml") + NewLogger() + ptrace.StartAgent() + defer ptrace.StopAgent() + + tr := otel.GetTracerProvider().Tracer(ptrace.TraceName) + ctx, span := tr.Start(context.Background(), "logger-trace", trace.WithAttributes(), trace.WithSpanKind(trace.SpanKindClient)) + defer span.End() + + InfoCtx(ctx, "test") +} diff --git a/common/logger/option.go b/common/logger/option.go new file mode 100644 index 0000000..5febbdb --- /dev/null +++ b/common/logger/option.go @@ -0,0 +1,81 @@ +package logger + +var ( + defaultOptions = Options{ + logDir: "/home/dev/go_project/logs/app_logs", + filename: "default.log", + maxSize: 500, + maxAge: 1, + maxBackups: 10, + callerSkip: 1, + } +) + +type Options struct { + logDir string + filename string + maxSize int + maxBackups int + maxAge int + compress bool + callerSkip int +} + +type Option interface { + apply(*Options) +} + +type OptionFunc func(*Options) + +func (o OptionFunc) apply(opts *Options) { + o(opts) +} + +// WithLogDir ... +func WithLogDir(dir string) Option { + return OptionFunc(func(options *Options) { + options.logDir = dir + }) +} + +// WithHistoryLogFileName ... +func WithHistoryLogFileName(fileName string) Option { + return OptionFunc(func(options *Options) { + options.filename = fileName + }) +} + +// WithMaxSize ... +func WithMaxSize(size int) Option { + return OptionFunc(func(options *Options) { + options.maxSize = size + }) +} + +// WithMaxBackups ... +func WithMaxBackups(backup int) Option { + return OptionFunc(func(options *Options) { + options.maxBackups = backup + }) +} + +// WithMaxAge ... +func WithMaxAge(maxAge int) Option { + return OptionFunc(func(options *Options) { + options.maxAge = maxAge + }) +} + +// WithCompress ... +func WithCompress(b bool) Option { + return OptionFunc(func(options *Options) { + options.compress = b + }) +} + +// WithCallerSkip ... +func WithCallerSkip(skip int) Option { + return OptionFunc(func(options *Options) { + options.callerSkip = skip + }) +} diff --git a/common/logger/trace.go b/common/logger/trace.go new file mode 100644 index 0000000..73583e0 --- /dev/null +++ b/common/logger/trace.go @@ -0,0 +1,20 @@ +package logger + +import ( + "context" + "go.opentelemetry.io/otel/trace" +) + +const ( + traceID = "trace_id" +) + +func GetTraceID(ctx context.Context) string { + var traceID string + span := trace.SpanFromContext(ctx) + if span.SpanContext().TraceID().IsValid() { + traceID = span.SpanContext().TraceID().String() + } + + return traceID +} diff --git a/common/prpc/client.go b/common/prpc/client.go new file mode 100644 index 0000000..d2b73a5 --- /dev/null +++ b/common/prpc/client.go @@ -0,0 +1,93 @@ +package prpc + +import ( + "context" + "fmt" + "time" + + "github.com/hardcore-os/plato/common/prpc/discov/plugin" + + "google.golang.org/grpc/resolver" + + "github.com/hardcore-os/plato/common/prpc/discov" + clientinterceptor "github.com/hardcore-os/plato/common/prpc/interceptor/client" + presolver "github.com/hardcore-os/plato/common/prpc/resolver" + "google.golang.org/grpc" + "google.golang.org/grpc/balancer/roundrobin" +) + +const ( + dialTimeout = 5 * time.Second +) + +type PClient struct { + serviceName string + d discov.Discovery + interceptors []grpc.UnaryClientInterceptor + conn *grpc.ClientConn +} + +// NewPClient ... +func NewPClient(serviceName string, interceptors ...grpc.UnaryClientInterceptor) (*PClient, error) { + p := &PClient{ + serviceName: serviceName, + interceptors: interceptors, + } + + if p.d == nil { + dis, err := plugin.GetDiscovInstance() + if err != nil { + panic(err) + } + + p.d = dis + } + + resolver.Register(presolver.NewDiscovBuilder(p.d)) + + conn, err := p.dial() + p.conn = conn + + return p, err +} + +// Conn return *grpc.ClientConn +func (p *PClient) Conn() *grpc.ClientConn { + return p.conn +} + +func (p *PClient) dial() (*grpc.ClientConn, error) { + svcCfg := fmt.Sprintf(`{"loadBalancingPolicy":"%s"}`, roundrobin.Name) + balancerOpt := grpc.WithDefaultServiceConfig(svcCfg) + + interceptors := []grpc.UnaryClientInterceptor{ + clientinterceptor.TraceUnaryClientInterceptor(), + clientinterceptor.MetricUnaryClientInterceptor(), + } + interceptors = append(interceptors, p.interceptors...) + + options := []grpc.DialOption{ + balancerOpt, + grpc.WithChainUnaryInterceptor(interceptors...), + grpc.WithInsecure(), + } + + ctx, _ := context.WithTimeout(context.Background(), dialTimeout) + + return grpc.DialContext(ctx, fmt.Sprintf("discov:///%v", p.serviceName), options...) +} + +func (p *PClient) DialByEndPoint(address string) (*grpc.ClientConn, error) { + interceptors := []grpc.UnaryClientInterceptor{ + clientinterceptor.TraceUnaryClientInterceptor(), + clientinterceptor.MetricUnaryClientInterceptor(), + } + interceptors = append(interceptors, p.interceptors...) + options := []grpc.DialOption{ + grpc.WithChainUnaryInterceptor(interceptors...), + grpc.WithInsecure(), + } + + ctx, _ := context.WithTimeout(context.TODO(), dialTimeout) + return grpc.DialContext(ctx, address, options...) +} diff --git a/common/prpc/client_test.go b/common/prpc/client_test.go new file mode 100644 index 0000000..8300cd0 --- /dev/null +++ b/common/prpc/client_test.go @@ -0,0 +1,19 @@ +package prpc + +import ( + "testing" + + "github.com/hardcore-os/plato/common/config" + + ptrace "github.com/hardcore-os/plato/common/prpc/trace" + "github.com/stretchr/testify/assert" +) + +func TestNewPClient(t *testing.T) { + config.Init("../../plato.yaml") + ptrace.StartAgent() + defer ptrace.StopAgent() + + _, err := NewPClient("plato_server") + assert.NoError(t, err) +} diff --git a/common/prpc/code/code.go b/common/prpc/code/code.go new file mode 100644 index 0000000..4af05ee --- /dev/null +++ b/common/prpc/code/code.go @@ -0,0 +1,10 @@ +package code + +import "google.golang.org/grpc/codes" + +const ( + // CodeTooManyRequest ... + CodeTooManyRequest codes.Code = 100 + // CodeCircuitBreak ... + CodeCircuitBreak codes.Code = 101 +) diff --git a/common/prpc/config/config.go b/common/prpc/config/config.go new file mode 100644 index 0000000..90ac1a1 --- /dev/null +++ b/common/prpc/config/config.go @@ -0,0 +1,35 @@ +package config + +import ( + "github.com/spf13/viper" +) + +// GetDiscovName 获取discov用哪种方式实现 +func GetDiscovName() string { + return viper.GetString("prpc.discov.name") +} + +// GetDiscovEndpoints 获取discov的 endpoints +func GetDiscovEndpoints() []string { + return viper.GetStringSlice("discovery.endpoints") +} + +// GetTraceEnable 是否开启trace +func GetTraceEnable() bool { + return viper.GetBool("prpc.trace.enable") +} + +// GetTraceCollectionUrl 获取trace collection url +func GetTraceCollectionUrl() string { + return viper.GetString("prpc.trace.url") +} + +// GetTraceServiceName 获取服务名 +func GetTraceServiceName() string { + return viper.GetString("prpc.trace.service_name") +} + +// GetTraceSampler 获取trace采样率 +func GetTraceSampler() float64 { + return viper.GetFloat64("prpc.trace.sampler") +} diff --git a/common/prpc/discov/discovery.go b/common/prpc/discov/discovery.go new file mode 100644 index 0000000..6fde519 --- /dev/null +++ b/common/prpc/discov/discovery.go @@ -0,0 +1,18 @@ +package discov + +import "context" + +type Discovery interface { + // Name 服务发现名字 eg: etcd zk consul + Name() string + // Register 注册服务 + Register(ctx context.Context, service *Service) + // UnRegister 取消注册服务 + UnRegister(ctx context.Context, service *Service) + // GetService 获取服务节点信息 + GetService(ctx context.Context, name string) *Service + // AddListener 增加监听者 + AddListener(ctx context.Context, f func()) + // NotifyListeners 通知所有的监听者 + NotifyListeners() +} diff --git a/common/prpc/discov/etcd/etcd_register.go b/common/prpc/discov/etcd/etcd_register.go new file mode 100644 index 0000000..c8cb0e0 --- /dev/null +++ b/common/prpc/discov/etcd/etcd_register.go @@ -0,0 +1,358 @@ +package etcd + +import ( + "context" + "encoding/json" + "fmt" + "github.com/bytedance/gopkg/util/logger" + "github.com/hardcore-os/plato/common/prpc/discov" + clientv3 "go.etcd.io/etcd/client/v3" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" +) + +const ( + KeyPrefix = "/plato/prpc" +) + +type Register struct { + Options + cli *clientv3.Client + serviceRegisterCh chan *discov.Service + serviceUnRegisterCh chan *discov.Service + lock sync.Mutex + downServices atomic.Value + registerServices map[string]*registerService + listeners []func() +} + +type registerService struct { + service *discov.Service + leaseID clientv3.LeaseID + isRegistered bool + keepAliveChan <-chan *clientv3.LeaseKeepAliveResponse +} + +func NewETCDRegister(opts ...Option) (discov.Discovery, error) { + opt := defaultOption + for _, o := range opts { + o(&opt) + } + + r := &Register{ + Options: opt, + serviceRegisterCh: make(chan *discov.Service), + serviceUnRegisterCh: make(chan *discov.Service), + lock: sync.Mutex{}, + downServices: atomic.Value{}, + registerServices: make(map[string]*registerService), + } + + if err := r.init(context.TODO()); err != nil { + return nil, err + } + + return r, nil +} + +func (r *Register) init(ctx context.Context) error { + var err error + r.cli, err = clientv3.New( + clientv3.Config{ + Endpoints: r.endpoints, + DialTimeout: r.dialTimeout, + }) + + if err != nil { + return err + } + + go r.run() + return nil +} + +func (r *Register) run() { + for { + select { + case service := <-r.serviceRegisterCh: + if _, ok := r.registerServices[service.Name]; ok { + r.registerServices[service.Name].service.Endpoints = append(r.registerServices[service.Name].service.Endpoints, service.Endpoints...) + r.registerServices[service.Name].isRegistered = false // 重新上报到etcd + } else { + r.registerServices[service.Name] = ®isterService{ + service: service, + isRegistered: false, + } + } + case service := <-r.serviceUnRegisterCh: + if _, ok := r.registerServices[service.Name]; !ok { + logger.CtxErrorf(context.TODO(), "UnRegisterService err, service %v was not registered", service.Name) + continue + } + r.unRegisterService(context.TODO(), service) + default: + r.registerServiceOrKeepAlive(context.TODO()) + time.Sleep(r.registerServiceOrKeepAliveInterval) + } + } +} + +func (r *Register) registerServiceOrKeepAlive(ctx context.Context) { + for _, service := range r.registerServices { + if !service.isRegistered { + r.registerService(ctx, service) + r.registerServices[service.service.Name].isRegistered = true + } else { + r.KeepAlive(ctx, service) + } + } +} + +func (r *Register) registerService(ctx context.Context, service *registerService) { + leaseGrantResp, err := r.cli.Grant(ctx, r.keepAliveInterval) + if err != nil { + logger.CtxErrorf(ctx, "register service grant, err:%v", err) + return + } + service.leaseID = leaseGrantResp.ID + + for _, endpoint := range service.service.Endpoints { + key := r.getEtcdRegisterKey(service.service.Name, endpoint.IP, endpoint.Port) + raw, err := json.Marshal(endpoint) + if err != nil { + logger.CtxErrorf(ctx, "register service err, err:%v, register data:%v", err, string(raw)) + continue + } + + _, err = r.cli.Put(ctx, key, string(raw), clientv3.WithLease(leaseGrantResp.ID)) + if err != nil { + logger.CtxErrorf(ctx, "register service err, err:%v, register data:%v", err, string(raw)) + continue + } + } + + keepAliveChan, err := r.cli.KeepAlive(ctx, leaseGrantResp.ID) + if err != nil { + logger.CtxErrorf(ctx, "register service keepalive err:%v", err) + return + } + + service.keepAliveChan = keepAliveChan + service.isRegistered = true +} + +func (r *Register) unRegisterService(ctx context.Context, service *discov.Service) { + endpoints := make([]*discov.Endpoint, 0) + for _, endpoint := range r.registerServices[service.Name].service.Endpoints { + var isRemove bool + for _, unRegisterEndpoint := range service.Endpoints { + if endpoint.IP == unRegisterEndpoint.IP && endpoint.Port == unRegisterEndpoint.Port { + _, err := r.cli.Delete(context.TODO(), r.getEtcdRegisterKey(service.Name, endpoint.IP, endpoint.Port)) + if err != nil { + logger.CtxErrorf(ctx, "UnRegisterService etcd del err, service %v was not registered", service.Name) + } + isRemove = true + break + } + } + + if !isRemove { + endpoints = append(endpoints, endpoint) + } + } + + if len(endpoints) == 0 { + delete(r.registerServices, service.Name) + } else { + // del service endpoints fail, put endpoints into map + r.registerServices[service.Name].service.Endpoints = endpoints + } +} + +func (r *Register) KeepAlive(ctx context.Context, service *registerService) { + for { + select { + case <-service.keepAliveChan: + default: + return + } + } +} + +func (r *Register) Name() string { + return "etcd" +} + +func (r *Register) AddListener(ctx context.Context, f func()) { + r.listeners = append(r.listeners, f) +} + +func (r *Register) NotifyListeners() { + for _, listener := range r.listeners { + listener() + } +} + +func (r *Register) Register(ctx context.Context, service *discov.Service) { + r.serviceRegisterCh <- service +} + +func (r *Register) UnRegister(ctx context.Context, service *discov.Service) { + r.serviceUnRegisterCh <- service +} + +func (r *Register) GetService(ctx context.Context, name string) *discov.Service { + allServices := r.getDownServices() + if val, ok := allServices[name]; ok { + return val + } + + // 防止并发获取service导致cache中的数据混乱 + r.lock.Lock() + defer r.lock.Unlock() + + key := r.getEtcdRegisterPrefixKey(name) + getResp, _ := r.cli.Get(ctx, key, clientv3.WithPrefix()) + service := &discov.Service{ + Name: name, + Endpoints: make([]*discov.Endpoint, 0), + } + + for _, item := range getResp.Kvs { + var endpoint discov.Endpoint + if err := json.Unmarshal(item.Value, &endpoint); err != nil { + continue + } + + service.Endpoints = append(service.Endpoints, &endpoint) + } + + allServices[name] = service + r.downServices.Store(allServices) + + go r.watch(ctx, key, getResp.Header.Revision+1) + return service +} + +func (r *Register) watch(ctx context.Context, key string, revision int64) { + rch := r.cli.Watch(ctx, key, clientv3.WithRev(revision), clientv3.WithPrefix()) + for n := range rch { + for _, ev := range n.Events { + switch ev.Type { + case clientv3.EventTypePut: + var endpoint discov.Endpoint + if err := json.Unmarshal(ev.Kv.Value, &endpoint); err != nil { + continue + } + serviceName, _, _ := r.getServiceNameByETCDKey(string(ev.Kv.Key)) + r.updateDownService(&discov.Service{ + Name: serviceName, + Endpoints: []*discov.Endpoint{&endpoint}, + }) + case clientv3.EventTypeDelete: + var endpoint discov.Endpoint + if err := json.Unmarshal(ev.Kv.Value, &endpoint); err != nil { + continue + } + serviceName, ip, port := r.getServiceNameByETCDKey(string(ev.Kv.Key)) + r.delDownService(&discov.Service{ + Name: serviceName, + Endpoints: []*discov.Endpoint{ + { + IP: ip, + Port: port, + }, + }, + }) + } + } + } +} + +func (r *Register) updateDownService(service *discov.Service) { + r.lock.Lock() + defer r.lock.Unlock() + + downService := r.downServices.Load().(map[string]*discov.Service) + if _, ok := downService[service.Name]; !ok { + downService[service.Name] = service + r.downServices.Store(downService) + return + } + + for _, newAddEndpoint := range service.Endpoints { + var isExist bool + for idx, endpoint := range downService[service.Name].Endpoints { + if newAddEndpoint.IP == endpoint.IP && newAddEndpoint.Port == endpoint.Port { + downService[service.Name].Endpoints[idx] = newAddEndpoint + isExist = true + break + } + } + + if !isExist { + downService[service.Name].Endpoints = append(downService[service.Name].Endpoints, newAddEndpoint) + } + } + + r.downServices.Store(downService) + r.NotifyListeners() +} + +func (r *Register) delDownService(service *discov.Service) { + r.lock.Lock() + defer r.lock.Unlock() + + downServices := r.downServices.Load().(map[string]*discov.Service) + if _, ok := downServices[service.Name]; !ok { + return + } + + endpoints := make([]*discov.Endpoint, 0) + for _, endpoint := range downServices[service.Name].Endpoints { + var isRemove bool + for _, delEndpoint := range service.Endpoints { + if delEndpoint.IP == endpoint.IP && delEndpoint.Port == endpoint.Port { + isRemove = true + break + } + } + + if !isRemove { + endpoints = append(endpoints, endpoint) + } + } + + downServices[service.Name].Endpoints = endpoints + r.downServices.Store(downServices) + + r.NotifyListeners() +} + +func (r *Register) getDownServices() map[string]*discov.Service { + allServices := r.downServices.Load() + if allServices == nil { + return make(map[string]*discov.Service, 0) + } + + return allServices.(map[string]*discov.Service) +} + +func (r *Register) getEtcdRegisterKey(name, ip string, port int) string { + return fmt.Sprintf(KeyPrefix+"%v/%v/%v", name, ip, port) +} + +func (r *Register) getEtcdRegisterPrefixKey(name string) string { + return fmt.Sprintf(KeyPrefix+"%v", name) +} + +func (r *Register) getServiceNameByETCDKey(key string) (string, string, int) { + trimStr := strings.TrimPrefix(key, KeyPrefix) + strs := strings.Split(trimStr, "/") + + ip, _ := strconv.Atoi(strs[2]) + return strs[0], strs[1], ip +} diff --git a/common/prpc/discov/etcd/etcd_register_test.go b/common/prpc/discov/etcd/etcd_register_test.go new file mode 100644 index 0000000..ddb4ca2 --- /dev/null +++ b/common/prpc/discov/etcd/etcd_register_test.go @@ -0,0 +1,39 @@ +package etcd + +import ( + "context" + "testing" + "time" + + "github.com/hardcore-os/plato/common/prpc/discov" + + "github.com/stretchr/testify/assert" +) + +func TestNewETCDRegister(t *testing.T) { + _, err := NewETCDRegister() + + assert.Nil(t, err) +} + +func TestRegister_Register(t *testing.T) { + register, _ := NewETCDRegister() + + service := &discov.Service{ + Name: "test", + Endpoints: []*discov.Endpoint{ + &discov.Endpoint{ + ServerName: "test", + IP: "127.0.0.1", + Port: 9557, + Weight: 100, + Enable: true, + }, + }, + } + register.Register(context.TODO(), service) + time.Sleep(2 * time.Second) + registerService := register.GetService(context.TODO(), "test") + + assert.Equal(t, *service.Endpoints[0], *registerService.Endpoints[0]) +} diff --git a/common/prpc/discov/etcd/option.go b/common/prpc/discov/etcd/option.go new file mode 100644 index 0000000..9272b07 --- /dev/null +++ b/common/prpc/discov/etcd/option.go @@ -0,0 +1,58 @@ +package etcd + +import "time" + +var ( + defaultOption = Options{ + // TODO replace default endpoints + endpoints: []string{"127.0.0.1:2379"}, + dialTimeout: 10 * time.Second, + syncFlushCacheInterval: 10 * time.Second, + keepAliveInterval: 10, + } +) + +type Options struct { + syncFlushCacheInterval time.Duration + endpoints []string + dialTimeout time.Duration + keepAliveInterval int64 + registerServiceOrKeepAliveInterval time.Duration +} + +type Option func(o *Options) + +// WithEndpoints ... +func WithEndpoints(endpoints []string) Option { + return func(o *Options) { + o.endpoints = endpoints + } +} + +// WithDialTimeout ... +func WithDialTimeout(dialTimeout time.Duration) Option { + return func(o *Options) { + o.dialTimeout = dialTimeout + } +} + +// WithSyncFlushCacheInterval ... +func WithSyncFlushCacheInterval(t time.Duration) Option { + return func(o *Options) { + o.syncFlushCacheInterval = t + } +} + +// WithKeepAliveInterval ... +func WithKeepAliveInterval(ttl int64) Option { + return func(o *Options) { + o.keepAliveInterval = ttl + } +} + +// WithRegisterServiceOrKeepAliveInterval ... +func WithRegisterServiceOrKeepAliveInterval(t time.Duration) Option { + return func(o *Options) { + o.registerServiceOrKeepAliveInterval = t + } +} diff --git a/common/prpc/discov/plugin/plugin.go b/common/prpc/discov/plugin/plugin.go new file mode 100644 index 0000000..a7bbd37 --- /dev/null +++ b/common/prpc/discov/plugin/plugin.go @@ -0,0 +1,18 @@ +package plugin + +import ( + "errors" + "fmt" + "github.com/hardcore-os/plato/common/prpc/config" + "github.com/hardcore-os/plato/common/prpc/discov" + "github.com/hardcore-os/plato/common/prpc/discov/etcd" +) + +func GetDiscovInstance() (discov.Discovery, error) { + name := config.GetDiscovName() + switch name { + case "etcd": + return etcd.NewETCDRegister(etcd.WithEndpoints(config.GetDiscovEndpoints())) + } + return nil, errors.New(fmt.Sprintf("not exist plugin:%v", name)) +} diff --git a/common/prpc/discov/service.go b/common/prpc/discov/service.go new file mode 100644 index 0000000..858c1df --- /dev/null +++ b/common/prpc/discov/service.go @@ -0,0 +1,14 @@ +package discov + +type Service struct { + Name string `json:"name"` + Endpoints []*Endpoint `json:"endpoints"` +} + +type Endpoint struct { + ServerName string `json:"server_name"` + IP string `json:"ip"` + Port int `json:"port"` + Weight int `json:"weight"` + Enable bool `json:"enable"` +} diff --git a/common/prpc/example/client/main.go b/common/prpc/example/client/main.go new file mode 100644 index 0000000..191a78f --- /dev/null +++ b/common/prpc/example/client/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "context" + "fmt" + "runtime" + "strings" + "time" + + "github.com/hardcore-os/plato/common/config" + "github.com/hardcore-os/plato/common/prpc" + "github.com/hardcore-os/plato/common/prpc/example/helloservice" + ptrace "github.com/hardcore-os/plato/common/prpc/trace" +) + +func main() { + config.Init(currentFileDir() + "/prpc_client.yaml") + + ptrace.StartAgent() + defer ptrace.StopAgent() + + pCli, _ := prpc.NewPClient("prpc_server") + + ctx, _ := context.WithTimeout(context.TODO(), 100*time.Second) + cli := helloservice.NewGreeterClient(pCli.Conn()) + resp, err := cli.SayHello(ctx, &helloservice.HelloRequest{ + Name: "xxxxxx", + }) + fmt.Println(resp, err) +} + +func currentFileDir() string { + _, file, _, ok := runtime.Caller(1) + parts := strings.Split(file, "/") + + if !ok { + return "" + } + + dir := "" + for i := 0; i < len(parts)-1; i++ { + dir += "/" + parts[i] + } + + return dir[1:] +} diff --git a/common/prpc/example/client/prpc_client.yaml b/common/prpc/example/client/prpc_client.yaml new file mode 100644 index 0000000..8878ee5 --- /dev/null +++ b/common/prpc/example/client/prpc_client.yaml @@ -0,0 +1,19 @@ +global: + env: debug +discovery: + endpoints: + - localhost:2379 + timeout: 5 +ip_conf: + service_path: /plato/ip_dispatcher +prpc: + discov: + name: etcd + endpoints: + - localhost:2379 + trace: + enable: true + url: http://127.0.0.1:14268/api/traces + service_name: prpc_client + sampler: 1.0 + diff --git a/common/prpc/example/helloservice/helloworld.pb.go b/common/prpc/example/helloservice/helloworld.pb.go new file mode 100644 index 0000000..2a9cd5c --- /dev/null +++ b/common/prpc/example/helloservice/helloworld.pb.go @@ -0,0 +1,199 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: helloworld.proto + +package helloservice + +import ( + context "context" + fmt "fmt" + math "math" + + proto "github.com/golang/protobuf/proto" + grpc "google.golang.org/grpc" +) + +// 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.ProtoPackageIsVersion3 // please upgrade the proto package + +// The request message containing the user's name. +type HelloRequest struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *HelloRequest) Reset() { *m = HelloRequest{} } +func (m *HelloRequest) String() string { return proto.CompactTextString(m) } +func (*HelloRequest) ProtoMessage() {} +func (*HelloRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_17b8c58d586b62f2, []int{0} +} + +func (m *HelloRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_HelloRequest.Unmarshal(m, b) +} +func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_HelloRequest.Marshal(b, m, deterministic) +} +func (m *HelloRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_HelloRequest.Merge(m, src) +} +func (m *HelloRequest) XXX_Size() int { + return xxx_messageInfo_HelloRequest.Size(m) +} +func (m *HelloRequest) XXX_DiscardUnknown() { + xxx_messageInfo_HelloRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_HelloRequest proto.InternalMessageInfo + +func (m *HelloRequest) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +// The response message containing the greetings +type HelloReply struct { + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *HelloReply) Reset() { *m = HelloReply{} } +func (m *HelloReply) String() string { return proto.CompactTextString(m) } +func (*HelloReply) ProtoMessage() {} +func (*HelloReply) Descriptor() ([]byte, []int) { + return fileDescriptor_17b8c58d586b62f2, []int{1} +} + +func (m *HelloReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_HelloReply.Unmarshal(m, b) +} +func (m *HelloReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_HelloReply.Marshal(b, m, deterministic) +} +func (m *HelloReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_HelloReply.Merge(m, src) +} +func (m *HelloReply) XXX_Size() int { + return xxx_messageInfo_HelloReply.Size(m) +} +func (m *HelloReply) XXX_DiscardUnknown() { + xxx_messageInfo_HelloReply.DiscardUnknown(m) +} + +var xxx_messageInfo_HelloReply proto.InternalMessageInfo + +func (m *HelloReply) GetMessage() string { + if m != nil { + return m.Message + } + return "" +} + +func init() { + proto.RegisterType((*HelloRequest)(nil), "helloworld.HelloRequest") + proto.RegisterType((*HelloReply)(nil), "helloworld.HelloReply") +} + +func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_17b8c58d586b62f2) } + +var fileDescriptor_17b8c58d586b62f2 = []byte{ + // 152 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9, + 0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x88, + 0x28, 0x29, 0x71, 0xf1, 0x78, 0x80, 0x78, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42, + 0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92, + 0x1a, 0x17, 0x17, 0x54, 0x4d, 0x41, 0x4e, 0xa5, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71, + 0x62, 0x3a, 0x4c, 0x11, 0x8c, 0x6b, 0xe4, 0xc9, 0xc5, 0xee, 0x5e, 0x94, 0x9a, 0x5a, 0x92, 0x5a, + 0x24, 0x64, 0xc7, 0xc5, 0x11, 0x9c, 0x58, 0x09, 0xd6, 0x25, 0x24, 0xa1, 0x87, 0xe4, 0x02, 0x64, + 0xcb, 0xa4, 0xc4, 0xb0, 0xc8, 0x14, 0xe4, 0x54, 0x2a, 0x31, 0x38, 0x71, 0x46, 0xb1, 0xeb, 0xe9, + 0x5b, 0x97, 0xa4, 0x16, 0x97, 0x24, 0xb1, 0x81, 0x1d, 0x6d, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, + 0x39, 0xe2, 0x94, 0x47, 0xc8, 0x00, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// GreeterClient is the client API for Greeter service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type GreeterClient interface { + // Sends a greeting + SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) +} + +type greeterClient struct { + cc *grpc.ClientConn +} + +func NewGreeterClient(cc *grpc.ClientConn) GreeterClient { + return &greeterClient{cc} +} + +func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { + out := new(HelloReply) + err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GreeterServer is the server API for Greeter service. +type GreeterServer interface { + // Sends a greeting + SayHello(context.Context, *HelloRequest) (*HelloReply, error) +} + +func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) { + s.RegisterService(&_Greeter_serviceDesc, srv) +} + +func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HelloRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GreeterServer).SayHello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/helloworld.Greeter/SayHello", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Greeter_serviceDesc = grpc.ServiceDesc{ + ServiceName: "helloworld.Greeter", + HandlerType: (*GreeterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SayHello", + Handler: _Greeter_SayHello_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "helloworld.proto", +} diff --git a/common/prpc/example/helloservice/helloworld.proto b/common/prpc/example/helloservice/helloworld.proto new file mode 100644 index 0000000..41b3b70 --- /dev/null +++ b/common/prpc/example/helloservice/helloworld.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +option go_package = "./;test"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) { + } +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} diff --git a/common/prpc/example/helloservice/service.go b/common/prpc/example/helloservice/service.go new file mode 100644 index 0000000..0e16a16 --- /dev/null +++ b/common/prpc/example/helloservice/service.go @@ -0,0 +1,18 @@ +package helloservice + +import ( + "context" +) + +type Service struct { +} + +type HelloServer struct { + service *Service +} + +func (s HelloServer) SayHello(ctx context.Context, re *HelloRequest) (*HelloReply, error) { + return &HelloReply{ + Message: "hello " + re.Name, + }, nil +} diff --git a/common/prpc/example/server/main.go b/common/prpc/example/server/main.go new file mode 100644 index 0000000..e682c25 --- /dev/null +++ b/common/prpc/example/server/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "context" + "runtime" + "strings" + + "github.com/hardcore-os/plato/common/config" + + "github.com/hardcore-os/plato/common/prpc" + "github.com/hardcore-os/plato/common/prpc/example/helloservice" + ptrace "github.com/hardcore-os/plato/common/prpc/trace" + "google.golang.org/grpc" +) + +const ( + testIp = "127.0.0.1" + testPort = 8867 +) + +func main() { + config.Init(currentFileDir() + "/prpc_server.yaml") + + ptrace.StartAgent() + defer ptrace.StopAgent() + + s := prpc.NewPServer(prpc.WithServiceName("prpc_server"), prpc.WithIP(testIp), prpc.WithPort(testPort), prpc.WithWeight(100)) + s.RegisterService(func(server *grpc.Server) { + helloservice.RegisterGreeterServer(server, helloservice.HelloServer{}) + }) + s.Start(context.TODO()) +} + +func currentFileDir() string { + _, file, _, ok := runtime.Caller(1) + parts := strings.Split(file, "/") + + if !ok { + return "" + } + + dir := "" + for i := 0; i < len(parts)-1; i++ { + dir += "/" + parts[i] + } + + return dir[1:] +} diff --git a/common/prpc/example/server/prpc_server.yaml b/common/prpc/example/server/prpc_server.yaml new file mode 100644 index 0000000..bdaef78 --- /dev/null +++ b/common/prpc/example/server/prpc_server.yaml @@ -0,0 +1,19 @@ +global: + env: debug +discovery: + endpoints: + - localhost:2379 + timeout: 5 +ip_conf: + service_path: /plato/ip_dispatcher +prpc: + discov: + name: etcd + endpoints: + - localhost:2379 + trace: + enable: true + url: http://127.0.0.1:14268/api/traces + service_name: prpc_server + sampler: 1.0 + diff --git a/common/prpc/interceptor/client/breaker_interceptor.go b/common/prpc/interceptor/client/breaker_interceptor.go new file mode 100644 index 0000000..88e9b24 --- /dev/null +++ b/common/prpc/interceptor/client/breaker_interceptor.go @@ -0,0 +1,43 @@ +package client + +import ( + "context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "time" + + "github.com/bytedance/gopkg/util/logger" + "github.com/sony/gobreaker" +) + +// BreakerUnaryClientInterceptor 熔断器,配置其实都可以考虑用option选项模式实现,等待有人缘人优化吧 +func BreakerUnaryClientInterceptor(name string, maxRequest uint32, interval, timeout time.Duration, readyToTrip func(counts gobreaker.Counts) bool) grpc.UnaryClientInterceptor { + var cb *gobreaker.CircuitBreaker + cb = gobreaker.NewCircuitBreaker(gobreaker.Settings{ + Name: name, + MaxRequests: maxRequest, + Interval: interval, + ReadyToTrip: readyToTrip, + IsSuccessful: func(err error) bool { + switch status.Code(err) { + case codes.DeadlineExceeded, codes.Internal, codes.Unavailable, codes.DataLoss, codes.Unimplemented: + return false + default: + return true + } + }, + OnStateChange: func(name string, from gobreaker.State, to gobreaker.State) { + logger.Errorf("name:%s,old message:%s,new message:%s", name, from, to) + }, + }) + + return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + _, err := cb.Execute(func() (interface{}, error) { + err := invoker(ctx, method, req, reply, cc, opts...) + return nil, err + }) + + return err + } +} diff --git a/common/prpc/interceptor/client/metric_interceptor.go b/common/prpc/interceptor/client/metric_interceptor.go new file mode 100644 index 0000000..76df13a --- /dev/null +++ b/common/prpc/interceptor/client/metric_interceptor.go @@ -0,0 +1,50 @@ +package client + +import ( + "context" + + "github.com/prometheus/client_golang/prometheus" + + "time" + + "github.com/hardcore-os/plato/common/prpc/prome" + "github.com/hardcore-os/plato/common/prpc/util" + "google.golang.org/grpc" + "google.golang.org/grpc/status" +) + +const nameSpace = "prpc_client" + +var ( + clientHandleCounter = prome.NewCounterVec( + prometheus.CounterOpts{ + Namespace: nameSpace, + Subsystem: "req", + Name: "client_handle_total", + }, + []string{"method", "server", "code", "ip"}, + ) + + clientHandleHistogram = prome.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: nameSpace, + Subsystem: "req", + Name: "client_handle_seconds", + }, + []string{"method", "server", "ip"}, + ) +) + +// MetricUnaryClientInterceptor ... +func MetricUnaryClientInterceptor() grpc.UnaryClientInterceptor { + return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + beg := time.Now() + err := invoker(ctx, method, req, reply, cc, opts...) + + code := status.Code(err) + clientHandleCounter.WithLabelValues(method, cc.Target(), code.String(), util.ExternalIP()).Inc() + clientHandleHistogram.WithLabelValues(method, cc.Target(), util.ExternalIP()).Observe(time.Since(beg).Seconds()) + + return err + } +} diff --git a/common/prpc/interceptor/client/metric_interceptor_test.go b/common/prpc/interceptor/client/metric_interceptor_test.go new file mode 100644 index 0000000..b3ddae7 --- /dev/null +++ b/common/prpc/interceptor/client/metric_interceptor_test.go @@ -0,0 +1,26 @@ +package client + +import ( + "context" + "testing" + "time" + + "google.golang.org/grpc" + + "github.com/hardcore-os/plato/common/prpc/prome" +) + +func TestMetricUnaryClientInterceptor(t *testing.T) { + prome.StartAgent("0.0.0.0", 8927) + + cc := new(grpc.ClientConn) + for { + MetricUnaryClientInterceptor()(context.TODO(), "/create", nil, nil, cc, + func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, + opts ...grpc.CallOption) error { + return nil + }) + time.Sleep(1 * time.Second) + } + +} diff --git a/common/prpc/interceptor/client/timeout_interceptor.go b/common/prpc/interceptor/client/timeout_interceptor.go new file mode 100644 index 0000000..b3bb793 --- /dev/null +++ b/common/prpc/interceptor/client/timeout_interceptor.go @@ -0,0 +1,38 @@ +package client + +import ( + "context" + "time" + + "github.com/bytedance/gopkg/util/logger" + "google.golang.org/grpc" + "google.golang.org/grpc/peer" +) + +// TimeoutUnaryClientInterceptor ... +func TimeoutUnaryClientInterceptor(timeout time.Duration, slowThreshold time.Duration) grpc.UnaryClientInterceptor { + return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + now := time.Now() + // 若无自定义超时设置,默认设置超时 + _, ok := ctx.Deadline() + if !ok { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, timeout) + defer cancel() + } + var p peer.Peer + + err := invoker(ctx, method, req, reply, cc, append(opts, grpc.Peer(&p))...) + + du := time.Since(now) + remoteIP := "" + if p.Addr != nil { + remoteIP = p.Addr.String() + } + + if slowThreshold > time.Duration(0) && du > slowThreshold { + logger.CtxErrorf(ctx, "grpc slowlog:method%s,tagert:%s,cost:%v,remotIP:%s", method, cc.Target(), du, remoteIP) + } + return err + } +} diff --git a/common/prpc/interceptor/client/timeout_interceptor_test.go b/common/prpc/interceptor/client/timeout_interceptor_test.go new file mode 100644 index 0000000..6380eef --- /dev/null +++ b/common/prpc/interceptor/client/timeout_interceptor_test.go @@ -0,0 +1,33 @@ +package client + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "google.golang.org/grpc" +) + +func TestTimeoutUnaryClientInterceptor(t *testing.T) { + cc := new(grpc.ClientConn) + err := TimeoutUnaryClientInterceptor(3*time.Second, 3*time.Second)(context.TODO(), "/create", nil, nil, cc, + func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, + opts ...grpc.CallOption) error { + return nil + }, + ) + + assert.NoError(t, err) + + err = TimeoutUnaryClientInterceptor(3*time.Second, 3*time.Second)(context.TODO(), "/create", nil, nil, cc, + func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, + opts ...grpc.CallOption) error { + time.Sleep(4 * time.Second) + return nil + }, + ) + + assert.NoError(t, err) +} diff --git a/common/prpc/interceptor/client/trace_interceptor.go b/common/prpc/interceptor/client/trace_interceptor.go new file mode 100644 index 0000000..6e5d2a5 --- /dev/null +++ b/common/prpc/interceptor/client/trace_interceptor.go @@ -0,0 +1,47 @@ +package client + +import ( + "context" + + ptrace "github.com/hardcore-os/plato/common/prpc/trace" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + "google.golang.org/grpc" + gcodes "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// TraceUnaryClientInterceptor trace middleware +func TraceUnaryClientInterceptor() grpc.UnaryClientInterceptor { + return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + md, ok := metadata.FromOutgoingContext(ctx) + if !ok { + md = metadata.MD{} + } + + tr := otel.GetTracerProvider().Tracer(ptrace.TraceName) + name, attrs := ptrace.BuildSpan(method, "") + ctx, span := tr.Start(ctx, name, trace.WithAttributes(attrs...), trace.WithSpanKind(trace.SpanKindClient)) + defer span.End() + + ptrace.Inject(ctx, otel.GetTextMapPropagator(), &md) + ctx = metadata.NewOutgoingContext(ctx, md) + + err := invoker(ctx, method, req, reply, cc, opts...) + if err != nil { + s, ok := status.FromError(err) + if ok { + span.SetStatus(codes.Error, s.Message()) + span.SetAttributes(ptrace.StatusCodeAttr(s.Code())) + } else { + span.SetStatus(codes.Error, err.Error()) + } + return err + } + + span.SetAttributes(ptrace.StatusCodeAttr(gcodes.OK)) + return nil + } +} diff --git a/common/prpc/interceptor/client/trace_interceptor_test.go b/common/prpc/interceptor/client/trace_interceptor_test.go new file mode 100644 index 0000000..08db601 --- /dev/null +++ b/common/prpc/interceptor/client/trace_interceptor_test.go @@ -0,0 +1,30 @@ +package client + +import ( + "context" + "testing" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + ptrace "github.com/hardcore-os/plato/common/prpc/trace" + "google.golang.org/grpc" +) + +func TestUnaryTraceInterceptor(t *testing.T) { + ptrace.StartAgent() + cc := new(grpc.ClientConn) + TraceUnaryClientInterceptor()(context.TODO(), "/create", nil, nil, cc, + func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, + opts ...grpc.CallOption) error { + return nil + }) + + TraceUnaryClientInterceptor()(context.TODO(), "/update", nil, nil, cc, + func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, + opts ...grpc.CallOption) error { + return status.Error(codes.DataLoss, "dummy") + }) + + defer ptrace.StopAgent() +} diff --git a/common/prpc/interceptor/server/metric_interceptor.go b/common/prpc/interceptor/server/metric_interceptor.go new file mode 100644 index 0000000..130a60c --- /dev/null +++ b/common/prpc/interceptor/server/metric_interceptor.go @@ -0,0 +1,50 @@ +package server + +import ( + "context" + "time" + + "github.com/prometheus/client_golang/prometheus" + + "github.com/hardcore-os/plato/common/prpc/prome" + + "github.com/hardcore-os/plato/common/prpc/util" + "google.golang.org/grpc" + "google.golang.org/grpc/status" +) + +const nameSpace = "prpc_server" + +var ( + serverHandleCounter = prome.NewCounterVec( + prometheus.CounterOpts{ + Namespace: nameSpace, + Subsystem: "req", + Name: "client_handle_total", + }, + []string{"method", "server", "code", "ip"}, + ) + + serverHandleHistogram = prome.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: nameSpace, + Subsystem: "req", + Name: "client_handle_seconds", + }, + []string{"method", "server", "ip"}, + ) +) + +// MetricUnaryServerInterceptor ... +func MetricUnaryServerInterceptor(serverName string) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { + beg := time.Now() + resp, err = handler(ctx, req) + + code := status.Code(err) + serverHandleCounter.WithLabelValues(info.FullMethod, serverName, code.String(), util.ExternalIP()).Inc() + serverHandleHistogram.WithLabelValues(info.FullMethod, serverName, util.ExternalIP()).Observe(time.Since(beg).Seconds()) + + return + } +} diff --git a/common/prpc/interceptor/server/ratelimit_interceptor.go b/common/prpc/interceptor/server/ratelimit_interceptor.go new file mode 100644 index 0000000..58c0fb2 --- /dev/null +++ b/common/prpc/interceptor/server/ratelimit_interceptor.go @@ -0,0 +1,41 @@ +package server + +import ( + "context" + "time" + + "github.com/bytedance/gopkg/util/logger" + pcode "github.com/hardcore-os/plato/common/prpc/code" + "github.com/juju/ratelimit" + "google.golang.org/grpc" + "google.golang.org/grpc/status" +) + +type MethodName string + +type RateLimitConfig struct { + Cap int64 `json:"cap"` + Rate float64 `json:"rate"` + WaitMaxDuration time.Duration `json:"wait_max_duration"` +} + +// RateLimitUnaryServerInterceptor ... +func RateLimitUnaryServerInterceptor(configs map[MethodName]RateLimitConfig) grpc.UnaryServerInterceptor { + buckets := make(map[MethodName]*ratelimit.Bucket) + for name, config := range configs { + buckets[name] = ratelimit.NewBucketWithRate(config.Rate, config.Cap) + } + + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { + if bucket, ok := buckets[MethodName(info.FullMethod)]; ok { + if _, ok := bucket.TakeMaxDuration(1, configs[MethodName(info.FullMethod)].WaitMaxDuration); !ok { + logger.CtxErrorf(ctx, "too many request") + return nil, status.Errorf(pcode.CodeTooManyRequest, "too many request") + } + + return handler(ctx, req) + } + + return handler(ctx, req) + } +} diff --git a/common/prpc/interceptor/server/ratelimit_interceptor_test.go b/common/prpc/interceptor/server/ratelimit_interceptor_test.go new file mode 100644 index 0000000..073d4c5 --- /dev/null +++ b/common/prpc/interceptor/server/ratelimit_interceptor_test.go @@ -0,0 +1,59 @@ +package server + +import ( + "context" + "testing" + + pcode "github.com/hardcore-os/plato/common/prpc/code" + "github.com/stretchr/testify/assert" + "google.golang.org/grpc" + "google.golang.org/grpc/status" +) + +func TestRateLimitUnaryServerInterceptor(t *testing.T) { + tests := []struct { + name string + config map[MethodName]RateLimitConfig + count int + res error + }{ + { + "suc", + map[MethodName]RateLimitConfig{ + "/helloworld.Greeter/SayHello": { + Cap: 3, + Rate: 1, + }, + }, + 1, + nil, + }, + { + "fail", + map[MethodName]RateLimitConfig{ + "/helloworld.Greeter/SayHello": { + Cap: 3, + Rate: 1, + }, + }, + 4, + status.Errorf(pcode.CodeTooManyRequest, "too many request"), + }, + } + + for _, item := range tests { + t.Run(item.name, func(t *testing.T) { + var err error + r := RateLimitUnaryServerInterceptor(item.config) + for i := 0; i < item.count; i++ { + _, err = r(context.Background(), nil, &grpc.UnaryServerInfo{ + FullMethod: "/helloworld.Greeter/SayHello", + }, func(ctx context.Context, req interface{}) (interface{}, error) { + return nil, nil + }) + } + + assert.Equal(t, item.res, err) + }) + } +} diff --git a/common/prpc/interceptor/server/recovery_interceptor.go b/common/prpc/interceptor/server/recovery_interceptor.go new file mode 100644 index 0000000..ce2f76c --- /dev/null +++ b/common/prpc/interceptor/server/recovery_interceptor.go @@ -0,0 +1,25 @@ +package server + +import ( + "context" + "runtime" + + "github.com/bytedance/gopkg/util/logger" + "google.golang.org/grpc" +) + +// RecoveryUnaryServerInterceptor recovery中间件最好放在第一个去执行 +func RecoveryUnaryServerInterceptor() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { + defer func() { + if err := recover(); err != nil { + stack := make([]byte, 4096) + stack = stack[:runtime.Stack(stack, false)] + logger.CtxErrorf(ctx, "err:%v\nstack:%s", err, stack) + } + + }() + + return handler(ctx, req) + } +} diff --git a/common/prpc/interceptor/server/recovery_interceptor_test.go b/common/prpc/interceptor/server/recovery_interceptor_test.go new file mode 100644 index 0000000..28d49c9 --- /dev/null +++ b/common/prpc/interceptor/server/recovery_interceptor_test.go @@ -0,0 +1,17 @@ +package server + +import ( + "context" + "testing" + + "google.golang.org/grpc" +) + +func TestRecoveryUnaryServerInterceptor(t *testing.T) { + RecoveryUnaryServerInterceptor()(context.Background(), nil, &grpc.UnaryServerInfo{ + FullMethod: "/helloworld.Greeter/SayHello", + }, func(ctx context.Context, req interface{}) (interface{}, error) { + panic("xxxxx") + return nil, nil + }) +} diff --git a/common/prpc/interceptor/server/trace_interceptor.go b/common/prpc/interceptor/server/trace_interceptor.go new file mode 100644 index 0000000..a3a0557 --- /dev/null +++ b/common/prpc/interceptor/server/trace_interceptor.go @@ -0,0 +1,46 @@ +package server + +import ( + "context" + + ptrace "github.com/hardcore-os/plato/common/prpc/trace" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// TraceUnaryServerInterceptor ... +func TraceUnaryServerInterceptor() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { + md := metadata.MD{} + header, ok := metadata.FromIncomingContext(ctx) + + if ok { + md = header.Copy() + } + + spanCtx := ptrace.Extract(ctx, otel.GetTextMapPropagator(), &md) + tr := otel.Tracer(ptrace.TraceName) + name, attrs := ptrace.BuildSpan(info.FullMethod, ptrace.PeerFromCtx(ctx)) + + ctx, span := tr.Start(trace.ContextWithRemoteSpanContext(ctx, spanCtx), name, trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes(attrs...)) + defer span.End() + + resp, err = handler(ctx, req) + if err != nil { + s, ok := status.FromError(err) + if ok { + span.SetStatus(codes.Error, s.Message()) + span.SetAttributes(ptrace.StatusCodeAttr(s.Code())) + } else { + span.SetStatus(codes.Error, err.Error()) + } + return nil, err + } + + return resp, nil + } +} diff --git a/common/prpc/interceptor/server/trace_interceptor_test.go b/common/prpc/interceptor/server/trace_interceptor_test.go new file mode 100644 index 0000000..047ee55 --- /dev/null +++ b/common/prpc/interceptor/server/trace_interceptor_test.go @@ -0,0 +1,30 @@ +package server + +import ( + "context" + "testing" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + ptrace "github.com/hardcore-os/plato/common/prpc/trace" + "google.golang.org/grpc" +) + +func TestTraceUnaryServerInterceptor(t *testing.T) { + ptrace.StartAgent() + defer ptrace.StopAgent() + + //cc := new(grpc.ClientConn) + TraceUnaryServerInterceptor()(context.Background(), nil, &grpc.UnaryServerInfo{ + FullMethod: "/helloworld.Greeter/SayHello", + }, func(ctx context.Context, req interface{}) (interface{}, error) { + return nil, nil + }) + + TraceUnaryServerInterceptor()(context.Background(), nil, &grpc.UnaryServerInfo{ + FullMethod: "/helloworld.Greeter/SayBye", + }, func(ctx context.Context, req interface{}) (interface{}, error) { + return nil, status.Error(codes.DataLoss, "dummy") + }) +} diff --git a/common/prpc/prome/agent.go b/common/prpc/prome/agent.go new file mode 100644 index 0000000..6a0982b --- /dev/null +++ b/common/prpc/prome/agent.go @@ -0,0 +1,26 @@ +package prome + +import ( + "fmt" + "net/http" + "sync" + + "github.com/bytedance/gopkg/util/logger" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +var once sync.Once + +// StartAgent 开启prometheus +func StartAgent(host string, port int) { + go func() { + once.Do(func() { + http.Handle("/", promhttp.Handler()) + addr := fmt.Sprintf("%s:%d", host, port) + logger.Infof("Starting prometheus agent at %s", addr) + if err := http.ListenAndServe(addr, nil); err != nil { + logger.Error(err) + } + }) + }() +} diff --git a/common/prpc/prome/prom.go b/common/prpc/prome/prom.go new file mode 100644 index 0000000..e14dfa7 --- /dev/null +++ b/common/prpc/prome/prom.go @@ -0,0 +1,21 @@ +package prome + +import "github.com/prometheus/client_golang/prometheus" + +// NewCounterVec ... +func NewCounterVec(opts prometheus.CounterOpts, labelNames []string) *prometheus.CounterVec { + counterVec := prometheus.NewCounterVec(opts, labelNames) + + prometheus.MustRegister(counterVec) + + return counterVec +} + +// NewHistogramVec ... +func NewHistogramVec(opts prometheus.HistogramOpts, labelNames []string) *prometheus.HistogramVec { + histogramVec := prometheus.NewHistogramVec(opts, labelNames) + + prometheus.MustRegister(histogramVec) + + return histogramVec +} diff --git a/common/prpc/resolver/discov_builder.go b/common/prpc/resolver/discov_builder.go new file mode 100644 index 0000000..2fc2a90 --- /dev/null +++ b/common/prpc/resolver/discov_builder.go @@ -0,0 +1,62 @@ +package resolver + +import ( + "context" + "fmt" + + "github.com/hardcore-os/plato/common/prpc/discov" + "google.golang.org/grpc/attributes" + "google.golang.org/grpc/resolver" +) + +type DiscovBuilder struct { + discov discov.Discovery +} + +// NewDiscovBuilder ... +func NewDiscovBuilder(d discov.Discovery) resolver.Builder { + return &DiscovBuilder{ + discov: d, + } +} + +func (d *DiscovBuilder) Scheme() string { + return DiscovBuilderScheme +} + +func (d *DiscovBuilder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) { + d.discov.GetService(context.TODO(), d.getServiceName(target)) + serviceName := d.getServiceName(target) + listener := func() { + service := d.discov.GetService(context.TODO(), serviceName) + var addrs []resolver.Address + for _, item := range service.Endpoints { + attr := attributes.New("weight", item.Weight) + addr := resolver.Address{ + Addr: fmt.Sprintf("%s:%d", item.IP, item.Port), + Attributes: attr, + } + + addrs = append(addrs, addr) + } + + cc.UpdateState(resolver.State{ + Addresses: addrs, + }) + } + + d.discov.AddListener(context.TODO(), listener) + listener() + + return d, nil +} + +func (d *DiscovBuilder) getServiceName(target resolver.Target) string { + return target.Endpoint() +} + +func (d *DiscovBuilder) Close() { +} + +func (d *DiscovBuilder) ResolveNow(options resolver.ResolveNowOptions) { +} diff --git a/common/prpc/resolver/discov_builder_test.go b/common/prpc/resolver/discov_builder_test.go new file mode 100644 index 0000000..d356a7b --- /dev/null +++ b/common/prpc/resolver/discov_builder_test.go @@ -0,0 +1 @@ +package resolver diff --git a/common/prpc/resolver/resolver.go b/common/prpc/resolver/resolver.go new file mode 100644 index 0000000..26b344b --- /dev/null +++ b/common/prpc/resolver/resolver.go @@ -0,0 +1,5 @@ +package resolver + +const ( + DiscovBuilderScheme = "discov" +) diff --git a/common/prpc/server.go b/common/prpc/server.go new file mode 100644 index 0000000..21aeec1 --- /dev/null +++ b/common/prpc/server.go @@ -0,0 +1,172 @@ +package prpc + +import ( + "context" + "fmt" + "net" + "os" + "os/signal" + "syscall" + "time" + + "github.com/hardcore-os/plato/common/prpc/discov/plugin" + + "github.com/bytedance/gopkg/util/logger" + + "github.com/hardcore-os/plato/common/prpc/discov" + serverinterceptor "github.com/hardcore-os/plato/common/prpc/interceptor/server" + "google.golang.org/grpc" +) + +type RegisterFn func(*grpc.Server) + +type PServer struct { + serverOptions + registers []RegisterFn + interceptors []grpc.UnaryServerInterceptor +} + +type serverOptions struct { + serviceName string + ip string + port int + weight int + health bool + d discov.Discovery +} + +type ServerOption func(opts *serverOptions) + +// WithServiceName set serviceName +func WithServiceName(serviceName string) ServerOption { + return func(opts *serverOptions) { + opts.serviceName = serviceName + } +} + +// WithIP set ip +func WithIP(ip string) ServerOption { + return func(opts *serverOptions) { + opts.ip = ip + } +} + +// WithPort set port +func WithPort(port int) ServerOption { + return func(opts *serverOptions) { + opts.port = port + } +} + +// WithWeight set weight +func WithWeight(weight int) ServerOption { + return func(opts *serverOptions) { + opts.weight = weight + } +} + +// WithHealth set health +func WithHealth(health bool) ServerOption { + return func(opts *serverOptions) { + opts.health = health + } +} + +func NewPServer(opts ...ServerOption) *PServer { + opt := serverOptions{} + for _, o := range opts { + o(&opt) + } + + if opt.d == nil { + dis, err := plugin.GetDiscovInstance() + if err != nil { + panic(err) + } + + opt.d = dis + } + + return &PServer{ + opt, + make([]RegisterFn, 0), + make([]grpc.UnaryServerInterceptor, 0), + } +} + +// RegisterService ... +// eg : +// +// p.RegisterService(func(server *grpc.Server) { +// test.RegisterGreeterServer(server, &Server{}) +// }) +func (p *PServer) RegisterService(register ...RegisterFn) { + p.registers = append(p.registers, register...) +} + +// RegisterUnaryServerInterceptor 注册自定义拦截器,例如限流拦截器或者自己的一些业务自定义拦截器 +func (p *PServer) RegisterUnaryServerInterceptor(i grpc.UnaryServerInterceptor) { + p.interceptors = append(p.interceptors, i) +} + +// Start 开启server +func (p *PServer) Start(ctx context.Context) { + service := discov.Service{ + Name: p.serviceName, + Endpoints: []*discov.Endpoint{ + { + ServerName: p.serviceName, + IP: p.ip, + Port: p.port, + Weight: p.weight, + Enable: true, + }, + }, + } + + // 加载中间件 + interceptors := []grpc.UnaryServerInterceptor{ + serverinterceptor.RecoveryUnaryServerInterceptor(), + serverinterceptor.TraceUnaryServerInterceptor(), + serverinterceptor.MetricUnaryServerInterceptor(p.serviceName), + } + interceptors = append(interceptors, p.interceptors...) + s := grpc.NewServer(grpc.ChainUnaryInterceptor(interceptors...)) + + // 注册服务 + for _, register := range p.registers { + register(s) + } + + lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", p.ip, p.port)) + if err != nil { + panic(err) + } + + go func() { + if err := s.Serve(lis); err != nil { + panic(err) + } + }() + // 服务注册 + p.d.Register(ctx, &service) + + logger.Info("start PRPC success") + + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + sig := <-c + switch sig { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + s.Stop() + p.d.UnRegister(ctx, &service) + time.Sleep(time.Second) + return + case syscall.SIGHUP: + default: + return + } + } + +} diff --git a/common/prpc/server_test.go b/common/prpc/server_test.go new file mode 100644 index 0000000..65a2742 --- /dev/null +++ b/common/prpc/server_test.go @@ -0,0 +1,31 @@ +package prpc + +import ( + "context" + "testing" + + "github.com/hardcore-os/plato/common/config" + + "github.com/hardcore-os/plato/common/prpc/example/helloservice" + + ptrace "github.com/hardcore-os/plato/common/prpc/trace" + "google.golang.org/grpc" +) + +const ( + testIp = "127.0.0.1" + testPort = 8867 +) + +func TestNewPServer(t *testing.T) { + config.Init("../../plato.yaml") + + ptrace.StartAgent() + defer ptrace.StopAgent() + + s := NewPServer(WithServiceName("plato_server"), WithIP(testIp), WithPort(testPort), WithWeight(100)) + s.RegisterService(func(server *grpc.Server) { + helloservice.RegisterGreeterServer(server, helloservice.HelloServer{}) + }) + s.Start(context.TODO()) +} diff --git a/common/prpc/trace/agent.go b/common/prpc/trace/agent.go new file mode 100644 index 0000000..19a84ea --- /dev/null +++ b/common/prpc/trace/agent.go @@ -0,0 +1,51 @@ +package trace + +import ( + "context" + "sync" + + "github.com/bytedance/gopkg/util/logger" + + "github.com/hardcore-os/plato/common/prpc/config" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/jaeger" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + tracesdk "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" +) + +var ( + tp *tracesdk.TracerProvider + once sync.Once +) + +// StartAgent 开启trace collector +func StartAgent() { + once.Do(func() { + exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(config.GetTraceCollectionUrl()))) + if err != nil { + logger.Errorf("trace start agent err:%s", err.Error()) + return + } + + tp = tracesdk.NewTracerProvider( + tracesdk.WithSampler(tracesdk.TraceIDRatioBased(config.GetTraceSampler())), + tracesdk.WithBatcher(exp), + tracesdk.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String(config.GetTraceServiceName()), + )), + ) + + otel.SetTracerProvider(tp) + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, propagation.Baggage{})) + }) +} + +// StopAgent 关闭trace collector,在服务停止时调用StopAgent,不然可能造成trace数据的丢失 +func StopAgent() { + _ = tp.Shutdown(context.TODO()) +} diff --git a/common/prpc/trace/attr.go b/common/prpc/trace/attr.go new file mode 100644 index 0000000..73c6c4f --- /dev/null +++ b/common/prpc/trace/attr.go @@ -0,0 +1,42 @@ +package trace + +import ( + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" + gcodes "google.golang.org/grpc/codes" +) + +const ( + // GRPCStatusCodeKey is convention for numeric status code of a gRPC request. + GRPCStatusCodeKey = attribute.Key("rpc.grpc.status_code") + // RPCNameKey is the name of message transmitted or received. + RPCNameKey = attribute.Key("name") + // RPCMessageTypeKey is the type of message transmitted or received. + RPCMessageTypeKey = attribute.Key("message.type") + // RPCMessageIDKey is the identifier of message transmitted or received. + RPCMessageIDKey = attribute.Key("message.id") + // RPCMessageCompressedSizeKey is the compressed size of the message transmitted or received in bytes. + RPCMessageCompressedSizeKey = attribute.Key("message.compressed_size") + // RPCMessageUncompressedSizeKey is the uncompressed size of the message + // transmitted or received in bytes. + RPCMessageUncompressedSizeKey = attribute.Key("message.uncompressed_size") + // ServerEnvironment ... + ServerEnvironment = attribute.Key("environment") +) + +// Semantic conventions for common RPC attributes. +var ( + // RPCSystemGRPC is the semantic convention for gRPC as the remoting system. + RPCSystemGRPC = semconv.RPCSystemKey.String("grpc") + // RPCNameMessage is the semantic convention for a message named message. + RPCNameMessage = RPCNameKey.String("message") + // RPCMessageTypeSent is the semantic conventions for sent RPC message types. + RPCMessageTypeSent = RPCMessageTypeKey.String("SENT") + // RPCMessageTypeReceived is the semantic conventions for the received RPC message types. + RPCMessageTypeReceived = RPCMessageTypeKey.String("RECEIVED") +) + +// StatusCodeAttr returns an attribute.KeyValue that represents the give c. +func StatusCodeAttr(c gcodes.Code) attribute.KeyValue { + return GRPCStatusCodeKey.Int64(int64(c)) +} diff --git a/common/prpc/trace/trace.go b/common/prpc/trace/trace.go new file mode 100644 index 0000000..ac00b7f --- /dev/null +++ b/common/prpc/trace/trace.go @@ -0,0 +1,56 @@ +package trace + +import ( + "context" + + sdktrace "go.opentelemetry.io/otel/trace" + + "go.opentelemetry.io/otel/propagation" + "google.golang.org/grpc/metadata" +) + +const ( + TraceName = "plato-trace" +) + +type metadataSupplier struct { + metadata *metadata.MD +} + +func (s *metadataSupplier) Get(key string) string { + values := s.metadata.Get(key) + if len(values) == 0 { + return "" + } + + return values[0] +} + +func (s *metadataSupplier) Set(key, value string) { + s.metadata.Set(key, value) +} + +func (s *metadataSupplier) Keys() []string { + out := make([]string, 0, len(*s.metadata)) + for key := range *s.metadata { + out = append(out, key) + } + + return out +} + +// Inject set cross-cutting concerns from the Context into the metadata. +func Inject(ctx context.Context, p propagation.TextMapPropagator, m *metadata.MD) { + p.Inject(ctx, &metadataSupplier{ + metadata: m, + }) +} + +// Extract reads cross-cutting concerns from the metadata into a Context. +func Extract(ctx context.Context, p propagation.TextMapPropagator, metadata *metadata.MD) sdktrace.SpanContext { + ctx = p.Extract(ctx, &metadataSupplier{ + metadata: metadata, + }) + + return sdktrace.SpanContextFromContext(ctx) +} diff --git a/common/prpc/trace/util.go b/common/prpc/trace/util.go new file mode 100644 index 0000000..1dd24ab --- /dev/null +++ b/common/prpc/trace/util.go @@ -0,0 +1,70 @@ +package trace + +import ( + "context" + "net" + "strings" + + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" + "google.golang.org/grpc/peer" +) + +const ( + localhost = "127.0.0.1" +) + +// BuildSpan returns the span info. +func BuildSpan(method, peerAddr string) (string, []attribute.KeyValue) { + attrs := make([]attribute.KeyValue, 0) + name, mAttrs := parseServiceAndMethod(method) + attrs = append(attrs, mAttrs...) + attrs = append(attrs, peerAttr(peerAddr)...) + return name, attrs +} + +// parseServiceAndMethod ... +func parseServiceAndMethod(fullMethod string) (string, []attribute.KeyValue) { + name := strings.TrimLeft(fullMethod, "/") + parts := strings.SplitN(name, "/", 2) + if len(parts) != 2 { + return name, []attribute.KeyValue(nil) + } + + var attrs []attribute.KeyValue + if service := parts[0]; service != "" { + attrs = append(attrs, semconv.RPCServiceKey.String(service)) + } + if method := parts[1]; method != "" { + attrs = append(attrs, semconv.RPCMethodKey.String(method)) + } + + return name, attrs +} + +// peerAttr returns the peer attributes. +func peerAttr(addr string) []attribute.KeyValue { + host, port, err := net.SplitHostPort(addr) + if err != nil { + return nil + } + + if len(host) == 0 { + host = localhost + } + + return []attribute.KeyValue{ + semconv.NetPeerIPKey.String(host), + semconv.NetPeerPortKey.String(port), + } +} + +// PeerFromCtx returns the peer from ctx. +func PeerFromCtx(ctx context.Context) string { + p, ok := peer.FromContext(ctx) + if !ok || p == nil { + return "" + } + + return p.Addr.String() +} diff --git a/common/prpc/util/ip.go b/common/prpc/util/ip.go new file mode 100644 index 0000000..57bd13e --- /dev/null +++ b/common/prpc/util/ip.go @@ -0,0 +1,56 @@ +package util + +import "net" + +const ( + localhost = "127.0.0.1" +) + +// ExternalIP 获取ip +func ExternalIP() string { + ifaces, err := net.Interfaces() + if err != nil { + return localhost + } + for _, iface := range ifaces { + if iface.Flags&net.FlagUp == 0 { + continue // interface down + } + if iface.Flags&net.FlagLoopback != 0 { + continue // loopback interface + } + addrs, err := iface.Addrs() + if err != nil { + return localhost + } + for _, addr := range addrs { + ip := getIpFromAddr(addr) + if ip == nil { + continue + } + return ip.String() + } + } + return localhost +} + +// 获取ip +func getIpFromAddr(addr net.Addr) net.IP { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + if ip == nil || ip.IsLoopback() { + return nil + } + ip = ip.To4() + if ip == nil { + return nil // not an ipv4 address + } + + return ip + +} diff --git a/common/router/table.go b/common/router/table.go new file mode 100644 index 0000000..f6fddf2 --- /dev/null +++ b/common/router/table.go @@ -0,0 +1,55 @@ +package router + +import ( + "context" + "fmt" + "github.com/bytedance/gopkg/util/logger" + "github.com/hardcore-os/plato/common/cache" + "strconv" + "strings" + "time" +) + +const ( + gatewayRouterKey = "gateway_router_%d" // router key + ttl7D = 7 * 24 * 60 * 60 * time.Second +) + +type Record struct { + Endpoint string + ConnID uint64 +} + +func Init(ctx context.Context) { + cache.InitRedis(ctx) +} + +func AddRouter(ctx context.Context, did uint64, endpoint string, connID uint64) error { + key := fmt.Sprintf(gatewayRouterKey, did) + value := fmt.Sprintf("%s-%d", endpoint, connID) + return cache.SetString(ctx, key, value, ttl7D) +} + +func DelRouter(ctx context.Context, did uint64) error { + key := fmt.Sprintf(gatewayRouterKey, did) + return cache.Del(ctx, key) +} + +func QueryRecord(ctx context.Context, did uint64) (*Record, error) { + key := fmt.Sprintf(gatewayRouterKey, did) + data, err := cache.GetString(ctx, key) + if err != nil { + // data can not be nil when err is not nil + return nil, err + } + ec := strings.Split(data, "-") + connID, err := strconv.ParseUint(ec[1], 10, 64) + if err != nil { + logger.CtxErrorf(ctx, "QueryRecord when transfer connID failed, data=%v, err=%v", data, err) + return nil, err + } + return &Record{ + Endpoint: ec[0], + ConnID: connID, + }, nil +} diff --git a/common/sdk/api.go b/common/sdk/api.go new file mode 100644 index 0000000..a784907 --- /dev/null +++ b/common/sdk/api.go @@ -0,0 +1,208 @@ +package sdk + +import ( + "encoding/json" + "fmt" + "github.com/golang/protobuf/proto" + "github.com/hardcore-os/plato/common/idl/message" + "github.com/hardcore-os/plato/common/tcp" + "net" + "sync" + "time" +) + +const ( + MsgTypeText = "text" + MsgTypeAck = "ack" + MsgTypeReConn = "reConn" + MsgTypeHeartbeat = "heartbeat" + MsgLogin = "loginMsg" +) + +type Chat struct { + Nick string + UserID string + SessionID string + conn *connect + closeChan chan struct{} + MsgClientIDTable map[string]uint64 + sync.RWMutex +} + +type Message struct { + Type string + Name string + FormUserID string + ToUserID string + Content string + Session string +} + +func NewChat(ip net.IP, port int, nick, userID, sessionID string) *Chat { + chat := &Chat{ + Nick: nick, + UserID: userID, + SessionID: sessionID, + conn: newConnet(ip, port), + closeChan: make(chan struct{}), + MsgClientIDTable: make(map[string]uint64), + } + go chat.loop() + chat.login() + go chat.heartbeat() + return chat +} + +func (chat *Chat) Send(msg *Message) { + data, _ := json.Marshal(msg) + key := fmt.Sprintf("%d", chat.conn.connID) + upMsg := &message.UPMsg{ + Head: &message.UPMsgHead{ + ClientID: chat.getClientID(key), + ConnID: chat.conn.connID, + }, + UPMsgBody: data, + } + payload, _ := proto.Marshal(upMsg) + err := chat.conn.send(message.CmdType_UP, payload) + if err != nil { + fmt.Printf("[chat] send up msg failed, err=%v\n", err) + } +} + +func (chat *Chat) GetCurClientID() uint64 { + key := fmt.Sprintf("%d", chat.conn.connID) + if id, ok := chat.MsgClientIDTable[key]; ok { + return id + } + return 0 +} + +// Close chat +func (chat *Chat) Close() { + chat.conn.close() + close(chat.closeChan) + close(chat.conn.sendChan) + close(chat.conn.recvChan) +} + +func (chat *Chat) ReConn() { + chat.Lock() + defer chat.Unlock() + chat.conn.reConn() + chat.reConn() +} + +// Recv receive message +func (chat *Chat) Recv() <-chan *Message { + return chat.conn.recv() +} + +func (chat *Chat) loop() { +Loop: + defer func() { + if r := recover(); r != nil { + fmt.Printf("[chat] loop panic, err=%v\n", r) + } + }() + for { + select { + case <-chat.closeChan: + return + default: + mc := &message.MsgCmd{} + data, err := tcp.ReadData(chat.conn.conn) + if err != nil { + fmt.Printf("[chat] loop: read data from conn err=%v, data=%v\n", err, string(data)) + goto Loop + } + err = proto.Unmarshal(data, mc) + if err != nil { + fmt.Printf("[chat] loop: Unmarshal data failed, err=%v, data=%v\n", err, string(data)) + continue + } + var msg *Message + switch mc.Type { + case message.CmdType_ACK: + msg = handAckMsg(chat.conn, mc.Payload) + case message.CmdType_Push: + msg = handlePushMsg(chat.conn, mc.Payload) + } + chat.conn.recvChan <- msg + } + } +} + +func (chat *Chat) login() { + loginMsg := message.LoginMsg{ + Head: &message.LoginMsgHead{ + DeviceID: 123, + }, + } + payload, err := proto.Marshal(&loginMsg) + if err != nil { + fmt.Printf("[chat] login, marshal msg failed, err=%v, loginMsg.Head=%+v, loginMsg.Body=%+v\n", err, loginMsg.Head, loginMsg.LoginMsgBody) + return + } + err = chat.conn.send(message.CmdType_Login, payload) + if err != nil { + fmt.Printf("[chat] send login msg failed, err=%v\n", err) + } +} + +func (chat *Chat) reConn() { + reConn := message.ReConnMsg{ + Head: &message.ReConnMsgHead{ + ConnID: chat.conn.connID, + }, + } + payload, err := proto.Marshal(&reConn) + if err != nil { + fmt.Printf("[chat] login, marshal msg failed, err=%v, ReConnMsg.Head=%+v, ReConnMsg.Body=%+v\n", err, reConn.Head, reConn.ReConnMsgBody) + return + } + err = chat.conn.send(message.CmdType_ReConn, payload) + if err != nil { + fmt.Printf("[chat] send reconn msg failed, err=%v\n", err) + } +} + +func (chat *Chat) heartbeat() { + tc := time.NewTicker(1 * time.Second) + defer func() { + chat.heartbeat() + }() + +loop: + for { + select { + case <-chat.closeChan: + return + case <-tc.C: + heartbeat := message.HeartbeatMsg{ + Head: &message.HeartbeatMsgHead{}, + } + payload, err := proto.Marshal(&heartbeat) + if err != nil { + fmt.Printf("[chat] heartbeat, marshal msg failed, err=%v\n", err) + goto loop + } + err = chat.conn.send(message.CmdType_Heartbeat, payload) + if err != nil { + fmt.Printf("[chat] heartbeat, send heartbeat msg failed, err=%v\n", err) + goto loop + } + } + } +} + +func (chat *Chat) getClientID(sessionID string) uint64 { + chat.Lock() + defer chat.Unlock() + var res uint64 + if id, ok := chat.MsgClientIDTable[sessionID]; ok { + res = id + } + chat.MsgClientIDTable[sessionID] = res + 1 + return res +} diff --git a/common/sdk/net.go b/common/sdk/net.go new file mode 100644 index 0000000..a35969f --- /dev/null +++ b/common/sdk/net.go @@ -0,0 +1,115 @@ +package sdk + +import ( + "context" + "encoding/json" + "fmt" + "github.com/bytedance/gopkg/util/logger" + "github.com/golang/protobuf/proto" + "github.com/hardcore-os/plato/common/idl/message" + "github.com/hardcore-os/plato/common/tcp" + "net" + "sync/atomic" +) + +type connect struct { + sendChan, recvChan chan *Message + conn *net.TCPConn + connID uint64 + ip net.IP + port int +} + +func newConnet(ip net.IP, port int) *connect { + clientConn := &connect{ + sendChan: make(chan *Message), + recvChan: make(chan *Message), + ip: ip, + port: port, + } + addr := &net.TCPAddr{ + IP: ip, + Port: port, + } + conn, err := net.DialTCP("tcp", nil, addr) + if err != nil { + fmt.Printf("DialTcp.err=%+v", err) + return nil + } + clientConn.conn = conn + return clientConn +} + +func handAckMsg(c *connect, data []byte) *Message { + ackMsg := &message.ACKMsg{} + _ = proto.Unmarshal(data, ackMsg) + switch ackMsg.Type { + case message.CmdType_Login, message.CmdType_ReConn: + atomic.StoreUint64(&c.connID, ackMsg.ConnID) + } + return &Message{ + Type: MsgTypeAck, + Name: "plato", + FormUserID: "1212121", + ToUserID: "222212122", + Content: ackMsg.Msg, + } +} + +func handlePushMsg(c *connect, data []byte) *Message { + pushMsg := &message.PushMsg{} + _ = proto.Unmarshal(data, pushMsg) + msg := &Message{} + _ = json.Unmarshal(pushMsg.Content, msg) + ackMsg := &message.ACKMsg{ + Type: message.CmdType_UP, + ConnID: c.connID, + } + ackData, _ := proto.Marshal(ackMsg) + err := c.send(message.CmdType_ACK, ackData) + if err != nil { + fmt.Printf("[chat] send push msg failed, pushMsg = %+v, err=%v\n", msg, err) + } + return msg +} + +func (c *connect) reConn() { + c.conn.Close() + addr := &net.TCPAddr{ + IP: c.ip, + Port: c.port, + } + conn, err := net.DialTCP("tcp", nil, addr) + if err != nil { + logger.CtxErrorf(context.TODO(), "reConn DialTcp.err=%+v", err) + } + c.conn = conn +} + +func (c *connect) send(ty message.CmdType, payload []byte) error { + // 直接发送给接收方 + msgCmd := message.MsgCmd{ + Type: ty, + Payload: payload, + } + msg, err := proto.Marshal(&msgCmd) + if err != nil { + fmt.Printf("[connect] send marshal msg failed, err=%v, type=%v, payload=%v\n", err, ty, string(payload)) + return err + } + dataPgk := tcp.DataPgk{ + Len: uint32(len(msg)), + Data: msg, + } + _, err = c.conn.Write(dataPgk.Marshal()) + return err +} + +func (c *connect) recv() <-chan *Message { + return c.recvChan +} + +func (c *connect) close() { + // 目前没啥值得回收的 + c.conn.Close() +} diff --git a/common/tcp/coder.go b/common/tcp/coder.go new file mode 100644 index 0000000..aadc9af --- /dev/null +++ b/common/tcp/coder.go @@ -0,0 +1,17 @@ +package tcp + +import ( + "bytes" + "encoding/binary" +) + +type DataPgk struct { + Len uint32 + Data []byte +} + +func (d *DataPgk) Marshal() []byte { + bytesBuffer := bytes.NewBuffer([]byte{}) + binary.Write(bytesBuffer, binary.BigEndian, d.Len) + return append(bytesBuffer.Bytes(), d.Data...) +} diff --git a/common/tcp/read.go b/common/tcp/read.go new file mode 100644 index 0000000..6651579 --- /dev/null +++ b/common/tcp/read.go @@ -0,0 +1,47 @@ +package tcp + +import ( + "bytes" + "encoding/binary" + "fmt" + "net" + "time" +) + +func ReadData(conn *net.TCPConn) ([]byte, error) { + var dataLen uint32 + dataLenBuf := make([]byte, 4) + if err := readFixedData(conn, dataLenBuf); err != nil { + return nil, err + } + buffer := bytes.NewBuffer(dataLenBuf) + if err := binary.Read(buffer, binary.BigEndian, &dataLen); err != nil { + return nil, err + } + if dataLen <= 0 { + return nil, fmt.Errorf("wrong headlen: %d", dataLen) + } + dataBuf := make([]byte, dataLen) + if err := readFixedData(conn, dataBuf); err != nil { + return nil, fmt.Errorf("wrong headlen error: %s", err.Error()) + } + return dataBuf, nil +} + +// 读取固定buf长度的数据 +func readFixedData(conn *net.TCPConn, buf []byte) error { + _ = (*conn).SetReadDeadline(time.Now().Add(time.Duration(120) * time.Second)) + var pos int = 0 + var totalSize int = len(buf) + for { + c, err := (*conn).Read(buf[pos:]) + if err != nil { + return err + } + pos += c + if pos == totalSize { + break + } + } + return nil +} diff --git a/common/tcp/write.go b/common/tcp/write.go new file mode 100644 index 0000000..437e85d --- /dev/null +++ b/common/tcp/write.go @@ -0,0 +1,19 @@ +package tcp + +import "net" + +func SendData(conn *net.TCPConn, data []byte) error { + totalLen := len(data) + writeLen := 0 + for { + length, err := conn.Write(data[writeLen:]) + if err != nil { + return err + } + writeLen += length + if writeLen >= totalLen { + break + } + } + return nil +} diff --git a/common/timingwheel/README.md b/common/timingwheel/README.md new file mode 100644 index 0000000..96150c6 --- /dev/null +++ b/common/timingwheel/README.md @@ -0,0 +1 @@ +fork in : https://github.com/RussellLuo/timingwheel \ No newline at end of file diff --git a/common/timingwheel/bucket.go b/common/timingwheel/bucket.go new file mode 100644 index 0000000..9d79cb7 --- /dev/null +++ b/common/timingwheel/bucket.go @@ -0,0 +1,127 @@ +package timingwheel + +import ( + "container/list" + "sync" + "sync/atomic" + "unsafe" +) + +type Timer struct { + expiration int64 // in milliseconds + task func() + + // The bucket that holds the list to which this timer's element belongs. + // + // NOTE: This field may be updated and read concurrently, + // through Timer.Stop() and Bucket.Flush(). + b unsafe.Pointer // type: *bucket + + // The timer's element. + element *list.Element +} + +func (t *Timer) getBucket() *bucket { + return (*bucket)(atomic.LoadPointer(&t.b)) +} + +func (t *Timer) setBucket(b *bucket) { + atomic.StorePointer(&t.b, unsafe.Pointer(b)) +} + +// Stop prevents the Timer from firing. It returns true if the call +// stops the timer, false if the timer has already expired or been stopped. +// +// If the timer t has already expired and the t.task has been started in its own +// goroutine; Stop does not wait for t.task to complete before returning. If the caller +// needs to know whether t.task is completed, it must coordinate with t.task explicitly. +func (t *Timer) Stop() bool { + stopped := false + for b := t.getBucket(); b != nil; b = t.getBucket() { + // If b.Remove is called just after the timing wheel's goroutine has: + // 1. removed t from b (through b.Flush -> b.remove) + // 2. moved t from b to another bucket ab (through b.Flush -> b.remove and ab.Add) + // this may fail to remove t due to the change of t's bucket. + stopped = b.Remove(t) + + // Thus, here we re-get t's possibly new bucket (nil for case 1, or ab (non-nil) for case 2), + // and retry until the bucket becomes nil, which indicates that t has finally been removed. + } + return stopped +} + +type bucket struct { + // 64-bit atomic operations require 64-bit alignment, but 32-bit + // compilers do not ensure it. So we must keep the 64-bit field + // as the first field of the struct. + // + // For more explanations, see https://golang.org/pkg/sync/atomic/#pkg-note-BUG + // and https://go101.org/article/memory-layout.html. + expiration int64 + + mu sync.Mutex + timers *list.List +} + +func newBucket() *bucket { + return &bucket{ + expiration: -1, + timers: list.New(), + } +} + +func (b *bucket) Expiration() int64 { + return atomic.LoadInt64(&b.expiration) +} + +func (b *bucket) SetExpiration(expiration int64) bool { + return atomic.SwapInt64(&b.expiration, expiration) != expiration +} + +func (b *bucket) Add(t *Timer) { + b.mu.Lock() + e := b.timers.PushBack(t) + t.setBucket(b) + t.element = e + + b.mu.Unlock() +} + +func (b *bucket) remove(t *Timer) bool { + if t.getBucket() != b { + return false + } + b.timers.Remove(t.element) + t.setBucket(nil) + t.element = nil + return true +} + +func (b *bucket) Remove(t *Timer) bool { + b.mu.Lock() + defer b.mu.Unlock() + + return b.remove(t) +} + +func (b *bucket) Flush(reinsert func(*Timer)) { + b.mu.Lock() + defer b.mu.Unlock() + + for e := b.timers.Front(); e != nil; { + next := e.Next() + + t := e.Value.(*Timer) + b.remove(t) + + // Note that this operation will either execute the timer's task, or + // insert the timer into another bucket belonging to a lower-level wheel. + // + // In either case, no further lock operation will happen to b.mu. + reinsert(t) + + e = next + } + + b.SetExpiration(-1) +} diff --git a/common/timingwheel/delayqueue.go b/common/timingwheel/delayqueue.go new file mode 100644 index 0000000..e290d71 --- /dev/null +++ b/common/timingwheel/delayqueue.go @@ -0,0 +1,183 @@ +package timingwheel + +import ( + "container/heap" + "sync" + "sync/atomic" + "time" +) + +// The start of PriorityQueue implementation. +// Borrowed from https://github.com/nsqio/nsq/blob/master/internal/pqueue/pqueue.go + +type item struct { + Value interface{} + Priority int64 + Index int +} + +// this is a priority queue as implemented by a min heap +// ie. the 0th element is the *lowest* value +type priorityQueue []*item + +func newPriorityQueue(capacity int) priorityQueue { + return make(priorityQueue, 0, capacity) +} + +func (pq priorityQueue) Len() int { + return len(pq) +} + +func (pq priorityQueue) Less(i, j int) bool { + return pq[i].Priority < pq[j].Priority +} + +func (pq priorityQueue) Swap(i, j int) { + pq[i], pq[j] = pq[j], pq[i] + pq[i].Index = i + pq[j].Index = j +} + +func (pq *priorityQueue) Push(x interface{}) { + n := len(*pq) + c := cap(*pq) + + if n+1 > c { + npq := make(priorityQueue, n, c*2) + copy(npq, *pq) + *pq = npq + } + *pq = (*pq)[0 : n+1] + item := x.(*item) + item.Index = n + (*pq)[n] = item +} + +func (pq *priorityQueue) Pop() interface{} { + n := len(*pq) + c := cap(*pq) + if n < (c/2) && c > 25 { + npq := make(priorityQueue, 0, c/2) + copy(npq, *pq) + *pq = npq + } + item := (*pq)[n-1] + item.Index = -1 + *pq = (*pq)[0 : n-1] + return item +} + +func (pq *priorityQueue) PeekAndShift(max int64) (*item, int64) { + if pq.Len() == 0 { + return nil, 0 + } + + item := (*pq)[0] + if item.Priority > max { + return nil, item.Priority - max + } + heap.Remove(pq, 0) + return item, 0 +} + +// The end of PriorityQueue implementation. + +// DelayQueue is an unbounded blocking queue of *Delayed* elements, in which +// an element can only be taken when its delay has expired. The head of the +// queue is the *Delayed* element whose delay expired furthest in the past. +type DelayQueue struct { + C chan interface{} + + mu sync.Mutex + pq priorityQueue + + // Similar to the sleeping message of runtime.timers + sleeping int32 + wakeupC chan struct{} +} + +// NewDelayQueue New creates an instance of delayQueue with the specified size. +func NewDelayQueue(size int) *DelayQueue { + return &DelayQueue{ + C: make(chan interface{}), + pq: newPriorityQueue(size), + wakeupC: make(chan struct{}), + } +} + +func (dq *DelayQueue) Offer(elem interface{}, expiration int64) { + item := &item{ + Value: elem, + Priority: expiration, + } + + dq.mu.Lock() + heap.Push(&dq.pq, item) + index := item.Index + dq.mu.Unlock() + + if index == 0 { + // A new item with the earliest expiration is added + if atomic.CompareAndSwapInt32(&dq.sleeping, 1, 0) { + dq.wakeupC <- struct{}{} + } + } +} + +func (dq *DelayQueue) Poll(exitC chan struct{}, nowF func() int64) { + for { + now := nowF() + dq.mu.Lock() + item, delta := dq.pq.PeekAndShift(now) + if item == nil { + // No items left or at least one item is pending. + + // We must ensure the atomicity of the whole operation, which is + // composed of the above PeekAndShift and the following StoreInt32, + // to avoid possible race conditions between Offer and Poll. + atomic.StoreInt32(&dq.sleeping, 1) + } + dq.mu.Unlock() + + if item == nil { + if delta == 0 { + // No items left + select { + case <-dq.wakeupC: + // wait until a new item added + continue + case <-exitC: + goto exit + } + } else if delta > 0 { + select { + case <-dq.wakeupC: + // A new item with an "earlier" expiration than the current "earliest" one is added. + continue + case <-time.After(time.Duration(delta) * time.Millisecond): + // The current "earliest" item expires. + + // Reset the sleeping message since there's no need to receive from wakeupC. + if atomic.SwapInt32(&dq.sleeping, 0) == 0 { + // A caller of Offer() is being blocked on sending to wakeupC, + // drain wakeupC to unblock the caller. + <-dq.wakeupC + } + continue + case <-exitC: + goto exit + } + } + } + select { + case dq.C <- item.Value: + // The expired element has been sent out successfully. + case <-exitC: + goto exit + } + } + +exit: + // Reset the states + atomic.StoreInt32(&dq.sleeping, 0) +} diff --git a/common/timingwheel/timeingwheel.go b/common/timingwheel/timeingwheel.go new file mode 100644 index 0000000..d01f2af --- /dev/null +++ b/common/timingwheel/timeingwheel.go @@ -0,0 +1,210 @@ +package timingwheel + +import ( + "errors" + "sync/atomic" + "time" + "unsafe" +) + +// TimingWheel is an implementation of Hierarchical Timing Wheels. +type TimingWheel struct { + tick int64 // in milliseconds + wheelSize int64 + + interval int64 // in milliseconds + currentTime int64 // in milliseconds + buckets []*bucket + queue *DelayQueue + + // The higher-level overflow wheel. + // + // NOTE: This field may be updated and read concurrently, through Add(). + overflowWheel unsafe.Pointer // type: *TimingWheel + + exitC chan struct{} + waitGroup waitGroupWrapper +} + +func NewTimingWheel(tick time.Duration, wheelSize int64) *TimingWheel { + tickMs := int64(tick / time.Millisecond) + if tickMs <= 0 { + panic(errors.New("tick must be greater than or equal to 1ms")) + } + + startMs := timeToMs(time.Now().UTC()) + + return newTimingWheel( + tickMs, + wheelSize, + startMs, + NewDelayQueue(int(wheelSize)), + ) +} + +func newTimingWheel(tickMs int64, wheelSize int64, startMs int64, queue *DelayQueue) *TimingWheel { + buckets := make([]*bucket, wheelSize) + for i := range buckets { + buckets[i] = newBucket() + } + + return &TimingWheel{ + tick: tickMs, + wheelSize: wheelSize, + interval: tickMs * wheelSize, + currentTime: truncate(startMs, tickMs), + buckets: buckets, + queue: queue, + exitC: make(chan struct{}), + } +} + +// add inserts the timer t into the current timing wheel. +func (tw *TimingWheel) add(t *Timer) bool { + currentTime := atomic.LoadInt64(&tw.currentTime) + if t.expiration < currentTime+tw.tick { + return false + } else if t.expiration < currentTime+tw.interval { + // Put it into its own bucket + virtualID := t.expiration / tw.tick + b := tw.buckets[virtualID%tw.wheelSize] + b.Add(t) + + // Set the bucket expiration time + if b.SetExpiration(virtualID * tw.tick) { + // The bucket needs to be enqueued since it was an expired bucket. + // We only need to enqueue the bucket when its expiration time has changed, + // i.e. the wheel has advanced and this bucket get reused with a new expiration. + // Any further calls to set the expiration within the same wheel cycle will + // pass in the same value and hence return false, thus the bucket with the + // same expiration will not be enqueued multiple times. + tw.queue.Offer(b, b.expiration) + } + + return true + } else { + // Out of the interval. Put it into the overflow wheel + overflowWheel := atomic.LoadPointer(&tw.overflowWheel) + if overflowWheel == nil { + atomic.CompareAndSwapPointer(&tw.overflowWheel, nil, unsafe.Pointer(newTimingWheel(tw.interval, tw.wheelSize, currentTime, tw.queue))) + overflowWheel = atomic.LoadPointer(&tw.overflowWheel) + } + return (*TimingWheel)(overflowWheel).add(t) + } +} + +// addOrRun inserts the timer t into the current timing wheel, or run the +// timer's task if it has already expired. +func (tw *TimingWheel) addOrRun(t *Timer) { + if !tw.add(t) { + // Already expired + + // Like the standard time. AfterFunc (https://golang.org/pkg/time/#AfterFunc), + // always execute the timer's task in its goroutine + go t.task() + } +} + +func (tw *TimingWheel) advanceClock(expiration int64) { + currentTime := atomic.LoadInt64(&tw.currentTime) + if expiration >= currentTime+tw.tick { + currentTime = truncate(expiration, tw.tick) + atomic.StoreInt64(&tw.currentTime, currentTime) + + // Try to advance the clock of the overflow wheel if present + overflowWheel := atomic.LoadPointer(&tw.overflowWheel) + if overflowWheel != nil { + (*TimingWheel)(overflowWheel).advanceClock(currentTime) + } + } +} + +func (tw *TimingWheel) Start() { + tw.waitGroup.Wrap(func() { + tw.queue.Poll(tw.exitC, func() int64 { + return timeToMs(time.Now().UTC()) + }) + }) + + tw.waitGroup.Wrap(func() { + for { + select { + case elem := <-tw.queue.C: + b := elem.(*bucket) + tw.advanceClock(b.Expiration()) + b.Flush(tw.addOrRun) + case <-tw.exitC: + return + } + } + }) +} + +// Stop stops the current timing wheel. +// +// If there is any timer's task being running in its own goroutine, Stop does +// not wait for the task to complete before returning. If the caller needs to +// know whether the task is completed, it must coordinate with the task explicitly. +func (tw *TimingWheel) Stop() { + close(tw.exitC) + tw.waitGroup.Wait() +} + +// AfterFunc waits for the duration to elapse and then calls f in its own goroutine. +// It returns a Timer that can be used to cancel the call using its Stop method. +func (tw *TimingWheel) AfterFunc(d time.Duration, f func()) *Timer { + t := &Timer{ + expiration: timeToMs(time.Now().UTC().Add(d)), + task: f, + } + tw.addOrRun(t) + return t +} + +type Scheduler interface { + // Next returns the next execution time after the given(previous) time. + // It will return a zero time if no next it scheduled. + // + // All times must be UTC + Next(time.Time) time.Time +} + +// ScheduleFunc calls f (in its own goroutine) according to the execution +// plan scheduled by s. It returns a Timer that can be used to cancel the +// call using its Stop method. +// +// If the caller want to terminate the execution plan halfway, it must +// stop the timer and ensure that the timer is stopped actually, since in +// the current implementation, there is a gap between the expiring and the +// restarting of the timer. The wait time for ensuring is short since the +// gap is very small. +// +// Internally, ScheduleFunc will ask the first execution time (by calling +// s.Next()) initially, and create a timer if the execution time is non-zero. +// Afterward, it will ask the next execution time each time f is about to +// be executed, and f will be called at the next execution time if the time +// is non-zero. +func (tw *TimingWheel) ScheduleFunc(s Scheduler, fn func()) (t *Timer) { + expiration := s.Next(time.Now().UTC()) + if expiration.IsZero() { + // No time is scheduled, return nil + return nil + } + + t = &Timer{ + expiration: timeToMs(expiration), + task: func() { + // Schedule the task to execute at the next time if possible + expiration := s.Next(msToTime(t.expiration)) + if !expiration.IsZero() { + t.expiration = timeToMs(expiration) + tw.addOrRun(t) + } + + // Actually execute the task + fn() + }, + } + tw.addOrRun(t) + return +} diff --git a/common/timingwheel/timingwheel_example_test.go b/common/timingwheel/timingwheel_example_test.go new file mode 100644 index 0000000..a97b370 --- /dev/null +++ b/common/timingwheel/timingwheel_example_test.go @@ -0,0 +1,39 @@ +package timingwheel + +import ( + "fmt" + "time" +) + +func Example_startTimer() { + tw := NewTimingWheel(time.Millisecond, 20) + tw.Start() + defer tw.Stop() + exitC := make(chan time.Time, 1) + tw.AfterFunc(time.Second, func() { + fmt.Println("The timer fires") + exitC <- time.Now().UTC() + }) + + <-exitC + + // Output: + // The timer fires +} + +func Example_stopTimer() { + tw := NewTimingWheel(time.Millisecond, 20) + tw.Start() + defer tw.Stop() + + t := tw.AfterFunc(time.Second, func() { + fmt.Println("The timer fires") + }) + + <-time.After(900 * time.Millisecond) + // Stop the timer before it fires + t.Stop() + + // Output: + // +} diff --git a/common/timingwheel/timingwheel_test.go b/common/timingwheel/timingwheel_test.go new file mode 100644 index 0000000..c0b1a31 --- /dev/null +++ b/common/timingwheel/timingwheel_test.go @@ -0,0 +1,89 @@ +package timingwheel + +import ( + "testing" + "time" +) + +func TestTimingWheel_AfterFunc(t *testing.T) { + tw := NewTimingWheel(time.Millisecond, 20) + tw.Start() + defer tw.Stop() + + durations := []time.Duration{ + 1 * time.Millisecond, + 5 * time.Millisecond, + 10 * time.Millisecond, + 50 * time.Millisecond, + 100 * time.Millisecond, + 500 * time.Millisecond, + 1 * time.Second, + } + for _, d := range durations { + t.Run("", func(t *testing.T) { + exitC := make(chan time.Time) + + start := time.Now().UTC() + tw.AfterFunc(d, func() { + exitC <- time.Now().UTC() + }) + + got := (<-exitC).Truncate(time.Millisecond) + min := start.Add(d).Truncate(time.Millisecond) + + err := 5 * time.Millisecond + if got.Before(min) || got.After(min.Add(err)) { + t.Errorf("Timer(%s) expiration: want [%s, %s], got %s", d, min, min.Add(err), got) + } + }) + } +} + +type scheduler struct { + intervals []time.Duration + current int +} + +func (s *scheduler) Next(prev time.Time) time.Time { + if s.current >= len(s.intervals) { + return time.Time{} + } + next := prev.Add(s.intervals[s.current]) + s.current += 1 + return next +} + +func TestTimingWheel_ScheduleFunc(t *testing.T) { + tw := NewTimingWheel(time.Millisecond, 20) + tw.Start() + defer tw.Stop() + + s := &scheduler{intervals: []time.Duration{ + 1 * time.Millisecond, // start + 1ms + 4 * time.Millisecond, // start + 5ms + 5 * time.Millisecond, // start + 10ms + 40 * time.Millisecond, // start + 50ms + 50 * time.Millisecond, // start + 100ms + 400 * time.Millisecond, // start + 500ms + 500 * time.Millisecond, // start + 1s + }} + + exitC := make(chan time.Time, len(s.intervals)) + + start := time.Now().UTC() + tw.ScheduleFunc(s, func() { + exitC <- time.Now().UTC() + }) + + accum := time.Duration(0) + for _, d := range s.intervals { + got := (<-exitC).Truncate(time.Millisecond) + accum += d + min := start.Add(accum).Truncate(time.Millisecond) + + err := 5 * time.Millisecond + if got.Before(min) || got.After(min.Add(err)) { + t.Errorf("Timer(%s) expiration: want [%s, %s], got %s", accum, min, min.Add(err), got) + } + } +} diff --git a/common/timingwheel/utils.go b/common/timingwheel/utils.go new file mode 100644 index 0000000..ea2f2b2 --- /dev/null +++ b/common/timingwheel/utils.go @@ -0,0 +1,45 @@ +package timingwheel + +import ( + "fmt" + "sync" + "time" +) + +// truncate returns the result of rounding x toward zero to a multiple of m. +// If m <= 0, Truncate returns x unchanged. +func truncate(x, m int64) int64 { + if m <= 0 { + return x + } + return x - x%m +} + +// timeToMs returns an integer number, which represents t in milliseconds. +func timeToMs(t time.Time) int64 { + return t.UnixNano() / int64(time.Millisecond) +} + +// msToTime returns the UTC time corresponding to the given Unix time, +// t milliseconds since January 1, 1970 UTC. +func msToTime(t int64) time.Time { + return time.Unix(0, t*int64(time.Millisecond)).UTC() +} + +type waitGroupWrapper struct { + sync.WaitGroup +} + +func (w *waitGroupWrapper) Wrap(cb func()) { + w.Add(1) + go func() { + defer func() { + if r := recover(); r != nil { + fmt.Printf("execute func panic: %+v", r) + } + }() + + cb() + w.Done() + }() +} diff --git a/common/utils/hash.go b/common/utils/hash.go new file mode 100644 index 0000000..7528c94 --- /dev/null +++ b/common/utils/hash.go @@ -0,0 +1,13 @@ +package utils + +import "hash/crc32" + +// HashStr hash func +func HashStr(key string) uint32 { + if len(key) < 64 { + var scratch [64]byte + copy(scratch[:], key) + return crc32.ChecksumIEEE(scratch[:len(key)]) + } + return crc32.ChecksumIEEE([]byte(key)) +} diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..a1b2611 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,39 @@ +version: '3.8' + +services: + plato-etcd: + image: bitnami/etcd:latest + environment: + - ETCD_ROOT_PASSWORD=123456 + ports: + - '2379:2379' + networks: + - plato-network + + plato-redis: + image: redis:latest + ports: + - '6379:6379' + networks: + - plato-network + + gateway: + image: plato + ports: + - '8900:8900' + - '8901:8901' + networks: + - plato-network + command: sh -c "/app/plato gateway" + + state: + image: plato + ports: + - '8902:8902' + networks: + - plato-network + command: sh -c "/app/plato state" + +networks: + plato-network: + driver: bridge diff --git a/gateway/connection.go b/gateway/connection.go new file mode 100644 index 0000000..beb0491 --- /dev/null +++ b/gateway/connection.go @@ -0,0 +1,107 @@ +package gateway + +import ( + "errors" + "net" + "sync" + "time" +) + +//var nextConnID uint64 // 全局ConnID + +var node *ConnIDGenerator + +const ( + version = uint64(0) // 版本控制 + sequenceBits = uint64(16) + + maxSequence = int64(-1) ^ (int64(-1) << sequenceBits) // 低sequenceBits个bit位都为1,其他高位都为0 + + timeLeft = uint8(16) // timeLeft = sequence // 时间戳向左偏移量 + versionLeft = uint8(63) // 左移动到最高位 + // 2020-05-20 08:00:00 +0800 CST + twepoch = int64(1589923200000) // 常量时间戳(毫秒) +) + +type ConnIDGenerator struct { + mu sync.Mutex + LastStamp int64 // 记录上一次ID的时间戳 + Sequence int64 // 当前毫秒已经生成的ID序列号(从0 开始累加) 1毫秒内最多生成2^16个ID +} + +type connection struct { + id uint64 // 进程级别的生命周期 + fd int + e *epoller + conn *net.TCPConn +} + +func init() { + node = &ConnIDGenerator{} +} + +func (c *ConnIDGenerator) getMilliSeconds() int64 { + return time.Now().UnixMilli() +} + +// NextID 这里的锁会自旋,不会多么影响性能,主要是临界区小 +func (c *ConnIDGenerator) NextID() (uint64, error) { + c.mu.Lock() + defer c.mu.Unlock() + return c.nextID() +} + +func (c *ConnIDGenerator) nextID() (uint64, error) { + timestamp := c.getMilliSeconds() + if timestamp < c.LastStamp { + return 0, errors.New("time is moving backwards, waiting until") + } + + if c.LastStamp == timestamp { + c.Sequence = (c.Sequence + 1) & maxSequence + if c.Sequence == 0 { // 如果这里发生溢出,就等到下一个毫秒时再分配,这样就一定不出现重复 + for timestamp <= c.LastStamp { + timestamp = c.getMilliSeconds() + } + } + } else { // 如果与上次分配的时间戳不等,则为了防止可能的时钟飘移现象,就必须重新计数 + c.Sequence = 0 + } + c.LastStamp = timestamp + // 减法可以压缩一下时间戳 + id := ((timestamp - twepoch) << timeLeft) | c.Sequence + connID := uint64(id) | (version << versionLeft) + return connID, nil +} + +func NewConnection(conn *net.TCPConn) *connection { + var ( + id uint64 + err error + ) + if id, err = node.NextID(); err != nil { + panic(err) //在线服务需要解决这个问题 ,报错而不能panic + } + return &connection{ + id: id, + fd: socketFD(conn), + conn: conn, + } +} + +func (c *connection) Close() error { + ep.tables.Delete(c.id) + if c.e != nil { + c.e.fdToConnTable.Delete(c.fd) + } + err := c.conn.Close() + panic(err) +} + +func (c *connection) RemoteAddr() string { + return c.conn.RemoteAddr().String() +} + +func (c *connection) BindEpoller(e *epoller) { + c.e = e +} diff --git a/gateway/epoll.go b/gateway/epoll.go new file mode 100644 index 0000000..447a576 --- /dev/null +++ b/gateway/epoll.go @@ -0,0 +1,243 @@ +package gateway + +import ( + "context" + "fmt" + "github.com/hardcore-os/plato/common/config" + "golang.org/x/sys/unix" + "net" + "reflect" + "runtime" + "sync" + "sync/atomic" + "syscall" + + "github.com/bytedance/gopkg/util/logger" +) + +// 全局对象 +var ( + ep *ePool // epoll池 + tcpNum int32 // 当前服务允许接入的最大tcp连接数 +) + +type ePool struct { + eChan chan *connection + tables sync.Map + eSize int + done chan struct{} + + ln *net.TCPListener + f func(conn *connection, ep *epoller) // callback func +} + +func InitEpoll(ln *net.TCPListener, f func(conn *connection, ep *epoller)) { + setLimit() + ep = NewEpoll(ln, f) + ep.createAcceptProcess() + ep.startEPoll() +} + +func NewEpoll(ln *net.TCPListener, callback func(conn *connection, ep *epoller)) *ePool { + return &ePool{ + eChan: make(chan *connection, config.GetGatewayEpollerChanNum()), + done: make(chan struct{}), + eSize: config.GetGatewayEpollerNum(), + tables: sync.Map{}, + ln: ln, + f: callback, + } +} + +// createAcceptProcess 创建一个专门处理 accept 事件的协程,与当前cpu的核数对应,能够发挥最大功效 +func (e *ePool) createAcceptProcess() { + for i := 0; i < runtime.NumCPU(); i++ { + go func() { + ctx := context.Background() + for { + conn, err := e.ln.AcceptTCP() + // 限流熔断 + if !checkTcp() { + _ = conn.Close() + continue + } + setTcpConfig(conn) + if err != nil { + if ne, ok := err.(net.Error); ok && ne.Temporary() { + logger.CtxErrorf(ctx, "accept temp err: %v", ne) + continue + } + fmt.Errorf("accept error: %v", err) + } + c := NewConnection(conn) + ep.addTask(c) + } + }() + } +} + +func (e *ePool) startEPoll() { + for i := 0; i < e.eSize; i++ { + go e.startEProc(i) + } +} + +// startEProc 轮询器池 处理器 +// epoll 的监听和处理逻辑 +func (e *ePool) startEProc(id int) { + epl, err := NewEpoller(id) + if err != nil { + panic(err) + } + ctx := context.Background() + // listen events + go func() { + for { + select { + case <-e.done: + return + case conn := <-e.eChan: + // supply logs in below method + addTcpNum() + if addConnErr := epl.add(conn); addConnErr != nil { + logger.CtxErrorf(ctx, "epoll_%d failed to add connection %v\n", id, addConnErr) + _ = conn.Close() + continue + } + logger.CtxInfof(ctx, "EpollerPool new connection[%v], epoll_%d, tcpSize:%d", conn.RemoteAddr(), id, tcpNum) + } + } + }() + + // wait for events + for { + select { + case <-e.done: + return + default: + connections, waitErr := epl.wait(200) // 200ms 一次轮询避免 忙轮询 + if waitErr != nil && waitErr != syscall.EINTR { + logger.CtxErrorf(ctx, "failed to epoll_%d wait %v\n", id, waitErr) + continue + } + + for _, conn := range connections { + if conn == nil { + break + } + e.f(conn, epl) + } + } + } +} + +func (e *ePool) addTask(conn *connection) { + e.eChan <- conn +} + +// epoller 对象 轮询器 +type epoller struct { + fd int + fdToConnTable sync.Map + id int // id of epoll +} + +func NewEpoller(id int) (*epoller, error) { + fd, err := unix.EpollCreate1(0) + if err != nil { + return nil, err + } + return &epoller{ + fd: fd, + id: id, + }, nil +} + +// TODO: 默认水平触发模式,可采用非阻塞FD,优化边沿触发模式 +func (epl *epoller) add(conn *connection) error { + // Extract file descriptor associated with the connection + fd := conn.fd + // the fd of epl is the epoll fd + // the fd of conn is the connection fd + err := unix.EpollCtl(epl.fd, syscall.EPOLL_CTL_ADD, fd, &unix.EpollEvent{Events: unix.EPOLLIN | unix.EPOLLHUP, Fd: int32(fd)}) + if err != nil { + return err + } + // epoll实例存储 fd -> conn 的映射 + epl.fdToConnTable.Store(conn.fd, conn) + // 全局epoll池存储 connID -> conn的映射 + ep.tables.Store(conn.id, conn) + // 每个连接绑定所属的epoll实例 + conn.BindEpoller(epl) + return nil +} + +func (epl *epoller) remove(conn *connection) error { + subTcpNum() + fd := conn.fd + err := unix.EpollCtl(epl.fd, syscall.EPOLL_CTL_DEL, fd, nil) + if err != nil { + return err + } + ep.tables.Delete(conn.id) + epl.fdToConnTable.Delete(conn.fd) + return nil +} + +func (epl *epoller) wait(millSec int) ([]*connection, error) { + events := make([]unix.EpollEvent, config.GetGatewayEpollWaitQueueSize()) + n, err := unix.EpollWait(epl.fd, events, millSec) + if err != nil { + return nil, err + } + var connections []*connection + for i := 0; i < n; i++ { + // 单个epoll实例是根据fd来反查conn的 + // 全局的conn是根据epoll池中的connID来查询的 + if conn, ok := epl.fdToConnTable.Load(int(events[i].Fd)); ok { + connections = append(connections, conn.(*connection)) + } + } + return connections, nil +} + +func socketFD(conn *net.TCPConn) int { + tcpConn := reflect.Indirect(reflect.ValueOf(*conn).FieldByName("conn")) + fdVal := tcpConn.FieldByName("fd") + pfdVal := reflect.Indirect(fdVal).FieldByName("pfd") + return int(pfdVal.FieldByName("Sysfd").Int()) +} + +func setLimit() { + var rLimit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil { + panic(err) + } + rLimit.Cur = rLimit.Max + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil { + panic(err) + } + logger.CtxInfof(context.Background(), "set cur limit: %d", rLimit.Cur) +} + +func addTcpNum() { + atomic.AddInt32(&tcpNum, 1) +} + +func getTcpNum() int32 { + return atomic.LoadInt32(&tcpNum) +} + +func subTcpNum() { + atomic.AddInt32(&tcpNum, -1) +} + +func checkTcp() bool { + num := getTcpNum() + maxTcpNum := config.GetGatewayMaxTcpNum() + return num <= maxTcpNum +} + +func setTcpConfig(c *net.TCPConn) { + _ = c.SetKeepAlive(true) +} diff --git a/gateway/rpc/Makefile b/gateway/rpc/Makefile new file mode 100644 index 0000000..4291017 --- /dev/null +++ b/gateway/rpc/Makefile @@ -0,0 +1,9 @@ +all: gateway + +gateway: + @echo "begin" + protoc -I service --go_out=service service/*.proto + @echo "*.pb.go success" + protoc -I service --go-grpc_out=service service/*.proto + @echo "*_grpc.pb.go success" + @echo "end" diff --git a/gateway/rpc/client/init.go b/gateway/rpc/client/init.go new file mode 100644 index 0000000..9276e7f --- /dev/null +++ b/gateway/rpc/client/init.go @@ -0,0 +1,32 @@ +package client + +import ( + "fmt" + "github.com/hardcore-os/plato/common/config" + "github.com/hardcore-os/plato/common/prpc" + "github.com/hardcore-os/plato/state/rpc/service" + "sync" +) + +var ( + stateClient service.StateClient + once sync.Once +) + +func Init() { + initStateClient() +} + +func initStateClient() { + once.Do(func() { + pCli, err := prpc.NewPClient(config.GetStateServiceName()) + if err != nil { + panic(err) + } + cli, err := pCli.DialByEndPoint(config.GetGatewayStateServerEndPoint()) + if err != nil { + panic(fmt.Sprintf("gateway.state client DialByEndPoint fialed, err=%v", err)) + } + stateClient = service.NewStateClient(cli) + }) +} diff --git a/gateway/rpc/client/state.go b/gateway/rpc/client/state.go new file mode 100644 index 0000000..eac97c1 --- /dev/null +++ b/gateway/rpc/client/state.go @@ -0,0 +1,35 @@ +package client + +import ( + "context" + "github.com/bytedance/gopkg/util/logger" + "github.com/hardcore-os/plato/state/rpc/service" + "time" +) + +func CancelConn(ctx *context.Context, endpoint string, connID uint64, payLoad []byte) error { + rpcCtx, _ := context.WithTimeout(*ctx, 100*time.Millisecond) + _, err := stateClient.CancelConn(rpcCtx, &service.StateRequest{ + Endpoint: endpoint, + ConnID: connID, + Data: payLoad, + }) + if err != nil { + return err + } + return nil +} + +func SendMsg(ctx *context.Context, endpoint string, connID uint64, payLoad []byte) error { + rpcCtx, _ := context.WithTimeout(*ctx, 100*time.Millisecond) + logger.CtxInfof(*ctx, "[gateway.stateClient] SendMsg, connID=%v, payload=%v", connID, string(payLoad)) + _, err := stateClient.SendMsg(rpcCtx, &service.StateRequest{ + Endpoint: endpoint, + ConnID: connID, + Data: payLoad, + }) + if err != nil { + return err + } + return nil +} diff --git a/gateway/rpc/service/gateway.pb.go b/gateway/rpc/service/gateway.pb.go new file mode 100644 index 0000000..ecbc902 --- /dev/null +++ b/gateway/rpc/service/gateway.pb.go @@ -0,0 +1,235 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v3.19.4 +// source: gateway.proto + +package service + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type GatewayRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ConnID uint64 `protobuf:"varint,1,opt,name=connID,proto3" json:"connID,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *GatewayRequest) Reset() { + *x = GatewayRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_gateway_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GatewayRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GatewayRequest) ProtoMessage() {} + +func (x *GatewayRequest) ProtoReflect() protoreflect.Message { + mi := &file_gateway_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GatewayRequest.ProtoReflect.Descriptor instead. +func (*GatewayRequest) Descriptor() ([]byte, []int) { + return file_gateway_proto_rawDescGZIP(), []int{0} +} + +func (x *GatewayRequest) GetConnID() uint64 { + if x != nil { + return x.ConnID + } + return 0 +} + +func (x *GatewayRequest) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type GatewayResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` + Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` +} + +func (x *GatewayResponse) Reset() { + *x = GatewayResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_gateway_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GatewayResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GatewayResponse) ProtoMessage() {} + +func (x *GatewayResponse) ProtoReflect() protoreflect.Message { + mi := &file_gateway_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GatewayResponse.ProtoReflect.Descriptor instead. +func (*GatewayResponse) Descriptor() ([]byte, []int) { + return file_gateway_proto_rawDescGZIP(), []int{1} +} + +func (x *GatewayResponse) GetCode() int32 { + if x != nil { + return x.Code + } + return 0 +} + +func (x *GatewayResponse) GetMsg() string { + if x != nil { + return x.Msg + } + return "" +} + +var File_gateway_proto protoreflect.FileDescriptor + +var file_gateway_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x3c, 0x0a, 0x0e, 0x47, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, + 0x6e, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x6e, + 0x49, 0x44, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x37, 0x0a, 0x0f, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, + 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x32, + 0x82, 0x01, 0x0a, 0x07, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x3c, 0x0a, 0x07, 0x44, + 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x6e, 0x12, 0x17, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x18, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x04, 0x50, 0x75, 0x73, + 0x68, 0x12, 0x17, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x47, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_gateway_proto_rawDescOnce sync.Once + file_gateway_proto_rawDescData = file_gateway_proto_rawDesc +) + +func file_gateway_proto_rawDescGZIP() []byte { + file_gateway_proto_rawDescOnce.Do(func() { + file_gateway_proto_rawDescData = protoimpl.X.CompressGZIP(file_gateway_proto_rawDescData) + }) + return file_gateway_proto_rawDescData +} + +var file_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_gateway_proto_goTypes = []interface{}{ + (*GatewayRequest)(nil), // 0: service.GatewayRequest + (*GatewayResponse)(nil), // 1: service.GatewayResponse +} +var file_gateway_proto_depIdxs = []int32{ + 0, // 0: service.Gateway.DelConn:input_type -> service.GatewayRequest + 0, // 1: service.Gateway.Push:input_type -> service.GatewayRequest + 1, // 2: service.Gateway.DelConn:output_type -> service.GatewayResponse + 1, // 3: service.Gateway.Push:output_type -> service.GatewayResponse + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_gateway_proto_init() } +func file_gateway_proto_init() { + if File_gateway_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_gateway_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GatewayRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gateway_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GatewayResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_gateway_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_gateway_proto_goTypes, + DependencyIndexes: file_gateway_proto_depIdxs, + MessageInfos: file_gateway_proto_msgTypes, + }.Build() + File_gateway_proto = out.File + file_gateway_proto_rawDesc = nil + file_gateway_proto_goTypes = nil + file_gateway_proto_depIdxs = nil +} diff --git a/gateway/rpc/service/gateway.proto b/gateway/rpc/service/gateway.proto new file mode 100644 index 0000000..163ca6e --- /dev/null +++ b/gateway/rpc/service/gateway.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +option go_package = "./;service"; + +package service; +// 网关机的 rpc server定义 +// cd gateway/rpc 下 执行 protoc -I service --go_out=plugins=grpc:service service/gateway.proto +// protoc -I service --go_out=service service/gateway.proto +// protoc -I service --go-grpc_out=service service/gateway.proto +service Gateway { + rpc DelConn (GatewayRequest) returns (GatewayResponse); + rpc Push (GatewayRequest) returns (GatewayResponse); +} + +message GatewayRequest{ + uint64 connID = 1; + bytes data = 2; +} + +message GatewayResponse { + int32 code = 1; + string msg = 2; +} \ No newline at end of file diff --git a/gateway/rpc/service/gateway_grpc.pb.go b/gateway/rpc/service/gateway_grpc.pb.go new file mode 100644 index 0000000..b48a7f4 --- /dev/null +++ b/gateway/rpc/service/gateway_grpc.pb.go @@ -0,0 +1,146 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v3.19.4 +// source: gateway.proto + +package service + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + Gateway_DelConn_FullMethodName = "/service.Gateway/DelConn" + Gateway_Push_FullMethodName = "/service.Gateway/Push" +) + +// GatewayClient is the client API for Gateway service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GatewayClient interface { + DelConn(ctx context.Context, in *GatewayRequest, opts ...grpc.CallOption) (*GatewayResponse, error) + Push(ctx context.Context, in *GatewayRequest, opts ...grpc.CallOption) (*GatewayResponse, error) +} + +type gatewayClient struct { + cc grpc.ClientConnInterface +} + +func NewGatewayClient(cc grpc.ClientConnInterface) GatewayClient { + return &gatewayClient{cc} +} + +func (c *gatewayClient) DelConn(ctx context.Context, in *GatewayRequest, opts ...grpc.CallOption) (*GatewayResponse, error) { + out := new(GatewayResponse) + err := c.cc.Invoke(ctx, Gateway_DelConn_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gatewayClient) Push(ctx context.Context, in *GatewayRequest, opts ...grpc.CallOption) (*GatewayResponse, error) { + out := new(GatewayResponse) + err := c.cc.Invoke(ctx, Gateway_Push_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GatewayServer is the server API for Gateway service. +// All implementations must embed UnimplementedGatewayServer +// for forward compatibility +type GatewayServer interface { + DelConn(context.Context, *GatewayRequest) (*GatewayResponse, error) + Push(context.Context, *GatewayRequest) (*GatewayResponse, error) + mustEmbedUnimplementedGatewayServer() +} + +// UnimplementedGatewayServer must be embedded to have forward compatible implementations. +type UnimplementedGatewayServer struct { +} + +func (UnimplementedGatewayServer) DelConn(context.Context, *GatewayRequest) (*GatewayResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DelConn not implemented") +} +func (UnimplementedGatewayServer) Push(context.Context, *GatewayRequest) (*GatewayResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Push not implemented") +} +func (UnimplementedGatewayServer) mustEmbedUnimplementedGatewayServer() {} + +// UnsafeGatewayServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GatewayServer will +// result in compilation errors. +type UnsafeGatewayServer interface { + mustEmbedUnimplementedGatewayServer() +} + +func RegisterGatewayServer(s grpc.ServiceRegistrar, srv GatewayServer) { + s.RegisterService(&Gateway_ServiceDesc, srv) +} + +func _Gateway_DelConn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GatewayRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GatewayServer).DelConn(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Gateway_DelConn_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GatewayServer).DelConn(ctx, req.(*GatewayRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Gateway_Push_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GatewayRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GatewayServer).Push(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Gateway_Push_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GatewayServer).Push(ctx, req.(*GatewayRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Gateway_ServiceDesc is the grpc.ServiceDesc for Gateway service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Gateway_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "service.Gateway", + HandlerType: (*GatewayServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "DelConn", + Handler: _Gateway_DelConn_Handler, + }, + { + MethodName: "Push", + Handler: _Gateway_Push_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "gateway.proto", +} diff --git a/gateway/rpc/service/service.go b/gateway/rpc/service/service.go new file mode 100644 index 0000000..923d638 --- /dev/null +++ b/gateway/rpc/service/service.go @@ -0,0 +1,49 @@ +package service + +import "context" + +const ( + DelConnCmd = 1 // DelConn + PushCmd = 2 //push + + successRespMsg = "success" +) + +type CmdContext struct { + Ctx *context.Context + Cmd int32 + ConnID uint64 + PayLoad []byte +} + +type Service struct { + UnimplementedGatewayServer + CmdChannel chan *CmdContext +} + +func (s *Service) DelConn(ctx context.Context, gr *GatewayRequest) (*GatewayResponse, error) { + c := context.TODO() + s.CmdChannel <- &CmdContext{ + Ctx: &c, + Cmd: DelConnCmd, + ConnID: gr.GetConnID(), + } + return &GatewayResponse{ + Code: 0, + Msg: successRespMsg, + }, nil +} + +func (s *Service) Push(ctx context.Context, gr *GatewayRequest) (*GatewayResponse, error) { + c := context.TODO() + s.CmdChannel <- &CmdContext{ + Ctx: &c, + Cmd: PushCmd, + ConnID: gr.GetConnID(), + PayLoad: gr.GetData(), + } + return &GatewayResponse{ + Code: 0, + Msg: successRespMsg, + }, nil +} diff --git a/gateway/server.go b/gateway/server.go new file mode 100644 index 0000000..99354d3 --- /dev/null +++ b/gateway/server.go @@ -0,0 +1,132 @@ +package gateway + +import ( + "context" + "errors" + "fmt" + "google.golang.org/grpc" + "io" + "net" + + "github.com/bytedance/gopkg/util/logger" + "github.com/hardcore-os/plato/common/config" + "github.com/hardcore-os/plato/common/prpc" + "github.com/hardcore-os/plato/common/tcp" + "github.com/hardcore-os/plato/gateway/rpc/client" + "github.com/hardcore-os/plato/gateway/rpc/service" +) + +var ( + cmdChannel chan *service.CmdContext +) + +func RunMain(path string) { + config.Init(path) + startTCPServer() + startRPCServer() +} + +func startTCPServer() { + ctx := context.Background() + ln, err := net.ListenTCP("tcp", &net.TCPAddr{Port: config.GetGatewayTCPServerPort()}) + if err != nil { + logger.CtxInfof(ctx, "StartTCPEPollServer err:%s", err.Error()) + panic(err) + } + initWorkPoll() + InitEpoll(ln, runProc) + logger.CtxInfof(context.Background(), "-------------IM Gateway started------------") +} + +func startRPCServer() { + ctx := context.Background() + cmdChannel = make(chan *service.CmdContext, config.GetGatewayCmdChannelNum()) + s := prpc.NewPServer( + prpc.WithServiceName(config.GetGatewayServiceName()), + prpc.WithIP(config.GetGatewayServiceAddr()), + prpc.WithPort(config.GetGatewayRPCServerPort()), + prpc.WithWeight(config.GetGatewayRPCWeight()), + ) + + s.RegisterService(func(server *grpc.Server) { + // 指定gateway server的实现类(之前都是接口,这里明确告诉grpc,该接口由哪个实现类实现,后续接收到grpc时,由该实现类接收请求) + service.RegisterGatewayServer(server, &service.Service{CmdChannel: cmdChannel}) + }) + // start client + client.Init() + // start cmdHandler + go cmdHandler() + // start rpc server + s.Start(context.TODO()) + logger.CtxInfof(ctx, "[gateway] serviceName:%s Addr:%s:%d weight:%d", config.GetGatewayServiceName(), config.GetGatewayServiceAddr(), config.GetGatewayRPCServerPort(), config.GetGatewayRPCWeight()) +} + +func runProc(c *connection, ep *epoller) { + ctx := context.Background() + // step1: 读取一个完整的消息包 + dataBuf, err := tcp.ReadData(c.conn) + if err != nil { + if errors.Is(err, io.EOF) { + logger.CtxInfof(context.Background(), "connection[%v] closed", c.RemoteAddr()) + ep.remove(c) + client.CancelConn(&ctx, getEndpoint(), c.id, nil) + } + return + } + err = wPool.Submit(func() { + // step2:交给 message server rpc 处理 + client.SendMsg(&ctx, getEndpoint(), c.id, dataBuf) + }) + + if err != nil { + logger.CtxInfof(context.Background(), "runProc.err: %+v", err.Error()) + fmt.Errorf("runProc.err: %+v\n", err.Error()) + } +} + +func cmdHandler() { + for cmd := range cmdChannel { + // async submit task to goroutine pool + switch cmd.Cmd { + case service.DelConnCmd: + wPool.Submit(func() { + closeConn(cmd) + }) + case service.PushCmd: + wPool.Submit(func() { + sendMsgByCmd(cmd) + }) + default: + panic("command undefined") + } + } +} + +func closeConn(cmd *service.CmdContext) { + if cmdChannel == nil { + return + } + if connPtr, ok := ep.tables.Load(cmd.ConnID); ok { + conn, _ := connPtr.(*connection) + conn.Close() + } +} + +func sendMsgByCmd(cmd *service.CmdContext) { + if cmdChannel == nil { + return + } + // 从全局epoll池中根据 connID 获取 conn + if connPtr, ok := ep.tables.Load(cmd.ConnID); ok { + conn, _ := connPtr.(*connection) + dp := tcp.DataPgk{ + Len: uint32(len(cmd.PayLoad)), + Data: cmd.PayLoad, + } + tcp.SendData(conn.conn, dp.Marshal()) + } +} + +func getEndpoint() string { + return fmt.Sprintf("%s:%d", config.GetGatewayServiceAddr(), config.GetGatewayRPCServerPort()) +} diff --git a/gateway/table.go b/gateway/table.go new file mode 100644 index 0000000..e78ada6 --- /dev/null +++ b/gateway/table.go @@ -0,0 +1,17 @@ +package gateway + +import ( + "sync" +) + +var tables table + +type table struct { + did2conn sync.Map +} + +func InitTable() { + tables = table{ + did2conn: sync.Map{}, + } +} diff --git a/gateway/workpool.go b/gateway/workpool.go new file mode 100644 index 0000000..c4ac246 --- /dev/null +++ b/gateway/workpool.go @@ -0,0 +1,17 @@ +package gateway + +import ( + "context" + "github.com/bytedance/gopkg/util/logger" + "github.com/hardcore-os/plato/common/config" + "github.com/panjf2000/ants" +) + +var wPool *ants.Pool + +func initWorkPoll() { + var err error + if wPool, err = ants.NewPool(config.GetGatewayWorkerPoolNum()); err != nil { + logger.CtxErrorf(context.Background(), "InitWorkPool.err: %s num:%d\n", err.Error(), config.GetGatewayWorkerPoolNum()) + } +} diff --git a/go.mod b/go.mod index f769a93..5c0f92d 100644 --- a/go.mod +++ b/go.mod @@ -2,17 +2,89 @@ module github.com/hardcore-os/plato go 1.18 +replace github.com/coreos/bbolt v1.3.7 => go.etcd.io/bbolt v1.3.7 + +require ( + github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b + github.com/cloudwego/hertz v0.7.0 + github.com/gookit/color v1.5.1 + github.com/juju/ratelimit v1.0.2 + github.com/panjf2000/ants v1.2.1 + github.com/prometheus/client_golang v1.17.0 + github.com/redis/go-redis/v9 v9.5.1 + github.com/rocket049/gocui v0.3.2 + github.com/sony/gobreaker v0.5.0 + github.com/spf13/cobra v1.5.0 + github.com/spf13/viper v1.16.0 + github.com/stretchr/testify v1.8.4 + go.etcd.io/etcd/api/v3 v3.5.9 + go.etcd.io/etcd/client/v3 v3.5.9 + go.opentelemetry.io/otel v1.19.0 + go.opentelemetry.io/otel/exporters/jaeger v1.17.0 + go.opentelemetry.io/otel/sdk v1.19.0 + go.opentelemetry.io/otel/trace v1.19.0 + google.golang.org/grpc v1.56.2 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/bytedance/go-tagexpr/v2 v2.9.2 // indirect + github.com/bytedance/sonic v1.8.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/cloudwego/netpoll v0.5.0 // indirect + github.com/coreos/go-systemd/v22 v22.3.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/henrylee2cn/ameda v1.4.10 // indirect + github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/nyaruka/phonenumbers v1.0.55 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.11.1 // indirect + github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + github.com/tidwall/gjson v1.14.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + require ( - github.com/gookit/color v1.5.1 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/nsf/termbox-go v1.1.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rocket049/gocui v0.3.2 // indirect - github.com/spf13/cobra v1.5.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect - golang.org/x/net v0.0.0-20220725212005-46097bf591d3 // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect - gopkg.in/sorcix/irc.v2 v2.0.0-20200812151606-3f15758ea8c7 // indirect + go.uber.org/multierr v1.10.0 // indirect + go.uber.org/zap v1.26.0 + golang.org/x/net v0.12.0 // indirect + golang.org/x/sys v0.12.0 + golang.org/x/text v0.11.0 // indirect + google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/protobuf v1.31.0 + gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) diff --git a/go.sum b/go.sum index db01c39..824fa6e 100644 --- a/go.sum +++ b/go.sum @@ -1,36 +1,647 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bytedance/go-tagexpr/v2 v2.9.2 h1:QySJaAIQgOEDQBLS3x9BxOWrnhqu5sQ+f6HaZIxD39I= +github.com/bytedance/go-tagexpr/v2 v2.9.2/go.mod h1:5qsx05dYOiUXOUgnQ7w3Oz8BYs2qtM/bJokdLb79wRM= +github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= +github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b h1:R6PWoQtxEMpWJPHnpci+9LgFxCS7iJCfOGBvCgZeTKI= +github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ= +github.com/bytedance/mockey v1.2.1 h1:g84ngI88hz1DR4wZTL3yOuqlEcq67MretBfQUdXwrmw= +github.com/bytedance/mockey v1.2.1/go.mod h1:+Jm/fzWZAuhEDrPXVjDf/jLM2BlLXJkwk94zf2JZ3X4= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.8.1 h1:NqAHCaGaTzro0xMmnTCLUyRlbEP6r8MCA1cJUrH3Pu4= +github.com/bytedance/sonic v1.8.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/hertz v0.7.0 h1:9LFXPqHkGBNH/k/Y1h3udEloS5LVPNOUWekDfH0bQNM= +github.com/cloudwego/hertz v0.7.0/go.mod h1:WliNtVbwihWHHgAaIQEbVXl0O3aWj0ks1eoPrcEAnjs= +github.com/cloudwego/netpoll v0.5.0 h1:oRrOp58cPCvK2QbMozZNDESvrxQaEHW2dCimmwH1lcU= +github.com/cloudwego/netpoll v0.5.0/go.mod h1:xVefXptcyheopwNDZjDPcfU6kIjZXZ4nY550k1yH9eQ= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ= github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/henrylee2cn/ameda v1.4.8/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4= +github.com/henrylee2cn/ameda v1.4.10 h1:JdvI2Ekq7tapdPsuhrc4CaFiqw6QXFvZIULWJgQyCAk= +github.com/henrylee2cn/ameda v1.4.10/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4= +github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8 h1:yE9ULgp02BhYIrO6sdV/FPe0xQM6fNHkVQW2IAymfM0= +github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8/go.mod h1:Nhe/DM3671a5udlv2AdV2ni/MZzgfv2qrPL5nIi3EGQ= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI= +github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= +github.com/nyaruka/phonenumbers v1.0.55 h1:bj0nTO88Y68KeUQ/n3Lo2KgK7lM1hF7L9NFuwcCl3yg= +github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U= +github.com/panjf2000/ants v1.2.1 h1:IlhLREssFi+YFOITnHdH3FHhulY6WDS0OB9e7+3fMHk= +github.com/panjf2000/ants v1.2.1/go.mod h1:AaACblRPzq35m1g3enqYcxspbbiOJJYaxU2wMpm1cXY= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +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/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= +github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= +github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= +github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rocket049/gocui v0.3.2 h1:6QEaEX85VheVRLwA5FjAe2tswlIkIg0mgoeBHEguRzA= github.com/rocket049/gocui v0.3.2/go.mod h1:oKkm50/BFqMgu7hwBRMs7/uketHe9hRMoo29X1gxK9A= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg= +github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= -golang.org/x/net v0.0.0-20220725212005-46097bf591d3 h1:2yWTtPWWRcISTw3/o+s/Y4UOMnQL71DWyToOANFusCg= -golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs= +go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= +go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE= +go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= +go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E= +go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/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/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= +google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/sorcix/irc.v2 v2.0.0-20200812151606-3f15758ea8c7 h1:XS4tmz0w7EYviIrBpFVww8IyKJQiIX5SU/1ptPVtBWI= -gopkg.in/sorcix/irc.v2 v2.0.0-20200812151606-3f15758ea8c7/go.mod h1:PmJkUcwbuPi1FiZ9Rarr6wzVMvzkO7uWqH1jwrMkgW0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/ipconf/api.go b/ipconf/api.go new file mode 100644 index 0000000..e7dfc7c --- /dev/null +++ b/ipconf/api.go @@ -0,0 +1,29 @@ +package ipconf + +import ( + "context" + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/common/utils" + "github.com/cloudwego/hertz/pkg/protocol/consts" + "github.com/hardcore-os/plato/ipconf/domain" +) + +type Response struct { + Message string `json:"message"` + Code int `json:"code"` + Data interface{} `json:"data"` +} + +func GetIpInfoList(ctx context.Context, appCtx *app.RequestContext) { + defer func() { + if r := recover(); r != nil { + appCtx.JSON(consts.StatusBadRequest, utils.H{"err": r}) + } + }() + // 构建客户端请求 + ipConfCtx := domain.BuildIpConfContext(&ctx, appCtx) + // ip调度 + eds := domain.Dispatch(ipConfCtx) + //获取分数top5返回 + ipConfCtx.AppCtx.JSON(consts.StatusOK, packRes(top5Endports(eds))) +} diff --git a/ipconf/domain/context.go b/ipconf/domain/context.go new file mode 100644 index 0000000..421c3f0 --- /dev/null +++ b/ipconf/domain/context.go @@ -0,0 +1,25 @@ +package domain + +import ( + "context" + "github.com/cloudwego/hertz/pkg/app" +) + +type IpConfContext struct { + Ctx *context.Context + AppCtx *app.RequestContext + ClientCtx *ClientContext +} + +type ClientContext struct { + IP string `json:"ip"` +} + +func BuildIpConfContext(c *context.Context, ctx *app.RequestContext) *IpConfContext { + ipConfConext := &IpConfContext{ + Ctx: c, + AppCtx: ctx, + ClientCtx: &ClientContext{}, + } + return ipConfConext +} diff --git a/ipconf/domain/dispatcher.go b/ipconf/domain/dispatcher.go new file mode 100644 index 0000000..454974a --- /dev/null +++ b/ipconf/domain/dispatcher.go @@ -0,0 +1,84 @@ +package domain + +import ( + "github.com/hardcore-os/plato/ipconf/source" + "sort" + "sync" +) + +type Dispatcher struct { + candidatePool map[string]*Endport + sync.RWMutex +} + +var dp *Dispatcher + +func Init() { + dp = &Dispatcher{} + dp.candidatePool = make(map[string]*Endport) + go func() { + for event := range source.EventChan() { + switch event.Type { + case source.AddEvent: + dp.AddNode(event) + case source.DelEvent: + dp.DelNode(event) + } + } + }() +} + +func Dispatch(ctx *IpConfContext) []*Endport { + // 获取候选candidate + eds := dp.getCandidateEndPort(ctx) + // 计算分数 + for _, ed := range eds { + ed.CalculateScore(ctx) + } + // 全局排序,返回排序策略 + sort.Slice(eds, func(i, j int) bool { + // 优先基于活跃分数进行排序 + if eds[i].ActiveScore > eds[j].ActiveScore { + return true + } + // 如果活跃分数相同,则使用静态分数排序 + if eds[i].ActiveScore == eds[j].ActiveScore { + return eds[i].StaticScore > eds[j].StaticScore + } + return false + }) + return eds +} + +func (d *Dispatcher) getCandidateEndPort(ctx *IpConfContext) []*Endport { + dp.RLock() + defer dp.RUnlock() + candidateList := make([]*Endport, 0, len(dp.candidatePool)) + for _, ed := range dp.candidatePool { + candidateList = append(candidateList, ed) + } + return candidateList +} + +func (d *Dispatcher) DelNode(event *source.Event) { + dp.Lock() + delete(dp.candidatePool, event.Key()) + dp.Unlock() +} + +func (d *Dispatcher) AddNode(event *source.Event) { + dp.Lock() + defer dp.Unlock() + var ( + ed *Endport + ok bool + ) + if ed, ok = d.candidatePool[event.Key()]; !ok { // not exist, then create EndPort + ed = NewEndport(event.IP, event.Port) + dp.candidatePool[event.Key()] = ed + } + ed.UpdateStat(&Stat{ + ConnectNum: event.ConnectNum, + MessageBytes: event.MessageBytes, + }) +} diff --git a/ipconf/domain/endport.go b/ipconf/domain/endport.go new file mode 100644 index 0000000..557f605 --- /dev/null +++ b/ipconf/domain/endport.go @@ -0,0 +1,44 @@ +package domain + +import ( + "sync/atomic" + "unsafe" +) + +type Endport struct { + IP string `json:"ip"` + Port string `json:"port"` + ActiveScore float64 `json:"-"` + StaticScore float64 `json:"-"` + Stats *Stat `json:"-"` + window *stateWindow `json:"-"` +} + +func NewEndport(ip string, port string) *Endport { + ed := &Endport{ + IP: ip, + Port: port, + } + ed.window = newStateWindow() + ed.Stats = ed.window.getStat() + go func() { + for stat := range ed.window.statChan { + ed.window.appendStat(stat) + newStat := ed.window.getStat() + atomic.SwapPointer((*unsafe.Pointer)((unsafe.Pointer)(ed.Stats)), unsafe.Pointer(newStat)) + } + }() + return ed +} + +func (ed *Endport) UpdateStat(s *Stat) { + ed.window.statChan <- s +} + +func (ed *Endport) CalculateScore(ctx *IpConfContext) { + // 如果 stats 字段是空的,则直接使用上一次计算的结果,此次不更新 + if ed.Stats != nil { + ed.ActiveScore = ed.Stats.CalculateActiveScore() + ed.StaticScore = ed.Stats.CalculateStaticScore() + } +} diff --git a/ipconf/domain/stat.go b/ipconf/domain/stat.go new file mode 100644 index 0000000..ae9c785 --- /dev/null +++ b/ipconf/domain/stat.go @@ -0,0 +1,67 @@ +package domain + +import "math" + +// Stat +// 对于gateway网关机来说,存在不同时期加入进来的物理机,所以机器的配置是不同的,使用负载来衡量会导致偏差。 +// 为更好的应对动态的机器配置变化,我们统计其剩余资源值,来衡量一个机器其是否更适合增加其负载。 +// 这里的数值代表的是,此endpoint对应的机器其,自身剩余的资源指标。 +type Stat struct { + ConnectNum float64 // 业务上,im gateway 总体持有的长连接数量 的剩余值 + MessageBytes float64 // 业务上,im gateway 每秒收发消息的总字节数 的剩余值 +} + +// CalculateActiveScore +// 因此假设网络带宽将是系统瓶颈所在,那么哪台机器富余的带宽资源多,哪台机器的负载就是最轻的。 +// TODO: 如何预估他的数量级?何时使用静态值衡量 +// TODO: json 的解析失效 +func (s *Stat) CalculateActiveScore() float64 { + return getGB(s.MessageBytes) +} + +func (s *Stat) Avg(num float64) { + s.ConnectNum /= num + s.MessageBytes /= num +} +func (s *Stat) Clone() *Stat { + newStat := &Stat{ + MessageBytes: s.MessageBytes, + ConnectNum: s.ConnectNum, + } + return newStat +} + +func (s *Stat) Add(st *Stat) { + if st == nil { + return + } + s.ConnectNum += st.ConnectNum + s.MessageBytes += st.MessageBytes +} + +func (s *Stat) Sub(st *Stat) { + if st == nil { + return + } + s.ConnectNum -= st.ConnectNum + s.MessageBytes -= st.MessageBytes +} + +func getGB(m float64) float64 { + return decimal(m / (1 << 30)) +} +func decimal(value float64) float64 { + return math.Trunc(value*1e2+0.5) * 1e-2 +} +func min(a, b, c float64) float64 { + m := func(k, j float64) float64 { + if k > j { + return j + } + return k + } + return m(a, m(b, c)) +} +func (s *Stat) CalculateStaticScore() float64 { + return s.ConnectNum +} diff --git a/ipconf/domain/window.go b/ipconf/domain/window.go new file mode 100644 index 0000000..114411e --- /dev/null +++ b/ipconf/domain/window.go @@ -0,0 +1,36 @@ +package domain + +const ( + windowSize = 5 +) + +type stateWindow struct { + stateQueue []*Stat + statChan chan *Stat + sumStat *Stat + idx int64 +} + +func newStateWindow() *stateWindow { + return &stateWindow{ + stateQueue: make([]*Stat, windowSize), + statChan: make(chan *Stat), + sumStat: &Stat{}, + } +} + +func (sw *stateWindow) getStat() *Stat { + res := sw.sumStat.Clone() + res.Avg(windowSize) + return res +} + +func (sw *stateWindow) appendStat(s *Stat) { + // 减去即将被删除的state(循环数组充当队列) + sw.sumStat.Sub(sw.stateQueue[sw.idx%windowSize]) + // 更新最新的stat + sw.stateQueue[sw.idx%windowSize] = s + // 计算最新的窗口和 + sw.sumStat.Add(s) + sw.idx++ +} diff --git a/ipconf/server.go b/ipconf/server.go new file mode 100644 index 0000000..5abe474 --- /dev/null +++ b/ipconf/server.go @@ -0,0 +1,21 @@ +package ipconf + +import ( + "github.com/hardcore-os/plato/common/config" + "github.com/hardcore-os/plato/ipconf/domain" + "github.com/hardcore-os/plato/ipconf/source" + + "github.com/cloudwego/hertz/pkg/app/server" +) + +func RunMain(path string) { + config.Init(path) + // 启动数据源 + source.Init() + // 初始化调度层 + domain.Init() + // 启动web容器 + s := server.Default(server.WithHostPorts(":6789")) + s.GET("/ip/list", GetIpInfoList) + s.Spin() +} diff --git a/ipconf/source/data.go b/ipconf/source/data.go new file mode 100644 index 0000000..18d5663 --- /dev/null +++ b/ipconf/source/data.go @@ -0,0 +1,53 @@ +package source + +import ( + "context" + "github.com/hardcore-os/plato/common/config" + + "github.com/bytedance/gopkg/util/logger" + "github.com/hardcore-os/plato/common/discovery" +) + +func Init() { + eventChan = make(chan *Event) + ctx := context.Background() + go DataHandler(&ctx) + if config.IsDebug() { + ctx := context.Background() + testServiceRegister(&ctx, "7896", "node1") + testServiceRegister(&ctx, "7897", "node2") + testServiceRegister(&ctx, "7898", "node3") + } +} + +// DataHandler 服务发现处理 +func DataHandler(ctx *context.Context) { + dis := discovery.NewServiceDiscovery(ctx) + defer dis.Close() + setFunc := func(key string, value string) { + if ed, err := discovery.UnMarshal([]byte(value)); err == nil { + if event := NewEvent(ed); event != nil { // 原文是ed != nil,应该是bug,应该是现在这样写 + event.Type = AddEvent + eventChan <- event + } + } else { + logger.CtxErrorf(*ctx, "DataHandler.setFunc.err :%s", err.Error()) + } + } + + delFunc := func(key string, value string) { + if ed, err := discovery.UnMarshal([]byte(value)); err == nil { + if event := NewEvent(ed); event != nil { + event.Type = DelEvent + eventChan <- event + } + } else { + logger.CtxErrorf(*ctx, "DataHandler.delFunc.err :%s", err.Error()) + } + } + + err := dis.WatchService(config.GetServicePathForIPConf(), setFunc, delFunc) + if err != nil { + panic(err) + } +} diff --git a/ipconf/source/event.go b/ipconf/source/event.go new file mode 100644 index 0000000..ea4ee9f --- /dev/null +++ b/ipconf/source/event.go @@ -0,0 +1,62 @@ +package source + +import ( + "fmt" + "github.com/hardcore-os/plato/common/discovery" +) + +var eventChan chan *Event + +func EventChan() chan *Event { + return eventChan +} + +type EventType string + +const ( + AddEvent EventType = "addNode" + DelEvent EventType = "delNode" + + connectNum string = "connect_num" + messageBytes string = "message_bytes" +) + +type Event struct { + Type EventType + IP string + Port string + ConnectNum float64 // 连接数量 + MessageBytes float64 // 接收的字节数量 +} + +func NewEvent(ed *discovery.EndpointInfo) *Event { + if ed == nil || ed.MetaData == nil { + return nil + } + var connNum, msgBytes float64 + if data, ok := ed.MetaData[connectNum]; ok { + connNum, ok = data.(float64) + if !ok { + panic("connNum transfer failed") + } + } + + if data, ok := ed.MetaData[messageBytes]; ok { + msgBytes, ok = data.(float64) + if !ok { + panic("messageBytes transfer failed") + } + } + + return &Event{ + Type: AddEvent, + IP: ed.IP, + Port: ed.Port, + ConnectNum: connNum, + MessageBytes: msgBytes, + } +} + +func (e *Event) Key() string { + return fmt.Sprintf("%s:%s", e.IP, e.Port) +} diff --git a/ipconf/source/mock.go b/ipconf/source/mock.go new file mode 100644 index 0000000..efd3aac --- /dev/null +++ b/ipconf/source/mock.go @@ -0,0 +1,42 @@ +package source + +import ( + "context" + "fmt" + "math/rand" + "time" + + "github.com/hardcore-os/plato/common/config" + "github.com/hardcore-os/plato/common/discovery" +) + +func testServiceRegister(ctx *context.Context, port, node string) { + // 模拟服务发现 + go func() { + ed := discovery.EndpointInfo{ + IP: "127.0.0.1", + Port: port, + MetaData: map[string]interface{}{ + "connect_num": float64(rand.Int63n(12312321231231131)), + "message_bytes": float64(rand.Int63n(1231232131556)), + }, + } + sr, err := discovery.NewServiceRegister(ctx, fmt.Sprintf("%s/%s", config.GetServicePathForIPConf(), node), &ed, time.Now().Unix()) + if err != nil { + panic(err) + } + go sr.ListenLeaseRespChan() + for { + ed = discovery.EndpointInfo{ + IP: "127.0.0.1", + Port: port, + MetaData: map[string]interface{}{ + "connect_num": float64(rand.Int63n(12312321231231131)), + "message_bytes": float64(rand.Int63n(1231232131556)), + }, + } + sr.UpdateValue(&ed) + time.Sleep(1 * time.Second) + } + }() +} diff --git a/ipconf/utils.go b/ipconf/utils.go new file mode 100644 index 0000000..e39fe13 --- /dev/null +++ b/ipconf/utils.go @@ -0,0 +1,18 @@ +package ipconf + +import "github.com/hardcore-os/plato/ipconf/domain" + +func top5Endports(eds []*domain.Endport) []*domain.Endport { + if len(eds) < 5 { + return eds + } + return eds[:5] +} + +func packRes(ed []*domain.Endport) Response { + return Response{ + Message: "ok", + Code: 0, + Data: ed, + } +} diff --git a/perf/perf.go b/perf/perf.go new file mode 100644 index 0000000..b1e9038 --- /dev/null +++ b/perf/perf.go @@ -0,0 +1,17 @@ +package perf + +import ( + "github.com/hardcore-os/plato/common/sdk" + "net" +) + +var ( + TcpConnNum int32 +) + +func RunMain() { + for i := 0; i < int(TcpConnNum); i++ { + sdk.NewChat(net.ParseIP("127.0.0.1"), 8900, "logic", "1223", "123") + } + select {} +} diff --git a/plato.yaml b/plato.yaml new file mode 100644 index 0000000..b238a26 --- /dev/null +++ b/plato.yaml @@ -0,0 +1,43 @@ +global: + env: debug +discovery: + endpoints: + - plato-etcd:2379 # 192.168.31.192:2379 + timeout: 5 +cache: + redis: + endpoints: + - plato-redis:6379 # 192.168.31.192:2379 +ip_conf: + service_path: /plato/ip_dispatcher +prpc: + discov: + name: etcd + endpoints: + - plato-etcd:2379 # 192.168.31.192:2379 + trace: + enable: true + url: http://127.0.0.1:14268/api/traces + service_name: plato + sampler: 1.0 +gateway: + service_name: "plato.access.gateway" + service_addr: "gateway" #127.0.0.1 + tcp_max_num: 70000 + epoll_channel_num: 100 + epoll_num: 8 + epoll_wait_queue_size: 100 + tcp_server_port: 8900 + rpc_server_port: 8901 + worker_pool_num: 1024 + cmd_channel_num: 2048 + weight: 100 + state_server_endpoint: "state:8902" #127.0.0.1 +state: + service_name: "plato.access.state" + servide_addr: "state" #127.0.0.1 + cmd_channel_num: 2048 + server_port: 8902 + weight: 100 + conn_state_slot_range: "0,512" + gateway_server_endpoint: "gateway:8901" #127.0.0.1 \ No newline at end of file diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..39ab21a --- /dev/null +++ b/start.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# 停止并删除现有的容器 +docker-compose down + +# 清理网络状态 +docker network prune -f + +# 重新启动服务 +docker-compose up \ No newline at end of file diff --git a/state/cache.go b/state/cache.go new file mode 100644 index 0000000..1ae6212 --- /dev/null +++ b/state/cache.go @@ -0,0 +1,248 @@ +package state + +import ( + "context" + "errors" + "fmt" + "github.com/bytedance/gopkg/util/logger" + "github.com/golang/protobuf/proto" + "github.com/hardcore-os/plato/common/cache" + "github.com/hardcore-os/plato/common/config" + "github.com/hardcore-os/plato/common/idl/message" + "github.com/hardcore-os/plato/common/router" + "github.com/hardcore-os/plato/state/rpc/service" + "strconv" + "strings" + "sync" +) + +var cs *cacheState + +// 远程cache状态 +type cacheState struct { + msgID uint64 // for test + connToStateTable sync.Map + server *service.Service +} + +// InitCacheState 初始化全局cache +func InitCacheState(ctx context.Context) { + cs = &cacheState{} + cache.InitRedis(ctx) + router.Init(ctx) + cs.connToStateTable = sync.Map{} + cs.initLoginSlot(ctx) + cs.server = &service.Service{ + CmdChannel: make(chan *service.CmdContext, config.GetStateCmdChannelNum()), + } +} + +// 初始化连接登陆槽 +func (cs *cacheState) initLoginSlot(ctx context.Context) error { + loginSlotRange := config.GetStateServerLoginSlotRange() + for _, slot := range loginSlotRange { + loginSlotKey := fmt.Sprintf(cache.LoginSlotSetKey, slot) + // async + go func() { + // 这里可以使用lua脚本进行批处理 + loginSlot, err := cache.SmembersStrSlice(ctx, loginSlotKey) + if err != nil { + logger.CtxErrorf(ctx, "loginSlotKey=%v, err=%v", loginSlotKey, err) + panic(err) + } + for _, mate := range loginSlot { + did, connID := cs.loginSlotUnmarshal(mate) + cs.connReLogin(ctx, did, connID) + } + }() + } + return nil +} + +func (cs *cacheState) newConnState(did uint64, connID uint64) *connState { + // create connState object + st := &connState{ + connID: connID, + did: did, + } + // start heartbeat timer + st.resetHeartTimer() + return st +} + +func (cs *cacheState) connLogin(ctx context.Context, did uint64, connID uint64) error { + st := cs.newConnState(did, connID) + // store login slot + slotKey := cs.getLoginSlotKey(connID) + meta := cs.loginSlotMarshal(did, connID) + err := cache.SADD(ctx, slotKey, meta) + if err != nil { + logger.CtxErrorf(ctx, "[connLogin] sadd login meta into redis failed, did=%v, connID=%v, err=%v", did, connID, err) + return err + } + + // add router in router table + endpoint := fmt.Sprintf("%s:%d", config.GetGatewayServiceAddr(), config.GetStateServerPort()) + err = router.AddRouter(ctx, did, endpoint, connID) + if err != nil { + logger.CtxErrorf(ctx, "[connLogin] add record in router table failed , did=%v, connID=%v, err=%v", did, connID, err) + return err + } + //TODO 上行消息 max_client_id 初始化, 现在相当于生命周期在conn维度,后面重构sdk时会调整到会话维度 + + // store local state + cs.storeConnIDState(connID, st) + return nil +} + +func (cs *cacheState) connReLogin(ctx context.Context, did uint64, connID uint64) { + st := cs.newConnState(did, connID) + cs.storeConnIDState(connID, st) + st.loadMsgTimer(ctx) +} + +func (cs *cacheState) connLogout(ctx context.Context, connID uint64) (uint64, error) { + if st, ok := cs.loadConnIDState(connID); ok { + did := st.did + return did, st.close(ctx) + } + return 0, nil +} + +func (cs *cacheState) reConn(ctx context.Context, oldConnID, newConnID uint64) error { + var ( + did uint64 + err error + ) + if did, err = cs.connLogout(ctx, oldConnID); err != nil { + return err + } + return cs.connLogin(ctx, did, newConnID) // todo 重连路由是不用更新的? +} + +func (cs *cacheState) resetHeartTimer(connID uint64) { + if st, ok := cs.loadConnIDState(connID); ok { + st.resetHeartTimer() + } +} + +func (cs *cacheState) loadConnIDState(connID uint64) (*connState, bool) { + if data, ok := cs.connToStateTable.Load(connID); ok { + st, _ := data.(*connState) + return st, true + } + return nil, false +} + +func (cs *cacheState) deleteConnIDState(ctx context.Context, connID uint64) { + cs.connToStateTable.Delete(connID) +} + +func (cs *cacheState) storeConnIDState(connID uint64, state *connState) { + cs.connToStateTable.Store(connID, state) +} + +// 获取登陆槽位的key +func (cs *cacheState) getLoginSlotKey(connID uint64) string { + connStateSlotList := config.GetStateServerLoginSlotRange() + slotSize := uint64(len(connStateSlotList)) + slot := connID % slotSize + slotKey := fmt.Sprintf(cache.LoginSlotSetKey, connStateSlotList[slot]) + return slotKey +} + +func (cs *cacheState) getConnStateSLot(connID uint64) uint64 { + connStateSlotList := config.GetStateServerLoginSlotRange() + slotSize := uint64(len(connStateSlotList)) + slot := connID % slotSize + return slot +} + +// 使用lua实现比较并自增 +func (cs *cacheState) compareAndIncrClientID(ctx context.Context, connID, oldMaxClientID uint64) bool { + slot := cs.getConnStateSLot(connID) + key := fmt.Sprintf(cache.MaxClientIDKey, slot, connID) + logger.CtxInfof(ctx, "[compareAndIncrClientID] run luaInt key=%v, oldClientID=%v, connID=%v", key, oldMaxClientID, connID) + + var ( + res int + err error + ) + if res, err = cache.RunLuaInt(ctx, cache.LuaCompareAndIncrClientID, []string{key}, oldMaxClientID, cache.TTL7D); err != nil { + logger.CtxErrorf(ctx, "[compareAndIncrClientID] run luaInt failed, key=%v, oldClientID=%v, connID=%v, err=%v", key, oldMaxClientID, connID, err) + panic(err) + } + return res > 0 +} + +func (cs *cacheState) appendLastMsg(ctx context.Context, connID uint64, pushMsg *message.PushMsg) error { + if pushMsg == nil { + logger.CtxWarnf(ctx, "[appendLastMsg] pushMsg is nil, return") + return nil + } + var ( + st *connState + ok bool + ) + if st, ok = cs.loadConnIDState(connID); !ok { + logger.CtxErrorf(ctx, "[appendLastMsg] load connState from map failed, connID=%v", connID) + return errors.New("connState is nil ") + } + slot := cs.getConnStateSLot(connID) + key := fmt.Sprintf(cache.LastMsgKey, slot, connID) + // TODO 现在假设一个链接只有一个会话,后面再讲IMserver,会进行重构 + msgTimerLock := fmt.Sprintf("%d_%d", pushMsg.SessionID, pushMsg.MsgID) + msgData, _ := proto.Marshal(pushMsg) + st.appendMsg(ctx, key, msgTimerLock, msgData) + return nil +} + +func (cs *cacheState) ackLastMsg(ctx context.Context, connID, sessionID, msgID uint64) { + var ( + st *connState + ok bool + ) + if st, ok = cs.loadConnIDState(connID); ok { + st.ackLastMsg(ctx, sessionID, msgID) + } +} + +func (cs *cacheState) getLastMsg(ctx context.Context, connID uint64) (*message.PushMsg, error) { + slot := cs.getConnStateSLot(connID) + key := fmt.Sprintf(cache.LastMsgKey, slot, connID) + data, err := cache.GetBytes(ctx, key) + if err != nil { + logger.CtxErrorf(ctx, "[getLastMsg] get lastNsg from cache failed, connID = %v, err=%v", connID, err) + return nil, err + } + if len(data) == 0 { + return nil, nil + } + pMsg := &message.PushMsg{} + err = proto.Unmarshal(data, pMsg) + if err != nil { + logger.CtxErrorf(ctx, "[getLastMsg] unmarshal data to pushMsg failed, connID = %v, data=%v, err=%v", connID, string(data), err) + return nil, err + } + return pMsg, nil +} + +func (cs *cacheState) loginSlotUnmarshal(mate string) (uint64, uint64) { + strSlice := strings.Split(mate, "|") + if len(strSlice) < 2 { + return 0, 0 + } + did, err := strconv.ParseUint(strSlice[0], 10, 64) + if err != nil { + return 0, 0 + } + connID, err := strconv.ParseUint(strSlice[1], 10, 64) + if err != nil { + return 0, 0 + } + return did, connID +} + +func (cs *cacheState) loginSlotMarshal(did, connID uint64) string { + return fmt.Sprintf("%d|%d", did, connID) +} diff --git a/state/rpc/Makefile b/state/rpc/Makefile new file mode 100644 index 0000000..40c86d3 --- /dev/null +++ b/state/rpc/Makefile @@ -0,0 +1,9 @@ +all: state + +state: + @echo "begin" + protoc -I service --go_out=service service/*.proto + @echo "*.pb.go success" + protoc -I service --go-grpc_out=service service/*.proto + @echo "*_grpc.pb.go success" + @echo "end" diff --git a/state/rpc/client/gateway.go b/state/rpc/client/gateway.go new file mode 100644 index 0000000..a99e6b0 --- /dev/null +++ b/state/rpc/client/gateway.go @@ -0,0 +1,31 @@ +package client + +import ( + "context" + "github.com/bytedance/gopkg/util/logger" + "github.com/hardcore-os/plato/gateway/rpc/service" + "time" +) + +func DelConn(ctx *context.Context, connID uint64, payLoad []byte) error { + rpcCtx, _ := context.WithTimeout(*ctx, 100*time.Millisecond) + gatewayClient.DelConn(rpcCtx, &service.GatewayRequest{ + ConnID: connID, + Data: payLoad, + }) + return nil +} + +func Push(ctx *context.Context, connID uint64, payLoad []byte) error { + rpcCtx, _ := context.WithTimeout(*ctx, 100*time.Millisecond) + resp, err := gatewayClient.Push(rpcCtx, &service.GatewayRequest{ + ConnID: connID, + Data: payLoad, + }) + if err != nil { + logger.CtxErrorf(rpcCtx, "message.gatewayClient push msg err, err=%v", err) + return err + } + logger.CtxInfof(rpcCtx, "message.gatewayClient push msg resp, resp=%+v", resp) + return nil +} diff --git a/state/rpc/client/init.go b/state/rpc/client/init.go new file mode 100644 index 0000000..2e1cf42 --- /dev/null +++ b/state/rpc/client/init.go @@ -0,0 +1,26 @@ +package client + +import ( + "fmt" + "github.com/hardcore-os/plato/common/config" + "github.com/hardcore-os/plato/common/prpc" + "github.com/hardcore-os/plato/gateway/rpc/service" +) + +var gatewayClient service.GatewayClient + +func Init() { + initGatewayClient() +} + +func initGatewayClient() { + pCli, err := prpc.NewPClient(config.GetGatewayServiceName()) + if err != nil { + panic(err) + } + conn, err := pCli.DialByEndPoint(config.GetStateServerGatewayServerEndpoint()) + if err != nil { + panic(fmt.Sprintf("state.gateway client DialByEndPoint fialed, err=%v", err)) + } + gatewayClient = service.NewGatewayClient(conn) +} diff --git a/state/rpc/service/service.go b/state/rpc/service/service.go new file mode 100644 index 0000000..f24ee53 --- /dev/null +++ b/state/rpc/service/service.go @@ -0,0 +1,58 @@ +package service + +import ( + "context" + "github.com/bytedance/gopkg/util/logger" +) + +const ( + CancelConnCmd = 1 + SendMsgCmd = 2 +) + +type CmdContext struct { + Ctx *context.Context + Cmd int32 + Endpoint string + ConnID uint64 + PayLoad []byte +} + +type Service struct { + UnimplementedStateServer + CmdChannel chan *CmdContext +} + +func (s *Service) CancelConn(ctx context.Context, sr *StateRequest) (*StateResponse, error) { + c := context.TODO() + s.CmdChannel <- &CmdContext{ + Ctx: &c, + Cmd: CancelConnCmd, + Endpoint: sr.GetEndpoint(), + ConnID: sr.GetConnID(), + } + + return &StateResponse{ + Code: 0, + Msg: "success", + }, nil +} + +func (s *Service) SendMsg(ctx context.Context, sr *StateRequest) (*StateResponse, error) { + logger.CtxInfof(ctx, "[message] receive SendMsg request ok") + c := context.TODO() + s.CmdChannel <- &CmdContext{ + Ctx: &c, + Cmd: SendMsgCmd, + ConnID: sr.GetConnID(), + Endpoint: sr.GetEndpoint(), + PayLoad: sr.GetData(), + } + + logger.CtxInfof(ctx, "[message] sendMsg, connID=%v, channel = %d", sr.GetConnID(), len(s.CmdChannel)) + + return &StateResponse{ + Code: 0, + Msg: "success", + }, nil +} diff --git a/state/rpc/service/state.pb.go b/state/rpc/service/state.pb.go new file mode 100644 index 0000000..44968b1 --- /dev/null +++ b/state/rpc/service/state.pb.go @@ -0,0 +1,244 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v3.19.4 +// source: message.proto + +package service + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type StateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Endpoint string `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + ConnID uint64 `protobuf:"varint,2,opt,name=connID,proto3" json:"connID,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *StateRequest) Reset() { + *x = StateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_state_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StateRequest) ProtoMessage() {} + +func (x *StateRequest) ProtoReflect() protoreflect.Message { + mi := &file_state_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StateRequest.ProtoReflect.Descriptor instead. +func (*StateRequest) Descriptor() ([]byte, []int) { + return file_state_proto_rawDescGZIP(), []int{0} +} + +func (x *StateRequest) GetEndpoint() string { + if x != nil { + return x.Endpoint + } + return "" +} + +func (x *StateRequest) GetConnID() uint64 { + if x != nil { + return x.ConnID + } + return 0 +} + +func (x *StateRequest) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type StateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` + Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` +} + +func (x *StateResponse) Reset() { + *x = StateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_state_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StateResponse) ProtoMessage() {} + +func (x *StateResponse) ProtoReflect() protoreflect.Message { + mi := &file_state_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StateResponse.ProtoReflect.Descriptor instead. +func (*StateResponse) Descriptor() ([]byte, []int) { + return file_state_proto_rawDescGZIP(), []int{1} +} + +func (x *StateResponse) GetCode() int32 { + if x != nil { + return x.Code + } + return 0 +} + +func (x *StateResponse) GetMsg() string { + if x != nil { + return x.Msg + } + return "" +} + +var File_state_proto protoreflect.FileDescriptor + +var file_state_proto_rawDesc = []byte{ + 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x56, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x6e, 0x49, 0x44, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x35, + 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, + 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6d, 0x73, 0x67, 0x32, 0x7e, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3b, + 0x0a, 0x0a, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x6e, 0x12, 0x15, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x07, 0x53, + 0x65, 0x6e, 0x64, 0x4d, 0x73, 0x67, 0x12, 0x15, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x3b, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_state_proto_rawDescOnce sync.Once + file_state_proto_rawDescData = file_state_proto_rawDesc +) + +func file_state_proto_rawDescGZIP() []byte { + file_state_proto_rawDescOnce.Do(func() { + file_state_proto_rawDescData = protoimpl.X.CompressGZIP(file_state_proto_rawDescData) + }) + return file_state_proto_rawDescData +} + +var file_state_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_state_proto_goTypes = []interface{}{ + (*StateRequest)(nil), // 0: service.StateRequest + (*StateResponse)(nil), // 1: service.StateResponse +} +var file_state_proto_depIdxs = []int32{ + 0, // 0: service.message.CancelConn:input_type -> service.StateRequest + 0, // 1: service.message.SendMsg:input_type -> service.StateRequest + 1, // 2: service.message.CancelConn:output_type -> service.StateResponse + 1, // 3: service.message.SendMsg:output_type -> service.StateResponse + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_state_proto_init() } +func file_state_proto_init() { + if File_state_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_state_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_state_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_state_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_state_proto_goTypes, + DependencyIndexes: file_state_proto_depIdxs, + MessageInfos: file_state_proto_msgTypes, + }.Build() + File_state_proto = out.File + file_state_proto_rawDesc = nil + file_state_proto_goTypes = nil + file_state_proto_depIdxs = nil +} diff --git a/state/rpc/service/state.proto b/state/rpc/service/state.proto new file mode 100644 index 0000000..43d8dc4 --- /dev/null +++ b/state/rpc/service/state.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +option go_package = "./;service"; + +package service; +// 网关机的 rpc server定义 +// cd state/rpc 下 执行 protoc -I service --go_out=plugins=grpc:service service/state.proto +service state { + rpc CancelConn (StateRequest) returns (StateResponse); + rpc SendMsg (StateRequest) returns (StateResponse); +} + +message StateRequest{ + string endpoint = 1; + uint64 connID = 2; + bytes data = 3; +} + +message StateResponse { + int32 code = 1; + string msg = 2; +} \ No newline at end of file diff --git a/state/rpc/service/state_grpc.pb.go b/state/rpc/service/state_grpc.pb.go new file mode 100644 index 0000000..0d9d814 --- /dev/null +++ b/state/rpc/service/state_grpc.pb.go @@ -0,0 +1,146 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v3.19.4 +// source: message.proto + +package service + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + State_CancelConn_FullMethodName = "/service.message/CancelConn" + State_SendMsg_FullMethodName = "/service.message/SendMsg" +) + +// StateClient is the client API for State service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type StateClient interface { + CancelConn(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*StateResponse, error) + SendMsg(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*StateResponse, error) +} + +type stateClient struct { + cc grpc.ClientConnInterface +} + +func NewStateClient(cc grpc.ClientConnInterface) StateClient { + return &stateClient{cc} +} + +func (c *stateClient) CancelConn(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*StateResponse, error) { + out := new(StateResponse) + err := c.cc.Invoke(ctx, State_CancelConn_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *stateClient) SendMsg(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*StateResponse, error) { + out := new(StateResponse) + err := c.cc.Invoke(ctx, State_SendMsg_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// StateServer is the server API for State service. +// All implementations must embed UnimplementedStateServer +// for forward compatibility +type StateServer interface { + CancelConn(context.Context, *StateRequest) (*StateResponse, error) + SendMsg(context.Context, *StateRequest) (*StateResponse, error) + mustEmbedUnimplementedStateServer() +} + +// UnimplementedStateServer must be embedded to have forward compatible implementations. +type UnimplementedStateServer struct { +} + +func (UnimplementedStateServer) CancelConn(context.Context, *StateRequest) (*StateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CancelConn not implemented") +} +func (UnimplementedStateServer) SendMsg(context.Context, *StateRequest) (*StateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendMsg not implemented") +} +func (UnimplementedStateServer) mustEmbedUnimplementedStateServer() {} + +// UnsafeStateServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to StateServer will +// result in compilation errors. +type UnsafeStateServer interface { + mustEmbedUnimplementedStateServer() +} + +func RegisterStateServer(s grpc.ServiceRegistrar, srv StateServer) { + s.RegisterService(&State_ServiceDesc, srv) +} + +func _State_CancelConn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StateServer).CancelConn(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: State_CancelConn_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StateServer).CancelConn(ctx, req.(*StateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _State_SendMsg_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StateServer).SendMsg(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: State_SendMsg_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StateServer).SendMsg(ctx, req.(*StateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// State_ServiceDesc is the grpc.ServiceDesc for State service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var State_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "service.message", + HandlerType: (*StateServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CancelConn", + Handler: _State_CancelConn_Handler, + }, + { + MethodName: "SendMsg", + Handler: _State_SendMsg_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "message.proto", +} diff --git a/state/server.go b/state/server.go new file mode 100644 index 0000000..27fda69 --- /dev/null +++ b/state/server.go @@ -0,0 +1,228 @@ +package state + +import ( + "context" + "github.com/bytedance/gopkg/util/logger" + "github.com/golang/protobuf/proto" + "github.com/hardcore-os/plato/common/config" + "github.com/hardcore-os/plato/common/idl/message" + "github.com/hardcore-os/plato/common/prpc" + "github.com/hardcore-os/plato/state/rpc/client" + "github.com/hardcore-os/plato/state/rpc/service" + "google.golang.org/grpc" +) + +func RunMain(path string) { + // init ctx + ctx := context.TODO() + // init config + config.Init(path) + // init rpc client + client.Init() + // start timeWheel + InitTimer() + // start remote cache + InitCacheState(ctx) + // start cmdHandler + go func() { + defer func() { + if r := recover(); r != nil { + logger.CtxErrorf(ctx, "cmdHandler panic, r=%v", r) + } + }() + cmdHandler() + }() + + // register rpc server + s := prpc.NewPServer( + prpc.WithServiceName(config.GetStateServiceName()), + prpc.WithIP(config.GetStateServiceAddr()), + prpc.WithPort(config.GetStateServerPort()), + prpc.WithWeight(config.GetStateRPCWeight())) + s.RegisterService(func(server *grpc.Server) { + service.RegisterStateServer(server, cs.server) + }) + logger.CtxInfof(ctx, "[state] serviceName:%s Addr:%s:%d weight:%d", config.GetStateServiceName(), config.GetStateServiceAddr(), config.GetStateServerPort(), config.GetStateRPCWeight()) + s.Start(ctx) +} + +// 消费信令通道,识别gateway与state server之间的协议路由 +func cmdHandler() { + for cmdCtx := range cs.server.CmdChannel { + switch cmdCtx.Cmd { + case service.CancelConnCmd: + logger.CtxInfof(*cmdCtx.Ctx, "cancelConn endpoint:%s, fd:%d, data:%+v", cmdCtx.Endpoint, cmdCtx.ConnID, cmdCtx.PayLoad) + cs.connLogout(*cmdCtx.Ctx, cmdCtx.ConnID) + case service.SendMsgCmd: + logger.CtxInfof(*cmdCtx.Ctx, "cmdHandler: %v\n", string(cmdCtx.PayLoad)) + msgCmd := &message.MsgCmd{} + err := proto.Unmarshal(cmdCtx.PayLoad, msgCmd) + if err != nil { + logger.CtxErrorf(*cmdCtx.Ctx, "SendMsgCmd: err=%s", err.Error()) + continue + } + msgCmdHandler(cmdCtx, msgCmd) + default: + logger.CtxInfof(*cmdCtx.Ctx, "invalid cmd type, type=%v", cmdCtx.Cmd) + } + } +} + +// 识别消息类型,识别客户端与state server之间的协议路由 +func msgCmdHandler(cmdCtx *service.CmdContext, msgCmd *message.MsgCmd) { + if msgCmd == nil { + return + } + switch msgCmd.Type { + case message.CmdType_Login: + loginMsgHandler(cmdCtx, msgCmd) + case message.CmdType_Heartbeat: + heartbeatMsgHandler(cmdCtx, msgCmd) + case message.CmdType_ReConn: + reConnMsgHandler(cmdCtx, msgCmd) + case message.CmdType_UP: + upMsgHandler(cmdCtx, msgCmd) + case message.CmdType_ACK: + ackMsgHandler(cmdCtx, msgCmd) + } +} + +func loginMsgHandler(cmdCtx *service.CmdContext, msgCmd *message.MsgCmd) { + loginMsg := &message.LoginMsg{} + err := proto.Unmarshal(msgCmd.Payload, loginMsg) + if err != nil { + logger.CtxErrorf(*cmdCtx.Ctx, "[loginMsgHandler] Unmarshal failed, payload=%v, err=%v", string(msgCmd.Payload), err) + return + } + if loginMsg.Head != nil { + // 这里会把 login msg 传送给业务层做处理 + logger.CtxInfof(*cmdCtx.Ctx, "[loginMsgHandler]:%v", loginMsg.Head.DeviceID) + } + err = cs.connLogin(*cmdCtx.Ctx, loginMsg.Head.DeviceID, cmdCtx.ConnID) + if err != nil { + logger.CtxErrorf(*cmdCtx.Ctx, "[loginMsgHandler] connStateLogin failed, did=%v, connID=%v, err=%v", loginMsg.Head.DeviceID, cmdCtx.ConnID, err) + return + } + sendAckMsg(message.CmdType_Login, cmdCtx.ConnID, 0, 0, "login ok") +} + +func heartbeatMsgHandler(cmdCtx *service.CmdContext, msgCmd *message.MsgCmd) { + heartMsg := &message.HeartbeatMsg{} + err := proto.Unmarshal(msgCmd.Payload, heartMsg) + if err != nil { + logger.CtxErrorf(*cmdCtx.Ctx, "[heartbeatMsgHandler] Unmarshal failed, err=%v", err) + return + } + cs.resetHeartTimer(cmdCtx.ConnID) + logger.CtxInfof(*cmdCtx.Ctx, "[heartbeatMsgHandler] reset heartbeat success, connID=%v", cmdCtx.ConnID) + // TODO 未减少通信量,可以暂时不回复心跳的ack +} + +func reConnMsgHandler(cmdCtx *service.CmdContext, msgCmd *message.MsgCmd) { + reConnMsg := &message.ReConnMsg{} + err := proto.Unmarshal(msgCmd.Payload, reConnMsg) + if err != nil { + logger.CtxErrorf(*cmdCtx.Ctx, "[reConnMsgHandler] Unmarshal failed, err=%v", err) + return + } + var code uint32 + msg := "reconnection OK" + // 重连的消息头中的connID才是上一次断开连接的connID + if err = cs.reConn(*cmdCtx.Ctx, reConnMsg.Head.ConnID, cmdCtx.ConnID); err != nil { + code, msg = 1, "reconnection failed" + logger.CtxErrorf(*cmdCtx.Ctx, "[reConnMsgHandler] reconnection failed, old connID=%v, newConnID=%v, err=%v", reConnMsg.Head.ConnID, cmdCtx.ConnID, err) + return + } + sendAckMsg(message.CmdType_ReConn, cmdCtx.ConnID, 0, code, msg) +} + +func sendAckMsg(ackType message.CmdType, connID, clientID uint64, code uint32, msg string) { + ackMsg := &message.ACKMsg{} + ackMsg.Code = code + ackMsg.Msg = msg + ackMsg.ConnID = connID + ackMsg.Type = ackType + ackMsg.ClientID = clientID + ctx := context.TODO() + downLoad, err := proto.Marshal(ackMsg) + if err != nil { + logger.CtxErrorf(ctx, "sendAckMsg err=%s, connID=%v, code=%v, msg=%v", err, connID, code, msg) + return + } + sendMsg(connID, message.CmdType_ACK, downLoad) +} + +func sendMsg(connID uint64, ty message.CmdType, download []byte) { + mc := &message.MsgCmd{} + mc.Type = ty + mc.Payload = download + data, err := proto.Marshal(mc) + ctx := context.TODO() + if err != nil { + logger.CtxErrorf(ctx, "sendMsg err=%s, connID=%v, ty=%v, msg=%v", err, connID, ty, string(download)) + return + } + client.Push(&ctx, connID, data) +} + +// 处理下行消息 +func ackMsgHandler(cmdCtx *service.CmdContext, msgCmd *message.MsgCmd) { + ackMsg := &message.ACKMsg{} + err := proto.Unmarshal(msgCmd.Payload, ackMsg) + if err != nil { + logger.CtxErrorf(*cmdCtx.Ctx, "ackMsgHandler err=%v", err.Error()) + return + } + cs.ackLastMsg(*cmdCtx.Ctx, ackMsg.ConnID, ackMsg.SessionID, ackMsg.MsgID) +} + +// 处理上行消息,并进行消息可靠性检查 +func upMsgHandler(cmdCtx *service.CmdContext, msgCmd *message.MsgCmd) { + upMsg := &message.UPMsg{} + err := proto.Unmarshal(msgCmd.Payload, upMsg) + if err != nil { + logger.CtxErrorf(*cmdCtx.Ctx, "upMsgHandler err=%v", err.Error()) + return + } + if cs.compareAndIncrClientID(*cmdCtx.Ctx, cmdCtx.ConnID, upMsg.Head.ClientID) { + // 调用下游业务层rpc,只有当rpc回复成功后才能更新max_clientID + sendAckMsg(message.CmdType_UP, cmdCtx.ConnID, upMsg.Head.ClientID, 0, "OK") + // TODO 这里应该调用业务层的代码 + pushMsg(*cmdCtx.Ctx, cmdCtx.ConnID, cs.msgID, 0, upMsg.UPMsgBody) + } +} + +func pushMsg(ctx context.Context, connID, sessionID, msgID uint64, data []byte) { + // TODO 先在这里push消息 + pMsg := &message.PushMsg{ + MsgID: cs.msgID, + Content: data, + } + pData, err := proto.Marshal(pMsg) + if err != nil { + logger.CtxErrorf(ctx, "[pushMsg] pushMsg failed, connID=%v, sessionID=%v, msgID=%v, data=%v, err=%v", connID, sessionID, msgID, string(data), err) + return + } + sendMsg(connID, message.CmdType_Push, pData) + if err = cs.appendLastMsg(ctx, connID, pMsg); err != nil { + logger.CtxErrorf(ctx, "[pushMsg] append last msg failed, connID=%v, sessionID=%v, msgID=%v, data=%v, err=%v", connID, sessionID, msgID, string(data), err) + } +} + +func rePush(connID uint64) { + ctx := context.TODO() + pMsg, err := cs.getLastMsg(ctx, connID) + if err != nil { + logger.CtxErrorf(ctx, "rePushMsg failed, connID=%v", connID) + return + } + msgData, err := proto.Marshal(pMsg) + if err != nil { + logger.CtxErrorf(ctx, "rePushMsg marshal msg failed, connID=%v, msg=%+v", connID, pMsg) + return + } + sendMsg(connID, message.CmdType_Push, msgData) + if st, ok := cs.loadConnIDState(connID); ok { + st.resetMsgTimer(connID, pMsg.SessionID, pMsg.MsgID) + } +} diff --git a/state/state.go b/state/state.go new file mode 100644 index 0000000..c35ed9c --- /dev/null +++ b/state/state.go @@ -0,0 +1,165 @@ +package state + +import ( + "context" + "fmt" + "github.com/bytedance/gopkg/util/logger" + "github.com/hardcore-os/plato/common/cache" + "github.com/hardcore-os/plato/common/router" + "github.com/hardcore-os/plato/common/timingwheel" + "github.com/hardcore-os/plato/state/rpc/client" + "sync" + "time" +) + +type connState struct { + sync.RWMutex + heartTimer *timingwheel.Timer + reConnTimer *timingwheel.Timer + msgTimer *timingwheel.Timer + msgTimerLock string + connID uint64 + did uint64 +} + +func (c *connState) close(ctx context.Context) error { + c.Lock() + defer c.Unlock() + if c.heartTimer != nil { + c.heartTimer.Stop() + } + if c.reConnTimer != nil { + c.reConnTimer.Stop() + } + if c.msgTimer != nil { + c.msgTimer.Stop() + } + // TODO 这里如何保证事务性,值得思考一下,或者说有没有必要保证 + // TODO 这里也可以使用lua或者pipeline 来尽可能合并两次redis的操作 通常在大规模的应用中这是有效的 + // TODO 这里是要好好思考一下,网络调用次数的时间&空间复杂度的 + + // TODO 可以加一个simple retry,redis的del操作是幂等的,但是也没有解决事务的问题 + slotKey := cs.getLoginSlotKey(c.connID) + meta := cs.loginSlotMarshal(c.did, c.connID) + err := cache.SREM(ctx, slotKey, meta) + if err != nil { + logger.CtxErrorf(ctx, "[close] cache set remove login meta failed, did=%v, connID=%v, err=%v", c.did, c.connID, err) + return err + } + slot := cs.getConnStateSLot(c.connID) + key := fmt.Sprintf(cache.MaxClientIDKey, slot, c.connID) + err = cache.Del(ctx, key) + if err != nil { + logger.CtxErrorf(ctx, "[close] cache remove login data failed, slot=%v, connID=%v, err=%v", slot, c.connID, err) + return err + } + err = router.DelRouter(ctx, c.did) + if err != nil { + logger.CtxErrorf(ctx, "[close] cache del record failed, did=%v, err=%v", c.did, err) + return err + } + lastMsgKey := fmt.Sprintf(cache.LastMsgKey, slot, c.connID) + err = cache.Del(ctx, lastMsgKey) + if err != nil { + logger.CtxErrorf(ctx, "[close] cache del lastMsg failed, slot=%v, connID=%v, err=%v", slot, c.connID, err) + return err + } + err = client.DelConn(&ctx, c.connID, nil) + if err != nil { + logger.CtxErrorf(ctx, "[close] gateway client del conn failed, connID=%v, err=%v", c.connID, err) + return err + } + cs.deleteConnIDState(ctx, c.connID) + return nil +} + +func (c *connState) appendMsg(ctx context.Context, key, msgTimerLock string, msgData []byte) { + c.Lock() + defer c.Unlock() + c.msgTimerLock = msgTimerLock + if c.msgTimer != nil { + c.msgTimer.Stop() + c.msgTimer = nil + } + // create timer + t := AfterFunc(100*time.Millisecond, func() { + rePush(c.connID) + }) + c.msgTimer = t + err := cache.SetBytes(ctx, key, msgData, cache.TTL7D) + if err != nil { + logger.CtxErrorf(ctx, "[appendMsg] set rePush cache failed, key=%v, msgData=%v, err=%v", key, msgData, err) + return + } +} + +func (c *connState) resetMsgTimer(connID, sessionID, msgID uint64) { + c.Lock() + defer c.Unlock() + if c.msgTimer != nil { + c.msgTimer.Stop() + c.msgTimer = nil + } + c.msgTimerLock = fmt.Sprintf("%d_%d", sessionID, msgID) + c.msgTimer = AfterFunc(100*time.Millisecond, func() { + rePush(c.connID) + }) +} + +func (c *connState) loadMsgTimer(ctx context.Context) { + // create timer + data, err := cs.getLastMsg(ctx, c.connID) + if err != nil { + return + } + if data == nil { + return + } + c.resetMsgTimer(c.connID, data.SessionID, data.MsgID) +} + +func (c *connState) resetHeartTimer() { + c.Lock() + defer c.Unlock() + if c.heartTimer != nil { + c.heartTimer.Stop() + c.heartTimer = nil + } + c.heartTimer = AfterFunc(5*time.Second, func() { + c.resetConnTimer() + }) +} + +func (c *connState) resetConnTimer() { + c.Lock() + defer c.Unlock() + if c.reConnTimer != nil { + c.reConnTimer.Stop() + c.reConnTimer = nil + } + + c.reConnTimer = AfterFunc(10*time.Second, func() { + ctx := context.TODO() + // 整体connID状态登出 + // 不马上清理,而是等待重连,10s之后才会清理连接的数据 + cs.connLogout(ctx, c.connID) + }) +} + +func (c *connState) ackLastMsg(ctx context.Context, sessionID, msgID uint64) bool { + c.Lock() + defer c.Unlock() + msgTimerLock := fmt.Sprintf("%d_%d", sessionID, msgID) + if c.msgTimerLock != msgTimerLock { + return false + } + slot := cs.getConnStateSLot(c.connID) + key := fmt.Sprintf(cache.LastMsgKey, slot, c.connID) + if err := cache.Del(ctx, key); err != nil { + return false + } + if c.msgTimer != nil { + c.msgTimer.Stop() + } + return true +} diff --git a/state/timer.go b/state/timer.go new file mode 100644 index 0000000..d0ce576 --- /dev/null +++ b/state/timer.go @@ -0,0 +1,23 @@ +package state + +import ( + "time" + + "github.com/hardcore-os/plato/common/timingwheel" +) + +var wheel *timingwheel.TimingWheel + +func InitTimer() { + wheel = timingwheel.NewTimingWheel(time.Millisecond, 20) + wheel.Start() +} + +func CloseTimer() { + wheel.Stop() +} + +func AfterFunc(d time.Duration, f func()) *timingwheel.Timer { + t := wheel.AfterFunc(d, f) + return t +}