This commit is contained in:
2022-03-17 15:55:27 +08:00
commit bd5a9fad97
92 changed files with 13861 additions and 0 deletions

54
Makefile Normal file
View File

@@ -0,0 +1,54 @@
# Go parameters
GOCMD=GO111MODULE=on go
GOBUILD=$(GOCMD) build
GOTEST=$(GOCMD) test
PKG_LIST := `go list ./... | grep -v "vendor"`
PKG_LIST_VET := `go list ./... | grep -v "vendor"`
all: test build
build:
rm -rf target/
mkdir target/
cp comet/conf/comet.toml target/comet.toml
cp logic/conf/logic.toml target/logic.toml
$(GOBUILD) -o target/comet comet/cmd/main.go
$(GOBUILD) -o target/logic logic/cmd/main.go
test:
$(GOTEST) -v ./...
clean:
rm -rf target/
run:
nohup target/logic -conf=target/logic.toml -logtostderr 2>&1 > target/logic.log &
nohup target/comet -conf=target/comet.toml -logtostderr 2>&1 > target/comet.log &
vet:
@go vet ${PKG_LIST_VET}
stop:
pkill -f target/comet
pkill -f target/logic
# 编译oa二进制 amd64
quick_build_amd:
@echo '┌ start quick build amd64'
@bash ./script/build/quick_build.sh
@echo '└ end quick build amd64'
# 编译oa二进制 amr64
quick_build_arm:
@echo '┌ start quick build arm64'
@bash ./script/build/quick_build.sh arm64
@echo '└ end quick build arm64'
build_linux_amd:
rm -rf target/
mkdir target/
cp comet/conf/comet.toml target/comet.toml
cp logic/conf/logic.toml target/logic.toml
GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v -o target/comet comet/cmd/main.go
GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v -o target/logic logic/cmd/main.go

66
README.md Normal file
View File

@@ -0,0 +1,66 @@
goim v2.0
==============
goim is a im server writen by golang.
## Features
* Light weight
* High performance
* Pure Golang
* Supports single push, multiple push and broadcasting
* Supports one key to multiple subscribers (Configurable maximum subscribers count)
* Supports heartbeats (Application heartbeats, TCP, KeepAlive, HTTP long pulling)
* Supports authentication (Unauthenticated user can't subscribe)
* Supports multiple protocols (WebSocketTCPHTTP
* Scalable architecture (Unlimited dynamic `comet` and `logic` modules)
* Asynchronous push notification based on Kafka
## Architecture
![arch](./docs/goim-arch.png)
## Quick Start
### Dependencies
* kakfa: 消息队列
* redis: 保存客户端连接信息
* etcd: 提供服务注册/发现, comet、logic 服务注册在 etcd
docker 快速启动相关依赖:
```
docker-compose -f benchmarks/kafka-docker-compose.yml up -d
docker run -d -p 6379:6379 redis
docker run -d -p 2379:2379 -p 2380:2380 --name etcd-v3.4.13 quay.io/coreos/etcd:v3.4.13 /usr/local/bin/etcd \
--name s1 \
--data-dir /etcd-data \
--listen-client-urls http://0.0.0.0:2379 \
--advertise-client-urls http://0.0.0.0:2379 \
--listen-peer-urls http://0.0.0.0:2380 \
--initial-advertise-peer-urls http://0.0.0.0:2380 \
--initial-cluster s1=http://0.0.0.0:2380 \
--initial-cluster-token tkn \
--initial-cluster-state new \
--log-level info \
--logger zap \
--log-outputs stderr
```
### Build
```
make build
```
### Run
```
make run
make stop
// or
nohup target/logic -conf=target/logic.toml -logtostderr 2>&1 > target/logic.log &
nohup target/comet -conf=target/comet.toml -logtostderr 2>&1 > target/comet.log &
```
### Configuration
You can view the comments in `target/comet.toml`, `target/logic.toml` to understand the meaning of the config.
## Document
[Protocol](./docs/proto.md)

1294
api/comet/grpc/comet.pb.go Normal file

File diff suppressed because it is too large Load Diff

109
api/comet/grpc/comet.proto Normal file
View File

@@ -0,0 +1,109 @@
// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto
syntax = "proto3";
package im.comet;
option go_package = "gitlab.33.cn/chat/im/api/comet/grpc";
message Proto {
int32 ver = 1;
int32 op = 2;
int32 seq = 3;
int32 ack = 4;
bytes body = 5;
}
enum Op {
Undefined = 0;
Auth = 1;
AuthReply = 2;
Heartbeat = 3;
HeartbeatReply = 4;
Disconnect = 5;
DisconnectReply = 6;
SendMsg = 7;
SendMsgReply = 8; //客户端回复消息已收到
ReceiveMsg = 9;
ReceiveMsgReply = 10;
ProtoReady = 11;
ProtoFinish = 12;
Raw = 13;
SyncMsgReq = 14;
SyncMsgReply = 15;
RePush = 16;
}
// Proto 中 Op 为 OpAuth 时, body 必须可以反序列化为 AuthMsg
message AuthMsg {
string appId = 1;
string token = 2;
bytes ext = 3; // 其它业务方可能需要的信息
}
// Proto 中 Op 为 SyncMsgReply 时, body 必须可以反序列化为 SyncMsg
message SyncMsg {
int64 logId = 1;
}
message Empty{}
message PushMsgReq {
repeated string keys = 1;
int32 protoOp = 2;
Proto proto = 3;
}
message PushMsgReply {
map<string,int32> index = 1;
}
message BroadcastReq{
int32 protoOp = 1;
Proto proto = 2;
}
message BroadcastReply{}
message BroadcastGroupReq {
string groupID = 1;
Proto proto = 2;
}
message BroadcastGroupReply{}
message JoinGroupsReq {
repeated string keys = 1;
repeated string gid = 2;
}
message JoinGroupsReply{}
message LeaveGroupsReq {
repeated string keys = 1;
repeated string gid = 2;
}
message LeaveGroupsReply{}
message DelGroupsReq {
repeated string gid = 1;
}
message DelGroupsReply{}
service Comet {
rpc PushMsg(PushMsgReq) returns (PushMsgReply);
rpc Broadcast(BroadcastReq) returns (BroadcastReply);
rpc BroadcastGroup(BroadcastGroupReq) returns (BroadcastGroupReply);
rpc JoinGroups(JoinGroupsReq) returns (JoinGroupsReply);
rpc LeaveGroups(LeaveGroupsReq) returns (LeaveGroupsReply);
rpc DelGroups(DelGroupsReq) returns (DelGroupsReply);
}

View File

@@ -0,0 +1,281 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package grpc
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
// CometClient is the client API for Comet 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 CometClient interface {
PushMsg(ctx context.Context, in *PushMsgReq, opts ...grpc.CallOption) (*PushMsgReply, error)
Broadcast(ctx context.Context, in *BroadcastReq, opts ...grpc.CallOption) (*BroadcastReply, error)
BroadcastGroup(ctx context.Context, in *BroadcastGroupReq, opts ...grpc.CallOption) (*BroadcastGroupReply, error)
JoinGroups(ctx context.Context, in *JoinGroupsReq, opts ...grpc.CallOption) (*JoinGroupsReply, error)
LeaveGroups(ctx context.Context, in *LeaveGroupsReq, opts ...grpc.CallOption) (*LeaveGroupsReply, error)
DelGroups(ctx context.Context, in *DelGroupsReq, opts ...grpc.CallOption) (*DelGroupsReply, error)
}
type cometClient struct {
cc grpc.ClientConnInterface
}
func NewCometClient(cc grpc.ClientConnInterface) CometClient {
return &cometClient{cc}
}
func (c *cometClient) PushMsg(ctx context.Context, in *PushMsgReq, opts ...grpc.CallOption) (*PushMsgReply, error) {
out := new(PushMsgReply)
err := c.cc.Invoke(ctx, "/im.comet.Comet/PushMsg", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *cometClient) Broadcast(ctx context.Context, in *BroadcastReq, opts ...grpc.CallOption) (*BroadcastReply, error) {
out := new(BroadcastReply)
err := c.cc.Invoke(ctx, "/im.comet.Comet/Broadcast", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *cometClient) BroadcastGroup(ctx context.Context, in *BroadcastGroupReq, opts ...grpc.CallOption) (*BroadcastGroupReply, error) {
out := new(BroadcastGroupReply)
err := c.cc.Invoke(ctx, "/im.comet.Comet/BroadcastGroup", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *cometClient) JoinGroups(ctx context.Context, in *JoinGroupsReq, opts ...grpc.CallOption) (*JoinGroupsReply, error) {
out := new(JoinGroupsReply)
err := c.cc.Invoke(ctx, "/im.comet.Comet/JoinGroups", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *cometClient) LeaveGroups(ctx context.Context, in *LeaveGroupsReq, opts ...grpc.CallOption) (*LeaveGroupsReply, error) {
out := new(LeaveGroupsReply)
err := c.cc.Invoke(ctx, "/im.comet.Comet/LeaveGroups", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *cometClient) DelGroups(ctx context.Context, in *DelGroupsReq, opts ...grpc.CallOption) (*DelGroupsReply, error) {
out := new(DelGroupsReply)
err := c.cc.Invoke(ctx, "/im.comet.Comet/DelGroups", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// CometServer is the server API for Comet service.
// All implementations must embed UnimplementedCometServer
// for forward compatibility
type CometServer interface {
PushMsg(context.Context, *PushMsgReq) (*PushMsgReply, error)
Broadcast(context.Context, *BroadcastReq) (*BroadcastReply, error)
BroadcastGroup(context.Context, *BroadcastGroupReq) (*BroadcastGroupReply, error)
JoinGroups(context.Context, *JoinGroupsReq) (*JoinGroupsReply, error)
LeaveGroups(context.Context, *LeaveGroupsReq) (*LeaveGroupsReply, error)
DelGroups(context.Context, *DelGroupsReq) (*DelGroupsReply, error)
mustEmbedUnimplementedCometServer()
}
// UnimplementedCometServer must be embedded to have forward compatible implementations.
type UnimplementedCometServer struct {
}
func (UnimplementedCometServer) PushMsg(context.Context, *PushMsgReq) (*PushMsgReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method PushMsg not implemented")
}
func (UnimplementedCometServer) Broadcast(context.Context, *BroadcastReq) (*BroadcastReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method Broadcast not implemented")
}
func (UnimplementedCometServer) BroadcastGroup(context.Context, *BroadcastGroupReq) (*BroadcastGroupReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method BroadcastGroup not implemented")
}
func (UnimplementedCometServer) JoinGroups(context.Context, *JoinGroupsReq) (*JoinGroupsReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method JoinGroups not implemented")
}
func (UnimplementedCometServer) LeaveGroups(context.Context, *LeaveGroupsReq) (*LeaveGroupsReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method LeaveGroups not implemented")
}
func (UnimplementedCometServer) DelGroups(context.Context, *DelGroupsReq) (*DelGroupsReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method DelGroups not implemented")
}
func (UnimplementedCometServer) mustEmbedUnimplementedCometServer() {}
// UnsafeCometServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to CometServer will
// result in compilation errors.
type UnsafeCometServer interface {
mustEmbedUnimplementedCometServer()
}
func RegisterCometServer(s grpc.ServiceRegistrar, srv CometServer) {
s.RegisterService(&Comet_ServiceDesc, srv)
}
func _Comet_PushMsg_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PushMsgReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CometServer).PushMsg(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.comet.Comet/PushMsg",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CometServer).PushMsg(ctx, req.(*PushMsgReq))
}
return interceptor(ctx, in, info, handler)
}
func _Comet_Broadcast_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BroadcastReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CometServer).Broadcast(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.comet.Comet/Broadcast",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CometServer).Broadcast(ctx, req.(*BroadcastReq))
}
return interceptor(ctx, in, info, handler)
}
func _Comet_BroadcastGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BroadcastGroupReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CometServer).BroadcastGroup(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.comet.Comet/BroadcastGroup",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CometServer).BroadcastGroup(ctx, req.(*BroadcastGroupReq))
}
return interceptor(ctx, in, info, handler)
}
func _Comet_JoinGroups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(JoinGroupsReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CometServer).JoinGroups(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.comet.Comet/JoinGroups",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CometServer).JoinGroups(ctx, req.(*JoinGroupsReq))
}
return interceptor(ctx, in, info, handler)
}
func _Comet_LeaveGroups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LeaveGroupsReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CometServer).LeaveGroups(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.comet.Comet/LeaveGroups",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CometServer).LeaveGroups(ctx, req.(*LeaveGroupsReq))
}
return interceptor(ctx, in, info, handler)
}
func _Comet_DelGroups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DelGroupsReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CometServer).DelGroups(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.comet.Comet/DelGroups",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CometServer).DelGroups(ctx, req.(*DelGroupsReq))
}
return interceptor(ctx, in, info, handler)
}
// Comet_ServiceDesc is the grpc.ServiceDesc for Comet service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Comet_ServiceDesc = grpc.ServiceDesc{
ServiceName: "im.comet.Comet",
HandlerType: (*CometServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "PushMsg",
Handler: _Comet_PushMsg_Handler,
},
{
MethodName: "Broadcast",
Handler: _Comet_Broadcast_Handler,
},
{
MethodName: "BroadcastGroup",
Handler: _Comet_BroadcastGroup_Handler,
},
{
MethodName: "JoinGroups",
Handler: _Comet_JoinGroups_Handler,
},
{
MethodName: "LeaveGroups",
Handler: _Comet_LeaveGroups_Handler,
},
{
MethodName: "DelGroups",
Handler: _Comet_DelGroups_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "comet.proto",
}

6
api/comet/grpc/create.sh Normal file
View File

@@ -0,0 +1,6 @@
#!/bin/sh
protoc -I. -I$GOPATH/src \
--go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
*.proto

304
api/comet/grpc/protocol.go Normal file
View File

@@ -0,0 +1,304 @@
package grpc
import (
"errors"
"github.com/Terry-Mao/goim/pkg/bufio"
"github.com/Terry-Mao/goim/pkg/bytes"
"github.com/Terry-Mao/goim/pkg/encoding/binary"
"github.com/Terry-Mao/goim/pkg/websocket"
gws "github.com/gorilla/websocket"
)
const (
// MaxBodySize max proto body size
MaxBodySize = int32(1 << 14)
)
const (
// size
_packSize = 4
_headerSize = 2
_verSize = 2
_opSize = 4
_seqSize = 4
_ackSize = 4
_heartSize = 4
_rawHeaderSize = _packSize + _headerSize + _verSize + _opSize + _seqSize + _ackSize
_maxPackSize = MaxBodySize + int32(_rawHeaderSize)
// offset
_packOffset = 0
_headerOffset = _packOffset + _packSize
_verOffset = _headerOffset + _headerSize
_opOffset = _verOffset + _verSize
_seqOffset = _opOffset + _opSize
_ackOffset = _seqOffset + _seqSize
_heartOffset = _ackOffset + _ackSize
)
var (
// ErrProtoPackLen proto packet len error
ErrProtoPackLen = errors.New("default server codec pack length error")
// ErrProtoHeaderLen proto header len error
ErrProtoHeaderLen = errors.New("default server codec header length error")
)
var (
// ProtoReady proto ready
ProtoReady = &Proto{Op: int32(Op_ProtoReady)}
// ProtoFinish proto finish
ProtoFinish = &Proto{Op: int32(Op_ProtoFinish)}
)
// WriteTo write a proto to bytes writer.
func (p *Proto) WriteTo(b *bytes.Writer) {
var (
packLen = _rawHeaderSize + int32(len(p.Body))
buf = b.Peek(_rawHeaderSize)
)
binary.BigEndian.PutInt32(buf[_packOffset:], packLen)
binary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))
binary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[_opOffset:], p.Op)
binary.BigEndian.PutInt32(buf[_seqOffset:], p.Seq)
binary.BigEndian.PutInt32(buf[_ackOffset:], p.Ack)
if p.Body != nil {
b.Write(p.Body)
}
}
// ReadTCP read a proto from TCP reader.
func (p *Proto) ReadTCP(rr *bufio.Reader) (err error) {
var (
bodyLen int
headerLen int16
packLen int32
buf []byte
)
if buf, err = rr.Pop(_rawHeaderSize); err != nil {
return
}
packLen = binary.BigEndian.Int32(buf[_packOffset:_headerOffset])
headerLen = binary.BigEndian.Int16(buf[_headerOffset:_verOffset])
p.Ver = int32(binary.BigEndian.Int16(buf[_verOffset:_opOffset]))
p.Op = binary.BigEndian.Int32(buf[_opOffset:_seqOffset])
p.Seq = binary.BigEndian.Int32(buf[_seqOffset:_ackOffset])
p.Ack = binary.BigEndian.Int32(buf[_ackOffset:])
if packLen > _maxPackSize {
return ErrProtoPackLen
}
if headerLen != _rawHeaderSize {
return ErrProtoHeaderLen
}
if bodyLen = int(packLen - int32(headerLen)); bodyLen > 0 {
p.Body, err = rr.Pop(bodyLen)
} else {
p.Body = nil
}
return
}
// WriteTCP write a proto to TCP writer.
func (p *Proto) WriteTCP(wr *bufio.Writer) (err error) {
var (
buf []byte
packLen int32
)
if p.Op == int32(Op_Raw) {
// write without buffer, job concact proto into raw buffer
_, err = wr.WriteRaw(p.Body)
return
}
packLen = _rawHeaderSize + int32(len(p.Body))
if buf, err = wr.Peek(_rawHeaderSize); err != nil {
return
}
binary.BigEndian.PutInt32(buf[_packOffset:], packLen)
binary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))
binary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[_opOffset:], p.Op)
binary.BigEndian.PutInt32(buf[_seqOffset:], p.Seq)
binary.BigEndian.PutInt32(buf[_ackOffset:], p.Ack)
if p.Body != nil {
_, err = wr.Write(p.Body)
}
return
}
// WriteTCPHeart write TCP heartbeat with room online.
func (p *Proto) WriteTCPHeart(wr *bufio.Writer, online int32) (err error) {
var (
buf []byte
packLen int
)
packLen = _rawHeaderSize + _heartSize
if buf, err = wr.Peek(packLen); err != nil {
return
}
// header
binary.BigEndian.PutInt32(buf[_packOffset:], int32(packLen))
binary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))
binary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[_opOffset:], p.Op)
binary.BigEndian.PutInt32(buf[_seqOffset:], p.Seq)
binary.BigEndian.PutInt32(buf[_ackOffset:], p.Ack)
// body
binary.BigEndian.PutInt32(buf[_heartOffset:], online)
return
}
// ReadWebsocket read a proto from websocket connection.
func (p *Proto) ReadWebsocket(ws *websocket.Conn) (err error) {
var (
bodyLen int
headerLen int16
packLen int32
buf []byte
)
if _, buf, err = ws.ReadMessage(); err != nil {
return
}
if len(buf) < _rawHeaderSize {
return ErrProtoPackLen
}
packLen = binary.BigEndian.Int32(buf[_packOffset:_headerOffset])
headerLen = binary.BigEndian.Int16(buf[_headerOffset:_verOffset])
p.Ver = int32(binary.BigEndian.Int16(buf[_verOffset:_opOffset]))
p.Op = binary.BigEndian.Int32(buf[_opOffset:_seqOffset])
p.Seq = binary.BigEndian.Int32(buf[_seqOffset:_ackOffset])
p.Ack = binary.BigEndian.Int32(buf[_ackOffset:])
if packLen < 0 || packLen > _maxPackSize {
return ErrProtoPackLen
}
if headerLen != _rawHeaderSize {
return ErrProtoHeaderLen
}
if bodyLen = int(packLen - int32(headerLen)); bodyLen > 0 {
p.Body = buf[headerLen:packLen]
} else {
p.Body = nil
}
return
}
// WriteWebsocket write a proto to websocket connection.
func (p *Proto) WriteWebsocket(ws *websocket.Conn) (err error) {
var (
buf []byte
packLen int
)
packLen = _rawHeaderSize + len(p.Body)
if err = ws.WriteHeader(websocket.BinaryMessage, packLen); err != nil {
return
}
if buf, err = ws.Peek(_rawHeaderSize); err != nil {
return
}
binary.BigEndian.PutInt32(buf[_packOffset:], int32(packLen))
binary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))
binary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[_opOffset:], p.Op)
binary.BigEndian.PutInt32(buf[_seqOffset:], p.Seq)
binary.BigEndian.PutInt32(buf[_ackOffset:], p.Ack)
if p.Body != nil {
err = ws.WriteBody(p.Body)
}
return
}
// WriteWebsocketHeart write websocket heartbeat with room online.
func (p *Proto) WriteWebsocketHeart(wr *websocket.Conn, online int32) (err error) {
var (
buf []byte
packLen int
)
packLen = _rawHeaderSize + _heartSize
// websocket header
if err = wr.WriteHeader(websocket.BinaryMessage, packLen); err != nil {
return
}
if buf, err = wr.Peek(packLen); err != nil {
return
}
// proto header
binary.BigEndian.PutInt32(buf[_packOffset:], int32(packLen))
binary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))
binary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[_opOffset:], p.Op)
binary.BigEndian.PutInt32(buf[_seqOffset:], p.Seq)
binary.BigEndian.PutInt32(buf[_ackOffset:], p.Ack)
// proto body
binary.BigEndian.PutInt32(buf[_heartOffset:], online)
return
}
// WriteWebsocket write a proto to websocket connection.
func (p *Proto) WriteWebsocket2(ws *gws.Conn) (err error) {
var (
buf []byte
packLen int32
)
wc, err := ws.NextWriter(websocket.BinaryMessage)
if err != nil {
return err
}
wr := bufio.NewWriter(wc)
packLen = _rawHeaderSize + int32(len(p.Body))
if buf, err = wr.Peek(_rawHeaderSize); err != nil {
return
}
binary.BigEndian.PutInt32(buf[_packOffset:], packLen)
binary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))
binary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[_opOffset:], p.Op)
binary.BigEndian.PutInt32(buf[_seqOffset:], p.Seq)
binary.BigEndian.PutInt32(buf[_ackOffset:], p.Ack)
if p.Body != nil {
_, err = wr.Write(p.Body)
}
if err != nil {
return
}
err = wr.Flush()
if err != nil {
return
}
return wc.Close()
}
// ReadWebsocket read a proto from websocket connection.
func (p *Proto) ReadWebsocket2(ws *gws.Conn) (err error) {
var (
bodyLen int
headerLen int16
packLen int32
buf []byte
)
if _, buf, err = ws.ReadMessage(); err != nil {
return
}
if len(buf) < _rawHeaderSize {
return ErrProtoPackLen
}
packLen = binary.BigEndian.Int32(buf[_packOffset:_headerOffset])
headerLen = binary.BigEndian.Int16(buf[_headerOffset:_verOffset])
p.Ver = int32(binary.BigEndian.Int16(buf[_verOffset:_opOffset]))
p.Op = binary.BigEndian.Int32(buf[_opOffset:_seqOffset])
p.Seq = binary.BigEndian.Int32(buf[_seqOffset:_ackOffset])
p.Ack = binary.BigEndian.Int32(buf[_ackOffset:])
if packLen > _maxPackSize {
return ErrProtoPackLen
}
if headerLen != _rawHeaderSize {
return ErrProtoHeaderLen
}
if bodyLen = int(packLen - int32(headerLen)); bodyLen > 0 {
p.Body = buf[headerLen:packLen]
} else {
p.Body = nil
}
return
}

6
api/logic/grpc/create.sh Normal file
View File

@@ -0,0 +1,6 @@
#!/bin/sh
protoc -I. -I$GOPATH/src \
--go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
*.proto

1312
api/logic/grpc/logic.pb.go Normal file

File diff suppressed because it is too large Load Diff

117
api/logic/grpc/logic.proto Normal file
View File

@@ -0,0 +1,117 @@
// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto
syntax = "proto3";
package im.logic;
option go_package = "gitlab.33.cn/chat/im/api/logic/grpc";
import "gitlab.33.cn/chat/im/api/comet/grpc/comet.proto";
enum Type {
PUSH = 0;
ROOM = 1;
BROADCAST = 2;
}
message ConnectReq {
string server = 1; // 客户端连接的是哪个 comet
im.comet.Proto proto = 3;
}
message ConnectReply {
string key = 1;
string appId = 2;
string mid = 3;
int64 heartbeat = 4;
}
message DisconnectReq {
string key = 1;
string server = 2;
}
message HeartbeatReq {
string key = 1;
string server = 2;
}
message ReceiveReq {
string key = 1;
im.comet.Proto proto = 3;
}
message Reply {
bool isOk = 1;
bytes msg = 2;
}
// logic --> mq
message BizMsg {
string appId = 1;
string fromId = 2;
Type type = 4;
int32 op = 5;
string key = 6;
bytes msg = 7;
}
// biz --> logic
message MidsMsg {
string appId = 1;
repeated string toIds = 2;
Type type = 3;
int32 op = 4;
bytes msg = 5;
}
// biz --> logic
message KeysMsg {
string appId = 1;
repeated string toKeys = 2;
Type type = 3;
int32 op = 4;
bytes msg = 5;
}
// biz --> logic
message GroupMsg {
string appId = 1;
string group = 2;
Type type = 3;
int32 op = 4;
bytes msg = 5;
}
// biz --> logic
message GroupsKey {
string appId = 1;
repeated string keys = 2;
repeated string gid = 3;
}
// biz --> logic
message GroupsMid {
string appId = 1;
repeated string mids = 2;
repeated string gid = 3;
}
// biz --> logic
message DelGroupsReq {
string appId = 1;
repeated string gid = 2;
}
service Logic {
rpc Connect(ConnectReq) returns (ConnectReply); //comet
rpc Disconnect(DisconnectReq) returns (Reply); //comet
rpc Heartbeat(HeartbeatReq) returns (Reply); //comet
rpc Receive(ReceiveReq) returns (Reply); //comet
rpc PushByMids(MidsMsg) returns (Reply); //biz
rpc PushByKeys(KeysMsg) returns (Reply); //biz
rpc PushGroup(GroupMsg) returns (Reply); //biz
rpc JoinGroupsByKeys(GroupsKey) returns (Reply); //biz
rpc JoinGroupsByMids(GroupsMid) returns (Reply); //biz
rpc LeaveGroupsByKeys(GroupsKey) returns (Reply); //biz
rpc LeaveGroupsByMids(GroupsMid) returns (Reply); //biz
rpc DelGroups(DelGroupsReq) returns (Reply); //biz
}

View File

@@ -0,0 +1,497 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package grpc
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
// LogicClient is the client API for Logic 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 LogicClient interface {
Connect(ctx context.Context, in *ConnectReq, opts ...grpc.CallOption) (*ConnectReply, error)
Disconnect(ctx context.Context, in *DisconnectReq, opts ...grpc.CallOption) (*Reply, error)
Heartbeat(ctx context.Context, in *HeartbeatReq, opts ...grpc.CallOption) (*Reply, error)
Receive(ctx context.Context, in *ReceiveReq, opts ...grpc.CallOption) (*Reply, error)
PushByMids(ctx context.Context, in *MidsMsg, opts ...grpc.CallOption) (*Reply, error)
PushByKeys(ctx context.Context, in *KeysMsg, opts ...grpc.CallOption) (*Reply, error)
PushGroup(ctx context.Context, in *GroupMsg, opts ...grpc.CallOption) (*Reply, error)
JoinGroupsByKeys(ctx context.Context, in *GroupsKey, opts ...grpc.CallOption) (*Reply, error)
JoinGroupsByMids(ctx context.Context, in *GroupsMid, opts ...grpc.CallOption) (*Reply, error)
LeaveGroupsByKeys(ctx context.Context, in *GroupsKey, opts ...grpc.CallOption) (*Reply, error)
LeaveGroupsByMids(ctx context.Context, in *GroupsMid, opts ...grpc.CallOption) (*Reply, error)
DelGroups(ctx context.Context, in *DelGroupsReq, opts ...grpc.CallOption) (*Reply, error)
}
type logicClient struct {
cc grpc.ClientConnInterface
}
func NewLogicClient(cc grpc.ClientConnInterface) LogicClient {
return &logicClient{cc}
}
func (c *logicClient) Connect(ctx context.Context, in *ConnectReq, opts ...grpc.CallOption) (*ConnectReply, error) {
out := new(ConnectReply)
err := c.cc.Invoke(ctx, "/im.logic.Logic/Connect", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *logicClient) Disconnect(ctx context.Context, in *DisconnectReq, opts ...grpc.CallOption) (*Reply, error) {
out := new(Reply)
err := c.cc.Invoke(ctx, "/im.logic.Logic/Disconnect", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *logicClient) Heartbeat(ctx context.Context, in *HeartbeatReq, opts ...grpc.CallOption) (*Reply, error) {
out := new(Reply)
err := c.cc.Invoke(ctx, "/im.logic.Logic/Heartbeat", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *logicClient) Receive(ctx context.Context, in *ReceiveReq, opts ...grpc.CallOption) (*Reply, error) {
out := new(Reply)
err := c.cc.Invoke(ctx, "/im.logic.Logic/Receive", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *logicClient) PushByMids(ctx context.Context, in *MidsMsg, opts ...grpc.CallOption) (*Reply, error) {
out := new(Reply)
err := c.cc.Invoke(ctx, "/im.logic.Logic/PushByMids", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *logicClient) PushByKeys(ctx context.Context, in *KeysMsg, opts ...grpc.CallOption) (*Reply, error) {
out := new(Reply)
err := c.cc.Invoke(ctx, "/im.logic.Logic/PushByKeys", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *logicClient) PushGroup(ctx context.Context, in *GroupMsg, opts ...grpc.CallOption) (*Reply, error) {
out := new(Reply)
err := c.cc.Invoke(ctx, "/im.logic.Logic/PushGroup", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *logicClient) JoinGroupsByKeys(ctx context.Context, in *GroupsKey, opts ...grpc.CallOption) (*Reply, error) {
out := new(Reply)
err := c.cc.Invoke(ctx, "/im.logic.Logic/JoinGroupsByKeys", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *logicClient) JoinGroupsByMids(ctx context.Context, in *GroupsMid, opts ...grpc.CallOption) (*Reply, error) {
out := new(Reply)
err := c.cc.Invoke(ctx, "/im.logic.Logic/JoinGroupsByMids", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *logicClient) LeaveGroupsByKeys(ctx context.Context, in *GroupsKey, opts ...grpc.CallOption) (*Reply, error) {
out := new(Reply)
err := c.cc.Invoke(ctx, "/im.logic.Logic/LeaveGroupsByKeys", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *logicClient) LeaveGroupsByMids(ctx context.Context, in *GroupsMid, opts ...grpc.CallOption) (*Reply, error) {
out := new(Reply)
err := c.cc.Invoke(ctx, "/im.logic.Logic/LeaveGroupsByMids", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *logicClient) DelGroups(ctx context.Context, in *DelGroupsReq, opts ...grpc.CallOption) (*Reply, error) {
out := new(Reply)
err := c.cc.Invoke(ctx, "/im.logic.Logic/DelGroups", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// LogicServer is the server API for Logic service.
// All implementations must embed UnimplementedLogicServer
// for forward compatibility
type LogicServer interface {
Connect(context.Context, *ConnectReq) (*ConnectReply, error)
Disconnect(context.Context, *DisconnectReq) (*Reply, error)
Heartbeat(context.Context, *HeartbeatReq) (*Reply, error)
Receive(context.Context, *ReceiveReq) (*Reply, error)
PushByMids(context.Context, *MidsMsg) (*Reply, error)
PushByKeys(context.Context, *KeysMsg) (*Reply, error)
PushGroup(context.Context, *GroupMsg) (*Reply, error)
JoinGroupsByKeys(context.Context, *GroupsKey) (*Reply, error)
JoinGroupsByMids(context.Context, *GroupsMid) (*Reply, error)
LeaveGroupsByKeys(context.Context, *GroupsKey) (*Reply, error)
LeaveGroupsByMids(context.Context, *GroupsMid) (*Reply, error)
DelGroups(context.Context, *DelGroupsReq) (*Reply, error)
mustEmbedUnimplementedLogicServer()
}
// UnimplementedLogicServer must be embedded to have forward compatible implementations.
type UnimplementedLogicServer struct {
}
func (UnimplementedLogicServer) Connect(context.Context, *ConnectReq) (*ConnectReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method Connect not implemented")
}
func (UnimplementedLogicServer) Disconnect(context.Context, *DisconnectReq) (*Reply, error) {
return nil, status.Errorf(codes.Unimplemented, "method Disconnect not implemented")
}
func (UnimplementedLogicServer) Heartbeat(context.Context, *HeartbeatReq) (*Reply, error) {
return nil, status.Errorf(codes.Unimplemented, "method Heartbeat not implemented")
}
func (UnimplementedLogicServer) Receive(context.Context, *ReceiveReq) (*Reply, error) {
return nil, status.Errorf(codes.Unimplemented, "method Receive not implemented")
}
func (UnimplementedLogicServer) PushByMids(context.Context, *MidsMsg) (*Reply, error) {
return nil, status.Errorf(codes.Unimplemented, "method PushByMids not implemented")
}
func (UnimplementedLogicServer) PushByKeys(context.Context, *KeysMsg) (*Reply, error) {
return nil, status.Errorf(codes.Unimplemented, "method PushByKeys not implemented")
}
func (UnimplementedLogicServer) PushGroup(context.Context, *GroupMsg) (*Reply, error) {
return nil, status.Errorf(codes.Unimplemented, "method PushGroup not implemented")
}
func (UnimplementedLogicServer) JoinGroupsByKeys(context.Context, *GroupsKey) (*Reply, error) {
return nil, status.Errorf(codes.Unimplemented, "method JoinGroupsByKeys not implemented")
}
func (UnimplementedLogicServer) JoinGroupsByMids(context.Context, *GroupsMid) (*Reply, error) {
return nil, status.Errorf(codes.Unimplemented, "method JoinGroupsByMids not implemented")
}
func (UnimplementedLogicServer) LeaveGroupsByKeys(context.Context, *GroupsKey) (*Reply, error) {
return nil, status.Errorf(codes.Unimplemented, "method LeaveGroupsByKeys not implemented")
}
func (UnimplementedLogicServer) LeaveGroupsByMids(context.Context, *GroupsMid) (*Reply, error) {
return nil, status.Errorf(codes.Unimplemented, "method LeaveGroupsByMids not implemented")
}
func (UnimplementedLogicServer) DelGroups(context.Context, *DelGroupsReq) (*Reply, error) {
return nil, status.Errorf(codes.Unimplemented, "method DelGroups not implemented")
}
func (UnimplementedLogicServer) mustEmbedUnimplementedLogicServer() {}
// UnsafeLogicServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to LogicServer will
// result in compilation errors.
type UnsafeLogicServer interface {
mustEmbedUnimplementedLogicServer()
}
func RegisterLogicServer(s grpc.ServiceRegistrar, srv LogicServer) {
s.RegisterService(&Logic_ServiceDesc, srv)
}
func _Logic_Connect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ConnectReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LogicServer).Connect(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.logic.Logic/Connect",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LogicServer).Connect(ctx, req.(*ConnectReq))
}
return interceptor(ctx, in, info, handler)
}
func _Logic_Disconnect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DisconnectReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LogicServer).Disconnect(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.logic.Logic/Disconnect",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LogicServer).Disconnect(ctx, req.(*DisconnectReq))
}
return interceptor(ctx, in, info, handler)
}
func _Logic_Heartbeat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HeartbeatReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LogicServer).Heartbeat(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.logic.Logic/Heartbeat",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LogicServer).Heartbeat(ctx, req.(*HeartbeatReq))
}
return interceptor(ctx, in, info, handler)
}
func _Logic_Receive_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ReceiveReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LogicServer).Receive(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.logic.Logic/Receive",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LogicServer).Receive(ctx, req.(*ReceiveReq))
}
return interceptor(ctx, in, info, handler)
}
func _Logic_PushByMids_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MidsMsg)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LogicServer).PushByMids(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.logic.Logic/PushByMids",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LogicServer).PushByMids(ctx, req.(*MidsMsg))
}
return interceptor(ctx, in, info, handler)
}
func _Logic_PushByKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(KeysMsg)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LogicServer).PushByKeys(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.logic.Logic/PushByKeys",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LogicServer).PushByKeys(ctx, req.(*KeysMsg))
}
return interceptor(ctx, in, info, handler)
}
func _Logic_PushGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GroupMsg)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LogicServer).PushGroup(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.logic.Logic/PushGroup",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LogicServer).PushGroup(ctx, req.(*GroupMsg))
}
return interceptor(ctx, in, info, handler)
}
func _Logic_JoinGroupsByKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GroupsKey)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LogicServer).JoinGroupsByKeys(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.logic.Logic/JoinGroupsByKeys",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LogicServer).JoinGroupsByKeys(ctx, req.(*GroupsKey))
}
return interceptor(ctx, in, info, handler)
}
func _Logic_JoinGroupsByMids_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GroupsMid)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LogicServer).JoinGroupsByMids(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.logic.Logic/JoinGroupsByMids",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LogicServer).JoinGroupsByMids(ctx, req.(*GroupsMid))
}
return interceptor(ctx, in, info, handler)
}
func _Logic_LeaveGroupsByKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GroupsKey)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LogicServer).LeaveGroupsByKeys(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.logic.Logic/LeaveGroupsByKeys",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LogicServer).LeaveGroupsByKeys(ctx, req.(*GroupsKey))
}
return interceptor(ctx, in, info, handler)
}
func _Logic_LeaveGroupsByMids_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GroupsMid)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LogicServer).LeaveGroupsByMids(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.logic.Logic/LeaveGroupsByMids",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LogicServer).LeaveGroupsByMids(ctx, req.(*GroupsMid))
}
return interceptor(ctx, in, info, handler)
}
func _Logic_DelGroups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DelGroupsReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LogicServer).DelGroups(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/im.logic.Logic/DelGroups",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LogicServer).DelGroups(ctx, req.(*DelGroupsReq))
}
return interceptor(ctx, in, info, handler)
}
// Logic_ServiceDesc is the grpc.ServiceDesc for Logic service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Logic_ServiceDesc = grpc.ServiceDesc{
ServiceName: "im.logic.Logic",
HandlerType: (*LogicServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Connect",
Handler: _Logic_Connect_Handler,
},
{
MethodName: "Disconnect",
Handler: _Logic_Disconnect_Handler,
},
{
MethodName: "Heartbeat",
Handler: _Logic_Heartbeat_Handler,
},
{
MethodName: "Receive",
Handler: _Logic_Receive_Handler,
},
{
MethodName: "PushByMids",
Handler: _Logic_PushByMids_Handler,
},
{
MethodName: "PushByKeys",
Handler: _Logic_PushByKeys_Handler,
},
{
MethodName: "PushGroup",
Handler: _Logic_PushGroup_Handler,
},
{
MethodName: "JoinGroupsByKeys",
Handler: _Logic_JoinGroupsByKeys_Handler,
},
{
MethodName: "JoinGroupsByMids",
Handler: _Logic_JoinGroupsByMids_Handler,
},
{
MethodName: "LeaveGroupsByKeys",
Handler: _Logic_LeaveGroupsByKeys_Handler,
},
{
MethodName: "LeaveGroupsByMids",
Handler: _Logic_LeaveGroupsByMids_Handler,
},
{
MethodName: "DelGroups",
Handler: _Logic_DelGroups_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "logic.proto",
}

View File

@@ -0,0 +1,234 @@
package main
// Start Commond eg: ./client 1 1000 localhost:3101
// first parameterbeginning userId
// second parameter: amount of clients
// third parameter: comet tcp-server addr
import (
"bufio"
"encoding/binary"
"flag"
"fmt"
"log"
"math/rand"
"net"
"os"
"runtime"
"strconv"
"sync/atomic"
"time"
"github.com/golang/protobuf/proto"
comet "gitlab.33.cn/chat/im/api/comet/grpc"
)
const (
rawHeaderLen = uint16(16)
heart = 5 * time.Second
)
type Proto struct {
PackLen int32 // package length
HeaderLen int16 // header length
Ver int16 // protocol version
Operation int32 // operation for request
Seq int32 // sequence number chosen by client
Body []byte // body
}
var (
countDown int64
aliveCount int64
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
flag.Parse()
begin, err := strconv.Atoi(os.Args[1])
if err != nil {
panic(err)
}
num, err := strconv.Atoi(os.Args[2])
if err != nil {
panic(err)
}
go result()
for i := begin; i < begin+num; i++ {
go client(int64(i))
}
// signal
var exit chan bool
<-exit
}
func result() {
var (
lastTimes int64
interval = int64(5)
)
for {
nowCount := atomic.LoadInt64(&countDown)
nowAlive := atomic.LoadInt64(&aliveCount)
diff := nowCount - lastTimes
lastTimes = nowCount
fmt.Println(fmt.Sprintf("%s alive:%d down:%d down/s:%d", time.Now().Format("2006-01-02 15:04:05"), nowAlive, nowCount, diff/interval))
time.Sleep(time.Second * time.Duration(interval))
}
}
func client(mid int64) {
for {
startClient(mid)
time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
}
}
func startClient(key int64) {
time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
atomic.AddInt64(&aliveCount, 1)
quit := make(chan bool, 1)
defer func() {
close(quit)
atomic.AddInt64(&aliveCount, -1)
}()
// connnect to server
conn, err := net.Dial("tcp", os.Args[3])
if err != nil {
log.Printf("net.Dial(%s) error(%v)", os.Args[3], err)
return
}
seq := int32(0)
wr := bufio.NewWriter(conn)
rd := bufio.NewReader(conn)
authMsg := &comet.AuthMsg{
AppId: "echo",
Token: "fdasfdsaf",
}
p := new(Proto)
p.Ver = 1
p.Operation = int32(comet.Op_Auth)
p.Seq = seq
p.Body, _ = proto.Marshal(authMsg)
if err = tcpWriteProto(wr, p); err != nil {
log.Printf("tcpWriteProto() error(%v)", err)
return
}
if err = tcpReadProto(rd, p); err != nil {
log.Printf("tcpReadProto() error(%v)", err)
return
}
log.Printf("key:%d auth ok, proto: %v", key, p)
seq++
// writer
go func() {
hbProto := new(Proto)
for {
// heartbeat
hbProto.Operation = int32(comet.Op_Heartbeat)
hbProto.Seq = seq
hbProto.Body = nil
if err = tcpWriteProto(wr, hbProto); err != nil {
log.Printf("key:%d tcpWriteProto() error(%v)", key, err)
return
}
log.Printf("key:%d Write heartbeat", key)
time.Sleep(heart)
seq++
select {
case <-quit:
return
default:
}
}
}()
// reader
for {
if err = tcpReadProto(rd, p); err != nil {
log.Printf("key:%d tcpReadProto() error(%v)", key, err)
quit <- true
return
}
if p.Operation == int32(comet.Op_AuthReply) {
log.Printf("key:%d auth success", key)
} else if p.Operation == int32(comet.Op_HeartbeatReply) {
log.Printf("key:%d receive heartbeat reply", key)
if err = conn.SetReadDeadline(time.Now().Add(heart + 60*time.Second)); err != nil {
log.Printf("conn.SetReadDeadline() error(%v)", err)
quit <- true
return
}
} else {
log.Printf("key:%d op:%d msg: %s", key, p.Operation, string(p.Body))
atomic.AddInt64(&countDown, 1)
}
}
}
func tcpWriteProto(wr *bufio.Writer, proto *Proto) (err error) {
// write
if err = binary.Write(wr, binary.BigEndian, uint32(rawHeaderLen)+uint32(len(proto.Body))); err != nil {
return
}
if err = binary.Write(wr, binary.BigEndian, rawHeaderLen); err != nil {
return
}
if err = binary.Write(wr, binary.BigEndian, proto.Ver); err != nil {
return
}
if err = binary.Write(wr, binary.BigEndian, proto.Operation); err != nil {
return
}
if err = binary.Write(wr, binary.BigEndian, proto.Seq); err != nil {
return
}
if proto.Body != nil {
if err = binary.Write(wr, binary.BigEndian, proto.Body); err != nil {
return
}
}
err = wr.Flush()
return
}
func tcpReadProto(rd *bufio.Reader, proto *Proto) (err error) {
var (
packLen int32
headerLen int16
)
// read
if err = binary.Read(rd, binary.BigEndian, &packLen); err != nil {
return
}
if err = binary.Read(rd, binary.BigEndian, &headerLen); err != nil {
return
}
if err = binary.Read(rd, binary.BigEndian, &proto.Ver); err != nil {
return
}
if err = binary.Read(rd, binary.BigEndian, &proto.Operation); err != nil {
return
}
if err = binary.Read(rd, binary.BigEndian, &proto.Seq); err != nil {
return
}
var (
n, t int
bodyLen = int(packLen - int32(headerLen))
)
if bodyLen > 0 {
proto.Body = make([]byte, bodyLen)
for {
if t, err = rd.Read(proto.Body[n:]); err != nil {
return
}
if n += t; n == bodyLen {
break
}
}
} else {
proto.Body = nil
}
return
}

View File

@@ -0,0 +1,253 @@
package main
// Start Commond eg: ./client 1 1000 localhost:3102
// first parameterbeginning userId
// second parameter: amount of clients
// third parameter: comet ws-server addr
import (
"bufio"
"encoding/binary"
"flag"
"fmt"
"log"
"math/rand"
"os"
"runtime"
"strconv"
"sync/atomic"
"time"
"github.com/golang/protobuf/proto"
"github.com/gorilla/websocket"
comet "gitlab.33.cn/chat/im/api/comet/grpc"
)
const (
rawHeaderLen = uint16(16)
heart = 5 * time.Second
)
type Proto struct {
PackLen int32 // package length
HeaderLen int16 // header length
Ver int16 // protocol version
Op int32 // operation for request
Seq int32 // sequence number chosen by client
Body []byte // body
}
var (
countDown int64
aliveCount int64
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
flag.Parse()
begin, err := strconv.Atoi(os.Args[1])
if err != nil {
panic(err)
}
num, err := strconv.Atoi(os.Args[2])
if err != nil {
panic(err)
}
go result()
for i := begin; i < begin+num; i++ {
go client(int64(i))
}
// signal
var exit chan bool
<-exit
}
func result() {
var (
lastTimes int64
interval = int64(5)
)
for {
nowCount := atomic.LoadInt64(&countDown)
nowAlive := atomic.LoadInt64(&aliveCount)
diff := nowCount - lastTimes
lastTimes = nowCount
fmt.Println(fmt.Sprintf("%s alive:%d down:%d down/s:%d", time.Now().Format("2006-01-02 15:04:05"), nowAlive, nowCount, diff/interval))
time.Sleep(time.Second * time.Duration(interval))
}
}
func client(mid int64) {
for {
startClient(mid)
time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
}
}
func startClient(key int64) {
time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
atomic.AddInt64(&aliveCount, 1)
quit := make(chan bool, 1)
defer func() {
close(quit)
atomic.AddInt64(&aliveCount, -1)
}()
// connnect to server
wsUrl := "ws://" + os.Args[3] + "/sub"
fmt.Println("wsUrl", wsUrl)
conn, _, err := websocket.DefaultDialer.Dial(wsUrl, nil)
if err != nil {
panic(err)
}
seq := int32(0)
authMsg := &comet.AuthMsg{
AppId: "echo",
Token: "fdasfdsaf",
}
p := new(Proto)
p.Ver = 1
p.Op = int32(comet.Op_Auth)
p.Seq = seq
p.Body, _ = proto.Marshal(authMsg)
//auth
if err = wsWriteProto(conn, p); err != nil {
log.Printf("wsWriteProto() error(%v)", err)
return
}
if err = wsReadProto(conn, p); err != nil {
log.Printf("tcpReadProto() error(%v)", err)
return
}
log.Printf("key:%d auth ok, proto: %v", key, p)
seq++
// writer
go func() {
hbProto := new(Proto)
for {
// heartbeat
hbProto.Op = int32(comet.Op_Heartbeat)
hbProto.Seq = seq
hbProto.Body = nil
if err = wsWriteProto(conn, hbProto); err != nil {
log.Printf("key:%d tcpWriteProto() error(%v)", key, err)
return
}
log.Printf("key:%d Write heartbeat", key)
time.Sleep(heart)
seq++
select {
case <-quit:
return
default:
}
}
}()
// reader
for {
if err = wsReadProto(conn, p); err != nil {
log.Printf("key:%d tcpReadProto() error(%v)", key, err)
quit <- true
return
}
if p.Op == int32(comet.Op_AuthReply) {
log.Printf("key:%d auth success", key)
} else if p.Op == int32(comet.Op_HeartbeatReply) {
log.Printf("key:%d receive heartbeat reply", key)
if err = conn.SetReadDeadline(time.Now().Add(heart + 60*time.Second)); err != nil {
log.Printf("conn.SetReadDeadline() error(%v)", err)
quit <- true
return
}
} else {
log.Printf("key:%d op:%d msg: %s", key, p.Op, string(p.Body))
atomic.AddInt64(&countDown, 1)
}
}
}
func wsWriteProto(conn *websocket.Conn, proto *Proto) (err error) {
wc, err := conn.NextWriter(websocket.BinaryMessage)
if err != nil {
panic(err)
}
wr := bufio.NewWriter(wc)
// write
if err = binary.Write(wr, binary.BigEndian, uint32(rawHeaderLen)+uint32(len(proto.Body))); err != nil {
return
}
if err = binary.Write(wr, binary.BigEndian, rawHeaderLen); err != nil {
return
}
if err = binary.Write(wr, binary.BigEndian, proto.Ver); err != nil {
return
}
if err = binary.Write(wr, binary.BigEndian, proto.Op); err != nil {
return
}
if err = binary.Write(wr, binary.BigEndian, proto.Seq); err != nil {
return
}
if proto.Body != nil {
if err = binary.Write(wr, binary.BigEndian, proto.Body); err != nil {
return
}
}
err = wr.Flush()
wc.Close()
return
}
func wsReadProto(conn *websocket.Conn, proto *Proto) (err error) {
var (
packLen int32
headerLen int16
)
_, rc, err := conn.NextReader()
if err != nil {
log.Printf("NextReader error(%v)", err)
return err
}
rd := bufio.NewReader(rc)
// read
if err = binary.Read(rd, binary.BigEndian, &packLen); err != nil {
return
}
if err = binary.Read(rd, binary.BigEndian, &headerLen); err != nil {
return
}
if err = binary.Read(rd, binary.BigEndian, &proto.Ver); err != nil {
return
}
if err = binary.Read(rd, binary.BigEndian, &proto.Op); err != nil {
return
}
if err = binary.Read(rd, binary.BigEndian, &proto.Seq); err != nil {
return
}
var (
n, t int
bodyLen = int(packLen - int32(headerLen))
)
if bodyLen > 0 {
proto.Body = make([]byte, bodyLen)
for {
if t, err = rd.Read(proto.Body[n:]); err != nil {
return
}
if n += t; n == bodyLen {
break
}
}
} else {
proto.Body = nil
}
return
}

View File

@@ -0,0 +1,42 @@
version: '2'
services:
zoo1:
image: wurstmeister/zookeeper
restart: unless-stopped
hostname: zoo1
ports:
- "2181:2181"
container_name: zookeeper
# kafka version: 1.1.0
# scala version: 2.12
kafka1:
image: wurstmeister/kafka
ports:
- "9092:9092"
environment:
KAFKA_ADVERTISED_HOST_NAME: localhost
KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181"
KAFKA_BROKER_ID: 1
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_CREATE_TOPICS: "stream-in:1:1,stream-out:1:1"
depends_on:
- zoo1
container_name: kafka
etcd1:
image: quay.io/coreos/etcd
container_name: etcd
command: etcd -name etcd1 -advertise-client-urls http://0.0.0.0:2379 -listen-client-urls http://0.0.0.0:2379 -listen-peer-urls http://0.0.0.0:2380 -initial-cluster-token tkn -initial-cluster-state new
ports:
- "2379:2379"
- "2380:2380"
redis1:
image: redis
container_name: redis
command: redis-server
ports:
- "6379:6379"

29
comet/CHANGELOG.md Normal file
View File

@@ -0,0 +1,29 @@
版本号`major.minor.patch`具体规则如下:
- major主版本号如有重大版本重构则该字段递增通常各主版本间接口不兼容。
- minor次版本号各次版本号间接口保持兼容如有接口新增或优化则该字段递增。
- patch补丁号如有功能改善或缺陷修复则该字段递增。
## version 3.0.2 @2021.10.28
优化 log 模块
配置文件更新
新增
```toml
[log]
Level="debug"
Mode="console"
Path=""
Display="json"
```
## example x.x.x @yy.mm.dd
**Feature**
**Bug Fixes**
**Improvement**
**Breaking Change**

159
comet/bucket.go Normal file
View File

@@ -0,0 +1,159 @@
package comet
import (
"sync"
"sync/atomic"
"gitlab.33.cn/chat/im/api/comet/grpc"
"gitlab.33.cn/chat/im/comet/conf"
)
// Bucket is a channel holder.
type Bucket struct {
c *conf.Bucket
cLock sync.RWMutex // protect the channels for chs
chs map[string]*Channel // map sub key to a channel
//group
groups map[string]*Group
routines []chan *grpc.BroadcastGroupReq
routinesNum uint64
}
// NewBucket new a bucket struct. store the key with im channel.
func NewBucket(c *conf.Bucket) (b *Bucket) {
b = new(Bucket)
b.chs = make(map[string]*Channel, c.Channel)
b.c = c
b.groups = make(map[string]*Group, c.Groups)
b.routines = make([]chan *grpc.BroadcastGroupReq, c.RoutineAmount)
for i := uint64(0); i < c.RoutineAmount; i++ {
c := make(chan *grpc.BroadcastGroupReq, c.RoutineSize)
b.routines[i] = c
go b.groupProc(c)
}
return
}
// ChannelCount channel count in the bucket
func (b *Bucket) ChannelCount() int {
return len(b.chs)
}
// Put put a channel according with sub key.
func (b *Bucket) Put(ch *Channel) (err error) {
b.cLock.Lock()
// close old channel
if dch := b.chs[ch.Key]; dch != nil {
dch.Close()
}
b.chs[ch.Key] = ch
b.cLock.Unlock()
return
}
// Del delete the channel by sub key.
func (b *Bucket) Del(dch *Channel) {
var (
ok bool
ch *Channel
)
b.cLock.Lock()
if ch, ok = b.chs[dch.Key]; ok {
if ch == dch {
//修改内容:获取channel下所有添加的群并逐个在群聊中删除该channel修改人dld;修改时间2021年5月8日16:36:00 c4f618a9-1c37-3459-3861-a24f26bb2d85
for id := range ch.Groups() {
if g := b.groups[id]; g != nil {
g.Del(ch)
}
}
//结束c4f618a9-1c37-3459-3861-a24f26bb2d85
delete(b.chs, ch.Key)
}
}
b.cLock.Unlock()
}
// Channel get a channel by sub key.
func (b *Bucket) Channel(key string) (ch *Channel) {
b.cLock.RLock()
ch = b.chs[key]
b.cLock.RUnlock()
return
}
// Broadcast push msgs to all channels in the bucket.
func (b *Bucket) Broadcast(p *grpc.Proto, op int32) {
var ch *Channel
b.cLock.RLock()
for _, ch = range b.chs {
_, _ = ch.Push(p)
}
b.cLock.RUnlock()
}
// group
// GroupCount room count in the bucket
func (b *Bucket) GroupCount() int {
return len(b.groups)
}
// GroupsCount get all group id where online number > 0.
func (b *Bucket) GroupsCount() (res map[string]int32) {
var (
groupID string
group *Group
)
b.cLock.RLock()
res = make(map[string]int32)
for groupID, group = range b.groups {
if group.Online > 0 {
res[groupID] = group.Online
}
}
b.cLock.RUnlock()
return
}
// Put put a group according with sub key.
func (b *Bucket) PutGroup(gid string) (group *Group, err error) {
var ok bool
b.cLock.Lock()
if group, ok = b.groups[gid]; !ok {
group = NewGroup(gid)
b.groups[gid] = group
}
b.cLock.Unlock()
return
}
// Group get a group by group id.
func (b *Bucket) Group(gid string) (group *Group) {
b.cLock.RLock()
group = b.groups[gid]
b.cLock.RUnlock()
return
}
// DelGroup delete a room by group id.
func (b *Bucket) DelGroup(group *Group) {
b.cLock.Lock()
delete(b.groups, group.ID)
b.cLock.Unlock()
group.Close()
}
// BroadcastGroup broadcast a message to specified group
func (b *Bucket) BroadcastGroup(arg *grpc.BroadcastGroupReq) {
num := atomic.AddUint64(&b.routinesNum, 1) % b.c.RoutineAmount
b.routines[num] <- arg
}
// group proc
func (b *Bucket) groupProc(c chan *grpc.BroadcastGroupReq) {
for {
arg := <-c
if group := b.Group(arg.GroupID); group != nil {
group.Push(arg.Proto)
}
}
}

102
comet/channel.go Normal file
View File

@@ -0,0 +1,102 @@
package comet
import (
"errors"
"sync"
"sync/atomic"
"github.com/Terry-Mao/goim/pkg/bufio"
"github.com/golang/protobuf/proto"
"gitlab.33.cn/chat/im/api/comet/grpc"
)
// Channel used by message pusher send msg to write goroutine.
type Channel struct {
CliProto Ring
signal chan *grpc.Proto
Writer bufio.Writer
Reader bufio.Reader
Seq int32
Key string
IP string
Port string
nodes map[string]*Node
mutex sync.RWMutex
}
// NewChannel new a channel.
func NewChannel(cli, svr int) *Channel {
c := new(Channel)
c.CliProto.Init(cli)
c.signal = make(chan *grpc.Proto, svr)
c.nodes = make(map[string]*Node)
return c
}
// Push server push message.
func (c *Channel) seqInc() int32 {
return atomic.AddInt32(&c.Seq, 1)
}
// Push server push message.
func (c *Channel) push(p *grpc.Proto) (err error) {
select {
case c.signal <- p:
default:
}
return
}
// Push server push message.
func (c *Channel) Push(p *grpc.Proto) (seq int32, err error) {
if p, ok := proto.Clone(p).(*grpc.Proto); ok {
if p.Op == int32(grpc.Op_ReceiveMsg) {
p.Seq = c.seqInc()
}
seq = p.Seq
return seq, c.push(p)
} else {
return 0, errors.New("protocol type gRPC proto failed")
}
}
// Ready check the channel ready or close?
func (c *Channel) Ready() *grpc.Proto {
return <-c.signal
}
// Signal send signal to the channel, protocol ready.
func (c *Channel) Signal() {
c.signal <- grpc.ProtoReady
}
// Close close the channel.
func (c *Channel) Close() {
c.signal <- grpc.ProtoFinish
}
// Close close the channel.
func (c Channel) Groups() map[string]*Node {
return c.nodes
}
//
func (c *Channel) DelNode(id string) {
c.mutex.Lock()
delete(c.nodes, id)
c.mutex.Unlock()
}
func (c *Channel) SetNode(id string, node *Node) {
c.mutex.Lock()
c.nodes[id] = node
c.mutex.Unlock()
}
func (c *Channel) GetNode(id string) *Node {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.nodes[id]
}

135
comet/cmd/main.go Normal file
View File

@@ -0,0 +1,135 @@
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"math/rand"
"net"
_ "net/http/pprof"
"os"
"os/signal"
"runtime"
"syscall"
"time"
"github.com/Terry-Mao/goim/pkg/ip"
"github.com/opentracing/opentracing-go"
"github.com/rs/zerolog/log"
"github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/config"
xlog "gitlab.33.cn/chat/im-pkg/log"
"gitlab.33.cn/chat/im-pkg/trace"
"gitlab.33.cn/chat/im/comet"
"gitlab.33.cn/chat/im/comet/conf"
"gitlab.33.cn/chat/im/comet/grpc"
"gitlab.33.cn/chat/im/comet/http"
"gitlab.33.cn/chat/im/naming"
)
const (
srvName = "comet"
)
var (
// projectVersion 项目版本
projectVersion = "3.0.3"
// goVersion go版本
goVersion = ""
// gitCommit git提交commit id
gitCommit = ""
// buildTime 编译时间
buildTime = ""
isShowVersion = flag.Bool("version", false, "show project version")
)
// showVersion 显示项目版本信息
func showVersion(isShow bool) {
if isShow {
fmt.Printf("Project: %s\n", srvName)
fmt.Printf(" Version: %s\n", projectVersion)
fmt.Printf(" Go Version: %s\n", goVersion)
fmt.Printf(" Git Commit: %s\n", gitCommit)
fmt.Printf(" Build Time: %s\n", buildTime)
os.Exit(0)
}
}
func main() {
flag.Parse()
showVersion(*isShowVersion)
if err := conf.Init(); err != nil {
panic(err)
}
rand.Seed(time.Now().UTC().UnixNano())
runtime.GOMAXPROCS(runtime.NumCPU())
//log init
var err error
log.Logger, err = xlog.Init(conf.Conf.Log)
if err != nil {
panic(err)
}
log.Logger.With().Str("service", srvName)
byte, _ := json.Marshal(conf.Conf)
log.Info().Str("config", string(byte)).Send()
// trace init
tracer, tracerCloser := trace.Init(srvName, conf.Conf.Trace, config.Logger(jaeger.NullLogger))
//不然后续不会有Jaeger实例
opentracing.SetGlobalTracer(tracer)
srv := comet.New(conf.Conf)
rpcSrv := grpc.New(conf.Conf.RPCServer, srv)
httpSrv := http.Start(":8000", srv)
if err := comet.InitWebsocket(srv, conf.Conf.Websocket.Bind, runtime.NumCPU()); err != nil {
panic(err)
}
if err := comet.InitTCP(srv, conf.Conf.TCP.Bind, runtime.NumCPU()); err != nil {
panic(err)
}
// register comet
_, port, _ := net.SplitHostPort(conf.Conf.RPCServer.Addr)
addr := fmt.Sprintf("%s:%s", ip.InternalIP(), port)
if err := naming.Register(conf.Conf.Reg.RegAddrs, conf.Conf.Reg.SrvName, addr, conf.Conf.Reg.Schema, 15); err != nil {
panic(err)
}
fmt.Println("register ok")
// signal
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info().Str("signal", s.String()).Send()
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
if err := naming.UnRegister(conf.Conf.Reg.SrvName, addr, conf.Conf.Reg.Schema); err != nil {
log.Error().Err(err).Msg("naming.UnRegister")
}
rpcSrv.GracefulStop()
srv.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := httpSrv.Shutdown(ctx); err != nil {
log.Fatal().Err(err).Msg("http server shutdown")
}
if err := tracerCloser.Close(); err != nil {
log.Error().Err(err).Msg("tracer close failed")
}
log.Info().Msg("comet exit")
xlog.Close()
//log.Flush()
return
case syscall.SIGHUP:
default:
return
}
}
}

126
comet/comet.go Normal file
View File

@@ -0,0 +1,126 @@
package comet
import (
"context"
"fmt"
"math/rand"
"net"
"time"
"github.com/Terry-Mao/goim/pkg/ip"
"github.com/zhenjl/cityhash"
"gitlab.33.cn/chat/im-pkg/trace"
comet "gitlab.33.cn/chat/im/api/comet/grpc"
logic "gitlab.33.cn/chat/im/api/logic/grpc"
"gitlab.33.cn/chat/im/comet/conf"
"gitlab.33.cn/chat/im/common"
"gitlab.33.cn/chat/im/naming"
"google.golang.org/grpc"
"google.golang.org/grpc/resolver"
)
// Comet is comet server.
type Comet struct {
c *conf.Config
round *Round
buckets []*Bucket // subkey bucket
bucketIdx uint32
serverID string
logicRPCClient logic.LogicClient
}
// NewServer returns a new Server.
func New(c *conf.Config) *Comet {
s := &Comet{
c: c,
round: NewRound(c),
logicRPCClient: newLogicClient(c),
}
// init bucket
s.buckets = make([]*Bucket, c.Bucket.Size)
s.bucketIdx = uint32(c.Bucket.Size)
for i := 0; i < c.Bucket.Size; i++ {
s.buckets[i] = NewBucket(c.Bucket)
}
addr := ip.InternalIP()
_, port, _ := net.SplitHostPort(c.RPCServer.Addr)
s.serverID = "grpc://" + addr + ":" + port
return s
}
func newLogicClient(c *conf.Config) logic.LogicClient {
rb := naming.NewResolver(c.Reg.RegAddrs, c.LogicRPCClient.Schema)
resolver.Register(rb)
addr := fmt.Sprintf("%s:///%s", c.LogicRPCClient.Schema, c.LogicRPCClient.SrvName) // "schema://[authority]/service"
fmt.Println("rpc client call addr:", addr)
conn, err := common.NewGRPCConn(addr, time.Duration(c.LogicRPCClient.Dial), grpc.WithUnaryInterceptor(trace.OpentracingClientInterceptor))
if err != nil {
panic(err)
}
return logic.NewLogicClient(conn)
}
// Buckets return all buckets.
func (s *Comet) Buckets() []*Bucket {
return s.buckets
}
// Bucket get the bucket by subkey.
func (s *Comet) Bucket(subKey string) *Bucket {
idx := cityhash.CityHash32([]byte(subKey), uint32(len(subKey))) % s.bucketIdx
return s.buckets[idx]
}
// RandServerHearbeat rand server heartbeat.
func (s *Comet) RandServerHearbeat() time.Duration {
return time.Duration(s.c.Protocol.MinHeartbeat) + time.Duration(rand.Int63n(int64(s.c.Protocol.MaxHeartbeat-s.c.Protocol.MinHeartbeat)))
}
// Close close the server.
func (s *Comet) Close() (err error) {
return
}
// Connect connected a connection.
func (s *Comet) Connect(c context.Context, p *comet.Proto) (key string, hb time.Duration, err error) {
var (
req logic.ConnectReq
reply *logic.ConnectReply
)
req.Server = s.serverID
req.Proto = p
reply, err = s.logicRPCClient.Connect(c, &req)
if err != nil {
return
}
return reply.Key, time.Duration(reply.Heartbeat), nil
}
// Disconnect disconnected a connection.
func (s *Comet) Disconnect(c context.Context, key string) (err error) {
_, err = s.logicRPCClient.Disconnect(context.Background(), &logic.DisconnectReq{
Server: s.serverID,
Key: key,
})
return
}
// Heartbeat heartbeat a connection session.
func (s *Comet) Heartbeat(ctx context.Context, key string) (err error) {
_, err = s.logicRPCClient.Heartbeat(ctx, &logic.HeartbeatReq{
Server: s.serverID,
Key: key,
})
return
}
// Receive receive a message.
func (s *Comet) Receive(ctx context.Context, key string, p *comet.Proto) (err error) {
_, err = s.logicRPCClient.Receive(ctx, &logic.ReceiveReq{Key: key, Proto: p})
return
}

70
comet/conf/comet.toml Normal file
View File

@@ -0,0 +1,70 @@
env="debug"
[log]
Level="debug"
Mode="console"
Path=""
Display="json"
[Trace]
ServiceName=""
Gen128Bit=true
[Trace.Sampler]
Type="const"
Param=1.0
[Trace.Reporter]
LogSpans=true
LocalAgentHostPort="172.16.101.130:6831"
[reg]
schema = "im"
srvName = "comet"
regAddrs = "127.0.0.1:2379"
[logicRPCClient]
schema = "im"
srvName = "logic"
dial = "1s"
timeout = "1s"
[RPCServer]
Network = "tcp"
Addr = ":3109"
Timeout = "1s"
KeepAliveMaxConnectionIdle = "60s"
KeepAliveMaxConnectionAge = "2h"
KeepAliveMaxMaxConnectionAgeGrace = "20s"
KeepAliveTime = "60s"
KeepAliveTimeout = "20s"
[tcp]
bind = [":3101"]
sndbuf = 4096
rcvbuf = 4096
keepalive = false
reader = 32
readBuf = 1024
readBufSize = 8192
writer = 32
writeBuf = 1024
writeBufSize = 8192
[websocket]
bind = [":3102"]
tlsOpen = false
tlsBind = [":3103"]
certFile = "../../cert.pem"
privateFile = "../../private.pem"
[protocol]
timer = 32
timerSize = 2048
svrProto = 10
cliProto = 5
handshakeTimeout = "8s"
minHeartbeat = "5s"
maxHeartbeat = "10s"
[bucket]
size = 32
channel = 1024

209
comet/conf/conf.go Normal file
View File

@@ -0,0 +1,209 @@
package conf
import (
"flag"
"github.com/uber/jaeger-client-go"
"os"
"time"
"github.com/BurntSushi/toml"
xtime "github.com/Terry-Mao/goim/pkg/time"
traceConfig "github.com/uber/jaeger-client-go/config"
xlog "gitlab.33.cn/chat/im-pkg/log"
)
const (
DebugMode = "debug"
ReleaseMode = "release"
TestMode = "test"
)
var (
confPath string
regAddress string
// Conf config
Conf *Config
)
func init() {
var (
defAddress = os.Getenv("REGADDRS")
)
flag.StringVar(&confPath, "conf", "comet.toml", "default config path.")
flag.StringVar(&regAddress, "reg", defAddress, "etcd register addrs. eg:127.0.0.1:2379")
}
// Init init config.
func Init() (err error) {
Conf = Default()
_, err = toml.DecodeFile(confPath, &Conf)
return
}
// Default new a config with specified defualt value.
func Default() *Config {
return &Config{
Env: "",
Log: xlog.Config{
Level: "debug",
Mode: "console",
Path: "",
Display: "console",
},
Trace: traceConfig.Configuration{
ServiceName: "answer",
Gen128Bit: true,
Sampler: &traceConfig.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &traceConfig.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "127.0.0.1:6831",
},
},
Reg: &Reg{
Schema: "im",
SrvName: "comet",
RegAddrs: regAddress,
},
LogicRPCClient: &RPCClient{
Schema: "im",
SrvName: "logic",
Dial: xtime.Duration(time.Second),
Timeout: xtime.Duration(time.Second),
},
RPCServer: &RPCServer{
Network: "tcp",
Addr: ":3109",
Timeout: xtime.Duration(time.Second),
IdleTimeout: xtime.Duration(time.Second * 60),
MaxLifeTime: xtime.Duration(time.Hour * 2),
ForceCloseWait: xtime.Duration(time.Second * 20),
KeepAliveInterval: xtime.Duration(time.Second * 60),
KeepAliveTimeout: xtime.Duration(time.Second * 20),
},
TCP: &TCP{
Bind: []string{":3101"},
Sndbuf: 4096,
Rcvbuf: 4096,
KeepAlive: false,
Reader: 32,
ReadBuf: 1024,
ReadBufSize: 8192,
Writer: 32,
WriteBuf: 1024,
WriteBufSize: 8192,
},
Websocket: &Websocket{
Bind: []string{":3102"},
},
Protocol: &Protocol{
Timer: 32,
TimerSize: 2048,
Task: 32,
TaskSize: 2048,
CliProto: 5,
SvrProto: 10,
HandshakeTimeout: xtime.Duration(time.Second * 5),
TaskDuration: xtime.Duration(time.Second * 5),
MinHeartbeat: xtime.Duration(time.Minute * 10),
MaxHeartbeat: xtime.Duration(time.Minute * 30),
},
Bucket: &Bucket{
Size: 32,
Channel: 1024,
Groups: 1024,
RoutineAmount: 32,
RoutineSize: 1024,
},
}
}
// Config is comet config.
type Config struct {
Env string
Log xlog.Config
Trace traceConfig.Configuration
Reg *Reg
TCP *TCP
Websocket *Websocket
Protocol *Protocol
Bucket *Bucket
LogicRPCClient *RPCClient
RPCServer *RPCServer
}
// Reg is service register/discovery config
type Reg struct {
Schema string
SrvName string // call
RegAddrs string // etcd addrs, seperate by ','
}
// RPCClient is RPC client config.
type RPCClient struct {
Schema string
SrvName string // call
Dial xtime.Duration
Timeout xtime.Duration
}
// RPCServer is RPC server config.
type RPCServer struct {
Network string
Addr string
Timeout xtime.Duration
IdleTimeout xtime.Duration
MaxLifeTime xtime.Duration
ForceCloseWait xtime.Duration
KeepAliveInterval xtime.Duration
KeepAliveTimeout xtime.Duration
}
// TCP is tcp config.
type TCP struct {
Bind []string
Sndbuf int
Rcvbuf int
KeepAlive bool
Reader int
ReadBuf int
ReadBufSize int
Writer int
WriteBuf int
WriteBufSize int
}
// Websocket is websocket config.
type Websocket struct {
Bind []string
TLSOpen bool
TLSBind []string
CertFile string
PrivateFile string
}
// Protocol is protocol config.
type Protocol struct {
Timer int
TimerSize int
Task int
TaskSize int
SvrProto int
CliProto int
HandshakeTimeout xtime.Duration
MinHeartbeat xtime.Duration
MaxHeartbeat xtime.Duration
TaskDuration xtime.Duration
}
// Bucket is bucket config.
type Bucket struct {
Size int
Channel int
Groups int
RoutineAmount uint64
RoutineSize int
}

34
comet/errors/errors.go Normal file
View File

@@ -0,0 +1,34 @@
package errors
import (
"errors"
)
// .
var (
// server
ErrHandshake = errors.New("handshake failed")
ErrOperation = errors.New("request operation not valid")
// ring
ErrRingEmpty = errors.New("ring buffer empty")
ErrRingFull = errors.New("ring buffer full")
// timer
ErrTimerFull = errors.New("timer full")
ErrTimerEmpty = errors.New("timer empty")
ErrTimerNoItem = errors.New("timer item not exist")
// channel
ErrUnconnected = errors.New("client unconnected error")
ErrJoinGroupArg = errors.New("rpc joingroup arg error")
ErrPushMsgArg = errors.New("rpc pushmsg arg error")
ErrPushMsgsArg = errors.New("rpc pushmsgs arg error")
ErrMPushMsgArg = errors.New("rpc mpushmsg arg error")
ErrMPushMsgsArg = errors.New("rpc mpushmsgs arg error")
// bucket
ErrBroadCastArg = errors.New("rpc broadcast arg error")
ErrBroadCastRoomArg = errors.New("rpc broadcast room arg error")
// group
ErrGroupDroped = errors.New("group droped")
// rpc
ErrLogic = errors.New("logic rpc is not available")
)

119
comet/group.go Normal file
View File

@@ -0,0 +1,119 @@
package comet
import (
"fmt"
"sync"
"gitlab.33.cn/chat/im/api/comet/grpc"
)
// Group is a group and store channel group info.
type Group struct {
ID string
rLock sync.RWMutex
next *Node
drop bool
Online int32 // dirty read is ok
AllOnline int32
}
// NewGroup new a group struct, store channel group info.
func NewGroup(id string) (r *Group) {
r = new(Group)
r.ID = id
r.drop = false
r.next = nil
r.Online = 0
return
}
// Put put channel into the group.
func (r *Group) Put(ch *Channel) (err error) {
r.rLock.Lock()
if !r.drop && ch.GetNode(r.ID) == nil {
node := &Node{
Current: ch,
Next: nil,
Prev: nil,
}
ch.SetNode(r.ID, node)
if r.next != nil {
r.next.Prev = node
}
node.Next = r.next
node.Prev = nil
r.next = node // insert to header
r.Online++
} /* else { //del: 2021年7月21日16:32:54 dld
err = errors.ErrGroupDroped
}*/
r.rLock.Unlock()
return
}
// Del delete channel from the group.
func (r *Group) Del(ch *Channel) bool {
r.rLock.Lock()
if node := ch.GetNode(r.ID); node != nil {
if node.Next != nil {
// if not footer
node.Next.Prev = node.Prev
}
if node.Prev != nil {
// if not header
node.Prev.Next = node.Next
} else {
r.next = node.Next
}
r.Online--
//r.drop = (r.Online == 0)
ch.DelNode(r.ID) //2021年6月10日 删除对应node防止再次put的时候报ErrGroupDroped错误dld
}
r.rLock.Unlock()
return r.drop
}
// Push push msg to the group, if chan full discard it.
func (r *Group) Push(p *grpc.Proto) {
r.rLock.RLock()
for node := r.next; node != nil; node = node.Next {
if node.Current != nil {
_, _ = node.Current.Push(p)
}
}
r.rLock.RUnlock()
}
// group members Key,IP
func (r *Group) Members() ([]string, []string) {
r.rLock.RLock()
members := make([]string, 0)
mIp := make([]string, 0)
for node := r.next; node != nil; node = node.Next {
if node.Current != nil {
members = append(members, node.Current.Key)
mIp = append(mIp, fmt.Sprintf("%v:%v", node.Current.IP, node.Current.Port))
}
}
r.rLock.RUnlock()
return members, mIp
}
// Close close the group.
func (r *Group) Close() {
r.rLock.Lock()
for node := r.next; node != nil; node = node.Next {
if ch := node.Current; ch != nil {
ch.DelNode(r.ID)
}
}
r.rLock.Unlock()
}
// OnlineNum the group all online.
func (r *Group) OnlineNum() int32 {
if r.AllOnline > 0 {
return r.AllOnline
}
return r.Online
}

121
comet/group_test.go Normal file
View File

@@ -0,0 +1,121 @@
package comet
import (
"strconv"
"sync/atomic"
"testing"
"time"
)
func TestGroup_Put(t *testing.T) {
//one channel
var gid int32 = 0
ch := NewChannel(10, 10)
//one groups
{
g := NewGroup(strconv.Itoa(int(atomic.AddInt32(&gid, 1))))
for i := 0; i < 10; i++ {
go func() {
for {
err := g.Put(ch)
if err != nil {
t.Error(err)
}
}
}()
}
}
//many groups
{
for i := 0; i < 10; i++ {
g := NewGroup(strconv.Itoa(int(atomic.AddInt32(&gid, 1))))
go func() {
for {
err := g.Put(ch)
if err != nil {
t.Error(err)
}
}
}()
}
}
time.Sleep(10 * time.Second)
}
func TestGroup_Del(t *testing.T) {
//one channel
var gid int32 = 0
//var lc sync.RWMutex
var groups = make(map[int32]*Group)
ch := NewChannel(10, 10)
//init
for i := 0; i < 10; i++ {
g := NewGroup(strconv.Itoa(int(atomic.AddInt32(&gid, 1))))
groups[gid] = g
}
gid = 0
go func() {
for _, g := range groups {
err := g.Put(ch)
if err != nil {
t.Error(err)
}
}
}()
go func() {
for _, g := range groups {
g.Del(ch)
}
}()
time.Sleep(10 * time.Second)
}
func TestGroup_Del2(t *testing.T) {
//one channel
var gid int32 = 0
//var lc sync.RWMutex
var groups = make(map[int32]*Group)
ch := NewChannel(10, 10)
//init
for i := 0; i < 10; i++ {
g := NewGroup(strconv.Itoa(int(atomic.AddInt32(&gid, 1))))
groups[gid] = g
}
gid = 0
go func() {
for _, g := range groups {
err := g.Put(ch)
if err != nil {
t.Error(err)
}
}
}()
go func() {
for _, g := range groups {
g.Del(ch)
}
}()
time.Sleep(10 * time.Second)
}
func convertAddress(addr string) string {
if len(addr) == 0 {
return addr
}
switch addr[0] {
case 'g':
return addr[7:]
default:
return addr
}
}
func Test_convertAddress(t *testing.T) {
t.Log(convertAddress("grpc://172.16.101.107:3109"))
}

152
comet/grpc/server.go Normal file
View File

@@ -0,0 +1,152 @@
package grpc
import (
"context"
"gitlab.33.cn/chat/im-pkg/trace"
"github.com/rs/zerolog/log"
"net"
"time"
pb "gitlab.33.cn/chat/im/api/comet/grpc"
"gitlab.33.cn/chat/im/comet"
"gitlab.33.cn/chat/im/comet/conf"
"gitlab.33.cn/chat/im/comet/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/keepalive"
)
// New comet grpc server.
func New(c *conf.RPCServer, s *comet.Comet) *grpc.Server {
keepParams := grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: time.Duration(c.IdleTimeout),
MaxConnectionAgeGrace: time.Duration(c.ForceCloseWait),
Time: time.Duration(c.KeepAliveInterval),
Timeout: time.Duration(c.KeepAliveTimeout),
MaxConnectionAge: time.Duration(c.MaxLifeTime),
})
connectionTimeout := grpc.ConnectionTimeout(time.Duration(c.Timeout))
srv := grpc.NewServer(keepParams, connectionTimeout,
grpc.ChainUnaryInterceptor(
trace.OpentracingServerInterceptor,
))
pb.RegisterCometServer(srv, &server{srv: s})
lis, err := net.Listen(c.Network, c.Addr)
if err != nil {
panic(err)
}
go func() {
if err := srv.Serve(lis); err != nil {
panic(err)
}
}()
return srv
}
type server struct {
pb.UnimplementedCometServer
srv *comet.Comet
}
// PushMsg push a message to specified sub keys.
func (s *server) PushMsg(ctx context.Context, req *pb.PushMsgReq) (reply *pb.PushMsgReply, err error) {
if len(req.Keys) == 0 || req.Proto == nil {
return nil, errors.ErrPushMsgArg
}
index := make(map[string]int32)
var seq int32
for _, key := range req.Keys {
if channel := s.srv.Bucket(key).Channel(key); channel != nil {
if seq, err = channel.Push(req.Proto); err != nil {
return
}
index[key] = seq
}
}
return &pb.PushMsgReply{Index: index}, nil
}
// Broadcast broadcast msg to all user.
func (s *server) Broadcast(ctx context.Context, req *pb.BroadcastReq) (*pb.BroadcastReply, error) {
if req.Proto == nil {
return nil, errors.ErrBroadCastArg
}
// TODO use broadcast queue
go func() {
for _, bucket := range s.srv.Buckets() {
bucket.Broadcast(req.GetProto(), req.ProtoOp)
}
}()
return &pb.BroadcastReply{}, nil
}
func (s *server) BroadcastGroup(ctx context.Context, req *pb.BroadcastGroupReq) (*pb.BroadcastGroupReply, error) {
if req.Proto == nil || req.GroupID == "" {
return nil, errors.ErrBroadCastArg
}
for _, bucket := range s.srv.Buckets() {
bucket.BroadcastGroup(req)
}
return &pb.BroadcastGroupReply{}, nil
}
func (s *server) JoinGroups(ctx context.Context, req *pb.JoinGroupsReq) (*pb.JoinGroupsReply, error) {
if len(req.Keys) == 0 || len(req.Gid) == 0 {
return nil, errors.ErrJoinGroupArg
}
for _, key := range req.Keys {
var channel *comet.Channel
bucket := s.srv.Bucket(key)
if channel = bucket.Channel(key); channel == nil {
log.Error().Str("key", key).Msg("JoinGroups get channel err")
continue
//return &pb.JoinGroupsReply{}, errors.ErrUnconnected todo 2021_12_08_14_36:
}
for _, gid := range req.Gid {
var group *comet.Group
if group = bucket.Group(gid); group == nil {
group, _ = bucket.PutGroup(gid)
}
err := group.Put(channel)
if err != nil {
log.Error().Err(err).Str("key", key).Str("gid", gid).
Int32("channel.Seq", channel.Seq).Str("channel.Key", channel.Key).Str("channel.Ip", channel.IP).Str("channel.Port", channel.Port).
Msg("JoinGroups get channel err")
continue
//return &pb.JoinGroupsReply{}, err todo 2021_12_08_14_36:
}
}
}
return &pb.JoinGroupsReply{}, nil
}
func (s *server) LeaveGroups(ctx context.Context, req *pb.LeaveGroupsReq) (*pb.LeaveGroupsReply, error) {
if len(req.Keys) == 0 || len(req.Gid) == 0 {
return nil, errors.ErrPushMsgArg
}
for _, key := range req.Keys {
var channel *comet.Channel
bucket := s.srv.Bucket(key)
if channel = bucket.Channel(key); channel == nil {
continue
//return &pb.LeaveGroupsReply{}, errors.ErrUnconnected todo 2021_12_08_14_36:
}
for _, gid := range req.Gid {
if group := bucket.Group(gid); group != nil {
group.Del(channel)
}
}
}
return &pb.LeaveGroupsReply{}, nil
}
func (s *server) DelGroups(ctx context.Context, req *pb.DelGroupsReq) (*pb.DelGroupsReply, error) {
for _, gid := range req.Gid {
for _, bucket := range s.srv.Buckets() {
if g := bucket.Group(gid); g != nil {
bucket.DelGroup(g)
}
}
}
return &pb.DelGroupsReply{}, nil
}

36
comet/http/result.go Normal file
View File

@@ -0,0 +1,36 @@
package http
import "github.com/gin-gonic/gin"
const (
// OK ok
OK = 0
// RequestErr request error
RequestErr = -400
// ServerErr server error
ServerErr = -500
contextErrCode = "context/err/code"
)
type resp struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
func errors(c *gin.Context, code int, msg string) {
c.Set(contextErrCode, code)
c.JSON(200, resp{
Code: code,
Message: msg,
})
}
func result(c *gin.Context, data interface{}, code int) {
c.Set(contextErrCode, code)
c.JSON(200, resp{
Code: code,
Data: data,
})
}

46
comet/http/server.go Normal file
View File

@@ -0,0 +1,46 @@
package http
import (
"github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"gitlab.33.cn/chat/im/comet"
"gitlab.33.cn/chat/im/comet/conf"
"net/http"
)
var (
srv *comet.Comet
)
func Start(addr string, s *comet.Comet) *http.Server {
srv = s
gin.ForceConsoleColor()
switch conf.Conf.Env {
case conf.DebugMode:
gin.SetMode(gin.DebugMode)
case conf.ReleaseMode:
gin.SetMode(gin.ReleaseMode)
}
engine := gin.Default()
SetupEngine(engine)
pprof.Register(engine)
srv := &http.Server{
Addr: addr,
Handler: engine,
}
go func() {
// service connections
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal().Err(err).Msg("http listen failed")
}
}()
return srv
}
func SetupEngine(e *gin.Engine) *gin.Engine {
e.GET("/statics", Statics)
e.GET("/groupDetails", GroupDetails)
return e
}

75
comet/http/statics.go Normal file
View File

@@ -0,0 +1,75 @@
package http
import "github.com/gin-gonic/gin"
func Statics(c *gin.Context) {
var arg struct {
Drop bool `form:"isDrop"`
}
if err := c.BindQuery(&arg); err != nil {
errors(c, RequestErr, err.Error())
return
}
res := map[string]interface{}{
"buckets": groupsInfo(arg.Drop),
}
result(c, res, OK)
}
func groupsInfo(isDrop bool) []map[string]interface{} {
var res = make([]map[string]interface{}, len(srv.Buckets()))
for i, bucket := range srv.Buckets() {
if !isDrop {
if bucket.GroupCount() == 0 {
continue
}
if len(bucket.GroupsCount()) == 0 {
continue
}
}
item := map[string]interface{}{
"counts": bucket.GroupCount(),
"group-members": bucket.GroupsCount(),
}
res[i] = item
}
return res
}
func GroupDetails(c *gin.Context) {
var arg struct {
Groups []string `form:"groups" binding:"required"`
}
if err := c.BindQuery(&arg); err != nil {
errors(c, RequestErr, err.Error())
return
}
var groups = make([]interface{}, 0)
for _, gid := range arg.Groups {
gInfo := map[string]interface{}{
"gid": gid,
"members": groupsMembers(gid),
}
groups = append(groups, gInfo)
}
res := map[string]interface{}{
"groups": groups,
}
result(c, res, OK)
}
func groupsMembers(gid string) map[string]string {
members := make(map[string]string, 0)
for _, bucket := range srv.Buckets() {
g := bucket.Group(gid)
if g == nil {
continue
}
mems, ips := g.Members()
for i, mem := range mems {
members[mem] = ips[i]
}
}
return members
}

7
comet/nodes.go Normal file
View File

@@ -0,0 +1,7 @@
package comet
type Node struct {
Current *Channel
Next *Node
Prev *Node
}

44
comet/operation.go Normal file
View File

@@ -0,0 +1,44 @@
package comet
import (
"context"
"strconv"
"gitlab.33.cn/chat/im/api/comet/grpc"
"gitlab.33.cn/chat/im/dtask"
)
// Operate operate.
func (s *Comet) Operate(ctx context.Context, p *grpc.Proto, ch *Channel, tsk *dtask.Task) error {
switch p.Op {
case int32(grpc.Op_SendMsg):
//标明Ack的消息序列
p.Ack = p.Seq
err := s.Receive(ctx, ch.Key, p)
if err != nil {
//下层业务调用失败返回error的话会直接断开连接
return err
}
//p.Op = int32(grpc.Op_SendMsgReply)
case int32(grpc.Op_ReceiveMsgReply):
//从task中删除某一条
if j := tsk.Get(strconv.FormatInt(int64(p.Ack), 10)); j != nil {
j.Cancel()
}
err := s.Receive(ctx, ch.Key, p)
if err != nil {
//下层业务调用失败返回error的话会直接断开连接
return err
}
case int32(grpc.Op_SyncMsgReq):
err := s.Receive(ctx, ch.Key, p)
if err != nil {
//下层业务调用失败返回error的话会直接断开连接
return err
}
p.Op = int32(grpc.Op_SyncMsgReply)
default:
return s.Receive(ctx, ch.Key, p)
}
return nil
}

80
comet/ring.go Normal file
View File

@@ -0,0 +1,80 @@
package comet
import (
"gitlab.33.cn/chat/im/api/comet/grpc"
"gitlab.33.cn/chat/im/comet/errors"
)
// Ring ring proto buffer.
type Ring struct {
// read
rp uint64
num uint64
mask uint64
// TODO split cacheline, many cpu cache line size is 64
// pad [40]byte
// write
wp uint64
data []grpc.Proto
}
// NewRing new a ring buffer.
func NewRing(num int) *Ring {
r := new(Ring)
r.init(uint64(num))
return r
}
// Init init ring.
func (r *Ring) Init(num int) {
r.init(uint64(num))
}
func (r *Ring) init(num uint64) {
// 2^N
if num&(num-1) != 0 {
for num&(num-1) != 0 {
num &= (num - 1)
}
num = num << 1
}
r.data = make([]grpc.Proto, num)
r.num = num
r.mask = r.num - 1
}
// Get get a proto from ring.
func (r *Ring) Get() (proto *grpc.Proto, err error) {
if r.rp == r.wp {
return nil, errors.ErrRingEmpty
}
proto = &r.data[r.rp&r.mask]
return
}
// GetAdv incr read index.
func (r *Ring) GetAdv() {
r.rp++
}
// Set get a proto to write.
func (r *Ring) Set() (proto *grpc.Proto, err error) {
if r.wp-r.rp >= r.num {
return nil, errors.ErrRingFull
}
proto = &r.data[r.wp&r.mask]
return
}
// SetAdv incr write index.
func (r *Ring) SetAdv() {
r.wp++
}
// Reset reset ring.
func (r *Ring) Reset() {
r.rp = 0
r.wp = 0
// prevent pad compiler optimization
// r.pad = [40]byte{}
}

78
comet/round.go Normal file
View File

@@ -0,0 +1,78 @@
package comet
import (
"github.com/Terry-Mao/goim/pkg/bytes"
"github.com/Terry-Mao/goim/pkg/time"
"gitlab.33.cn/chat/im/comet/conf"
)
// RoundOptions round options.
type RoundOptions struct {
Timer int
TimerSize int
Reader int
ReadBuf int
ReadBufSize int
Writer int
WriteBuf int
WriteBufSize int
Task int
TaskSize int
}
// Round userd for connection round-robin get a reader/writer/timer for split big lock.
type Round struct {
readers []bytes.Pool
writers []bytes.Pool
timers []time.Timer
options RoundOptions
}
// NewRound new a round struct.
func NewRound(c *conf.Config) (r *Round) {
var i int
r = &Round{
options: RoundOptions{
Reader: c.TCP.Reader,
ReadBuf: c.TCP.ReadBuf,
ReadBufSize: c.TCP.ReadBufSize,
Writer: c.TCP.Writer,
WriteBuf: c.TCP.WriteBuf,
WriteBufSize: c.TCP.WriteBufSize,
Timer: c.Protocol.Timer,
TimerSize: c.Protocol.TimerSize,
Task: c.Protocol.Task,
TaskSize: c.Protocol.TaskSize,
}}
// reader
r.readers = make([]bytes.Pool, r.options.Reader)
for i = 0; i < r.options.Reader; i++ {
r.readers[i].Init(r.options.ReadBuf, r.options.ReadBufSize)
}
// writer
r.writers = make([]bytes.Pool, r.options.Writer)
for i = 0; i < r.options.Writer; i++ {
r.writers[i].Init(r.options.WriteBuf, r.options.WriteBufSize)
}
// timer
r.timers = make([]time.Timer, r.options.Timer)
for i = 0; i < r.options.Timer; i++ {
r.timers[i].Init(r.options.TimerSize)
}
return
}
// Timer get a timer.
func (r *Round) Timer(rn int) *time.Timer {
return &(r.timers[rn%r.options.Timer])
}
// Reader get a reader memory buffer.
func (r *Round) Reader(rn int) *bytes.Pool {
return &(r.readers[rn%r.options.Reader])
}
// Writer get a writer memory buffer pool.
func (r *Round) Writer(rn int) *bytes.Pool {
return &(r.writers[rn%r.options.Writer])
}

304
comet/tcp.go Normal file
View File

@@ -0,0 +1,304 @@
package comet
import (
"context"
"fmt"
"io"
"net"
"strconv"
"strings"
"time"
"github.com/Terry-Mao/goim/pkg/bufio"
"github.com/Terry-Mao/goim/pkg/bytes"
xtime "github.com/Terry-Mao/goim/pkg/time"
"github.com/golang/protobuf/proto"
"github.com/rs/zerolog/log"
"gitlab.33.cn/chat/im/api/comet/grpc"
"gitlab.33.cn/chat/im/dtask"
)
// InitTCP listen all tcp.bind and start accept connections.
func InitTCP(server *Comet, addrs []string, accept int) (err error) {
var (
bind string
listener *net.TCPListener
addr *net.TCPAddr
)
for _, bind = range addrs {
if addr, err = net.ResolveTCPAddr("tcp", bind); err != nil {
log.Error().Stack().Err(err).Msg(fmt.Sprintf("net.ResolveTCPAddr(tcp, %s)", bind))
return
}
if listener, err = net.ListenTCP("tcp", addr); err != nil {
log.Error().Stack().Err(err).Msg(fmt.Sprintf("net.ListenTCP(tcp, %s)", bind))
return
}
log.Info().Str("bind", bind).Msg("start tcp listen")
// split N core accept
for i := 0; i < accept; i++ {
go acceptTCP(server, listener)
}
}
return
}
// Accept accepts connections on the listener and serves requests
// for each incoming connection. Accept blocks; the caller typically
// invokes it in a go statement.
func acceptTCP(server *Comet, lis *net.TCPListener) {
var (
conn *net.TCPConn
err error
r int
)
for {
if conn, err = lis.AcceptTCP(); err != nil {
// if listener close then return
log.Error().Stack().Err(err).Msg(fmt.Sprintf("listener.Accept(\"%s\")", lis.Addr().String()))
continue
//return
}
log.Info().Str("remoteIP", conn.RemoteAddr().String()).Msg("accept tcp conn")
if err = conn.SetKeepAlive(server.c.TCP.KeepAlive); err != nil {
log.Error().Stack().Err(err).Msg("conn.SetKeepAlive()")
return
}
if err = conn.SetReadBuffer(server.c.TCP.Rcvbuf); err != nil {
log.Error().Stack().Err(err).Msg("conn.SetReadBuffer()")
return
}
if err = conn.SetWriteBuffer(server.c.TCP.Sndbuf); err != nil {
log.Error().Stack().Err(err).Msg("conn.SetWriteBuffer()")
return
}
go serveTCP(server, conn, r)
if r++; r == maxInt {
r = 0
}
}
}
func serveTCP(s *Comet, conn *net.TCPConn, r int) {
var (
// timer
tr = s.round.Timer(r)
rp = s.round.Reader(r)
wp = s.round.Writer(r)
)
s.ServeTCP(conn, rp, wp, tr)
}
// ServeTCP serve a tcp connection.
func (s *Comet) ServeTCP(conn *net.TCPConn, rp, wp *bytes.Pool, tr *xtime.Timer) {
var (
err error
hb time.Duration
p *grpc.Proto
b *Bucket
trd *xtime.TimerData
lastHb = time.Now()
rb = rp.Get()
wb = wp.Get()
ch = NewChannel(s.c.Protocol.CliProto, s.c.Protocol.SvrProto)
rr = &ch.Reader
wr = &ch.Writer
tsk *dtask.Task
)
ch.Reader.ResetBuffer(conn, rb.Bytes())
ch.Writer.ResetBuffer(conn, wb.Bytes())
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// handshake
step := 0
trd = tr.Add(time.Duration(s.c.Protocol.HandshakeTimeout), func() {
conn.Close()
log.Error().Int("step", step).Str("key", ch.Key).Str("remoteIP", conn.RemoteAddr().String()).Msg("tcp handshake timeout")
})
ch.IP, ch.Port, _ = net.SplitHostPort(conn.RemoteAddr().String())
// must not setadv, only used in auth
step = 1
if p, err = ch.CliProto.Set(); err == nil {
if ch.Key, hb, err = s.authTCP(ctx, rr, wr, p); err == nil {
log.Info().Str("key", ch.Key).Str("remoteIP", conn.RemoteAddr().String()).Msg("authoried")
b = s.Bucket(ch.Key)
err = b.Put(ch)
}
}
step = 2
if err != nil {
conn.Close()
rp.Put(rb)
wp.Put(wb)
tr.Del(trd)
log.Error().Str("key", ch.Key).Err(err).Msg("handshake failed")
return
}
trd.Key = ch.Key
tr.Set(trd, hb)
step = 3
// hanshake ok start dispatch goroutine
tsk = dtask.NewTask()
go s.dispatchTCP(conn, wr, wp, wb, ch, tsk)
serverHeartbeat := s.RandServerHearbeat()
for {
if p, err = ch.CliProto.Set(); err != nil {
break
}
if err = p.ReadTCP(rr); err != nil {
log.Info().Err(err).Msg("ReadTCP failed")
break
}
if p.Op == int32(grpc.Op_Heartbeat) {
tr.Set(trd, hb)
p.Op = int32(grpc.Op_HeartbeatReply)
p.Body = nil
// NOTE: send server heartbeat for a long time
if now := time.Now(); now.Sub(lastHb) > serverHeartbeat {
if err1 := s.Heartbeat(ctx, ch.Key); err1 == nil {
lastHb = now
}
}
step++
} else {
if err = s.Operate(ctx, p, ch, tsk); err != nil {
break
}
}
// msg sent from client will be dispatched to client itself
ch.CliProto.SetAdv()
ch.Signal()
}
if err != nil && err != io.EOF && !strings.Contains(err.Error(), "closed") {
log.Error().Str("key", ch.Key).Err(err).Msg("server tcp failed")
}
b.Del(ch)
tr.Del(trd)
rp.Put(rb)
conn.Close()
ch.Close()
if err = s.Disconnect(ctx, ch.Key); err != nil {
log.Error().Str("key", ch.Key).Err(err).Msg("operator do disconnect")
}
}
// dispatch accepts connections on the listener and serves requests
// for each incoming connection. dispatch blocks; the caller typically
// invokes it in a go statement.
func (s *Comet) dispatchTCP(conn *net.TCPConn, wr *bufio.Writer, wp *bytes.Pool, wb *bytes.Buffer, ch *Channel, tsk *dtask.Task) {
var (
err error
finish bool
online int32
)
for {
var p = ch.Ready()
switch p {
case grpc.ProtoFinish:
finish = true
goto failed
case grpc.ProtoReady:
// fetch message from svrbox(client send)
for {
if p, err = ch.CliProto.Get(); err != nil {
break
}
if p.Op == int32(grpc.Op_HeartbeatReply) {
if err = p.WriteTCPHeart(wr, online); err != nil {
goto failed
}
} else if p.Op == int32(grpc.Op_ReceiveMsgReply) {
//skip
} else if p.Op == int32(grpc.Op_SendMsg) {
//skip
} else {
if err = p.WriteTCP(wr); err != nil {
goto failed
}
}
p.Body = nil // avoid memory leak
ch.CliProto.GetAdv()
}
default:
switch p.Op {
case int32(grpc.Op_SendMsgReply):
if err = p.WriteTCP(wr); err != nil {
goto failed
}
case int32(grpc.Op_RePush):
pro := proto.Clone(p)
if p, ok := pro.(*grpc.Proto); ok {
p.Op = int32(grpc.Op_ReceiveMsg)
if err = p.WriteTCP(wr); err != nil {
goto failed
}
} else {
log.Error().Msg("proto can`t clone Proto")
}
case int32(grpc.Op_ReceiveMsg):
if err = p.WriteTCP(wr); err != nil {
goto failed
}
p.Op = int32(grpc.Op_RePush)
seq := strconv.FormatInt(int64(p.Seq), 10)
if j := tsk.Get(seq); j != nil {
continue
}
//push into task pool
job, inserted := tsk.AddJobRepeat(time.Second*5, 0, func() {
if _, err = ch.Push(p); err != nil {
log.Error().Err(err).Msg("task job ch.Push error")
return
}
})
if !inserted {
log.Error().Err(err).Msg("tsk.AddJobRepeat error")
goto failed
}
tsk.Add(seq, job)
default:
continue
}
}
// only hungry flush response
if err = wr.Flush(); err != nil {
break
}
}
failed:
if err != nil {
log.Error().Str("key", ch.Key).Err(err).Msg("dispatch tcp failed")
}
tsk.Stop()
conn.Close()
wp.Put(wb)
// must ensure all channel message discard, for reader won't blocking Signal
for !finish {
finish = (ch.Ready() == grpc.ProtoFinish)
}
}
// auth for goim handshake with client, use rsa & aes.
func (s *Comet) authTCP(ctx context.Context, rr *bufio.Reader, wr *bufio.Writer, p *grpc.Proto) (key string, hb time.Duration, err error) {
for {
if err = p.ReadTCP(rr); err != nil {
return
}
if p.Op == int32(grpc.Op_Auth) {
break
} else {
log.Error().Int32("option", p.Op).Msg("tcp request option not auth")
}
}
if key, hb, err = s.Connect(ctx, p); err != nil {
log.Error().Err(err).Msg("can not call logic.Connect")
return
}
p.Op = int32(grpc.Op_AuthReply)
p.Body = nil
if err = p.WriteTCP(wr); err != nil {
return
}
err = wr.Flush()
return
}

341
comet/ws.go Normal file
View File

@@ -0,0 +1,341 @@
package comet
import (
"context"
"fmt"
"io"
"net"
"runtime"
"strconv"
"strings"
"time"
"github.com/Terry-Mao/goim/pkg/bytes"
xtime "github.com/Terry-Mao/goim/pkg/time"
"github.com/Terry-Mao/goim/pkg/websocket"
"github.com/rs/zerolog/log"
"gitlab.33.cn/chat/im/api/comet/grpc"
"gitlab.33.cn/chat/im/dtask"
)
const (
maxInt = 1<<31 - 1
)
// InitWebsocket listen all tcp.bind and start accept connections.
func InitWebsocket(server *Comet, addrs []string, accept int) (err error) {
var (
bind string
listener *net.TCPListener
addr *net.TCPAddr
)
for _, bind = range addrs {
if addr, err = net.ResolveTCPAddr("tcp", bind); err != nil {
log.Error().Stack().Err(err).Msg(fmt.Sprintf("net.ResolveTCPAddr(tcp, %s)", bind))
return
}
if listener, err = net.ListenTCP("tcp", addr); err != nil {
log.Error().Stack().Err(err).Msg(fmt.Sprintf("net.ListenTCP(tcp, %s)", bind))
return
}
log.Info().Str("bind", bind).Msg("start ws listen")
// split N core accept
for i := 0; i < accept; i++ {
go acceptWebsocket(server, listener)
}
}
return
}
// Accept accepts connections on the listener and serves requests
// for each incoming connection. Accept blocks; the caller typically
// invokes it in a go statement.
func acceptWebsocket(server *Comet, lis *net.TCPListener) {
defer func() {
buf := make([]byte, 1024*3)
runtime.Stack(buf, false)
log.Error().Str("panic", string(buf)).Msg("acceptWebsocket done")
if r := recover(); r != nil {
buf := make([]byte, 1024*3)
runtime.Stack(buf, false)
log.Error().Interface("recover", r).Str("panic", string(buf)).Msg("Recovered in acceptWebsocket")
}
}()
var (
conn *net.TCPConn
err error
r int
)
for {
if conn, err = lis.AcceptTCP(); err != nil {
// if listener close then return
log.Error().Stack().Err(err).Msg(fmt.Sprintf("listener.Accept(\"%s\")", lis.Addr().String()))
continue
//return
}
log.Info().Str("remoteIP", conn.RemoteAddr().String()).Msg("accept ws conn")
if err = conn.SetKeepAlive(server.c.TCP.KeepAlive); err != nil {
log.Error().Stack().Err(err).Msg("conn.SetKeepAlive()")
return
}
if err = conn.SetReadBuffer(server.c.TCP.Rcvbuf); err != nil {
log.Error().Stack().Err(err).Msg("conn.SetReadBuffer()")
return
}
if err = conn.SetWriteBuffer(server.c.TCP.Sndbuf); err != nil {
log.Error().Stack().Err(err).Msg("conn.SetWriteBuffer()")
return
}
go serveWebsocket(server, conn, r)
if r++; r == maxInt {
r = 0
}
}
}
func serveWebsocket(s *Comet, conn net.Conn, r int) {
var (
// timer
tr = s.round.Timer(r)
rp = s.round.Reader(r)
wp = s.round.Writer(r)
)
s.ServeWebsocket(conn, rp, wp, tr)
}
// ServeWebsocket serve a websocket connection.
func (s *Comet) ServeWebsocket(conn net.Conn, rp, wp *bytes.Pool, tr *xtime.Timer) {
var (
err error
hb time.Duration
p *grpc.Proto
b *Bucket
trd *xtime.TimerData
lastHB = time.Now()
rb = rp.Get()
ch = NewChannel(s.c.Protocol.CliProto, s.c.Protocol.SvrProto)
rr = &ch.Reader
wr = &ch.Writer
ws *websocket.Conn // websocket
req *websocket.Request
tsk *dtask.Task
)
// reader
ch.Reader.ResetBuffer(conn, rb.Bytes())
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// handshake
step := 0
trd = tr.Add(time.Duration(s.c.Protocol.HandshakeTimeout), func() {
// NOTE: fix close block for tls
_ = conn.SetDeadline(time.Now().Add(time.Millisecond * 100))
_ = conn.Close()
log.Error().Int("step", step).Str("key", ch.Key).Str("remoteIP", conn.RemoteAddr().String()).Msg("ws handshake timeout")
})
// websocket
ch.IP, ch.Port, _ = net.SplitHostPort(conn.RemoteAddr().String())
step = 1
if req, err = websocket.ReadRequest(rr); err != nil || req.RequestURI != "/sub" {
conn.Close()
tr.Del(trd)
rp.Put(rb)
if err != io.EOF {
log.Error().Err(err).Msg("http.ReadRequest(rr)")
}
return
}
// writer
wb := wp.Get()
ch.Writer.ResetBuffer(conn, wb.Bytes())
step = 2
if ws, err = websocket.Upgrade(conn, rr, wr, req); err != nil {
conn.Close()
tr.Del(trd)
rp.Put(rb)
wp.Put(wb)
if err != io.EOF {
log.Error().Err(err).Msg("websocket.NewServerConn")
}
return
}
// must not setadv, only used in auth
step = 3
if p, err = ch.CliProto.Set(); err == nil {
if ch.Key, hb, err = s.authWebsocket(ctx, ws, p); err == nil {
log.Info().Str("key", ch.Key).Str("remoteIP", conn.RemoteAddr().String()).Msg("authoried")
b = s.Bucket(ch.Key)
err = b.Put(ch)
}
}
step = 4
if err != nil {
ws.Close()
rp.Put(rb)
wp.Put(wb)
tr.Del(trd)
if err != io.EOF && err != websocket.ErrMessageClose {
log.Error().Int("step", step).Str("key", ch.Key).Str("remoteIP", conn.RemoteAddr().String()).Msg("ws handshake failed")
}
return
}
trd.Key = ch.Key
tr.Set(trd, hb)
// hanshake ok start dispatch goroutine
step = 5
tsk = dtask.NewTask()
go s.dispatchWebsocket(ws, wp, wb, ch, tsk)
serverHeartbeat := s.RandServerHearbeat()
for {
if p, err = ch.CliProto.Set(); err != nil {
break
}
if err = p.ReadWebsocket(ws); err != nil {
break
}
if p.Op == int32(grpc.Op_Heartbeat) {
tr.Set(trd, hb)
p.Op = int32(grpc.Op_HeartbeatReply)
p.Body = nil
// NOTE: send server heartbeat for a long time
if now := time.Now(); now.Sub(lastHB) > serverHeartbeat {
if err1 := s.Heartbeat(ctx, ch.Key); err1 == nil {
lastHB = now
}
}
step++
} else {
if err = s.Operate(ctx, p, ch, tsk); err != nil {
break
}
}
ch.CliProto.SetAdv()
ch.Signal()
}
if err != nil && err != io.EOF && err != websocket.ErrMessageClose && !strings.Contains(err.Error(), "closed") {
log.Error().Err(err).Str("key", ch.Key).Msg("server ws failed")
}
b.Del(ch)
tr.Del(trd)
ws.Close()
ch.Close()
rp.Put(rb)
if err = s.Disconnect(ctx, ch.Key); err != nil {
log.Error().Err(err).Str("key", ch.Key).Msg("operator do disconnect")
}
}
// dispatch accepts connections on the listener and serves requests
// for each incoming connection. dispatch blocks; the caller typically
// invokes it in a go statement.
func (s *Comet) dispatchWebsocket(ws *websocket.Conn, wp *bytes.Pool, wb *bytes.Buffer, ch *Channel, tsk *dtask.Task) {
var (
err error
finish bool
online int32
)
for {
var p = ch.Ready()
switch p {
case grpc.ProtoFinish:
finish = true
goto failed
case grpc.ProtoReady:
// fetch message from svrbox(client send)
for {
if p, err = ch.CliProto.Get(); err != nil {
break
}
if p.Op == int32(grpc.Op_HeartbeatReply) {
if err = p.WriteWebsocketHeart(ws, online); err != nil {
goto failed
}
} else if p.Op == int32(grpc.Op_ReceiveMsgReply) {
//skip
} else if p.Op == int32(grpc.Op_SendMsg) {
//skip
} else {
if err = p.WriteWebsocket(ws); err != nil {
goto failed
}
}
p.Body = nil // avoid memory leak
ch.CliProto.GetAdv()
}
default:
switch p.Op {
case int32(grpc.Op_SendMsgReply):
if err = p.WriteWebsocket(ws); err != nil {
goto failed
}
case int32(grpc.Op_RePush):
p.Op = int32(grpc.Op_ReceiveMsg)
if err = p.WriteWebsocket(ws); err != nil {
goto failed
}
case int32(grpc.Op_ReceiveMsg):
if err = p.WriteWebsocket(ws); err != nil {
goto failed
}
p.Op = int32(grpc.Op_RePush)
seq := strconv.FormatInt(int64(p.Seq), 10)
if j := tsk.Get(seq); j != nil {
continue
}
//push into task pool
job, inserted := tsk.AddJobRepeat(time.Second*5, 0, func() {
if _, err = ch.Push(p); err != nil {
log.Error().Err(err).Msg("task job ch.Push error")
return
}
})
if !inserted {
log.Error().Err(err).Msg("tsk.AddJobRepeat error")
goto failed
}
tsk.Add(seq, job)
default:
continue
}
}
// only hungry flush response
if err = ws.Flush(); err != nil {
break
}
}
failed:
if err != nil && err != io.EOF && err != websocket.ErrMessageClose {
log.Error().Err(err).Str("key", ch.Key).Msg("dispatch ws error")
}
tsk.Stop()
ws.Close()
wp.Put(wb)
// must ensure all channel message discard, for reader won't blocking Signal
for !finish {
finish = (ch.Ready() == grpc.ProtoFinish)
}
}
// auth for goim handshake with client, use rsa & aes.
func (s *Comet) authWebsocket(ctx context.Context, ws *websocket.Conn, p *grpc.Proto) (key string, hb time.Duration, err error) {
for {
if err = p.ReadWebsocket(ws); err != nil {
return
}
if p.Op == int32(grpc.Op_Auth) {
break
} else {
log.Error().Int32("operation", p.Op).Msg("ws request operation not auth")
}
}
if key, hb, err = s.Connect(ctx, p); err != nil {
log.Error().Err(err).Msg("can not call logic.Connect")
return
}
p.Op = int32(grpc.Op_AuthReply)
p.Body = nil
if err = p.WriteWebsocket(ws); err != nil {
return
}
err = ws.Flush()
return
}

66
common/grpc.go Normal file
View File

@@ -0,0 +1,66 @@
package common
import (
"context"
"fmt"
"time"
xkey "gitlab.33.cn/chat/im/naming/balancer/key"
"google.golang.org/grpc"
"google.golang.org/grpc/balancer/roundrobin"
"google.golang.org/grpc/keepalive"
)
const (
grpcInitialWindowSize = 1 << 24
grpcInitialConnWindowSize = 1 << 24
grpcMaxSendMsgSize = 1 << 24
grpcMaxCallMsgSize = 1 << 24
grpcKeepAliveTime = time.Second * 10
grpcKeepAliveTimeout = time.Second * 3
grpcBackoffMaxDelay = time.Second * 3
)
func NewGRPCConn(addr string, timeout time.Duration, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
dialOpts := []grpc.DialOption{
grpc.WithInsecure(),
grpc.WithInitialWindowSize(grpcInitialWindowSize),
grpc.WithInitialConnWindowSize(grpcInitialConnWindowSize),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcMaxCallMsgSize)),
grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(grpcMaxSendMsgSize)),
grpc.WithBackoffMaxDelay(grpcBackoffMaxDelay),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: grpcKeepAliveTime,
Timeout: grpcKeepAliveTimeout,
PermitWithoutStream: true,
}),
grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, roundrobin.Name)),
}
dialOpts = append(dialOpts, opts...)
return grpc.DialContext(ctx, addr, dialOpts...)
}
func NewGRPCConnWithKey(addr string, timeout time.Duration, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
dialOpts := []grpc.DialOption{
grpc.WithInsecure(),
grpc.WithInitialWindowSize(grpcInitialWindowSize),
grpc.WithInitialConnWindowSize(grpcInitialConnWindowSize),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcMaxCallMsgSize)),
grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(grpcMaxSendMsgSize)),
grpc.WithBackoffMaxDelay(grpcBackoffMaxDelay),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: grpcKeepAliveTime,
Timeout: grpcKeepAliveTimeout,
PermitWithoutStream: true,
}),
grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, xkey.Name)),
}
dialOpts = append(dialOpts, opts...)
return grpc.DialContext(ctx, addr, dialOpts...)
}

BIN
docs/goim-arch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

63
docs/proto.md Normal file
View File

@@ -0,0 +1,63 @@
# comet 客户端通讯协议
`comet` 支持两种协议和客户端通讯 `websocket``tcp`
**请求URL**
```
ws://DOMAIN/sub/
tcp://DOMAIN
```
**协议格式**
二进制,请求和返回协议一致
**请求&返回参数**
| 参数名 | 必选 | 类型 | 说明 |
| :----- | :--- | :--- | :--- |
| package length | true | int32 bigendian | 包长度 |
| header Length | true | int16 bigendian | 包头长度 |
| ver | true | int16 bigendian | 协议版本 |
| operation | true | int32 bigendian | 协议指令 |
| seq | true | int32 bigendian | 序列号 |
| ack | true | int32 bigendian | 确认号 |
| body | false | binary | $(package lenth) - $(header length) |
## 指令
| 指令 | 说明 |
| :----- | :--- |
| 1 | 客户端连接认证 |
| 2 | 客户端连接认证响应 |
| 3 | 客户端请求心跳 |
| 4 | 服务端心跳答复 |
| 5 | 客户端断开连接 |
| 6 | 客户端断开连接响应 |
| 7 | 客户端发送消息 |
| 8 | 客户端发送消息响应 |
| 9 | 客户端接收消息 |
| 10 | 客户端接收消息响应 |
| 14 | 多端同步指令(未使用) |
| 15 | 多端同步指令响应(未使用) |
### 连接认证
operation 为 1 时, body 必须可以反序列化为 AuthMsg
```
message AuthMsg {
string appId = 1;
string token = 2;
bytes ext = 3; // 其它业务方可能需要的信息
}
```
operation 为 15 时, body 必须可以反序列化为 SyncMsg
```
message SyncMsg {
int64 logId = 1;
}
```
具体协议参考 `api/comet/grpc/comet.proto``api/logic/grpc/comet.proto`

327
dtask/clock.go Normal file
View File

@@ -0,0 +1,327 @@
// Package clock is a low consumption, low latency support for frequent updates of large capacity timing manager
// 1、能够添加一次性、重复性任务并能在其执行前撤销或频繁更改。
// 2、支持同一时间点多个任务提醒。
// 3、适用于中等密度大跨度的单次、多次定时任务。
// 4、支持10万次/秒的定时任务执行、提醒、撤销或添加操作平均延迟10微秒内
// 5、支持注册任务的函数调用及事件通知。
// 基本处理逻辑:
// 1、重复性任务流程是
// a、注册重复任务
// b、时间抵达时控制器调用注册函数并发送通知
// c、如果次数达到限制则撤销否则控制器更新该任务的下次执行时间点
// d、控制器等待下一个最近需要执行的任务
// 2、一次性任务可以是服务运行时当前时间点之后的任意事件流程是
// a、注册一次性任务
// b、时间抵达时控制器调用注册函数并发送通知
// c、控制器释放该任务
// d、控制器等待下一个最近需要执行的任务
// 使用方式,参见示例代码。
package dtask
import (
"gitlab.33.cn/chat/im/dtask/pkg/rbtree"
"math"
"sync"
"sync/atomic"
"time"
)
/*
This code is based on the following resources:
source code: https://github.com/alex023/clock.git
*/
const _UNTOUCHED = time.Duration(math.MaxInt64)
var (
defaultClock *Clock
oncedo sync.Once
)
//Default return singal default clock
func Default() *Clock {
oncedo.Do(initClock)
return defaultClock
}
func initClock() {
defaultClock = NewClock()
}
// Clock is jobs schedule
type Clock struct {
seq uint64
count uint64 //已执行次数不得大于times
waitJobsNum uint64 //num of jobs which wait for action
jobQueue *rbtree.Rbtree //inner memory storage
pauseChan chan struct{}
resumeChan chan struct{}
exitChan chan struct{}
}
var singal = struct{}{}
//NewClock Create a task queue controller
func NewClock() *Clock {
c := &Clock{
jobQueue: rbtree.New(),
pauseChan: make(chan struct{}, 0),
resumeChan: make(chan struct{}, 0),
exitChan: make(chan struct{}, 0),
}
c.start()
return c
}
func (jl *Clock) start() {
now := time.Now()
untouchedJob := jobItem{
createTime: now,
intervalTime: time.Duration(math.MaxInt64),
fn: func() {
//this jobItem is untouched.
},
}
_, inserted := jl.addJob(now, untouchedJob.intervalTime, 1, untouchedJob.fn)
if !inserted {
panic("[clock] internal error.Reason cannot insert job.")
}
//开启守护协程
go jl.schedule()
jl.resume()
}
func (jl *Clock) pause() {
jl.pauseChan <- singal
}
func (jl *Clock) resume() {
jl.resumeChan <- singal
}
func (jl *Clock) exit() {
jl.exitChan <- singal
}
func (jl *Clock) immediate() {
for {
if item := jl.jobQueue.Min(); item != nil {
atomic.AddUint64(&jl.count, 1)
job := item.(*jobItem)
job.action(false)
jl.removeJob(job)
} else {
break
}
}
}
func (jl *Clock) schedule() {
var (
timeout time.Duration
job *jobItem
timer = newSafeTimer(_UNTOUCHED)
)
defer timer.Stop()
Pause:
<-jl.resumeChan
for {
job, _ = jl.jobQueue.Min().(*jobItem) //ignore ok-assert
timeout = job.actionTime.Sub(time.Now())
timer.SafeReset(timeout)
select {
case <-timer.C:
timer.SCR()
atomic.AddUint64(&jl.count, 1)
job.action(true)
if job.actionMax == 0 || job.actionMax > job.actionCount {
jl.jobQueue.Delete(job)
job.actionTime = job.actionTime.Add(job.intervalTime)
jl.jobQueue.Insert(job)
} else {
jl.removeJob(job)
}
case <-jl.pauseChan:
goto Pause
case <-jl.exitChan:
goto Exit
}
}
Exit:
}
// UpdateJobTimeout update a timed task with time duration after now
// @job: job identifier
// @actionTime: new job schedule time,must be greater than 0
func (jl *Clock) UpdateJobTimeout(job Job, actionTime time.Duration) (updated bool) {
if job == nil || actionTime.Nanoseconds() <= 0 || !job.isAvailable() {
return false
}
now := time.Now()
item, ok := job.(*jobItem)
if !ok {
return false
}
jl.pause()
defer jl.resume()
// update jobitem in job queue
jl.jobQueue.Delete(item)
item.actionTime = now.Add(actionTime)
jl.jobQueue.Insert(item)
updated = true
return
}
// AddJobWithInterval insert a timed task with time duration after now
// @actionTime: Duration after now
// @jobFunc: Callback function,not nil
// return
// @jobScheduled: A reference to a task that has been scheduled.
func (jl *Clock) AddJobWithInterval(actionInterval time.Duration, jobFunc func()) (jobScheduled Job, inserted bool) {
if jobFunc == nil || actionInterval.Nanoseconds() <= 0 {
return
}
now := time.Now()
jl.pause()
jobScheduled, inserted = jl.addJob(now, actionInterval, 1, jobFunc)
jl.resume()
return
}
// AddJobWithDeadtime insert a timed task with time point after now
// @actionTime: Execution start time. must after now
// @jobFunc: Callback function,not nil
// return
// @jobScheduled : A reference to a task that has been scheduled.
// @inserted : return false ,if actionTime before time.Now or jobFunc is nil
func (jl *Clock) AddJobWithDeadtime(actionTime time.Time, jobFunc func()) (jobScheduled Job, inserted bool) {
actionInterval := actionTime.Sub(time.Now())
if jobFunc == nil || actionInterval.Nanoseconds() <= 0 {
return
}
now := time.Now()
jl.pause()
jobScheduled, inserted = jl.addJob(now, actionInterval, 1, jobFunc)
jl.resume()
return
}
// AddJobRepeat add a repeat task with interval duration
// @interval: The interval between two actions of the job
// @actionMax: The number of job execution
// @jobFunc: Callback function,not nil
// return
// @jobScheduled : A reference to a task that has been scheduled.
// @inserted : return false ,if interval is not Positiveor jobFunc is nil
//Note
// when jobTimes==0,the job will be executed without limitation。If you no longer use, be sure to call the DelJob method to release
func (jl *Clock) AddJobRepeat(interval time.Duration, actionMax uint64, jobFunc func()) (jobScheduled Job, inserted bool) {
if jobFunc == nil || interval.Nanoseconds() <= 0 {
return
}
now := time.Now()
jl.pause()
jobScheduled, inserted = jl.addJob(now, interval, actionMax, jobFunc)
jl.resume()
return
}
func (jl *Clock) addJob(createTime time.Time, actionInterval time.Duration, actionMax uint64, jobFunc func()) (job *jobItem, inserted bool) {
jl.seq++
jl.waitJobsNum++
job = &jobItem{
id: jl.seq,
actionMax: actionMax,
createTime: createTime,
actionTime: createTime.Add(actionInterval),
intervalTime: actionInterval,
msgChan: make(chan Job, 10),
fn: jobFunc,
clock: jl,
}
jl.jobQueue.Insert(job)
inserted = true
return
}
func (jl *Clock) removeJob(item *jobItem) {
if jl.jobQueue.Delete(item) != nil {
jl.waitJobsNum--
//job.Cancel --> rmJob -->removeJob; schedule -->removeJob
//it is call repeatly when Job.Cancel
if atomic.CompareAndSwapInt32(&item.cancelFlag, 0, 1) {
item.innerCancel()
}
}
}
func (jl *Clock) rmJob(job *jobItem) {
jl.pause()
defer jl.resume()
jl.removeJob(job)
return
}
// Count 已经执行的任务数。对于重复任务,会计算多次
func (jl *Clock) Count() uint64 {
return atomic.LoadUint64(&jl.count)
}
//重置Clock的内部状态
func (jl *Clock) Reset() *Clock {
jl.exit()
jl.count = 0
jl.cleanJobs()
jl.start()
return jl
}
func (jl *Clock) cleanJobs() {
item := jl.jobQueue.Min()
for item != nil {
job, ok := item.(*jobItem)
if ok {
jl.removeJob(job)
}
item = jl.jobQueue.Min()
}
}
//WaitJobs get how much jobs waiting for call
func (jl *Clock) WaitJobs() uint64 {
jobs := atomic.LoadUint64(&jl.waitJobsNum) - 1
return jobs
}
//Stop stop clock , and cancel all waiting jobs
func (jl *Clock) Stop() {
jl.exit()
jl.cleanJobs()
}
//StopGracefull stop clock ,and do once every waiting job including Once\Reapeat
//Note:对于任务队列中,即使安排执行多次或者不限次数的,也仅仅执行一次。
func (jl *Clock) StopGraceful() {
jl.exit()
jl.immediate()
}

BIN
dtask/cmd/cli_task Normal file

Binary file not shown.

BIN
dtask/cmd/client/dtaskCli Normal file

Binary file not shown.

143
dtask/cmd/client/main.go Normal file
View File

@@ -0,0 +1,143 @@
package main
import (
"bufio"
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"net/url"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"github.com/gorilla/websocket"
"github.com/oofpgDLD/dtask/proto"
)
var addr = flag.String("addr", "172.16.101.107:17070", "http service address")
var (
closer = make(chan int, 1)
times = 5
users = 10000
u = url.URL{Scheme: "ws", Host: *addr, Path: "/test"}
)
func main() {
flag.Parse()
log.SetFlags(0)
for i := 0; i < users; i++ {
go NewUser(i, times)
}
go getInputByScanner()
// init signal
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Printf("client get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
log.Print("client exit")
return
case syscall.SIGHUP:
// TODO reload
default:
return
}
}
}
func NewUser(id int, times int) {
uid := fmt.Sprint(id)
log.Printf("connecting to %s, id %v", u.String(), fmt.Sprint(id))
c, resp, err := websocket.DefaultDialer.Dial(u.String(), http.Header{
"FZM-UID": {fmt.Sprint(id)},
})
if err != nil {
log.Fatal("dial:", err, resp, u.String())
return
}
defer c.Close()
done := make(chan struct{})
go func() {
defer close(done)
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
return
}
log.Printf("recv: %s", message)
}
}()
seq := 0
ticker := time.NewTicker(time.Millisecond * 500)
defer ticker.Stop()
for {
select {
case <-done:
return
case <-ticker.C:
seq++
msg, err := Msg(uid, int64(seq))
if err != nil {
log.Println("marshal proto:", err)
return
}
err = c.WriteMessage(websocket.TextMessage, msg)
if err != nil {
log.Println("write:", err)
return
}
if seq >= times {
ticker.Stop()
}
case index := <-closer:
log.Println("interrupt")
if index == id {
log.Println("exit")
return
}
closer <- index
}
}
}
func Msg(uid string, seq int64) ([]byte, error) {
p := &proto.Proto{
Uid: uid,
Opt: proto.Start,
Seq: seq,
}
return json.Marshal(p)
}
func getInputByScanner() string {
var str string
for {
//使用os.Stdin开启输入流
in := bufio.NewScanner(os.Stdin)
if in.Scan() {
str = in.Text()
} else {
str = "Find input error"
}
index, err := strconv.ParseInt(str, 10, 64)
if err != nil {
continue
}
closer <- int(index)
}
}

BIN
dtask/cmd/dtaskSrv Normal file

Binary file not shown.

188
dtask/cmd/main.go Normal file
View File

@@ -0,0 +1,188 @@
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"runtime/pprof"
"runtime/trace"
"syscall"
"time"
gpprof "github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/oofpgDLD/dtask"
"github.com/oofpgDLD/dtask/proto"
)
const (
_trace = false
_pprof = false
_pprof_net = true
)
var addr = flag.String("addr", ":17070", "http service address")
func main() {
if _trace {
f, err := os.Create("trace.out")
if err != nil {
panic(err)
}
defer f.Close()
err = trace.Start(f)
if err != nil {
panic(err)
}
defer trace.Stop()
}
if _pprof {
f, err := os.Create("pprof.out")
if err != nil {
panic(err)
}
err = pprof.StartCPUProfile(f)
if err != nil {
panic(err)
}
defer pprof.StopCPUProfile()
}
r := gin.Default()
// websocket
r.GET("/test", ServeWs)
r.GET("/test2", func(context *gin.Context) {
context.String(http.StatusOK, "success")
})
srv := &http.Server{
Addr: *addr,
Handler: r,
}
if _pprof_net {
log.Printf("serve %s", *addr)
gpprof.Register(r)
}
go func() {
err := srv.ListenAndServe()
if err != nil {
log.Printf("http server stop %v", err)
}
}()
// init signal
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Printf("service get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
log.Print("server exit")
return
case syscall.SIGHUP:
// TODO reload
default:
return
}
}
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
// allow cross-origin
CheckOrigin: func(r *http.Request) bool {
return true
},
}
var (
newline = []byte{'\n'}
space = []byte{' '}
)
func ServeWs(context *gin.Context) {
var (
ck *dtask.Clock
jobCache map[string]dtask.Job
wCh chan []byte
)
// userId = context.GetHeader("FZM-UID")
c, err := upgrader.Upgrade(context.Writer, context.Request, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
wCh = make(chan []byte)
defer c.Close()
defer onClose(ck)
done := make(chan struct{})
ck = dtask.NewClock()
jobCache = make(map[string]dtask.Job)
go func() {
defer close(done)
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
return
}
log.Printf("recv: %s", message)
onRecvMsg(ck, jobCache, wCh, message)
}
}()
for {
select {
case <-done:
return
case data := <-wCh:
err = c.WriteMessage(websocket.TextMessage, data)
if err != nil {
log.Println("write:", err)
break
}
}
}
}
func onRecvMsg(ck *dtask.Clock, jc map[string]dtask.Job, wCh chan []byte, msg []byte) {
msg = bytes.TrimSpace(bytes.Replace(msg, newline, space, -1))
p := proto.Proto{}
json.Unmarshal(msg, &p)
switch p.Opt {
case proto.Start:
job, inserted := ck.AddJobRepeat(time.Second*5, 0, func() {
wCh <- msg
})
if !inserted {
break
}
jc[fmt.Sprint(p.Seq)] = job
case proto.Stop:
if job := jc[fmt.Sprint(p.Seq)]; job != nil {
job.Cancel()
}
default:
}
}
func onClose(ck *dtask.Clock) {
if ck == nil {
log.Printf("onClose WsConnection args can not find")
return
}
ck.Stop()
}

BIN
dtask/cmd/trace.out Normal file

Binary file not shown.

6
dtask/debug.go Normal file
View File

@@ -0,0 +1,6 @@
package dtask
const (
// Debug debug switch
Debug = false
)

99
dtask/job.go Normal file
View File

@@ -0,0 +1,99 @@
package dtask
import (
"gitlab.33.cn/chat/im/dtask/pkg/rbtree"
"log"
"runtime/debug"
"sync/atomic"
"time"
)
/*
This code is based on the following resources:
source code: https://github.com/alex023/clock.git
*/
// Job External access interface for timed tasks
type Job interface {
C() <-chan Job //C Get a Chanwhich can get message if Job is executed
Count() uint64 //计数器,表示已执行(或触发)的次数
Max() uint64 //允许执行的最大次数
Cancel() //撤销加载的任务,不再定时执行
isAvailable() bool //return trueif job not action or not cancel
}
// jobItem implementation of "Job" interface and "rbtree.Item" interface
type jobItem struct {
id uint64 //唯一键值,内部由管理器生成,以区分同一时刻的不同任务事件
actionCount uint64 //计数器,表示已执行(或触发)的次数
actionMax uint64 //允许执行的最大次数
intervalTime time.Duration //间隔时间
createTime time.Time //创建时间,略有误差
actionTime time.Time //计算得出的最近一次执行时间点
fn func() //事件函数
msgChan chan Job //消息通道,执行时,控制器通过该通道向外部传递消息
cancelFlag int32
clock *Clock
}
// Less Based rbtree implements Item interface for sort
func (je jobItem) Less(another rbtree.Item) bool {
item, ok := another.(*jobItem)
if !ok {
return false
}
if !je.actionTime.Equal(item.actionTime) {
return je.actionTime.Before(item.actionTime)
}
return je.id < item.id
}
func (je *jobItem) C() <-chan Job {
return je.msgChan
}
func (je *jobItem) action(async bool) {
je.actionCount++
if async {
go safeCall(je.fn)
} else {
safeCall(je.fn)
}
select {
case je.msgChan <- je:
default:
//some times,client should not receive msgChan,so must discard jobItem when blocking
}
}
func (je *jobItem) Cancel() {
if atomic.CompareAndSwapInt32(&je.cancelFlag, 0, 1) {
je.clock.rmJob(je)
je.innerCancel()
}
}
func (je *jobItem) isAvailable() bool {
return je.cancelFlag == 0
}
func (je *jobItem) innerCancel() {
je.clock = nil
close(je.msgChan)
}
// Count implement for Job
func (je jobItem) Count() uint64 {
return je.actionCount
}
// Max implement for Job
func (je jobItem) Max() uint64 {
return je.actionMax
}
func safeCall(fn func()) {
defer func() {
if err := recover(); err != nil {
log.Printf("[clock] recovering reason is %+v. More detail:", err)
log.Println(string(debug.Stack()))
}
}()
fn()
}

27
dtask/pkg/rbtree/LICENSE Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2015, Hu Keping
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
(*) Redistributions of source code must retain the above copyright notice, this list
of conditions and the following disclaimer.
(*) Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
(*) Neither the name of Hu Keping nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

143
dtask/pkg/rbtree/README.md Normal file
View File

@@ -0,0 +1,143 @@
# Rbtree [![GoDoc](https://godoc.org/github.com/HuKeping/rbtree?status.svg)](https://godoc.org/github.com/HuKeping/rbtree)
This is an implementation of Red-Black tree written by Golang and does **not** support `duplicate keys`.
## Installation
With a healthy Go language installed, simply run `go get github.com/HuKeping/rbtree`
## Example
All you have to do is to implement a comparison function `Less() bool` for your Item
which will be store in the Red-Black tree, here are some examples.
#### A simple case for `int` items.
package main
import (
"fmt"
"github.com/HuKeping/rbtree"
)
func main() {
rbt := rbtree.New()
m := 0
n := 10
for m < n {
rbt.Insert(rbtree.Int(m))
m++
}
m = 0
for m < n {
if m%2 == 0 {
rbt.Delete(rbtree.Int(m))
}
m++
}
// 1, 3, 5, 7, 9 were expected.
rbt.Ascend(rbt.Min(), Print)
}
func Print(item rbtree.Item) bool {
i, ok := item.(rbtree.Int)
if !ok {
return false
}
fmt.Println(i)
return true
}
#### A simple case for `string` items.
package main
import (
"fmt"
"github.com/HuKeping/rbtree"
)
func main() {
rbt := rbtree.New()
rbt.Insert(rbtree.String("Hello"))
rbt.Insert(rbtree.String("World"))
rbt.Ascend(rbt.Min(), Print)
}
func Print(item rbtree.Item) bool {
i, ok := item.(rbtree.String)
if !ok {
return false
}
fmt.Println(i)
return true
}
#### A quite interesting case for `struct` items.
package main
import (
"fmt"
"github.com/HuKeping/rbtree"
"time"
)
type Var struct {
Expiry time.Time `json:"expiry,omitempty"`
ID string `json:"id",omitempty`
}
// We will order the node by `Time`
func (x Var) Less(than rbtree.Item) bool {
return x.Expiry.Before(than.(Var).Expiry)
}
func main() {
rbt := rbtree.New()
var1 := Var{
Expiry: time.Now().Add(time.Second * 10),
ID: "var1",
}
var2 := Var{
Expiry: time.Now().Add(time.Second * 20),
ID: "var2",
}
var3 := Var{
Expiry: var2.Expiry,
ID: "var2-dup",
}
var4 := Var{
Expiry: time.Now().Add(time.Second * 40),
ID: "var4",
}
var5 := Var{
Expiry: time.Now().Add(time.Second * 50),
ID: "var5",
}
rbt.Insert(var1)
rbt.Insert(var2)
rbt.Insert(var3)
rbt.Insert(var4)
rbt.Insert(var5)
tmp := Var{
Expiry: var4.Expiry,
ID: "This field is not the key factor",
}
// var4 and var5 were expected
rbt.Ascend(rbt.Get(tmp), Print)
}
func Print(item rbtree.Item) bool {
i, ok := item.(Var)
if !ok {
return false
}
fmt.Printf("%+v\n", i)
return true
}

View File

@@ -0,0 +1,42 @@
// Copyright 2015, Hu Keping. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"github.com/oofpgDLD/dtask/pkg/rbtree"
)
func main() {
rbt := rbtree.New()
m := 0
n := 10
for m < n {
rbt.Insert(rbtree.Int(m))
m++
}
m = 0
for m < n {
if m%2 == 0 {
rbt.Delete(rbtree.Int(m))
}
m++
}
rbt.Ascend(rbt.Min(), print)
}
func print(item rbtree.Item) bool {
i, ok := item.(rbtree.Int)
if !ok {
return false
}
fmt.Println(i)
return true
}

View File

@@ -0,0 +1,29 @@
// Copyright 2015, Hu Keping. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"github.com/oofpgDLD/dtask/pkg/rbtree"
)
func main() {
rbt := rbtree.New()
rbt.Insert(rbtree.String("Hello"))
rbt.Insert(rbtree.String("World"))
rbt.Ascend(rbt.Min(), print)
}
func print(item rbtree.Item) bool {
i, ok := item.(rbtree.String)
if !ok {
return false
}
fmt.Println(i)
return true
}

View File

@@ -0,0 +1,71 @@
// Copyright 2015, Hu Keping. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"time"
"github.com/oofpgDLD/dtask/pkg/rbtree"
)
// Var is the node of a struct
type Var struct {
Expiry time.Time `json:"expiry,omitempty"`
ID string `json:"id,omitempty"`
}
// Less will order the node by `Time`
func (x Var) Less(than rbtree.Item) bool {
return x.Expiry.Before(than.(Var).Expiry)
}
func main() {
rbt := rbtree.New()
var1 := Var{
Expiry: time.Now().Add(time.Second * 10),
ID: "var1",
}
var2 := Var{
Expiry: time.Now().Add(time.Second * 20),
ID: "var2",
}
var3 := Var{
Expiry: var2.Expiry,
ID: "var2-dup",
}
var4 := Var{
Expiry: time.Now().Add(time.Second * 40),
ID: "var4",
}
var5 := Var{
Expiry: time.Now().Add(time.Second * 50),
ID: "var5",
}
rbt.Insert(var1)
rbt.Insert(var2)
rbt.Insert(var3)
rbt.Insert(var4)
rbt.Insert(var5)
tmp := Var{
Expiry: var4.Expiry,
ID: "This field is not the key factor",
}
// var4 and var5 were expected
rbt.Ascend(rbt.Get(tmp), print)
}
func print(item rbtree.Item) bool {
i, ok := item.(Var)
if !ok {
return false
}
fmt.Printf("%+v\n", i)
return true
}

View File

@@ -0,0 +1,93 @@
// Copyright 2015, Hu Keping. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rbtree
// Iterator is the function of iteration entity which would be
// used by those functions like `Ascend`, `Dscend`, etc.
//
// A typical Iterator with Print :
// func loop_with_print(item rbtree.Item) bool {
// i, ok := item.(XXX)
// if !ok {
// return false
// }
// fmt.Printf("%+v\n", i)
// return true
// }
type Iterator func(i Item) bool
// Ascend will call iterator once for each element greater or equal than pivot
// in ascending order. It will stop whenever the iterator returns false.
func (t *Rbtree) Ascend(pivot Item, iterator Iterator) {
t.ascend(t.root, pivot, iterator)
}
func (t *Rbtree) ascend(x *Node, pivot Item, iterator Iterator) bool {
if x == t.NIL {
return true
}
if !less(x.Item, pivot) {
if !t.ascend(x.Left, pivot, iterator) {
return false
}
if !iterator(x.Item) {
return false
}
}
return t.ascend(x.Right, pivot, iterator)
}
// Descend will call iterator once for each element less or equal than pivot
// in descending order. It will stop whenever the iterator returns false.
func (t *Rbtree) Descend(pivot Item, iterator Iterator) {
t.descend(t.root, pivot, iterator)
}
func (t *Rbtree) descend(x *Node, pivot Item, iterator Iterator) bool {
if x == t.NIL {
return true
}
if !less(pivot, x.Item) {
if !t.descend(x.Right, pivot, iterator) {
return false
}
if !iterator(x.Item) {
return false
}
}
return t.descend(x.Left, pivot, iterator)
}
// AscendRange will call iterator once for elements greater or equal than @ge
// and less than @lt, which means the range would be [ge, lt).
// It will stop whenever the iterator returns false.
func (t *Rbtree) AscendRange(ge, lt Item, iterator Iterator) {
t.ascendRange(t.root, ge, lt, iterator)
}
func (t *Rbtree) ascendRange(x *Node, inf, sup Item, iterator Iterator) bool {
if x == t.NIL {
return true
}
if !less(x.Item, sup) {
return t.ascendRange(x.Left, inf, sup, iterator)
}
if less(x.Item, inf) {
return t.ascendRange(x.Right, inf, sup, iterator)
}
if !t.ascendRange(x.Left, inf, sup, iterator) {
return false
}
if !iterator(x.Item) {
return false
}
return t.ascendRange(x.Right, inf, sup, iterator)
}

426
dtask/pkg/rbtree/rbtree.go Normal file
View File

@@ -0,0 +1,426 @@
// Copyright 2015, Hu Keping . All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package rbtree implements operations on Red-Black tree.
package rbtree
//
// Red-Black tree properties: http://en.wikipedia.org/wiki/Rbtree
//
// 1) A node is either red or black
// 2) The root is black
// 3) All leaves (NULL) are black
// 4) Both children of every red node are black
// 5) Every simple path from root to leaves contains the same number
// of black nodes.
//
// Node of the rbtree has a pointer of the node of parent, left, right, also has own color and Item which client uses
type Node struct {
Left *Node
Right *Node
Parent *Node
Color uint
// for use by client.
Item
}
const (
// RED represents the color of the node is red
RED = 0
// BLACK represents the color of the node is black
BLACK = 1
)
// Item has a method to compare items which is less
type Item interface {
Less(than Item) bool
}
// Rbtree represents a Red-Black tree.
type Rbtree struct {
NIL *Node
root *Node
count uint
}
func less(x, y Item) bool {
return x.Less(y)
}
// New returns an initialized Red-Black tree
func New() *Rbtree { return new(Rbtree).Init() }
// Init returns the initial of rbtree
func (t *Rbtree) Init() *Rbtree {
node := &Node{nil, nil, nil, BLACK, nil}
return &Rbtree{
NIL: node,
root: node,
count: 0,
}
}
func (t *Rbtree) leftRotate(x *Node) {
// Since we are doing the left rotation, the right child should *NOT* nil.
if x.Right == t.NIL {
return
}
//
// The illation of left rotation
//
// | |
// X Y
// / \ left rotate / \
// α Y -------------> X γ
// / \ / \
// β γ α β
//
// It should be note that during the rotating we do not change
// the Nodes' color.
//
y := x.Right
x.Right = y.Left
if y.Left != t.NIL {
y.Left.Parent = x
}
y.Parent = x.Parent
if x.Parent == t.NIL {
t.root = y
} else if x == x.Parent.Left {
x.Parent.Left = y
} else {
x.Parent.Right = y
}
y.Left = x
x.Parent = y
}
func (t *Rbtree) rightRotate(x *Node) {
// Since we are doing the right rotation, the left child should *NOT* nil.
if x.Left == t.NIL {
return
}
//
// The illation of right rotation
//
// | |
// X Y
// / \ right rotate / \
// Y γ -------------> α X
// / \ / \
// α β β γ
//
// It should be note that during the rotating we do not change
// the Nodes' color.
//
y := x.Left
x.Left = y.Right
if y.Right != t.NIL {
y.Right.Parent = x
}
y.Parent = x.Parent
if x.Parent == t.NIL {
t.root = y
} else if x == x.Parent.Left {
x.Parent.Left = y
} else {
x.Parent.Right = y
}
y.Right = x
x.Parent = y
}
func (t *Rbtree) insert(z *Node) *Node {
x := t.root
y := t.NIL
for x != t.NIL {
y = x
if less(z.Item, x.Item) {
x = x.Left
} else if less(x.Item, z.Item) {
x = x.Right
} else {
return x
}
}
z.Parent = y
if y == t.NIL {
t.root = z
} else if less(z.Item, y.Item) {
y.Left = z
} else {
y.Right = z
}
t.count++
t.insertFixup(z)
return z
}
func (t *Rbtree) insertFixup(z *Node) {
for z.Parent.Color == RED {
//
// Howerver, we do not need the assertion of non-nil grandparent
// because
//
// 2) The root is black
//
// Since the color of the parent is RED, so the parent is not root
// and the grandparent must be exist.
//
if z.Parent == z.Parent.Parent.Left {
// Take y as the uncle, although it can be NIL, in that case
// its color is BLACK
y := z.Parent.Parent.Right
if y.Color == RED {
//
// Case 1:
// Parent and uncle are both RED, the grandparent must be BLACK
// due to
//
// 4) Both children of every red node are black
//
// Since the current node and its parent are all RED, we still
// in violation of 4), So repaint both the parent and the uncle
// to BLACK and grandparent to RED(to maintain 5)
//
// 5) Every simple path from root to leaves contains the same
// number of black nodes.
//
z.Parent.Color = BLACK
y.Color = BLACK
z.Parent.Parent.Color = RED
z = z.Parent.Parent
} else {
if z == z.Parent.Right {
//
// Case 2:
// Parent is RED and uncle is BLACK and the current node
// is right child
//
// A left rotation on the parent of the current node will
// switch the roles of each other. This still leaves us in
// violation of 4).
// The continuation into Case 3 will fix that.
//
z = z.Parent
t.leftRotate(z)
}
//
// Case 3:
// Parent is RED and uncle is BLACK and the current node is
// left child
//
// At the very beginning of Case 3, current node and parent are
// both RED, thus we violate 4).
// Repaint parent to BLACK will fix it, but 5) does not allow
// this because all paths that go through the parent will get
// 1 more black node. Then repaint grandparent to RED (as we
// discussed before, the grandparent is BLACK) and do a right
// rotation will fix that.
//
z.Parent.Color = BLACK
z.Parent.Parent.Color = RED
t.rightRotate(z.Parent.Parent)
}
} else { // same as then clause with "right" and "left" exchanged
y := z.Parent.Parent.Left
if y.Color == RED {
z.Parent.Color = BLACK
y.Color = BLACK
z.Parent.Parent.Color = RED
z = z.Parent.Parent
} else {
if z == z.Parent.Left {
z = z.Parent
t.rightRotate(z)
}
z.Parent.Color = BLACK
z.Parent.Parent.Color = RED
t.leftRotate(z.Parent.Parent)
}
}
}
t.root.Color = BLACK
}
// Just traverse the node from root to left recursively until left is NIL.
// The node whose left is NIL is the node with minimum value.
func (t *Rbtree) min(x *Node) *Node {
if x == t.NIL {
return t.NIL
}
for x.Left != t.NIL {
x = x.Left
}
return x
}
// Just traverse the node from root to right recursively until right is NIL.
// The node whose right is NIL is the node with maximum value.
func (t *Rbtree) max(x *Node) *Node {
if x == t.NIL {
return t.NIL
}
for x.Right != t.NIL {
x = x.Right
}
return x
}
func (t *Rbtree) search(x *Node) *Node {
p := t.root
for p != t.NIL {
if less(p.Item, x.Item) {
p = p.Right
} else if less(x.Item, p.Item) {
p = p.Left
} else {
break
}
}
return p
}
//TODO: Need Document
func (t *Rbtree) successor(x *Node) *Node {
if x == t.NIL {
return t.NIL
}
// Get the minimum from the right sub-tree if it existed.
if x.Right != t.NIL {
return t.min(x.Right)
}
y := x.Parent
for y != t.NIL && x == y.Right {
x = y
y = y.Parent
}
return y
}
//TODO: Need Document
func (t *Rbtree) delete(key *Node) *Node {
z := t.search(key)
if z == t.NIL {
return t.NIL
}
ret := &Node{t.NIL, t.NIL, t.NIL, z.Color, z.Item}
var y *Node
var x *Node
if z.Left == t.NIL || z.Right == t.NIL {
y = z
} else {
y = t.successor(z)
}
if y.Left != t.NIL {
x = y.Left
} else {
x = y.Right
}
// Even if x is NIL, we do the assign. In that case all the NIL nodes will
// change from {nil, nil, nil, BLACK, nil} to {nil, nil, ADDR, BLACK, nil},
// but do not worry about that because it will not affect the compare
// between Node-X with Node-NIL
x.Parent = y.Parent
if y.Parent == t.NIL {
t.root = x
} else if y == y.Parent.Left {
y.Parent.Left = x
} else {
y.Parent.Right = x
}
if y != z {
z.Item = y.Item
}
if y.Color == BLACK {
t.deleteFixup(x)
}
t.count--
return ret
}
func (t *Rbtree) deleteFixup(x *Node) {
for x != t.root && x.Color == BLACK {
if x == x.Parent.Left {
w := x.Parent.Right
if w.Color == RED {
w.Color = BLACK
x.Parent.Color = RED
t.leftRotate(x.Parent)
w = x.Parent.Right
}
if w.Left.Color == BLACK && w.Right.Color == BLACK {
w.Color = RED
x = x.Parent
} else {
if w.Right.Color == BLACK {
w.Left.Color = BLACK
w.Color = RED
t.rightRotate(w)
w = x.Parent.Right
}
w.Color = x.Parent.Color
x.Parent.Color = BLACK
w.Right.Color = BLACK
t.leftRotate(x.Parent)
// this is to exit while loop
x = t.root
}
} else { // the code below is has left and right switched from above
w := x.Parent.Left
if w.Color == RED {
w.Color = BLACK
x.Parent.Color = RED
t.rightRotate(x.Parent)
w = x.Parent.Left
}
if w.Left.Color == BLACK && w.Right.Color == BLACK {
w.Color = RED
x = x.Parent
} else {
if w.Left.Color == BLACK {
w.Right.Color = BLACK
w.Color = RED
t.leftRotate(w)
w = x.Parent.Left
}
w.Color = x.Parent.Color
x.Parent.Color = BLACK
w.Left.Color = BLACK
t.rightRotate(x.Parent)
x = t.root
}
}
}
x.Color = BLACK
}

View File

@@ -0,0 +1,200 @@
// Copyright 2015, Hu Keping. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rbtree
import (
"reflect"
"testing"
)
func TestInsertAndDelete(t *testing.T) {
rbt := New()
m := 0
n := 1000
for m < n {
rbt.Insert(Int(m))
m++
}
if rbt.Len() != uint(n) {
t.Errorf("tree.Len() = %d, expect %d", rbt.Len(), n)
}
for m > 0 {
rbt.Delete(Int(m))
m--
}
if rbt.Len() != 1 {
t.Errorf("tree.Len() = %d, expect %d", rbt.Len(), 1)
}
}
type testStruct struct {
id int
text string
}
func (ts *testStruct) Less(than Item) bool {
return ts.id < than.(*testStruct).id
}
func TestInsertOrGet(t *testing.T) {
rbt := New()
items := []*testStruct{
{1, "this"},
{2, "is"},
{3, "a"},
{4, "test"},
}
for i := range items {
rbt.Insert(items[i])
}
newItem := &testStruct{items[0].id, "not"}
newItem = rbt.InsertOrGet(newItem).(*testStruct)
if newItem.text != items[0].text {
t.Errorf("tree.InsertOrGet = {id: %d, text: %s}, expect {id %d, text %s}", newItem.id, newItem.text, items[0].id, items[0].text)
}
newItem = &testStruct{5, "new"}
newItem = rbt.InsertOrGet(newItem).(*testStruct)
if newItem.text != "new" {
t.Errorf("tree.InsertOrGet = {id: %d, text: %s}, expect {id %d, text %s}", newItem.id, newItem.text, 5, "new")
}
}
func TestInsertString(t *testing.T) {
rbt := New()
rbt.Insert(String("go"))
rbt.Insert(String("lang"))
if rbt.Len() != 2 {
t.Errorf("tree.Len() = %d, expect %d", rbt.Len(), 2)
}
}
// Test for duplicate
func TestInsertDup(t *testing.T) {
rbt := New()
rbt.Insert(String("go"))
rbt.Insert(String("go"))
rbt.Insert(String("go"))
if rbt.Len() != 1 {
t.Errorf("tree.Len() = %d, expect %d", rbt.Len(), 1)
}
}
func TestDescend(t *testing.T) {
rbt := New()
m := 0
n := 10
for m < n {
rbt.Insert(Int(m))
m++
}
var ret []Item
rbt.Descend(Int(1), func(i Item) bool {
ret = append(ret, i)
return true
})
expected := []Item{Int(1), Int(0)}
if !reflect.DeepEqual(ret, expected) {
t.Errorf("expected %v but got %v", expected, ret)
}
ret = nil
rbt.Descend(Int(10), func(i Item) bool {
ret = append(ret, i)
return true
})
expected = []Item{Int(9), Int(8), Int(7), Int(6), Int(5), Int(4), Int(3), Int(2), Int(1), Int(0)}
if !reflect.DeepEqual(ret, expected) {
t.Errorf("expected %v but got %v", expected, ret)
}
}
func TestGet(t *testing.T) {
rbt := New()
rbt.Insert(Int(1))
rbt.Insert(Int(2))
rbt.Insert(Int(3))
no := rbt.Get(Int(100))
ok := rbt.Get(Int(1))
if no != nil {
t.Errorf("100 is expect not exists")
}
if ok == nil {
t.Errorf("1 is expect exists")
}
}
func TestAscend(t *testing.T) {
rbt := New()
rbt.Insert(String("a"))
rbt.Insert(String("b"))
rbt.Insert(String("c"))
rbt.Insert(String("d"))
rbt.Delete(rbt.Min())
var ret []Item
rbt.Ascend(rbt.Min(), func(i Item) bool {
ret = append(ret, i)
return true
})
expected := []Item{String("b"), String("c"), String("d")}
if !reflect.DeepEqual(ret, expected) {
t.Errorf("expected %v but got %v", expected, ret)
}
}
func TestMax(t *testing.T) {
rbt := New()
rbt.Insert(String("z"))
rbt.Insert(String("h"))
rbt.Insert(String("a"))
expected := String("z")
if rbt.Max() != expected {
t.Errorf("expected Max of tree as %v but got %v", expected, rbt.Max())
}
}
func TestAscendRange(t *testing.T) {
rbt := New()
strings := []String{"a", "b", "c", "aa", "ab", "ac", "abc", "acb", "bac"}
for _, v := range strings {
rbt.Insert(v)
}
var ret []Item
rbt.AscendRange(String("ab"), String("b"), func(i Item) bool {
ret = append(ret, i)
return true
})
expected := []Item{String("ab"), String("abc"), String("ac"), String("acb")}
if !reflect.DeepEqual(ret, expected) {
t.Errorf("expected %v but got %v", expected, ret)
}
}

88
dtask/pkg/rbtree/stats.go Normal file
View File

@@ -0,0 +1,88 @@
// Copyright 2015, Hu Keping . All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rbtree
// This file contains most of the methods that can be used
// by the user. Anyone who wants to look for some API about
// the rbtree, this is the right place.
// Len returns number of nodes in the tree.
func (t *Rbtree) Len() uint { return t.count }
// Insert func inserts a item as a new RED node
func (t *Rbtree) Insert(item Item) {
if item == nil {
return
}
// Always insert a RED node
t.insert(&Node{t.NIL, t.NIL, t.NIL, RED, item})
}
//InsertOrGet inserts or retrieves the item in the tree. If the
//item is already in the tree then the return value will be that.
//If the item is not in the tree the return value will be the item
//you put in.
func (t *Rbtree) InsertOrGet(item Item) Item {
if item == nil {
return nil
}
return t.insert(&Node{t.NIL, t.NIL, t.NIL, RED, item}).Item
}
//Delete delete the item in the tree
func (t *Rbtree) Delete(item Item) Item {
if item == nil {
return nil
}
// The `color` field here is nobody
return t.delete(&Node{t.NIL, t.NIL, t.NIL, RED, item}).Item
}
//Get search for the specified items which is carried by a Node
func (t *Rbtree) Get(item Item) Item {
if item == nil {
return nil
}
// The `color` field here is nobody
ret := t.search(&Node{t.NIL, t.NIL, t.NIL, RED, item})
if ret == nil {
return nil
}
return ret.Item
}
// Search does only search the node which includes it node
//TODO: This is for debug, delete it in the future
func (t *Rbtree) Search(item Item) *Node {
return t.search(&Node{t.NIL, t.NIL, t.NIL, RED, item})
}
// Min return the item minimum one
func (t *Rbtree) Min() Item {
x := t.min(t.root)
if x == t.NIL {
return nil
}
return x.Item
}
// Max return the item maxmum one
func (t *Rbtree) Max() Item {
x := t.max(t.root)
if x == t.NIL {
return nil
}
return x.Item
}

View File

@@ -0,0 +1,152 @@
// Copyright 2015, Hu Keping. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rbtree
import (
"fmt"
"reflect"
"sync"
"testing"
"time"
)
func TestDeleteReturnValue(t *testing.T) {
rbt := New()
rbt.Insert(String("go"))
rbt.Insert(String("lang"))
if rbt.Len() != 2 {
t.Errorf("tree.Len() = %d, expect %d", rbt.Len(), 2)
}
// go should be in the rbtree
deletedGo := rbt.Delete(String("go"))
if deletedGo != String("go") {
t.Errorf("expect %v, got %v", "go", deletedGo)
}
// C should not be in the rbtree
deletedC := rbt.Delete(String("C"))
if deletedC != nil {
t.Errorf("expect %v, got %v", nil, deletedC)
}
}
func TestMin(t *testing.T) {
rbt := New()
m := 0
n := 1000
for m < n {
rbt.Insert(Int(m))
m++
}
if rbt.Len() != uint(n) {
t.Errorf("tree.Len() = %d, expect %d", rbt.Len(), n)
}
for m >= 0 {
rbt.Delete(rbt.Min())
m--
}
if rbt.Len() != 0 {
t.Errorf("tree.Len() = %d, expect %d", rbt.Len(), 0)
}
}
//
// This test will first add 1000 numbers into a tree and then delete some
// from it.
//
// All the adding and deleting are in goroutine so that the delete action
// will not always succeed for example `delete(400)` but `add(400)` has not
// been executed yet.
//
// Finally, we'll add back all the deleted nodes to see if there is
// anything wrong.
//
func TestWithGoroutine(t *testing.T) {
var Cache struct {
rbt *Rbtree
locker *sync.Mutex
}
var DeletedArray struct {
array []Item
locker *sync.Mutex
}
// Init the rbtree and the locker for later use
Cache.rbt = New()
Cache.locker = new(sync.Mutex)
// Init the locker for later use
DeletedArray.locker = new(sync.Mutex)
i, m := 0, 0
j, n := 1000, 1000
var expected []Item
// This loop will add intergers [m~n) to the rbtree.
for m < n {
expected = append(expected, Int(m))
go func(x Int) {
Cache.locker.Lock()
Cache.rbt.Insert(x)
Cache.locker.Unlock()
}(Int(m))
m++
}
// This loop will try to delete the even integers in [m~n),
// Be noticed that the delete will not always succeeds since we are
// in the goroutines.
// We will record which ones have been removed.
for i < j {
if i%2 == 0 {
go func(x Int) {
Cache.locker.Lock()
value := Cache.rbt.Delete(x)
Cache.locker.Unlock()
DeletedArray.locker.Lock()
DeletedArray.array = append(DeletedArray.array, value)
DeletedArray.locker.Unlock()
}(Int(i))
}
i++
}
// Let's give a little time to those goroutines to finish their job.
time.Sleep(time.Second * 1)
// Add deleted Items back
cnt := 0
DeletedArray.locker.Lock()
for _, v := range DeletedArray.array {
if v != nil {
Cache.locker.Lock()
Cache.rbt.Insert(v)
Cache.locker.Unlock()
cnt++
}
}
DeletedArray.locker.Unlock()
fmt.Printf("In TestWithGoroutine(), we have deleted [%v] nodes.\n", cnt)
var ret []Item
Cache.rbt.Ascend(Cache.rbt.Min(), func(item Item) bool {
ret = append(ret, item)
return true
})
if !reflect.DeepEqual(ret, expected) {
t.Errorf("expected %v but got %v", expected, ret)
}
}

21
dtask/pkg/rbtree/util.go Normal file
View File

@@ -0,0 +1,21 @@
// Copyright 2015, Hu Keping. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rbtree
// Int is type of int
type Int int
// Less returns whether x(Int) is smaller than specified item
func (x Int) Less(than Item) bool {
return x < than.(Int)
}
// String is type of string
type String string
// Less returns whether x(String) is smaller than specified item
func (x String) Less(than Item) bool {
return x < than.(String)
}

12
dtask/proto/proto.go Normal file
View File

@@ -0,0 +1,12 @@
package proto
const (
Start = 1
Stop = 2
)
type Proto struct {
Uid string
Opt int
Seq int64
}

33
dtask/safetimer.go Normal file
View File

@@ -0,0 +1,33 @@
package dtask
import "time"
/*
This code is based on the following resources:
source code: https://play.golang.org/p/Ys9qqanqmU
discuss: https://groups.google.com/forum/#!msg/golang-dev/c9UUfASVPoU/tlbK2BpFEwAJ
*/
type safetimer struct {
*time.Timer
scr bool
}
//saw channel read, must be called after receiving value from safetimer chan
func (t *safetimer) SCR() {
t.scr = true
}
func (t *safetimer) SafeReset(d time.Duration) bool {
ret := t.Stop()
if !ret && !t.scr {
<-t.C
}
t.Timer.Reset(d)
t.scr = false
return ret
}
func newSafeTimer(d time.Duration) *safetimer {
return &safetimer{
Timer: time.NewTimer(d),
}
}

71
dtask/script/pprof.sh Normal file
View File

@@ -0,0 +1,71 @@
#!/bin/bash
default_url="http://172.16.101.107:16060"
default_prefix="debug/pprof"
default_type="profile"
read -p "input the request url (default url is '${default_url}'): " url
if [ -z ${url} ]; then
url=${default_url}
fi
read -p "input the pprof prefix (default prefix is '${default_prefix}'): " prefix
if [ -z ${prefix} ]; then
prefix=${default_prefix}
fi
function show() {
echo "chose the pprof type:"
echo " 0: break the loop"
echo " 1: profile"
echo " 2: heap"
echo " 3: allocs"
echo " 4: goroutine"
echo " 5: mutex"
echo " 6: block"
echo " 7: trace"
echo " 8: threadcreate"
echo " 9: cmdline"
echo -n "input the number: "
}
while true; do
show
read num
case $num in
0)
break
;;
1)
type="profile"
;;
2)
type="heap"
;;
3)
type="allocs"
;;
4)
type="goroutine"
;;
5)
type="mutex"
;;
6)
type="block"
;;
7)
type="trace"
;;
8)
type="threadcreate"
;;
9)
type="cmdline"
;;
*)
type=${default_type}
;;
esac
# echo 'file://wsl$/Ubuntu-18.04/tmp/' | clip.exe
# echo "request: ${url}/${prefix}/${type}"
go tool pprof "${url}/${prefix}/${type}"
done

1308
dtask/script/torch.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 54 KiB

40
dtask/task.go Normal file
View File

@@ -0,0 +1,40 @@
package dtask
import (
"sync"
"time"
)
type Task struct {
sync.RWMutex
clock *Clock
jobCache map[string]Job
}
func NewTask() *Task {
t := &Task{
clock: NewClock(),
jobCache: make(map[string]Job),
}
return t
}
func (t *Task) Add(key string, job Job) {
t.Lock()
t.jobCache[key] = job
t.Unlock()
}
func (t *Task) Get(key string) Job {
t.RLock()
defer t.RUnlock()
return t.jobCache[key]
}
func (t *Task) Stop() {
t.clock.Stop()
}
func (t *Task) AddJobRepeat(interval time.Duration, actionMax uint64, jobFunc func()) (jobScheduled Job, inserted bool) {
return t.clock.AddJobRepeat(interval, actionMax, jobFunc)
}

29
go.mod Normal file
View File

@@ -0,0 +1,29 @@
module gitlab.33.cn/chat/im
go 1.14
require (
github.com/BurntSushi/toml v0.3.1
github.com/Terry-Mao/goim v0.0.0-20201205074412-7c5316e1c2e5
github.com/gin-contrib/pprof v1.3.0
github.com/gin-gonic/gin v1.6.3
github.com/golang/protobuf v1.5.2
github.com/gomodule/redigo v2.0.0+incompatible
github.com/google/uuid v1.1.2
github.com/gorilla/websocket v1.4.2
github.com/oofpgDLD/dtask v1.0.2
github.com/opentracing/opentracing-go v1.2.0
github.com/rs/zerolog v1.21.0
github.com/uber/jaeger-client-go v2.30.0+incompatible
github.com/zhenjl/cityhash v0.0.0-20131128155616-cdd6a94144ab
gitlab.33.cn/btrade/auto_trade_tools v1.2.8
//gitlab.33.cn/chat/im-pkg v0.0.0-00010101000000-000000000000
gitlab.33.cn/chat/im-pkg v0.0.2
go.etcd.io/etcd/api/v3 v3.5.0
go.etcd.io/etcd/client/v3 v3.5.0
google.golang.org/grpc v1.38.0
google.golang.org/protobuf v1.26.0
gopkg.in/Shopify/sarama.v1 v1.19.0
)
//replace gitlab.33.cn/chat/im-pkg => ../im-pkg

876
go.sum Normal file
View File

@@ -0,0 +1,876 @@
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=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/33cn/chain33 v0.0.0-20200527072033-e43d8da29c46/go.mod h1:RJsUKcMdXtCgpqp1W1ga6jTCieuJm6n7qd3XmnPXwa4=
github.com/33cn/chain33 v0.0.0-20200605043414-355d96f9ec97/go.mod h1:RJsUKcMdXtCgpqp1W1ga6jTCieuJm6n7qd3XmnPXwa4=
github.com/33cn/chain33 v1.64.0/go.mod h1:RJsUKcMdXtCgpqp1W1ga6jTCieuJm6n7qd3XmnPXwa4=
github.com/33cn/plugin v1.64.1-0.20200529022142-4c8918d6741c/go.mod h1:PKdkkj3I3NoPh2ahQa0cZ4l94PCtG34Bxm8YzaUq1rs=
github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE=
github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.0.0-20170626110600-a368813c5e64/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y=
github.com/NebulousLabs/Sia v1.3.7/go.mod h1:SCASk6mV8QdEojKyecjj/Jd0OGSXkZonkhow7XXKk6Q=
github.com/NebulousLabs/entropy-mnemonics v0.0.0-20170316012907-7b01a644a636/go.mod h1:ed2ZsnmJfqVNZOwxWWFZaSHJY3ifOjCS7i5yX9dvKHs=
github.com/NebulousLabs/errors v0.0.0-20171229012116-7ead97ef90b8/go.mod h1:J7tUI9Fg4YuFLsqeLE5uIp93Fot9oBCw2vwZJvLmWso=
github.com/NebulousLabs/fastrand v0.0.0-20180208210444-3cf7173006a0/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ=
github.com/NebulousLabs/merkletree v0.0.0-20181025040823-2a1d1d1dc33c/go.mod h1:Cn056wBLKay+uIS9LJn7ymwhgC5mqbOtG6iOhEvyy4M=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/Terry-Mao/goim v0.0.0-20201205074412-7c5316e1c2e5 h1:C/AeojfBsXn0IneJFOQdUaVZzp6qs2xa0a72G+EQvyw=
github.com/Terry-Mao/goim v0.0.0-20201205074412-7c5316e1c2e5/go.mod h1:TbixifnJSmnDOkbQHYrXgQ04vqlSq6zlVQ5BWvadH44=
github.com/XiaoMi/pegasus-go-client v0.0.0-20181029071519-9400942c5d1c/go.mod h1:KcL6D/4RZ8RAYzQ5gKI0odcdWUmCVlbQTOlWrhP71CY=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/thrift v0.0.0-20171203172758-327ebb6c2b6d/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
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/bilibili/discovery v1.0.1/go.mod h1:daS5nEYEBt0scrrmuoNCxWXDHFK6gtEpjhVKG6MUxUg=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/bsm/sarama-cluster v2.1.15+incompatible h1:RkV6WiNRnqEEbp81druK8zYhmnIgdOjqSVi0+9Cnl2A=
github.com/bsm/sarama-cluster v2.1.15+incompatible/go.mod h1:r7ao+4tTNXvWm+VRpRJchr2kQhqxgmAp2iEX5W96gMM=
github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8=
github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.15+incompatible h1:+9RjdC18gMxNQVvSiXvObLu29mOFmkgdsB4cRTlV+EE=
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
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/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/dchest/blake256 v1.0.0/go.mod h1:xXNWCE1jsAP8DAjP+rKw2MbeqLczjI3TRx2VK+9OEYY=
github.com/decred/base58 v1.0.0/go.mod h1:LLY1p5e3g91byL/UO1eiZaYd+uRoVRarybgcoymu9Ks=
github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q=
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/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.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0=
github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v0.0.0-20180512030042-bf7803815b0b/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
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.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 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
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.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
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 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/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.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20180424202546-8dffc02ea1cb/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU=
github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48=
github.com/haltingstate/secp256k1-go v0.0.0-20151224084235-572209b26df6 h1:HE4YDtvtpZgjRJ2tCOmaXlcpBTFG2e0jvfNntM5sXOs=
github.com/haltingstate/secp256k1-go v0.0.0-20151224084235-572209b26df6/go.mod h1:73mKQiY8bLnscfGakn57WAJZTzT0eSUAy3qgMQNR/DI=
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/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc=
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb v1.7.9/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE=
github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE=
github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=
github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8=
github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s=
github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc=
github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc=
github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM=
github.com/ipfs/go-todocounter v0.0.1/go.mod h1:l5aErvQc8qKE2r7NDMjmq5UNAvuZy0rC8BHOplkWvZ4=
github.com/issue9/assert v1.0.0/go.mod h1:KLwR3U/5rbCxqwAnV3aCr+dz07aoIyIfk2lefIVr2BA=
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs=
github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA=
github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs=
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY=
github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v0.0.0-20180526014329-8744d7c5c7b4/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
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/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/koron/go-ssdp v0.0.0-20180514024734-4a0ed625a78b/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ=
github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ=
github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM=
github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc=
github.com/libp2p/go-eventbus v0.0.2/go.mod h1:Hr/yGlwxA/stuLnpMiu82lpNKpvRy3EaJxPu40XYOwk=
github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4=
github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8=
github.com/libp2p/go-flow-metrics v0.0.2/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs=
github.com/libp2p/go-libp2p v0.3.1/go.mod h1:e6bwxbdYH1HqWTz8faTChKGR0BjPc8p+6SyP8GTTR7Y=
github.com/libp2p/go-libp2p v0.4.0/go.mod h1:9EsEIf9p2UDuwtPd0DwJsAl0qXVxgAnuDGRvHbfATfI=
github.com/libp2p/go-libp2p-autonat v0.1.0/go.mod h1:1tLf2yXxiE/oKGtDwPYWTSYG3PtvYlJmg7NeVtPRqH8=
github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro=
github.com/libp2p/go-libp2p-blankhost v0.1.3/go.mod h1:KML1//wiKR8vuuJO0y3LUd1uLv+tlkGTAr3jC0S5cLg=
github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU=
github.com/libp2p/go-libp2p-circuit v0.1.1/go.mod h1:Ahq4cY3V9VJcHcn1SBXjr78AbFkZeIRmfunbA7pmFh8=
github.com/libp2p/go-libp2p-circuit v0.1.3/go.mod h1:Xqh2TjSy8DD5iV2cCOMzdynd6h8OTBGoV1AWbWor3qM=
github.com/libp2p/go-libp2p-connmgr v0.2.0/go.mod h1:C4r2FWWfbfk7U9NFcvY8oegIVxK3jPtOwFrEmtDunTg=
github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco=
github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I=
github.com/libp2p/go-libp2p-core v0.0.6/go.mod h1:0d9xmaYAVY5qmbp/fcgxHT3ZJsLjYeYPMJAUKpaCHrE=
github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI=
github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0=
github.com/libp2p/go-libp2p-core v0.2.3/go.mod h1:GqhyQqyIAPsxFYXHMjfXgMv03lxsvM0mFzuYA9Ib42A=
github.com/libp2p/go-libp2p-core v0.2.5/go.mod h1:6+5zJmKhsf7yHn1RbmYDu08qDUpIUxGdqHuEZckmZOA=
github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI=
github.com/libp2p/go-libp2p-discovery v0.1.0/go.mod h1:4F/x+aldVHjHDHuX85x1zWoFTGElt8HnoDzwkFZm29g=
github.com/libp2p/go-libp2p-kad-dht v0.2.1/go.mod h1:k7ONOlup7HKzQ68dE6lSnp07cdxdkmnRa+6B4Fh9/w0=
github.com/libp2p/go-libp2p-kbucket v0.2.1/go.mod h1:/Rtu8tqbJ4WQ2KTCOMJhggMukOLNLNPY1EtEWWLxUvc=
github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90=
github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo=
github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE=
github.com/libp2p/go-libp2p-nat v0.0.4/go.mod h1:N9Js/zVtAXqaeT99cXgTV9e75KpnWCvVOiGzlcHmBbY=
github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU=
github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY=
github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY=
github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI=
github.com/libp2p/go-libp2p-record v0.1.1/go.mod h1:VRgKajOyMVgP/F0L5g3kH7SVskp17vFi2xheb5uMJtg=
github.com/libp2p/go-libp2p-routing v0.1.0/go.mod h1:zfLhI1RI8RLEzmEaaPwzonRvXeeSHddONWkcTcB54nE=
github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8=
github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g=
github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4=
github.com/libp2p/go-libp2p-swarm v0.2.1/go.mod h1:x07b4zkMFo2EvgPV2bMTlNmdQc8i+74Jjio7xGvsTgU=
github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU=
github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E=
github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E=
github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E=
github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0=
github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA=
github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8=
github.com/libp2p/go-libp2p-yamux v0.2.1/go.mod h1:1FBXiHDk1VyRM1C0aez2bCfHQ4vMZKkAQzZbkSQt5fI=
github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q=
github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M=
github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0=
github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU=
github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ=
github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ=
github.com/libp2p/go-nat v0.0.3/go.mod h1:88nUEt0k0JD45Bk93NIwDqjlhiOwOoV36GchpcVc1yI=
github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0=
github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc=
github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA=
github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs=
github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14=
github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc=
github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc=
github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY=
github.com/libp2p/go-ws-transport v0.1.0/go.mod h1:rjw1MG1LU9YDC6gzmwObkPd/Sqwhw7yT74kj3raBFuo=
github.com/libp2p/go-ws-transport v0.1.2/go.mod h1:dsh2Ld8F+XNmzpkaAijmg5Is+e9l6/1tK/6VFOdN69Y=
github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow=
github.com/libp2p/go-yamux v1.2.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow=
github.com/lotus-king/SM3 v0.0.0-20161018102718-5a3b85ca5a40/go.mod h1:BZEQO3B4MHBIfALOrsqsiZ1hNMdVZ0j7E4q6zXLh2aM=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180511053014-58118c1ea916/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=
github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4=
github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q=
github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q=
github.com/multiformats/go-multiaddr-dns v0.0.3/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q=
github.com/multiformats/go-multiaddr-dns v0.1.0/go.mod h1:01k2RAqtoXIuPa3DCavAE9/6jc6nM0H3EgZyfUhN2oY=
github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q=
github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=
github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU=
github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ=
github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs=
github.com/multiformats/go-multicodec v0.1.6/go.mod h1:lliaRHbcG8q33yf4Ot9BGD7JqR/Za9HE7HTyVyKwrUQ=
github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U=
github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po=
github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg=
github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/oofpgDLD/dtask v1.0.2 h1:q7LvdqCKZocuazc1cPWDXqv6PZaRvCAU/KTkESf7wxs=
github.com/oofpgDLD/dtask v1.0.2/go.mod h1:r07DPvHuXY0iEq7QSFBZ5gVYRmi3Ohv6d8rek5CNPuk=
github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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 v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ=
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.21.0 h1:Q3vdXlfLNT+OftyBHsU0Y445MD+8m8axjKgf2si0QcM=
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180301161246-7678a5452ebe/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/gunit v0.0.0-20180314194857-6f0d6275bdcd/go.mod h1:XUKj4gbqj2QvJk/OdLWzyZ3FYli0f+MdpngyryX0gcw=
github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY=
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0=
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/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 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tendermint/ed25519 v0.0.0-20171027050219-d8387025d2b9/go.mod h1:nt45hbhDkWVdMBkr2TOgOzCrpBccXdN09WOiOYTHVEk=
github.com/thinkboy/log4go v0.0.0-20160303045050-f91a411e4a18/go.mod h1:IeFvD+ls8ldW9O62n+3QrktXSvtXuUDi6iLnISh6q80=
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/uber/jaeger-client-go v2.28.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/ugorji/go v0.0.0-20180407103000-f3cacc17c85e/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.5.0/go.mod h1:eriCz9OhZjKCGfJ185a/IDgNl0bg9IbzfpcslMZXU1c=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc/go.mod h1:r45hJU7yEoA81k6MWNhpMj/kms0n14dkzkxYHoB96UM=
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc=
github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM=
github.com/whyrusleeping/go-notifier v0.0.0-20170827234753-097c5d47330f/go.mod h1:cZNvX9cFybI01GriPRMXDtczuvUhgbcYr9iCGaNlRv8=
github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA=
github.com/whyrusleeping/mdns v0.0.0-20180901202407-ef14215e6b30/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4=
github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4=
github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI=
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zhenjl/cityhash v0.0.0-20131128155616-cdd6a94144ab h1:BWHvAOZz0pBILkGl/ebPQKZDrqbaWj/iN9RE8AvaTvg=
github.com/zhenjl/cityhash v0.0.0-20131128155616-cdd6a94144ab/go.mod h1:P6L88wrqK99Njntah9SB7AyzFpUXsXYq06LkjixxQmY=
gitlab.33.cn/btrade/auto_trade_tools v1.2.8 h1:3FSdvO9FRs4VKP66XlypbWteo91klcsWxbScbqhCFOI=
gitlab.33.cn/btrade/auto_trade_tools v1.2.8/go.mod h1:XbudTl/FDkpdcGDKwDIZ0QTuY2r9NVElcE/e9afixv8=
gitlab.33.cn/btrade/blockchain v1.1.0/go.mod h1:4XRBV9vjiAB2tQbXAtWCoAcI55OmNIVu9Gc8yrzg1cM=
gitlab.33.cn/btrade/common v1.0.2/go.mod h1:BJhb/g0OgtLBNJxS8zZA54jTtr6oY0/T5N5jTKbIxLY=
gitlab.33.cn/btrade/crypto v1.0.1/go.mod h1:FYhKLN69inc9nhXJw49qodvRRmFQCqEyj/JhfeNLfZk=
gitlab.33.cn/btrade/log v1.0.2/go.mod h1:grfvrYpf0OJOJ5HCUefcPlaOunEc53wOXw4WnAHLNwM=
gitlab.33.cn/chat/im-pkg v0.0.1 h1:kFU4SVq7xC0lvufGv4zjTzVKn5BPjqa0Am5IYWPKbOU=
gitlab.33.cn/chat/im-pkg v0.0.1/go.mod h1:5kmgKeovttxiKqaNqGQovwIm3v3ZZWPxD/xGj8teBj0=
gitlab.33.cn/chat/im-pkg v0.0.2 h1:f4EUCFoGNaxTYRCoE8fLpCt+G2MGTMbKU/TCCauEkhM=
gitlab.33.cn/chat/im-pkg v0.0.2/go.mod h1:5kmgKeovttxiKqaNqGQovwIm3v3ZZWPxD/xGj8teBj0=
gitlab.33.cn/contract/exchange v1.0.7/go.mod h1:MylnHconA4rUfp10jWZc6zZ+3LBIt13Ow9HUWGACAto=
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.2.0 h1:6I+W7f5VwC5SV9dNrZ3qXrDB9mD0dyGOi/ZJmYw03T4=
go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/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-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
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-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
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.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.2/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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-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-20190227160552-c95aed5357e7/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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/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-20200520004742-59133d7f0dd7/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-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
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-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
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-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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/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-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
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-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/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 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0 h1:xQwXv67TxFo9nC1GJFyab5eq/5B590r6RlnL/G8Sz7w=
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/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-20190206041539-40960b6deb8e/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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/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-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/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-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200310143817-43be25429f5a h1:lRlI5zu6AFy3iU/F8YWyNrAmn/tPCnhiTxfwhWb76eU=
google.golang.org/genproto v0.0.0-20200310143817-43be25429f5a/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/grpc v0.0.0-20181030232906-a88340f3c899/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
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.0/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.27.0/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.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
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 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
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.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 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/Shopify/sarama.v1 v1.19.0 h1:yvI/R1jfMpKvvwmX4r/AQjaI5oszWEOlvKxUdaj53OM=
gopkg.in/Shopify/sarama.v1 v1.19.0/go.mod h1:AxnvoaevB2nBjNK17cG61A3LleFcWFwVBHBt+cot4Oc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/go-playground/webhooks.v5 v5.2.0/go.mod h1:LZbya/qLVdbqDR1aKrGuWV6qbia2zCYSR5dpom2SInQ=
gopkg.in/h2non/gock.v1 v1.0.8/go.mod h1:KHI4Z1sxDW6P4N3DfTWSEza07YpkQP7KJBfglRMEjKY=
gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8=
gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/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.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

38
logic/CHANGELOG.md Normal file
View File

@@ -0,0 +1,38 @@
版本号`major.minor.patch`具体规则如下:
- major主版本号如有重大版本重构则该字段递增通常各主版本间接口不兼容。
- minor次版本号各次版本号间接口保持兼容如有接口新增或优化则该字段递增。
- patch补丁号如有功能改善或缺陷修复则该字段递增。
## version 3.1.4 @2021.10.28
优化 log 模块
配置文件更新
新增
```toml
[log]
Level="debug"
Mode="console"
Path=""
Display="json"
```
## version 3.1.3 @2021.7.29
修复当 addr 为空产生 panic
## version 3.1.2 @2021.7.22
**Feature**
- 支持根据 server 使用指定位置的 comet rpc @3.1.2 2021.7.22
## example x.x.x @yy.mm.dd
**Feature**
**Bug Fixes**
**Improvement**
**Breaking Change**

23
logic/auth/auth.go Normal file
View File

@@ -0,0 +1,23 @@
package auth
import "time"
var execAuth = make(map[string]CreateFunc)
type CreateFunc func(url string, timeout time.Duration) Auth
func Register(name string, exec CreateFunc) {
execAuth[name] = exec
}
func Load(name string) (CreateFunc, error) {
exec, ok := execAuth[name]
if !ok {
return nil, nil
}
return exec, nil
}
type Auth interface {
DoAuth(token string) (string, error)
}

67
logic/auth/dtalk/auth.go Normal file
View File

@@ -0,0 +1,67 @@
package acc
import (
"encoding/json"
"errors"
"time"
"gitlab.33.cn/btrade/auto_trade_tools/reqtypes"
"gitlab.33.cn/btrade/auto_trade_tools/util"
)
type talkClient struct {
url string
timeout time.Duration
}
func (a *talkClient) DoAuth(token string) (uid string, err error) {
var (
bytes []byte
)
headers := map[string]string{}
headers["FZM-SIGNATURE"] = token
bytes, err = util.HttpReq(&reqtypes.HttpParams{
Method: "POST",
ReqUrl: a.url,
HeaderMap: headers,
Timeout: a.timeout,
})
if err != nil {
return
}
var res map[string]interface{}
err = json.Unmarshal(bytes, &res)
if err != nil {
return
}
if e, ok := res["error"]; ok {
err = errors.New(e.(string))
return
}
if _, ok := res["data"]; !ok {
err = errors.New("invalid auth res")
return
}
data, ok := res["data"].(map[string]interface{})
if !ok {
err = errors.New("invalid auth data format")
return
}
if _, ok := data["address"]; !ok {
err = errors.New("invalid auth data")
return
}
uid, ok = data["address"].(string)
if !ok {
err = errors.New("invalid auth data id format")
return
}
return
}

View File

@@ -0,0 +1,17 @@
package acc
import (
"time"
"gitlab.33.cn/chat/im/logic/auth"
)
const Name = "dtalk"
func init() {
auth.Register(Name, NewAuth)
}
func NewAuth(url string, timeout time.Duration) auth.Auth {
return &talkClient{url: url, timeout: timeout}
}

67
logic/auth/zhaobi/auth.go Normal file
View File

@@ -0,0 +1,67 @@
package acc
import (
"encoding/json"
"errors"
"time"
"gitlab.33.cn/btrade/auto_trade_tools/reqtypes"
"gitlab.33.cn/btrade/auto_trade_tools/util"
)
type talkClient struct {
url string
timeout time.Duration
}
func (a *talkClient) DoAuth(token string) (uid string, err error) {
var (
bytes []byte
)
headers := map[string]string{}
headers["Authorization"] = token
bytes, err = util.HttpReq(&reqtypes.HttpParams{
Method: "GET",
ReqUrl: a.url,
HeaderMap: headers,
Timeout: a.timeout,
})
if err != nil {
return
}
var res map[string]interface{}
err = json.Unmarshal(bytes, &res)
if err != nil {
return
}
if e, ok := res["error"]; ok {
err = errors.New(e.(string))
return
}
if _, ok := res["data"]; !ok {
err = errors.New("invalid auth res")
return
}
data, ok := res["data"].(map[string]interface{})
if !ok {
err = errors.New("invalid auth data format")
return
}
if _, ok := data["user_id"]; !ok {
err = errors.New("invalid auth data")
return
}
uid, ok = data["user_id"].(string)
if !ok {
err = errors.New("invalid auth data id format")
return
}
return
}

View File

@@ -0,0 +1,17 @@
package acc
import (
"time"
"gitlab.33.cn/chat/im/logic/auth"
)
const Name = "zb_otc"
func init() {
auth.Register(Name, NewAuth)
}
func NewAuth(url string, timeout time.Duration) auth.Auth {
return &talkClient{url: url, timeout: timeout}
}

130
logic/cmd/main.go Normal file
View File

@@ -0,0 +1,130 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"net"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"syscall"
"github.com/Terry-Mao/goim/pkg/ip"
"github.com/opentracing/opentracing-go"
"github.com/rs/zerolog/log"
"github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/config"
xlog "gitlab.33.cn/chat/im-pkg/log"
"gitlab.33.cn/chat/im-pkg/trace"
"gitlab.33.cn/chat/im/logic"
_ "gitlab.33.cn/chat/im/logic/auth/dtalk"
_ "gitlab.33.cn/chat/im/logic/auth/zhaobi"
"gitlab.33.cn/chat/im/logic/conf"
"gitlab.33.cn/chat/im/logic/grpc"
"gitlab.33.cn/chat/im/naming"
)
const (
srvName = "logic"
)
var (
debug bool
)
func init() {
flag.BoolVar(&debug, "debug", false, "sets log level to debug")
}
var (
// projectVersion 项目版本
projectVersion = "3.1.4"
// goVersion go版本
goVersion = ""
// gitCommit git提交commit id
gitCommit = ""
// buildTime 编译时间
buildTime = ""
isShowVersion = flag.Bool("version", false, "show project version")
)
// showVersion 显示项目版本信息
func showVersion(isShow bool) {
if isShow {
fmt.Printf("Project: %s\n", srvName)
fmt.Printf(" Version: %s\n", projectVersion)
fmt.Printf(" Go Version: %s\n", goVersion)
fmt.Printf(" Git Commit: %s\n", gitCommit)
fmt.Printf(" Build Time: %s\n", buildTime)
os.Exit(0)
}
}
func main() {
flag.Parse()
showVersion(*isShowVersion)
if err := conf.Init(); err != nil {
panic(err)
}
//log init
var err error
log.Logger, err = xlog.Init(conf.Conf.Log)
if err != nil {
panic(err)
}
log.Logger.With().Str("service", srvName)
byte, _ := json.Marshal(conf.Conf)
log.Info().Str("config", string(byte)).Send()
// trace init
tracer, tracerCloser := trace.Init(srvName, conf.Conf.Trace, config.Logger(jaeger.NullLogger))
//不然后续不会有Jaeger实例
opentracing.SetGlobalTracer(tracer)
srv := logic.New(conf.Conf)
rpcSrv := grpc.New(conf.Conf.RPCServer, srv)
go func() {
if err := http.ListenAndServe(":8001", nil); err != nil {
panic(err)
}
}()
// register logic
_, port, _ := net.SplitHostPort(conf.Conf.RPCServer.Addr)
addr := fmt.Sprintf("%s:%s", ip.InternalIP(), port)
if err := naming.Register(conf.Conf.Reg.RegAddrs, conf.Conf.Reg.SrvName, addr, conf.Conf.Reg.Schema, 15); err != nil {
panic(err)
}
fmt.Println("register ok")
// signal
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info().Str("signal", s.String()).Send()
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
if err := naming.UnRegister(conf.Conf.Reg.SrvName, addr, conf.Conf.Reg.Schema); err != nil {
log.Error().Err(err).Msg("naming.UnRegister")
}
srv.Close()
rpcSrv.GracefulStop()
if err := tracerCloser.Close(); err != nil {
log.Error().Err(err).Msg("tracer close failed")
}
//log.Flush()
return
case syscall.SIGHUP:
default:
return
}
}
}

6
logic/cmd/run.sh Normal file
View File

@@ -0,0 +1,6 @@
# -log_dir=log 日志输出文件夹
# -logtostderr=true 打印到标准错误而不是文件。
# -alsologtostderr=true 同时打印到标准错误。
# -v=4 设置日志级别
# -vmodule=main=5 设置单个文件日志级别
./main -v=4 -log_dir=log -alsologtostderr=true

164
logic/conf/conf.go Normal file
View File

@@ -0,0 +1,164 @@
package conf
import (
"flag"
"os"
"time"
"github.com/BurntSushi/toml"
xtime "github.com/Terry-Mao/goim/pkg/time"
"github.com/uber/jaeger-client-go"
traceConfig "github.com/uber/jaeger-client-go/config"
xlog "gitlab.33.cn/chat/im-pkg/log"
)
var (
confPath string
regAddrs string
// Conf config
Conf *Config
)
func init() {
var (
defAddrs = os.Getenv("REGADDRS")
)
flag.StringVar(&confPath, "conf", "logic.toml", "default config path")
flag.StringVar(&regAddrs, "reg", defAddrs, "etcd register addrs. eg:127.0.0.1:2379")
}
// Init init config.
func Init() (err error) {
Conf = Default()
_, err = toml.DecodeFile(confPath, &Conf)
return
}
// Default new a config with specified defualt value.
func Default() *Config {
return &Config{
Env: "",
Log: xlog.Config{
Level: "debug",
Mode: "console",
Path: "",
Display: "console",
},
Trace: traceConfig.Configuration{
ServiceName: "answer",
Gen128Bit: true,
Sampler: &traceConfig.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &traceConfig.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "127.0.0.1:6831",
},
},
Reg: &Reg{
Schema: "im",
SrvName: "logic",
RegAddrs: regAddrs,
},
CometRPCClient: &RPCClient{
Schema: "im",
SrvName: "comet",
Dial: xtime.Duration(time.Second),
Timeout: xtime.Duration(time.Second)},
RPCServer: &RPCServer{
Network: "tcp",
Addr: "3119",
Timeout: xtime.Duration(time.Second),
IdleTimeout: xtime.Duration(time.Second * 60),
MaxLifeTime: xtime.Duration(time.Hour * 2),
ForceCloseWait: xtime.Duration(time.Second * 20),
KeepAliveInterval: xtime.Duration(time.Second * 60),
KeepAliveTimeout: xtime.Duration(time.Second * 20),
},
Backoff: &Backoff{MaxDelay: 300, BaseDelay: 3, Factor: 1.8, Jitter: 1.3},
}
}
// Config config.
type Config struct {
Env string
Log xlog.Config
Trace traceConfig.Configuration
Reg *Reg
CometRPCClient *RPCClient
RPCServer *RPCServer
Kafka *Kafka
Redis *Redis
Node *Node
Backoff *Backoff
Apps []*App
}
// Reg is service register/discovery config
type Reg struct {
Schema string
SrvName string // call
RegAddrs string // etcd addrs, seperate by ','
}
type App struct {
AppId string
AuthUrl string
Timeout xtime.Duration
}
// Node node config.
type Node struct {
HeartbeatMax int
Heartbeat xtime.Duration
}
// Backoff backoff.
type Backoff struct {
MaxDelay int32
BaseDelay int32
Factor float32
Jitter float32
}
// Redis .
type Redis struct {
Network string
Addr string
Auth string
Active int
Idle int
DialTimeout xtime.Duration
ReadTimeout xtime.Duration
WriteTimeout xtime.Duration
IdleTimeout xtime.Duration
Expire xtime.Duration
}
// Kafka .
type Kafka struct {
Topic string
Brokers []string
}
// RPCClient is RPC client config.
type RPCClient struct {
Schema string
SrvName string
Dial xtime.Duration
Timeout xtime.Duration
}
// RPCServer is RPC server config.
type RPCServer struct {
Network string
Addr string
Timeout xtime.Duration
IdleTimeout xtime.Duration
MaxLifeTime xtime.Duration
ForceCloseWait xtime.Duration
KeepAliveInterval xtime.Duration
KeepAliveTimeout xtime.Duration
}

69
logic/conf/logic.toml Normal file
View File

@@ -0,0 +1,69 @@
env="debug"
[log]
Level="debug"
Mode="console"
Path=""
Display="json"
[Trace]
ServiceName=""
Gen128Bit=true
[Trace.Sampler]
Type="const"
Param=1.0
[Trace.Reporter]
LogSpans=true
LocalAgentHostPort="172.16.101.130:6831"
[reg]
schema = "im"
srvName = "logic"
regAddrs = "127.0.0.1:2379"
[node]
heartbeat = "4m"
heartbeatMax = 2
[backoff]
maxDelay = 300
baseDelay = 3
factor = 1.8
jitter = 0.3
[RPCServer]
Network = "tcp"
Addr = ":3119"
Timeout = "1s"
KeepAliveMaxConnectionIdle = "60s"
KeepAliveMaxConnectionAge = "2h"
KeepAliveMaxMaxConnectionAgeGrace = "20s"
KeepAliveTime = "60s"
KeepAliveTimeout = "20s"
[CometRPCClient]
schema = "im"
srvName = "comet"
dial = "1s"
timeout = "1s"
[kafka]
topic = "goim-push-topic"
brokers = ["127.0.0.1:9092"]
[redis]
network = "tcp"
addr = "127.0.0.1:6379"
active = 60000
idle = 1024
dialTimeout = "200ms"
readTimeout = "500ms"
writeTimeout = "500ms"
idleTimeout = "120s"
expire = "30m"
[[apps]]
appId = "dtalk"
authUrl = "http://127.0.0.1:18002/user/login"
timeout = "1s"

72
logic/dao/dao.go Normal file
View File

@@ -0,0 +1,72 @@
package dao
import (
"context"
"time"
"github.com/gomodule/redigo/redis"
"gitlab.33.cn/chat/im/logic/conf"
kafka "gopkg.in/Shopify/sarama.v1"
)
// Dao dao.
type Dao struct {
c *conf.Config
kafkaPub kafka.SyncProducer
redis *redis.Pool
redisExpire int32
}
// New new a dao and return.
func New(c *conf.Config) *Dao {
d := &Dao{
c: c,
kafkaPub: newKafkaPub(c.Kafka),
redis: newRedis(c.Redis),
redisExpire: int32(time.Duration(c.Redis.Expire) / time.Second),
}
return d
}
func newKafkaPub(c *conf.Kafka) kafka.SyncProducer {
kc := kafka.NewConfig()
kc.Producer.RequiredAcks = kafka.WaitForAll // Wait for all in-sync replicas to ack the message
kc.Producer.Retry.Max = 10 // Retry up to 10 times to produce the message
kc.Producer.Return.Successes = true
kc.Version = kafka.V0_11_0_2
pub, err := kafka.NewSyncProducer(c.Brokers, kc)
if err != nil {
panic(err)
}
return pub
}
func newRedis(c *conf.Redis) *redis.Pool {
return &redis.Pool{
MaxIdle: c.Idle,
MaxActive: c.Active,
IdleTimeout: time.Duration(c.IdleTimeout),
Dial: func() (redis.Conn, error) {
conn, err := redis.Dial(c.Network, c.Addr,
redis.DialConnectTimeout(time.Duration(c.DialTimeout)),
redis.DialReadTimeout(time.Duration(c.ReadTimeout)),
redis.DialWriteTimeout(time.Duration(c.WriteTimeout)),
redis.DialPassword(c.Auth),
)
if err != nil {
return nil, err
}
return conn, nil
},
}
}
// Close close the resource.
func (d *Dao) Close() error {
return d.redis.Close()
}
// Ping dao ping.
func (d *Dao) Ping(c context.Context) error {
return d.pingRedis(c)
}

44
logic/dao/kafka.go Normal file
View File

@@ -0,0 +1,44 @@
package dao
import (
"context"
"fmt"
"github.com/opentracing/opentracing-go"
"gitlab.33.cn/chat/im-pkg/trace"
comet "gitlab.33.cn/chat/im/api/comet/grpc"
"github.com/golang/protobuf/proto"
"github.com/rs/zerolog/log"
pb "gitlab.33.cn/chat/im/api/logic/grpc"
"gopkg.in/Shopify/sarama.v1"
)
// PushMsg push a message to databus.
func (d *Dao) PublishMsg(ctx context.Context, appId string, fromId string, op comet.Op, key string, msg []byte) (err error) {
tracer := opentracing.GlobalTracer()
span, ctx := opentracing.StartSpanFromContextWithTracer(ctx, tracer, fmt.Sprintf("Publish -%v-%v", appId, op.String()))
defer span.Finish()
pushMsg := &pb.BizMsg{
AppId: appId,
FromId: fromId,
Op: int32(op),
Key: key,
Msg: msg,
}
b, err := proto.Marshal(pushMsg)
if err != nil {
return
}
appTopic := fmt.Sprintf("goim-%s-topic", appId)
m := &sarama.ProducerMessage{
Key: sarama.StringEncoder(fromId),
Topic: appTopic,
Value: sarama.ByteEncoder(b),
}
trace.InjectMQHeader(tracer, span.Context(), ctx, m)
if _, _, err = d.kafkaPub.SendMessage(m); err != nil {
log.Error().Interface("pushMsg", pushMsg).Err(err).Msg("kafkaPub.SendMessage error")
}
return
}

32
logic/dao/main_test.go Normal file
View File

@@ -0,0 +1,32 @@
package dao
import (
"os"
"testing"
"time"
xtime "github.com/Terry-Mao/goim/pkg/time"
"github.com/gomodule/redigo/redis"
"gitlab.33.cn/chat/im/logic/conf"
)
var (
testConf *conf.Config
testRedis *redis.Pool
)
func TestMain(m *testing.M) {
testRedis = newRedis(&conf.Redis{
Network: "tcp",
Addr: "127.0.0.1:6379",
Auth: "",
Active: 60000,
Idle: 1024,
DialTimeout: xtime.Duration(200 * time.Millisecond),
ReadTimeout: xtime.Duration(500 * time.Millisecond),
WriteTimeout: xtime.Duration(500 * time.Millisecond),
IdleTimeout: xtime.Duration(120 * time.Second),
Expire: xtime.Duration(30 * time.Minute),
})
os.Exit(m.Run())
}

300
logic/dao/redis.go Normal file
View File

@@ -0,0 +1,300 @@
package dao
import (
"context"
"errors"
"fmt"
"strings"
"github.com/gomodule/redigo/redis"
"github.com/rs/zerolog/log"
)
const (
MidFmt = "%s:%v" // {appId}:{uid}
_prefixMidServer = "mid_%s:%v" // mid_{appId}:{uid} -> key:server
_prefixKeyServer = "key_%s" // key_{key} -> server
_prefixKeyUser = "usr_%s" // usr_{key} -> {appId}:{uid}
_prefixGroupServer = "group_%s:%s" // group_{appId}:{gid} -> {server}:{score}
)
func keyMidServer(appId string, mid string) string {
return fmt.Sprintf(_prefixMidServer, appId, mid)
}
func keyKeyServer(key string) string {
return fmt.Sprintf(_prefixKeyServer, key)
}
func keyKeyUser(key string) string {
return fmt.Sprintf(_prefixKeyUser, key)
}
func keyGroupServer(appId, gid string) string {
return fmt.Sprintf(_prefixGroupServer, appId, gid)
}
func (d *Dao) pingRedis(c context.Context) (err error) {
conn := d.redis.Get()
_, err = conn.Do("SET", "PING", "PONG")
conn.Close()
return
}
func (d *Dao) GetMember(c context.Context, key string) (appId string, mid string, err error) {
conn := d.redis.Get()
defer conn.Close()
ss, err := redis.String(conn.Do("GET", keyKeyUser(key)))
if err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.DO(GET)")
return "", "", err
}
arr := strings.Split(ss, ":")
if len(arr) != 2 {
return "", "", errors.New("invalid key")
}
appId = arr[0]
mid = arr[1]
return
}
func (d *Dao) GetServer(c context.Context, key string) (server string, err error) {
conn := d.redis.Get()
defer conn.Close()
if server, err = redis.String(conn.Do("GET", keyKeyServer(key))); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.DO(GET)")
}
return
}
func (d *Dao) AddMapping(c context.Context, mid string, appId string, key string, server string) (err error) {
conn := d.redis.Get()
defer conn.Close()
var n = 4
if mid != "" {
if err = conn.Send("HSET", keyMidServer(appId, mid), key, server); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(HSET %s,%s,%s,%s) error", appId, mid, server, key))
return
}
if err = conn.Send("EXPIRE", keyMidServer(appId, mid), d.redisExpire); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s,%s,%s,%s)", appId, mid, key, server))
return
}
n += 2
}
if err = conn.Send("SET", keyKeyServer(key), server); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(HSET %s,%s,%s) error", mid, server, key))
return
}
if err = conn.Send("EXPIRE", keyKeyServer(key), d.redisExpire); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s,%s,%s) error", mid, key, server))
return
}
user := fmt.Sprintf(MidFmt, appId, mid)
if err = conn.Send("SET", keyKeyUser(key), user); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(HSET %s,%s,%s) error", mid, appId, key))
return
}
if err = conn.Send("EXPIRE", keyKeyUser(key), d.redisExpire); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s,%s,%s) error", mid, appId, key))
return
}
if err = conn.Flush(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Flush() error")
return
}
for i := 0; i < n; i++ {
if _, err = conn.Receive(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Receive() error")
return
}
}
return
}
// ExpireMapping expire a mapping.
func (d *Dao) ExpireMapping(c context.Context, mid string, appId string, key string) (has bool, err error) {
conn := d.redis.Get()
defer conn.Close()
var n = 2
if mid != "" {
if err = conn.Send("EXPIRE", keyMidServer(appId, mid), d.redisExpire); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s) error", keyMidServer(appId, mid)))
return
}
n++
}
if err = conn.Send("EXPIRE", keyKeyServer(key), d.redisExpire); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s) error", keyKeyServer(key)))
return
}
if err = conn.Send("EXPIRE", keyKeyUser(key), d.redisExpire); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s) error", keyKeyServer(key)))
return
}
if err = conn.Flush(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Flush() error")
return
}
for i := 0; i < n; i++ {
if has, err = redis.Bool(conn.Receive()); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Receive() error")
return
}
}
return
}
func (d *Dao) DelMapping(c context.Context, mid string, appId string, key string) (has bool, err error) {
conn := d.redis.Get()
defer conn.Close()
var n = 2
if mid != "" {
if err = conn.Send("HDEL", keyMidServer(appId, mid), key); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(HDEL %s) error", keyMidServer(appId, mid)))
return
}
n++
}
if err = conn.Send("DEL", keyKeyServer(key)); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(DEL %s) error", keyKeyServer(key)))
return
}
if err = conn.Send("DEL", keyKeyUser(key)); err != nil {
log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(DEL %s) error", keyKeyUser(key)))
return
}
if err = conn.Flush(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Flush() error")
return
}
for i := 0; i < n; i++ {
if has, err = redis.Bool(conn.Receive()); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Receive() error")
return
}
}
return
}
// ServersByKeys get a server by key.
func (d *Dao) ServersByKeys(c context.Context, keys []string) (res []string, err error) {
conn := d.redis.Get()
defer conn.Close()
var args []interface{}
for _, key := range keys {
args = append(args, keyKeyServer(key))
}
if res, err = redis.Strings(conn.Do("MGET", args...)); err != nil {
log.Error().Err(err).Msg(fmt.Sprintf("conn.Do(MGET %v) error", args))
}
return
}
// KeysByMids get a key server by mid.
func (d *Dao) KeysByMids(c context.Context, appId string, mids []string) (ress map[string]string, olMids []string, err error) {
conn := d.redis.Get()
defer conn.Close()
ress = make(map[string]string)
for _, mid := range mids {
if err = conn.Send("HGETALL", keyMidServer(appId, mid)); err != nil {
log.Error().Err(err).Msg(fmt.Sprintf("conn.Do(HGETALL %s) error", mid))
return
}
}
if err = conn.Flush(); err != nil {
log.Error().Err(err).Msg("conn.Flush() error")
return
}
for idx := 0; idx < len(mids); idx++ {
var (
res map[string]string
)
if res, err = redis.StringMap(conn.Receive()); err != nil {
log.Error().Err(err).Msg("conn.Receive() error")
return
}
if len(res) > 0 {
olMids = append(olMids, mids[idx])
}
for k, v := range res {
ress[k] = v
}
}
return
}
//groups
func (d *Dao) IncGroupServer(c context.Context, appId, key, server string, gid []string) (err error) {
conn := d.redis.Get()
defer conn.Close()
var n = 0
for _, g := range gid {
if err = conn.Send("ZINCRBY", keyGroupServer(appId, g), "1", server); err != nil {
log.Error().Str("key", key).Err(err).Msg(
fmt.Sprintf("conn.Send(ZINCRBY %s,%s,%s) error", keyGroupServer(appId, g), "1", server))
return
}
//if err = conn.Send("EXPIRE", keyGroupServer(appId, g), d.redisExpire); err != nil {
// log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s,%s,%s,%s)", appId, mid, key, server))
// return
//}
n++
}
if err = conn.Flush(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Flush() error")
return
}
for i := 0; i < n; i++ {
if _, err = conn.Receive(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Receive() error")
return
}
}
return
}
func (d *Dao) DecGroupServer(c context.Context, appId, key, server string, gid []string) (err error) {
conn := d.redis.Get()
defer conn.Close()
var n = 0
for _, g := range gid {
if err = conn.Send("ZINCRBY", keyGroupServer(appId, g), "-1", server); err != nil {
log.Error().Str("key", key).Err(err).Msg(
fmt.Sprintf("conn.Send(ZINCRBY %s,%s,%s) error", keyGroupServer(appId, g), "-1", server))
return
}
//if err = conn.Send("EXPIRE", keyGroupServer(appId, g), d.redisExpire); err != nil {
// log.Error().Str("key", key).Err(err).Msg(fmt.Sprintf("conn.Send(EXPIRE %s,%s,%s,%s)", appId, mid, key, server))
// return
//}
n++
}
if err = conn.Flush(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Flush() error")
return
}
for i := 0; i < n; i++ {
if _, err = conn.Receive(); err != nil {
log.Error().Str("key", key).Err(err).Msg("conn.Receive() error")
return
}
}
return
}
// KeysByMids get a key server by mid.
func (d *Dao) ServersByGid(c context.Context, appId string, gid string) (res []string, err error) {
conn := d.redis.Get()
defer conn.Close()
res = make([]string, 0)
ress := make(map[string]string)
if ress, err = redis.StringMap(conn.Do("ZRANGE", keyGroupServer(appId, gid), "0", "-1", "WITHSCORES")); err != nil {
log.Error().Str("appId", appId).Str("gid", gid).Err(err).Msg(
fmt.Sprintf("conn.DO(ZRANGE %s,%s,%s,%s) error", keyGroupServer(appId, gid), "0", "-1", "WITHSCORES"))
}
for k, _ := range ress {
res = append(res, k)
}
return
}

231
logic/dao/redis_test.go Normal file
View File

@@ -0,0 +1,231 @@
package dao
import (
"context"
"github.com/gomodule/redigo/redis"
"gopkg.in/Shopify/sarama.v1"
"testing"
"gitlab.33.cn/chat/im/logic/conf"
)
func TestDao_IncGroupServer(t *testing.T) {
type fields struct {
c *conf.Config
kafkaPub sarama.SyncProducer
redis *redis.Pool
redisExpire int32
}
type args struct {
c context.Context
appId string
key string
server string
gid []string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
// TODO: Add test cases.
{
name: "",
fields: fields{
c: testConf,
kafkaPub: nil,
redis: testRedis,
redisExpire: 0,
},
args: args{
c: nil,
appId: "dtalk",
key: "1",
server: "grpc://172.0.0.1:8080",
gid: []string{"1"},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &Dao{
c: tt.fields.c,
kafkaPub: tt.fields.kafkaPub,
redis: tt.fields.redis,
redisExpire: tt.fields.redisExpire,
}
if err := d.IncGroupServer(tt.args.c, tt.args.appId, tt.args.key, tt.args.server, tt.args.gid); (err != nil) != tt.wantErr {
t.Errorf("IncGroupServer() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestDao_DecGroupServer(t *testing.T) {
type fields struct {
c *conf.Config
kafkaPub sarama.SyncProducer
redis *redis.Pool
redisExpire int32
}
type args struct {
c context.Context
appId string
key string
server string
gid []string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
// TODO: Add test cases.
{
name: "",
fields: fields{
c: testConf,
kafkaPub: nil,
redis: testRedis,
redisExpire: 0,
},
args: args{
c: nil,
appId: "dtalk",
key: "1",
server: "grpc://172.0.0.1:8080",
gid: []string{"1"},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &Dao{
c: tt.fields.c,
kafkaPub: tt.fields.kafkaPub,
redis: tt.fields.redis,
redisExpire: tt.fields.redisExpire,
}
if err := d.DecGroupServer(tt.args.c, tt.args.appId, tt.args.key, tt.args.server, tt.args.gid); (err != nil) != tt.wantErr {
t.Errorf("IncGroupServer() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestDao_ServersByGid(t *testing.T) {
type fields struct {
c *conf.Config
kafkaPub sarama.SyncProducer
redis *redis.Pool
redisExpire int32
}
type args struct {
c context.Context
appId string
gid string
}
tests := []struct {
name string
fields fields
args args
wantRes []string
wantErr bool
}{
// TODO: Add test cases.
{
name: "",
fields: fields{
c: nil,
kafkaPub: nil,
redis: testRedis,
redisExpire: 0,
},
args: args{
c: nil,
appId: "dtalk",
gid: "1",
},
wantRes: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &Dao{
c: tt.fields.c,
kafkaPub: tt.fields.kafkaPub,
redis: tt.fields.redis,
redisExpire: tt.fields.redisExpire,
}
gotRes, err := d.ServersByGid(tt.args.c, tt.args.appId, tt.args.gid)
if (err != nil) != tt.wantErr {
t.Errorf("ServersByGid() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i, re := range gotRes {
t.Logf("got %v:%v\n", i, re)
}
})
}
}
func TestDao_KeysByMids(t *testing.T) {
type fields struct {
c *conf.Config
kafkaPub sarama.SyncProducer
redis *redis.Pool
redisExpire int32
}
type args struct {
c context.Context
appId string
mids []string
}
tests := []struct {
name string
fields fields
args args
wantRess map[string]string
wantOlMids []string
wantErr bool
}{
{
name: "",
fields: fields{
c: testConf,
kafkaPub: nil,
redis: testRedis,
redisExpire: 0,
},
args: args{
c: context.Background(),
appId: "dtalk",
mids: []string{"1ygj6Un2UzL2rev6ub6NukWrGcKjW8LoG", "1LNaxM1BtkkRpWEGty8bDxmvWwRwxsCy1B", "14si8HGSBKN2B4Ps7QQJeRLvqWoXHX2NwB", ""},
},
wantRess: nil,
wantOlMids: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &Dao{
c: tt.fields.c,
kafkaPub: tt.fields.kafkaPub,
redis: tt.fields.redis,
redisExpire: tt.fields.redisExpire,
}
gotRess, gotOlMids, err := d.KeysByMids(tt.args.c, tt.args.appId, tt.args.mids)
if err != nil {
t.Error(err)
}
t.Log(gotRess)
t.Log(gotOlMids)
})
}
}

187
logic/grpc/server.go Normal file
View File

@@ -0,0 +1,187 @@
package grpc
import (
"context"
"net"
"time"
"github.com/golang/protobuf/proto"
"gitlab.33.cn/chat/im-pkg/trace"
pb "gitlab.33.cn/chat/im/api/logic/grpc"
"gitlab.33.cn/chat/im/logic"
"gitlab.33.cn/chat/im/logic/conf"
"google.golang.org/grpc"
_ "google.golang.org/grpc/encoding/gzip"
"google.golang.org/grpc/keepalive"
)
func New(c *conf.RPCServer, l *logic.Logic) *grpc.Server {
keepParams := grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: time.Duration(c.IdleTimeout),
MaxConnectionAgeGrace: time.Duration(c.ForceCloseWait),
Time: time.Duration(c.KeepAliveInterval),
Timeout: time.Duration(c.KeepAliveTimeout),
MaxConnectionAge: time.Duration(c.MaxLifeTime),
})
connectionTimeout := grpc.ConnectionTimeout(time.Duration(c.Timeout))
srv := grpc.NewServer(keepParams, connectionTimeout,
grpc.ChainUnaryInterceptor(
trace.OpentracingServerInterceptor,
))
pb.RegisterLogicServer(srv, &server{srv: l})
lis, err := net.Listen(c.Network, c.Addr)
if err != nil {
panic(err)
}
go func() {
if err := srv.Serve(lis); err != nil {
panic(err)
}
}()
return srv
}
type server struct {
pb.UnimplementedLogicServer
srv *logic.Logic
}
var _ pb.LogicServer = &server{}
// Connect connect a conn.
func (s *server) Connect(ctx context.Context, req *pb.ConnectReq) (*pb.ConnectReply, error) {
mid, appId, key, hb, err := s.srv.Connect(ctx, req.Server, req.Proto)
if err != nil {
return nil, err
}
return &pb.ConnectReply{Key: key, AppId: appId, Mid: mid, Heartbeat: hb}, nil
}
// Disconnect disconnect a conn.
func (s *server) Disconnect(ctx context.Context, req *pb.DisconnectReq) (*pb.Reply, error) {
_, err := s.srv.Disconnect(ctx, req.Key, req.Server)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true}, nil
}
// Heartbeat beartbeat a conn.
func (s *server) Heartbeat(ctx context.Context, req *pb.HeartbeatReq) (*pb.Reply, error) {
if err := s.srv.Heartbeat(ctx, req.Key, req.Server); err != nil {
return nil, err
}
return &pb.Reply{IsOk: true}, nil
}
// Receive receive a message from client.
func (s *server) Receive(ctx context.Context, req *pb.ReceiveReq) (*pb.Reply, error) {
if err := s.srv.Receive(ctx, req.Key, req.Proto); err != nil {
return nil, err
}
return &pb.Reply{IsOk: true}, nil
}
// Push push a biz message to client.
func (s *server) PushByMids(ctx context.Context, req *pb.MidsMsg) (*pb.Reply, error) {
reply, err := s.srv.PushByMids(ctx, req.AppId, req.ToIds, req.Msg)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}
// Push push a biz message to client.
func (s *server) PushByKeys(ctx context.Context, req *pb.KeysMsg) (*pb.Reply, error) {
reply, err := s.srv.PushByKeys(ctx, req.AppId, req.ToKeys, req.Msg)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}
// Push push a biz message to client.
func (s *server) PushGroup(ctx context.Context, req *pb.GroupMsg) (*pb.Reply, error) {
reply, err := s.srv.PushGroup(ctx, req.AppId, req.Group, req.Msg)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}
// Push push a biz message to client.
func (s *server) JoinGroupsByKeys(ctx context.Context, req *pb.GroupsKey) (*pb.Reply, error) {
reply, err := s.srv.JoinGroupsByKeys(ctx, req.AppId, req.Keys, req.Gid)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}
// Push push a biz message to client.
func (s *server) JoinGroupsByMids(ctx context.Context, req *pb.GroupsMid) (*pb.Reply, error) {
reply, err := s.srv.JoinGroupsByMids(ctx, req.AppId, req.Mids, req.Gid)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}
// Push push a biz message to client.
func (s *server) LeaveGroupsByKeys(ctx context.Context, req *pb.GroupsKey) (*pb.Reply, error) {
reply, err := s.srv.LeaveGroupsByKeys(ctx, req.AppId, req.Keys, req.Gid)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}
// Push push a biz message to client.
func (s *server) LeaveGroupsByMids(ctx context.Context, req *pb.GroupsMid) (*pb.Reply, error) {
reply, err := s.srv.LeaveGroupsByMids(ctx, req.AppId, req.Mids, req.Gid)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}
// Push push a biz message to client.
func (s *server) DelGroups(ctx context.Context, req *pb.DelGroupsReq) (*pb.Reply, error) {
reply, err := s.srv.DelGroups(ctx, req.AppId, req.Gid)
if err != nil {
return nil, err
}
msg, err := proto.Marshal(reply)
if err != nil {
return nil, err
}
return &pb.Reply{IsOk: true, Msg: msg}, nil
}

426
logic/logic.go Normal file
View File

@@ -0,0 +1,426 @@
package logic
import (
"context"
"errors"
"fmt"
"time"
"github.com/golang/protobuf/proto"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"gitlab.33.cn/chat/im-pkg/trace"
comet "gitlab.33.cn/chat/im/api/comet/grpc"
"gitlab.33.cn/chat/im/common"
"gitlab.33.cn/chat/im/logic/auth"
"gitlab.33.cn/chat/im/logic/conf"
"gitlab.33.cn/chat/im/logic/dao"
"gitlab.33.cn/chat/im/naming"
xkey "gitlab.33.cn/chat/im/naming/balancer/key"
"google.golang.org/grpc"
"google.golang.org/grpc/resolver"
)
var ErrInvalidAuthReq = errors.New("ErrInvalidAuthReq")
var ErrInvalidAppId = errors.New("ErrInvalidAppId")
type Logic struct {
c *conf.Config
dao *dao.Dao
cometClient comet.CometClient
apps map[string]auth.Auth
}
func New(c *conf.Config) (l *Logic) {
cometClient, err := newCometClient(c)
if err != nil {
panic(err)
}
l = &Logic{
c: c,
dao: dao.New(c),
cometClient: cometClient,
apps: make(map[string]auth.Auth),
}
l.loadApps()
return l
}
func newCometClient(c *conf.Config) (comet.CometClient, error) {
rb := naming.NewResolver(c.Reg.RegAddrs, c.CometRPCClient.Schema)
resolver.Register(rb)
addr := fmt.Sprintf("%s:///%s", c.CometRPCClient.Schema, c.CometRPCClient.SrvName) // "schema://[authority]/service"
fmt.Println("rpc client call addr:", addr)
conn, err := common.NewGRPCConnWithKey(addr, time.Duration(c.CometRPCClient.Dial), grpc.WithUnaryInterceptor(trace.OpentracingClientInterceptor))
if err != nil {
panic(err)
}
return comet.NewCometClient(conn), err
}
func (l *Logic) loadApps() {
for _, app := range l.c.Apps {
newAuth, _ := auth.Load(app.AppId)
if newAuth == nil {
panic("exec auth not exist:" + app.AppId)
}
exec := newAuth(app.AuthUrl, time.Duration(app.Timeout))
l.apps[app.AppId] = exec
}
}
func (l *Logic) Close() {
l.dao.Close()
}
// Connect connected a conn.
func (l *Logic) Connect(c context.Context, server string, p *comet.Proto) (mid string, appId string, key string, hb int64, err error) {
var (
authMsg comet.AuthMsg
bytes []byte
)
log.Info().Str("appId", authMsg.AppId).Bytes("body", p.Body).Str("token", authMsg.Token).Msg("call Connect")
err = proto.Unmarshal(p.Body, &authMsg)
if err != nil {
return
}
if authMsg.AppId == "" || authMsg.Token == "" {
err = ErrInvalidAuthReq
return
}
log.Info().Str("appId", authMsg.AppId).Str("token", authMsg.Token).Msg("call auth")
appId = authMsg.AppId
authExec, _ := l.apps[authMsg.AppId]
if authExec == nil {
err = ErrInvalidAppId
return
}
mid, err = authExec.DoAuth(authMsg.Token)
if err != nil {
return
}
hb = int64(l.c.Node.Heartbeat) * int64(l.c.Node.HeartbeatMax)
key = uuid.New().String() //连接标识
if err = l.dao.AddMapping(c, mid, appId, key, server); err != nil {
log.Error().Err(err).Msg(fmt.Sprintf("l.dao.AddMapping(%s,%s,%s) error", mid, key, server))
return
}
log.Info().Str("key", key).Str("mid", mid).Str("appId", appId).Str("comet", server).Msg("conn connected")
// notify biz user connected
bytes, err = proto.Marshal(p)
if err != nil {
return
}
err = l.dao.PublishMsg(c, appId, mid, comet.Op_Auth, key, bytes)
return
}
// Disconnect disconnect a conn.
func (l *Logic) Disconnect(c context.Context, key string, server string) (has bool, err error) {
appId, mid, err := l.dao.GetMember(c, key)
if err != nil {
return false, err
}
if has, err = l.dao.DelMapping(c, mid, appId, key); err != nil {
log.Error().Err(err).Msg(fmt.Sprintf("l.dao.DelMapping(%s,%s) error", mid, appId))
return
}
log.Info().Str("key", key).Str("mid", mid).Str("appId", appId).Str("comet", server).Msg("conn disconnected")
// notify biz user disconnected
l.dao.PublishMsg(c, appId, mid, comet.Op_Disconnect, key, nil)
return
}
// Heartbeat heartbeat a conn.
func (l *Logic) Heartbeat(c context.Context, key string, server string) (err error) {
appId, mid, err := l.dao.GetMember(c, key)
if err != nil {
return err
}
var has bool
has, err = l.dao.ExpireMapping(c, mid, appId, key)
if err != nil {
log.Error().Err(err).Msg(fmt.Sprintf("l.dao.ExpireMapping(%s,%s) error", mid, appId))
return
}
if !has {
if err = l.dao.AddMapping(c, mid, appId, key, server); err != nil {
log.Error().Err(err).Msg(fmt.Sprintf("l.dao.AddMapping(%s,%s,%s) error", mid, appId, server))
return
}
}
log.Debug().Str("key", key).Str("mid", mid).Str("appId", appId).Str("comet", server).Msg("conn heartbeat")
return
}
// Receive receive a message from client.
func (l *Logic) Receive(c context.Context, key string, p *comet.Proto) (err error) {
appId, mid, err := l.dao.GetMember(c, key)
if err != nil {
return err
}
log.Debug().Str("appId", appId).Str("mid", mid).Interface("proto", p).Msg("receive proto")
msg, err := proto.Marshal(p)
if err != nil {
return err
}
return l.dao.PublishMsg(c, appId, mid, comet.Op(p.Op), key, msg)
}
// PushByMids Push push a biz message to client.
func (l *Logic) PushByMids(c context.Context, appId string, toIds []string, msg []byte) (reply *comet.PushMsgReply, err error) {
log.Debug().Str("appId", appId).Strs("mids", toIds).Msg("PushByMids start")
var p comet.Proto
err = proto.Unmarshal(msg, &p)
if err != nil {
return
}
server2keys, err := l.getServerByMids(c, appId, toIds)
if err != nil {
return
}
for server, keys := range server2keys {
log.Debug().Strs("keys", keys).Str("server", server).Msg("PushByMids pushing")
reply, err = l.cometClient.PushMsg(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.PushMsgReq{Keys: keys, Proto: &p})
if err != nil {
log.Error().Err(err).Strs("keys", keys).Str("server", server).Msg("PushByMids l.cometClient.PushMsg")
}
}
return
}
// PushByKeys Push push a biz message to client.
func (l *Logic) PushByKeys(c context.Context, appId string, keys []string, msg []byte) (reply *comet.PushMsgReply, err error) {
log.Debug().Str("appId", appId).Strs("keys", keys).Msg("PushByKeys start")
var p comet.Proto
err = proto.Unmarshal(msg, &p)
if err != nil {
return
}
server2keys, err := l.getServerByKeys(c, keys)
if err != nil {
return
}
for server, keys := range server2keys {
log.Debug().Strs("keys", keys).Str("server", server).Msg("PushByMids pushing")
reply, err = l.cometClient.PushMsg(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.PushMsgReq{Keys: keys, Proto: &p})
if err != nil {
log.Error().Err(err).Strs("keys", keys).Str("server", server).Msg("PushByMids l.cometClient.PushMsg")
}
}
return
}
// PushGroup Push push a biz message to client.
func (l *Logic) PushGroup(c context.Context, appId string, group string, msg []byte) (reply *comet.BroadcastGroupReply, err error) {
log.Debug().Str("appId", appId).Str("group", group).Msg("PushGroup start")
var p comet.Proto
err = proto.Unmarshal(msg, &p)
if err != nil {
return
}
cometServers, err := l.dao.ServersByGid(c, appId, group)
if err != nil {
return
}
for _, server := range cometServers {
log.Debug().Str("appId", appId).Str("group", group).Str("server", server).Msg("PushGroup pushing")
reply, err = l.cometClient.BroadcastGroup(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.BroadcastGroupReq{GroupID: appGid(appId, group), Proto: &p})
if err != nil {
log.Error().Err(err).Str("appId", appId).Str("group", group).Str("server", server).Msg("PushGroup l.cometClient.BroadcastGroup")
}
}
return
}
// JoinGroupsByMids Push push a biz message to client.
func (l *Logic) JoinGroupsByMids(c context.Context, appId string, mids []string, gids []string) (reply *comet.JoinGroupsReply, err error) {
log.Debug().Str("appId", appId).Strs("mids", mids).Strs("group", gids).Msg("JoinGroupsByMids start")
server2keys, err := l.getServerByMids(c, appId, mids)
if err != nil {
return
}
for server, keys := range server2keys {
for _, key := range keys {
_ = l.dao.IncGroupServer(c, appId, key, server, gids)
}
log.Debug().Strs("keys", keys).Str("server", server).Msg("JoinGroupsByMids pushing")
reply, err = l.cometClient.JoinGroups(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.JoinGroupsReq{Keys: keys, Gid: appGids(appId, gids)})
if err != nil {
log.Error().Err(err).Strs("keys", keys).Str("server", server).Msg("JoinGroupsByMids l.cometClient.JoinGroups")
}
}
return
}
// JoinGroupsByKeys Push push a biz message to client.
func (l *Logic) JoinGroupsByKeys(c context.Context, appId string, keys []string, gids []string) (reply *comet.JoinGroupsReply, err error) {
log.Debug().Str("appId", appId).Strs("keys", keys).Strs("group", gids).Msg("JoinGroupsByKeys start")
server2keys, err := l.getServerByKeys(c, keys)
if err != nil {
return
}
for server, keys := range server2keys {
for _, key := range keys {
_ = l.dao.IncGroupServer(c, appId, key, server, gids)
}
log.Debug().Strs("keys", keys).Str("server", server).Msg("JoinGroupsByKeys pushing")
reply, err = l.cometClient.JoinGroups(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.JoinGroupsReq{Keys: keys, Gid: appGids(appId, gids)})
if err != nil {
log.Error().Err(err).Strs("keys", keys).Str("server", server).Msg("JoinGroupsByKeys l.cometClient.JoinGroups")
}
}
return
}
// LeaveGroupsByMids Push push a biz message to client.
func (l *Logic) LeaveGroupsByMids(c context.Context, appId string, mids []string, gids []string) (reply *comet.LeaveGroupsReply, err error) {
log.Debug().Str("appId", appId).Strs("mids", mids).Strs("group", gids).Msg("LeaveGroupsByMids start")
server2keys, err := l.getServerByMids(c, appId, mids)
if err != nil {
return
}
for server, keys := range server2keys {
for _, key := range keys {
_ = l.dao.DecGroupServer(c, appId, key, server, gids)
}
log.Debug().Strs("keys", keys).Str("server", server).Msg("LeaveGroupsByMids pushing")
reply, err = l.cometClient.LeaveGroups(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.LeaveGroupsReq{Keys: keys, Gid: appGids(appId, gids)})
if err != nil {
log.Error().Err(err).Strs("keys", keys).Str("server", server).Msg("LeaveGroupsByMids l.cometClient.LeaveGroups")
}
}
return
}
// LeaveGroupsByKeys Push push a biz message to client.
func (l *Logic) LeaveGroupsByKeys(c context.Context, appId string, keys []string, gids []string) (reply *comet.LeaveGroupsReply, err error) {
log.Debug().Str("appId", appId).Strs("keys", keys).Strs("group", gids).Msg("LeaveGroupsByKeys start")
server2keys, err := l.getServerByKeys(c, keys)
if err != nil {
return
}
for server, keys := range server2keys {
for _, key := range keys {
_ = l.dao.DecGroupServer(c, appId, key, server, gids)
}
log.Debug().Strs("keys", keys).Str("server", server).Msg("LeaveGroupsByKeys pushing")
reply, err = l.cometClient.LeaveGroups(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.LeaveGroupsReq{Keys: keys, Gid: appGids(appId, gids)})
if err != nil {
log.Error().Err(err).Strs("keys", keys).Str("server", server).Msg("LeaveGroupsByKeys l.cometClient.LeaveGroups")
}
}
return
}
// DelGroups Push push a biz message to client.
func (l *Logic) DelGroups(c context.Context, appId string, gids []string) (reply *comet.DelGroupsReply, err error) {
log.Debug().Str("appId", appId).Strs("groups", gids).Msg("DelGroups start")
server2gids := make(map[string][]string)
for _, gid := range gids {
cometServers, err := l.dao.ServersByGid(c, appId, gid)
if err != nil {
continue
}
for _, server := range cometServers {
server2gids[server] = append(server2gids[server], gid)
}
}
for server, gids := range server2gids {
log.Debug().Str("appId", appId).Interface("gids", gids).Str("server", server).Msg("DelGroups pushing")
reply, err = l.cometClient.DelGroups(context.WithValue(c, xkey.DefaultKey, convertAddress(server)), &comet.DelGroupsReq{Gid: appGids(appId, gids)})
if err != nil {
log.Error().Err(err).Str("appId", appId).Strs("groups", gids).Str("server", server).Msg("DelGroups l.cometClient.DelGroups")
}
}
return
}
// getServerByMids 通过 appid 和 mids 找到所在的 comet servers
func (l *Logic) getServerByMids(c context.Context, appId string, mids []string) (map[string][]string, error) {
ress, _, err := l.dao.KeysByMids(c, appId, mids)
if err != nil {
return nil, err
}
server2keys := make(map[string][]string)
for k, s := range ress {
server2keys[s] = append(server2keys[s], k)
}
return server2keys, nil
}
// getServerByKeys 通过 keys 找到所在的 comet servers
func (l *Logic) getServerByKeys(c context.Context, keys []string) (map[string][]string, error) {
server2keys := make(map[string][]string)
for _, key := range keys {
server, _ := l.dao.GetServer(c, key)
server2keys[server] = append(server2keys[server], key)
}
return server2keys, nil
}
func appGid(appId, gid string) string {
return fmt.Sprintf("%s_%s", appId, gid)
}
func appGids(appId string, gids []string) []string {
res := make([]string, 0, len(gids))
for _, g := range gids {
res = append(res, appGid(appId, g))
}
return res
}
func convertAddress(addr string) string {
// todo
if len(addr) == 0 {
return addr
}
switch addr[0] {
case 'g':
return addr[7:]
default:
return addr
}
}

View File

@@ -0,0 +1,88 @@
package key
import (
"github.com/rs/zerolog/log"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/balancer/base"
"google.golang.org/grpc/grpclog"
"sync"
)
const (
Name = "picker_key"
DefaultKey = "X-rand-string-balabala"
)
func init() {
balancer.Register(newBuilder(DefaultKey))
}
func newBuilder(key string) balancer.Builder {
return base.NewBalancerBuilder(
Name,
&keyPickerBuilder{key: key},
base.Config{HealthCheck: false},
)
}
type keyPickerBuilder struct {
key string
}
func (b *keyPickerBuilder) Build(info base.PickerBuildInfo) balancer.Picker {
grpclog.Infof("keyPickerBuilder: newPicker called with info: %v", info)
if len(info.ReadySCs) == 0 {
return base.NewErrPicker(balancer.ErrNoSubConnAvailable)
}
picker := &keyPicker{
addr2sc: make(map[string]balancer.SubConn),
key: b.key,
}
for sc, conInfo := range info.ReadySCs {
addr := conInfo.Address.Addr
picker.setAddr2sc(addr, sc)
}
return picker
}
type keyPicker struct {
addr2sc map[string]balancer.SubConn
sync.RWMutex
key string
}
func (p *keyPicker) setAddr2sc(addr string, sc balancer.SubConn) {
p.Lock()
defer p.Unlock()
p.addr2sc[addr] = sc
log.Debug().Str("addr", addr).Msg("set addr 2 sc")
}
func (p *keyPicker) getAddr2sc(addr string) (balancer.SubConn, error) {
p.RLock()
defer p.RUnlock()
sc, ok := p.addr2sc[addr]
if ok {
return sc, nil
}
log.Warn().Str("addr", addr).Msg("server warn")
// TODO 如果找不到就随便给一个
for _, tsc := range p.addr2sc {
return tsc, nil
}
return nil, nil
}
func (p *keyPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
var res balancer.PickResult
addr, _ := info.Ctx.Value(p.key).(string)
sc, _ := p.getAddr2sc(addr)
res.SubConn = sc
return res, nil
}

86
naming/register.go Normal file
View File

@@ -0,0 +1,86 @@
package naming
import (
"context"
"fmt"
"log"
"strings"
"time"
"go.etcd.io/etcd/client/v3"
)
func Register(etcdAddr, name, addr, schema string, ttl int64) error {
var err error
if cli == nil {
cli, err = clientv3.New(clientv3.Config{
Endpoints: strings.Split(etcdAddr, ";"),
DialTimeout: 5 * time.Second,
})
if err != nil {
log.Printf("connect to etcd err:%s", err)
return err
}
}
ticker := time.NewTicker(time.Second * time.Duration(ttl))
go func() {
for {
getResp, err := cli.Get(context.Background(), "/"+schema+"/"+name+"/"+addr)
if err != nil {
fmt.Println("etcd get err:", err)
} else if getResp.Count == 0 {
err = withAlive(name, addr, schema, ttl)
if err != nil {
log.Printf("keep alive:%s", err)
}
}
<-ticker.C
}
}()
return nil
}
func withAlive(name, addr, schema string, ttl int64) error {
leaseResp, err := cli.Grant(context.Background(), ttl)
if err != nil {
return err
}
log.Printf("key:%v\n", "/"+schema+"/"+name+"/"+addr)
_, err = cli.Put(context.Background(), "/"+schema+"/"+name+"/"+addr, addr, clientv3.WithLease(leaseResp.ID))
if err != nil {
log.Printf("put etcd error:%s", err)
return err
}
ch, err := cli.KeepAlive(context.Background(), leaseResp.ID)
if err != nil {
log.Printf("keep alive error:%s", err)
return err
}
go func() {
for range ch {
}
//for leaseKeepResp := range ch {
// log.Println("续约成功", leaseKeepResp.ID)
//}
//
//log.Println("关闭续租")
}()
return nil
}
func UnRegister(name, addr, schema string) error {
if cli != nil {
//fmt.Println("unregister...")
_, err := cli.Delete(context.Background(), "/"+schema+"/"+name+"/"+addr)
return err
}
return nil
}

112
naming/resolver.go Normal file
View File

@@ -0,0 +1,112 @@
package naming
import (
"context"
"log"
"strings"
"time"
"go.etcd.io/etcd/api/v3/mvccpb"
"go.etcd.io/etcd/client/v3"
"google.golang.org/grpc/resolver"
)
var schema string
var cli *clientv3.Client
type etcdResolver struct {
rawAddr string
schema string
cc resolver.ClientConn
}
func NewResolver(etcdAddr, schema string) resolver.Builder {
return &etcdResolver{rawAddr: etcdAddr, schema: schema}
}
func (r *etcdResolver) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
//fmt.Println("target:", target)
var err error
if cli == nil {
cli, err = clientv3.New(clientv3.Config{
Endpoints: strings.Split(r.rawAddr, ";"),
DialTimeout: 15 * time.Second,
})
if err != nil {
return nil, err
}
}
r.cc = cc
go r.watch("/" + target.Scheme + "/" + target.Endpoint + "/")
return r, nil
}
func (r etcdResolver) Scheme() string {
return r.schema
}
func (r etcdResolver) ResolveNow(rn resolver.ResolveNowOptions) {
//log.Println("ResolveNow")
}
func (r etcdResolver) Close() {
//log.Println("Close")
}
func (r *etcdResolver) watch(keyPrefix string) {
var addrList []resolver.Address
getResp, err := cli.Get(context.Background(), keyPrefix, clientv3.WithPrefix())
if err != nil {
log.Println(err)
} else {
for i := range getResp.Kvs {
addrList = append(addrList, resolver.Address{Addr: strings.TrimPrefix(string(getResp.Kvs[i].Key), keyPrefix)})
}
}
// 新版本etcd去除了NewAddress方法 以UpdateState代替
r.cc.UpdateState(resolver.State{Addresses: addrList})
rch := cli.Watch(context.Background(), keyPrefix, clientv3.WithPrefix())
for n := range rch {
for _, ev := range n.Events {
addr := strings.TrimPrefix(string(ev.Kv.Key), keyPrefix)
switch ev.Type {
case mvccpb.PUT:
if !exist(addrList, addr) {
addrList = append(addrList, resolver.Address{Addr: addr})
r.cc.UpdateState(resolver.State{Addresses: addrList})
}
case mvccpb.DELETE:
if s, ok := remove(addrList, addr); ok {
addrList = s
r.cc.UpdateState(resolver.State{Addresses: addrList})
}
}
log.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
}
}
}
func exist(l []resolver.Address, addr string) bool {
for i := range l {
if l[i].Addr == addr {
return true
}
}
return false
}
func remove(s []resolver.Address, addr string) ([]resolver.Address, bool) {
for i := range s {
if s[i].Addr == addr {
s[i] = s[len(s)-1]
return s[:len(s)-1], true
}
}
return nil, false
}

View File

@@ -0,0 +1,43 @@
#!/bin/bash
file_path=$(
cd "$(dirname "$0")" || exit
pwd
)/../..
# shellcheck source=./util.sh
source "${file_path}"/script/build/util.sh
quickBuildService() {
file_path="$1"
pkg_name="$2"
service_name="$3"
flags=$(getFlags)
cd "${file_path}/${service_name}/cmd" || exit
echo "┌ start building ${service_name} service"
go build -ldflags "${flags}" -o "${file_path}/${pkg_name}/${service_name}" || exit
echo "└ building ${service_name} service success"
}
mkDir() {
file_path="$1"
pkg_path="$2"
rm -rf ${file_path}/${pkg_path}
mkdir -pv "${file_path}/${pkg_path}"
}
env_type="$1"
initOS ${env_type}
os=$(initOS ${env_type})
api_version=""
project_name="im"
now_time=$(date "+%Y_%m_%d")
pkg_path="${project_name}_bin_${now_time}_${os}"
mkDir "${file_path}" "${pkg_path}"
quickBuildService "${file_path}" "${pkg_path}" "comet"
quickBuildService "${file_path}" "${pkg_path}" "logic"

26
script/build/util.sh Normal file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
# 获取编译参数
getFlags() {
main_path="main"
go_version=$(go version | awk '{ print $3 }')
build_time=$(date "+%Y-%m-%d %H:%M:%S %Z")
git_commit=$(git rev-parse --short=10 HEAD)
flags="-X '${main_path}.goVersion=${go_version}' -X '${main_path}.buildTime=${build_time}' -X '${main_path}.gitCommit=${git_commit}' -X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'"
echo "${flags}"
}
# 设置目标打包环境
# 默认 linux amd64
initOS() {
env_type="amd64"
if [ -n "$1" ]; then
env_type="$1"
fi
export GOOS=linux
export GOARCH=${env_type}
export GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn
echo "linux_${env_type}"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

82
script/im.lua Normal file
View File

@@ -0,0 +1,82 @@
local name = "im"
local p_im = Proto(name, "im layer protocal");
local f_pack_size = ProtoField.uint32(name .. ".packsize", "Packet Size", base.DEC)
local f_header_len = ProtoField.uint16(name .. ".header", "Header Length", base.DEC)
local f_version = ProtoField.uint16(name .. ".version", "Version", base.DEC)
local f_option = ProtoField.uint32(name .. ".option", "Option", base.DEC)
local f_seq = ProtoField.uint32(name .. ".seq", "Seq", base.DEC)
local f_ack = ProtoField.uint32(name .. ".ack", "Ack", base.DEC)
local f_data = ProtoField.bytes(name .. ".data", "Payload", FT_BYTES)
p_im.fields = { f_pack_size,f_header_len,f_version,f_option,f_seq,f_ack,f_data }
local option_name = {
[0] = 'Undefined',
[1] = 'Auth',
[2] = 'AuthReply',
[3] = 'Heartbeat',
[4] = 'HeartbeatReply',
[5] = 'Disconnect',
[6] = 'DisconnectReply',
[7] = 'SendMsg',
[8] = 'SendMsgReply',
[9] = 'ReceiveMsg',
[10] = 'ReceiveMsgReply',
[14] = 'SyncMsgReq',
[15] = 'SyncMsgReply'
}
local dtalk_proto = Dissector.get("dtalk")
local data_dis = Dissector.get("data")
function p_im.dissector(buf, pkt, tree)
local offset = 0
local subtree = tree:add(p_im, buf())
pkt.cols.protocol = p_im.name
--packate size
local pkglen_tvbr = buf(offset, 4)
local pkglen = pkglen_tvbr:uint()
subtree:add(f_pack_size, pkglen_tvbr)
offset = offset + 4
if pkglen ~= buf:len() then
data_dis:call(buf(2):tvb(), pkt, tree)
else
--deal im parse
-- header Length
subtree:add(f_header_len, buf(offset, 2))
offset = offset + 2
--version
subtree:add(f_version, buf(offset, 2))
offset = offset + 2
--option
local opt_tvbr = buf(offset, 4)
local optcode = opt_tvbr:uint()
local opttree = subtree:add(f_option, opt_tvbr)
offset = offset + 4
opttree:append_text(' (' .. option_name[optcode] .. ') ')
pkt.private["dtalk_opt_type"] = optcode
--sequence
subtree:add(f_seq, buf(offset, 4))
offset = offset + 4
--acknowledge
subtree:add(f_ack, buf(offset, 4))
offset = offset + 4
--data
if buf:len()-offset > 0 then
subtree:add(f_data, buf(offset, buf:len()-offset))
--sub protocal
if dtalk_proto ~= nil then
dtalk_proto:call(buf(offset):tvb(), pkt, tree)
end
else
subtree:add('Payload:', 'empty')
end
end
end
local websocket_table = DissectorTable.get("ws.port")
websocket_table:add(3102, p_im)

70
target/comet.toml Normal file
View File

@@ -0,0 +1,70 @@
env="debug"
[log]
Level="debug"
Mode="console"
Path=""
Display="json"
[Trace]
ServiceName=""
Gen128Bit=true
[Trace.Sampler]
Type="const"
Param=1.0
[Trace.Reporter]
LogSpans=true
LocalAgentHostPort="172.16.101.130:6831"
[reg]
schema = "im"
srvName = "comet"
regAddrs = "127.0.0.1:2379"
[logicRPCClient]
schema = "im"
srvName = "logic"
dial = "1s"
timeout = "1s"
[RPCServer]
Network = "tcp"
Addr = ":3109"
Timeout = "1s"
KeepAliveMaxConnectionIdle = "60s"
KeepAliveMaxConnectionAge = "2h"
KeepAliveMaxMaxConnectionAgeGrace = "20s"
KeepAliveTime = "60s"
KeepAliveTimeout = "20s"
[tcp]
bind = [":3101"]
sndbuf = 4096
rcvbuf = 4096
keepalive = false
reader = 32
readBuf = 1024
readBufSize = 8192
writer = 32
writeBuf = 1024
writeBufSize = 8192
[websocket]
bind = [":3102"]
tlsOpen = false
tlsBind = [":3103"]
certFile = "../../cert.pem"
privateFile = "../../private.pem"
[protocol]
timer = 32
timerSize = 2048
svrProto = 10
cliProto = 5
handshakeTimeout = "8s"
minHeartbeat = "5s"
maxHeartbeat = "10s"
[bucket]
size = 32
channel = 1024

69
target/logic.toml Normal file
View File

@@ -0,0 +1,69 @@
env="debug"
[log]
Level="debug"
Mode="console"
Path=""
Display="json"
[Trace]
ServiceName=""
Gen128Bit=true
[Trace.Sampler]
Type="const"
Param=1.0
[Trace.Reporter]
LogSpans=true
LocalAgentHostPort="172.16.101.130:6831"
[reg]
schema = "im"
srvName = "logic"
regAddrs = "127.0.0.1:2379"
[node]
heartbeat = "4m"
heartbeatMax = 2
[backoff]
maxDelay = 300
baseDelay = 3
factor = 1.8
jitter = 0.3
[RPCServer]
Network = "tcp"
Addr = ":3119"
Timeout = "1s"
KeepAliveMaxConnectionIdle = "60s"
KeepAliveMaxConnectionAge = "2h"
KeepAliveMaxMaxConnectionAgeGrace = "20s"
KeepAliveTime = "60s"
KeepAliveTimeout = "20s"
[CometRPCClient]
schema = "im"
srvName = "comet"
dial = "1s"
timeout = "1s"
[kafka]
topic = "goim-push-topic"
brokers = ["127.0.0.1:9092"]
[redis]
network = "tcp"
addr = "127.0.0.1:6379"
active = 60000
idle = 1024
dialTimeout = "200ms"
readTimeout = "500ms"
writeTimeout = "500ms"
idleTimeout = "120s"
expire = "30m"
[[apps]]
appId = "dtalk"
authUrl = "http://127.0.0.1:18002/user/login"
timeout = "1s"