From c93fbb83743f0358d822aa93c3d112c9f7db2b69 Mon Sep 17 00:00:00 2001 From: wayslog Date: Mon, 9 Jul 2018 19:29:52 +0800 Subject: [PATCH 01/87] add first commit of redis --- lib/bufio/io.go | 25 ++++ lib/conv/conv.go | 15 +++ proto/redis/proxy.go | 138 +++++++++++++++++++++ proto/redis/redis.go | 187 +++++++++++++++++++++++++++++ proto/redis/resp.go | 120 +++++++++++++++++++ proto/redis/sub.go | 65 ++++++++++ proto/redis/types.go | 279 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 829 insertions(+) create mode 100644 proto/redis/proxy.go create mode 100644 proto/redis/redis.go create mode 100644 proto/redis/resp.go create mode 100644 proto/redis/sub.go create mode 100644 proto/redis/types.go diff --git a/lib/bufio/io.go b/lib/bufio/io.go index 6470d5b2..52b8239f 100644 --- a/lib/bufio/io.go +++ b/lib/bufio/io.go @@ -18,6 +18,10 @@ var ( ErrBufferFull = bufio.ErrBufferFull ) +var ( + crlfBytes = []byte("\r\n") +) + // Reader implements buffering for an io.Reader object. type Reader struct { rd io.Reader @@ -42,6 +46,14 @@ func (r *Reader) fill() error { return nil } +func (r *Reader) Mark() int { + return r.b.r +} + +func (r *Reader) AdvanceTo(mark int) { + r.Advance(mark - r.b.r) +} + // Advance proxy to buffer advance func (r *Reader) Advance(n int) { r.b.Advance(n) @@ -72,6 +84,19 @@ func (r *Reader) Read() error { return nil } +// ReadLine will read until meet the first crlf bytes. +func (r *Reader) ReadLine() (line []byte, err error) { + idx := bytes.Index(r.b.buf[r.b.r:r.b.w], crlfBytes) + if idx == -1 { + line = nil + err = ErrBufferFull + return + } + line = r.b.buf[r.b.r : r.b.r+idx+2] + r.b.r += idx + 2 + return +} + // ReadSlice will read until the delim or return ErrBufferFull. // It never contains any I/O operation func (r *Reader) ReadSlice(delim byte) (data []byte, err error) { diff --git a/lib/conv/conv.go b/lib/conv/conv.go index 799b8e84..6ed1d86e 100644 --- a/lib/conv/conv.go +++ b/lib/conv/conv.go @@ -64,6 +64,21 @@ func UpdateToLower(src []byte) { } } +const ( + step = byte('a') - byte('A') + lowerBegin = byte('a') + lowerEnd = byte('z') +) + +// UpdateToUpper will convert to lower case +func UpdateToUpper(src []byte) { + for i := range src { + if src[i] < lowerBegin || src[i] > lowerEnd { + src[i] -= step + } + } +} + // ToLower returns a copy of the string s with all Unicode letters mapped to their lower case. func ToLower(src []byte) []byte { var lower [maxCmdLen]byte diff --git a/proto/redis/proxy.go b/proto/redis/proxy.go new file mode 100644 index 00000000..7437d18e --- /dev/null +++ b/proto/redis/proxy.go @@ -0,0 +1,138 @@ +package redis + +import ( + "overlord/lib/bufio" + "overlord/lib/conv" + "overlord/proto" +) + +type redisConn struct { + br bufio.Reader + completed bool + + bw bufio.Writer +} + +func (rc *redisConn) Decode(msgs []*proto.Message) ([]*proto.Message, error) { + var err error + if rc.completed { + err = rc.br.Read() + if err != nil { + return nil, err + } + } + for i := range msgs { + rc.completed = false + // set msg type + msgs[i].Type = proto.CacheTypeRedis + // decode + err = rc.decode(msgs[i]) + if err == bufio.ErrBufferFull { + rc.completed = true + msgs[i].Reset() + return msgs[:i], nil + } else if err != nil { + msgs[i].Reset() + return msgs[:i], err + } + msgs[i].MarkStart() + } + + return msgs, nil +} + +func (rc *redisConn) decode(msg *proto.Message) (err error) { + robj, err := rc.decodeResp() + if err != nil { + return + } + if isComplex(robj.nth(0).data) { + cmds, inerr := newSubCmd(robj) + if inerr != nil { + err = inerr + return + } + for _, cmd := range cmds { + msg.WithRequest(cmd) + } + } else { + msg.WithRequest(newCommand(robj)) + } + return +} + +func (rc *redisConn) decodeResp() (robj *resp, err error) { + var ( + line []byte + size int + ) + line, err = rc.br.ReadLine() + if err != nil { + return nil, err + } + + rtype := line[0] + switch rtype { + case respString, respInt, respError: + // decocde use one line to parse + robj = rc.decodePlain(rtype, line) + case respBulk: + // decode bulkString + size, err = decodeInt(line[1 : len(line)-2]) + if err != nil { + return + } + robj, err = rc.decodeBulk(size, len(line)) + case respArray: + size, err = decodeInt(line[1 : len(line)-2]) + if err != nil { + return + } + robj, err = rc.decodeArray(size, len(line)) + } + return +} + +func (rc *redisConn) decodePlain(rtype byte, line []byte) *resp { + return newRespPlain(rtype, line[0:len(line)-2]) +} + +func (rc *redisConn) decodeBulk(size, lineSize int) (*resp, error) { + if size == -1 { + return newRespNull(respBulk), nil + } + data, err := rc.br.ReadExact(size + 2) + if err == bufio.ErrBufferFull { + rc.br.Advance(-(lineSize + size + 2)) + } else if err != nil { + return nil, err + } + return newRespBulk(data[:len(data)-2]), nil +} + +func (rc *redisConn) decodeArray(size int, lineSize int) (*resp, error) { + if size == -1 { + return newRespNull(respArray), nil + } + robj := newRespArrayWithCapcity(size) + mark := rc.br.Mark() + for i := 0; i < size; i++ { + sub, err := rc.decodeResp() + if err != nil { + rc.br.AdvanceTo(mark) + rc.br.Advance(lineSize) + return nil, err + } + robj.replace(i, sub) + } + return robj, nil +} + +func (rc *redisConn) Encode(msg *proto.Message) error { + return nil +} + +func decodeInt(data []byte) (int, error) { + i, err := conv.Btoi(data) + return int(i), err +} diff --git a/proto/redis/redis.go b/proto/redis/redis.go new file mode 100644 index 00000000..e49d87bc --- /dev/null +++ b/proto/redis/redis.go @@ -0,0 +1,187 @@ +package redis + +import ( + "bytes" +) + +// Command type +// MSET split with command change command as SET +// EXISTS will return an existed count +// DELETE will return delete count +// CLUSTER NODES will return an mock response of cluster + +// CmdType is the type of proxy +type CmdType = uint8 + +// command types for read/write spliting +const ( + CmdTypeRead uint8 = iota + CmdTypeWrite + CmdTypeNotSupport + CmdTypeCtl +) + +var cmdTypeMap = map[string]CmdType{ + "DEL": CmdTypeWrite, + "DUMP": CmdTypeRead, + "EXISTS": CmdTypeRead, + "EXPIRE": CmdTypeWrite, + "EXPIREAT": CmdTypeWrite, + "KEYS": CmdTypeNotSupport, + "MIGRATE": CmdTypeNotSupport, + "MOVE": CmdTypeNotSupport, + "OBJECT": CmdTypeNotSupport, + "PERSIST": CmdTypeWrite, + "PEXPIRE": CmdTypeWrite, + "PEXPIREAT": CmdTypeWrite, + "PTTL": CmdTypeRead, + "RANDOMKEY": CmdTypeNotSupport, + "RENAME": CmdTypeNotSupport, + "RENAMENX": CmdTypeNotSupport, + "RESTORE": CmdTypeWrite, + "SCAN": CmdTypeNotSupport, + "SORT": CmdTypeWrite, + "TTL": CmdTypeRead, + "TYPE": CmdTypeRead, + "WAIT": CmdTypeNotSupport, + "APPEND": CmdTypeWrite, + "BITCOUNT": CmdTypeRead, + "BITOP": CmdTypeNotSupport, + "BITPOS": CmdTypeRead, + "DECR": CmdTypeWrite, + "DECRBY": CmdTypeWrite, + "GET": CmdTypeRead, + "GETBIT": CmdTypeRead, + "GETRANGE": CmdTypeRead, + "GETSET": CmdTypeWrite, + "INCR": CmdTypeWrite, + "INCRBY": CmdTypeWrite, + "INCRBYFLOAT": CmdTypeWrite, + "MGET": CmdTypeRead, + "MSET": CmdTypeWrite, + "MSETNX": CmdTypeNotSupport, + "PSETEX": CmdTypeWrite, + "SET": CmdTypeWrite, + "SETBIT": CmdTypeWrite, + "SETEX": CmdTypeWrite, + "SETNX": CmdTypeWrite, + "SETRANGE": CmdTypeWrite, + "STRLEN": CmdTypeRead, + "HDEL": CmdTypeWrite, + "HEXISTS": CmdTypeRead, + "HGET": CmdTypeRead, + "HGETALL": CmdTypeRead, + "HINCRBY": CmdTypeWrite, + "HINCRBYFLOAT": CmdTypeWrite, + "HKEYS": CmdTypeRead, + "HLEN": CmdTypeRead, + "HMGET": CmdTypeRead, + "HMSET": CmdTypeWrite, + "HSET": CmdTypeWrite, + "HSETNX": CmdTypeWrite, + "HSTRLEN": CmdTypeRead, + "HVALS": CmdTypeRead, + "HSCAN": CmdTypeRead, + "BLPOP": CmdTypeNotSupport, + "BRPOP": CmdTypeNotSupport, + "BRPOPLPUSH": CmdTypeNotSupport, + "LINDEX": CmdTypeRead, + "LINSERT": CmdTypeWrite, + "LLEN": CmdTypeRead, + "LPOP": CmdTypeWrite, + "LPUSH": CmdTypeWrite, + "LPUSHX": CmdTypeWrite, + "LRANGE": CmdTypeRead, + "LREM": CmdTypeWrite, + "LSET": CmdTypeWrite, + "LTRIM": CmdTypeWrite, + "RPOP": CmdTypeWrite, + "RPOPLPUSH": CmdTypeWrite, + "RPUSH": CmdTypeWrite, + "RPUSHX": CmdTypeWrite, + "SADD": CmdTypeWrite, + "SCARD": CmdTypeRead, + "SDIFF": CmdTypeRead, + "SDIFFSTORE": CmdTypeWrite, + "SINTER": CmdTypeRead, + "SINTERSTORE": CmdTypeWrite, + "SISMEMBER": CmdTypeRead, + "SMEMBERS": CmdTypeRead, + "SMOVE": CmdTypeWrite, + "SPOP": CmdTypeWrite, + "SRANDMEMBER": CmdTypeRead, + "SREM": CmdTypeWrite, + "SUNION": CmdTypeRead, + "SUNIONSTORE": CmdTypeWrite, + "SSCAN": CmdTypeRead, + "ZADD": CmdTypeWrite, + "ZCARD": CmdTypeRead, + "ZCOUNT": CmdTypeRead, + "ZINCRBY": CmdTypeWrite, + "ZINTERSTORE": CmdTypeWrite, + "ZLEXCOUNT": CmdTypeRead, + "ZRANGE": CmdTypeRead, + "ZRANGEBYLEX": CmdTypeRead, + "ZRANGEBYSCORE": CmdTypeRead, + "ZRANK": CmdTypeRead, + "ZREM": CmdTypeWrite, + "ZREMRANGEBYLEX": CmdTypeWrite, + "ZREMRANGEBYRANK": CmdTypeWrite, + "ZREMRANGEBYSCORE": CmdTypeWrite, + "ZREVRANGE": CmdTypeRead, + "ZREVRANGEBYLEX": CmdTypeRead, + "ZREVRANGEBYSCORE": CmdTypeRead, + "ZREVRANK": CmdTypeRead, + "ZSCORE": CmdTypeRead, + "ZUNIONSTORE": CmdTypeWrite, + "ZSCAN": CmdTypeRead, + "PFADD": CmdTypeWrite, + "PFCOUNT": CmdTypeRead, + "PFMERGE": CmdTypeWrite, + "EVAL": CmdTypeWrite, + "EVALSHA": CmdTypeNotSupport, + + "PING": CmdTypeRead, + "AUTH": CmdTypeNotSupport, + "ECHO": CmdTypeNotSupport, + "INFO": CmdTypeNotSupport, + "PROXY": CmdTypeNotSupport, + "SLOWLOG": CmdTypeNotSupport, + "QUIT": CmdTypeNotSupport, + "SELECT": CmdTypeNotSupport, + "TIME": CmdTypeNotSupport, + "CONFIG": CmdTypeNotSupport, +} + +func getCmdType(cmd string) CmdType { + if ctype, ok := cmdTypeMap[cmd]; ok { + return ctype + } + return CmdTypeNotSupport +} + +// MergeType is used to decript the merge operation. +type MergeType = uint8 + +// merge types +const ( + MergeTypeCount MergeType = iota + MergeTypeOk + MergeTypeJoin + MergeTypeBasic +) + +func getMergeType(cmd []byte) MergeType { + // TODO: impl with tire tree to search quickly + if bytes.Equal(cmd, cmdMGetBytes) { + return MergeTypeJoin + } + if bytes.Equal(cmd, cmdMSetBytes) { + return MergeTypeOk + } + if bytes.Equal(cmd, cmdExistsBytes) || bytes.Equal(cmd, cmdDelBytes) { + return MergeTypeCount + } + + return MergeTypeBasic +} diff --git a/proto/redis/resp.go b/proto/redis/resp.go new file mode 100644 index 00000000..7bdffe50 --- /dev/null +++ b/proto/redis/resp.go @@ -0,0 +1,120 @@ +package redis + +import "strconv" + +// respType is the type of redis resp +type respType = byte + +// resp type define +const ( + respString respType = '+' + respError respType = '-' + respInt respType = ':' + respBulk respType = '$' + respArray respType = '*' +) + +// resp is a redis resp protocol item. +type resp struct { + rtype respType + // in Bulk this is the size field + // in array this is the count field + data []byte + array []*resp +} + +func newRespBulk(data []byte) *resp { + return &resp{ + rtype: respBulk, + data: data, + array: nil, + } +} + +func newRespPlain(rtype respType, data []byte) *resp { + return &resp{ + rtype: rtype, + data: data, + array: nil, + } +} + +func newRespString(val string) *resp { + return &resp{ + rtype: respString, + data: []byte(val), + array: nil, + } +} + +func newRespNull(rtype respType) *resp { + return &resp{ + rtype: rtype, + data: nil, + array: nil, + } +} + +func newRespArray(resps []*resp) *resp { + return &resp{ + rtype: respArray, + data: nil, + array: resps, + } +} + +func newRespArrayWithCapcity(length int) *resp { + return &resp{ + rtype: respArray, + data: nil, + array: make([]*resp, length), + } +} + +func newRespInt(val int) *resp { + s := strconv.Itoa(val) + return &resp{ + rtype: respInt, + data: []byte(s), + array: nil, + } +} + +func (r *resp) nth(pos int) *resp { + return r.array[pos] +} + +func (r *resp) isNull() bool { + if r.rtype == respArray { + return r.array == nil + } + if r.rtype == respBulk { + return r.data == nil + } + return false +} + +func (r *resp) replaceAll(begin int, newers []*resp) { + copy(r.array[begin:], newers) +} + +func (r *resp) replace(pos int, newer *resp) { + r.array[pos] = newer +} + +func (r *resp) slice() []*resp { + return r.array +} + +// Len represent the respArray type's length +func (r *resp) Len() int { + return len(r.array) +} + +func (r *resp) String() string { + if r.rtype == respString || r.rtype == respBulk { + return string(r.data) + } + // TODO(wayslog): 实现其他的命令的 string + return "" +} diff --git a/proto/redis/sub.go b/proto/redis/sub.go new file mode 100644 index 00000000..543b68e6 --- /dev/null +++ b/proto/redis/sub.go @@ -0,0 +1,65 @@ +package redis + +import "errors" +import "bytes" + +const ( + parityBit int = 0x0000000000000001 +) + +// errors +var ( + ErrBadCommandSize = errors.New("wrong command format") +) + +var ( + robjGet = newRespBulk([]byte("get")) + + cmdMSetBytes = []byte("MSET") + cmdMGetBytes = []byte("MGET") + cmdDelBytes = []byte("DEL") + cmdExistsBytes = []byte("EXITS") +) + +func isEven(v int) bool { + return v&parityBit == 0 +} + +func isComplex(cmd []byte) bool { + return bytes.Equal(cmd, cmdMSetBytes) || bytes.Equal(cmd, cmdMGetBytes) || bytes.Equal(cmd, cmdDelBytes) || bytes.Equal(cmd, cmdExistsBytes) +} + +func newSubCmd(robj *resp) ([]*Command, error) { + if bytes.Equal(robj.nth(0).data, cmdMSetBytes) { + return subCmdMset(robj) + } + return subCmdByKeys(robj) +} + +func subCmdMset(robj *resp) ([]*Command, error) { + if !isEven(robj.Len() - 1) { + return nil, ErrBadCommandSize + } + + mid := robj.Len() / 2 + cmds := make([]*Command, mid) + for i := 0; i < mid; i++ { + cmdObj := newRespArrayWithCapcity(3) + cmdObj.replace(0, robjGet) + cmdObj.replace(1, robj.nth(i*2+1)) + cmdObj.replace(2, robj.nth(i*2+2)) + cmds[i] = newCommand(cmdObj) + } + return cmds, nil +} + +func subCmdByKeys(robj *resp) ([]*Command, error) { + cmds := make([]*Command, robj.Len()-1) + for i, sub := range robj.slice()[1:] { + cmdObj := newRespArrayWithCapcity(2) + cmdObj.replace(0, robj.nth(0)) + cmdObj.replace(1, sub) + cmds[i] = newCommand(cmdObj) + } + return cmds, nil +} diff --git a/proto/redis/types.go b/proto/redis/types.go new file mode 100644 index 00000000..22c67f92 --- /dev/null +++ b/proto/redis/types.go @@ -0,0 +1,279 @@ +package redis + +import ( + "errors" + "strings" + // "crc" +) + +var ( + crlfBytes = []byte{'\r', '\n'} + lfByte = byte('\n') + movedBytes = []byte("MOVED") + askBytes = []byte("ASK") +) + +// errors +var ( + ErrProxyFail = errors.New("fail to send proxy") + ErrRequestBadFormat = errors.New("redis must be a RESP array") + ErrRedirectBadFormat = errors.New("bad format of MOVED or ASK") +) + +// const values +const ( + SlotCount = 16384 + SlotShiled = 0x3fff +) + +// Command is the type of a complete redis command +type Command struct { + respObj *resp + mergeType MergeType +} + +// NewCommand will create new command by given args +// example: +// NewCommand("GET", "mykey") +// NewCommand("MGET", "mykey", "yourkey") +// func NewCommand(cmd string, args ...string) *Command { +// respObj := newRespArrayWithCapcity(len(args) + 1) +// respObj.replace(0, newRespBulk([]byte(cmd))) +// maxLen := len(args) + 1 +// for i := 1; i < maxLen; i++ { +// respObj.replace(i, newRespBulk([]byte(args[i-1]))) +// } +// return newCommand(respObj) +// } + +func newCommand(robj *resp) *Command { + r := &Command{respObj: robj} + r.mergeType = getMergeType(robj.nth(0).data) + return r +} + +// // Slot will caculate the redis crc and return the slot value +// func (rr *Command) Slot() int { +// keyData := rr.respObj.nth(1).data + +// // support HashTag +// idx := bytes.IndexByte(keyData, '{') +// if idx != -1 { +// eidx := bytes.IndexByte(keyData, '}') +// if eidx > idx { +// // matched +// keyData = keyData[idx+1 : eidx] +// } +// } +// // TODO: crc16 +// // crcVal := crc.Crc16(string(keyData)) +// // return int(crcVal) & SlotShiled +// return 0 +// } + +// CmdString get the cmd +func (c *Command) CmdString() string { + return strings.ToUpper(c.respObj.nth(0).String()) +} + +// Cmd get the cmd +func (c *Command) Cmd() []byte { + return c.respObj.nth(0).data +} + +// Key impl the proto.protoRequest and get the Key of redis +func (c *Command) Key() []byte { + return c.respObj.nth(1).data +} + +// Put the resource back to pool +func (c *Command) Put() { +} + +// Resp return the response bytes of resp +func (c *Command) Resp() []byte { + return []byte{} +} + +// // IsBatch impl the proto.protoRequest and check if the command is batchable +// func (rr *Command) IsBatch() bool { +// return rr.batchStep != defaultBatchStep +// } + +// // // Batch impl the proto.protoRequest and split the command into divided part. +// // func (rr *Command) Batch() ([]proto.Request, *proto.Response) { +// // if rr.batchStep == defaultBatchStep { +// // // batch but only split one +// // return rr.batchOne() +// // } + +// // return rr.batchByStep(rr.batchStep) +// // } + +// // func (rr *Command) batchOne() ([]proto.Request, *proto.Response) { +// // reqs := []proto.Request{ +// // proto.Request{ +// // Type: proto.CacheTypeRedis, +// // }, +// // } +// // reqs[0].WithProto(rr) +// // response := &proto.Response{ +// // Type: proto.CacheTypeRedis, +// // } +// // return reqs, response +// // } + +// // func (rr *Command) batchByStep(step int) ([]proto.Request, *proto.Response) { +// // // NEEDTEST(wayslog): we assume that the request is full. + +// // // trim cmd +// // cmd := rr.Cmd() +// // mergeType := getMergeType(cmd) + +// // slice := rr.respObj.slice()[1:] + +// // items := (rr.respObj.Len() - 1) / step +// // resps := make([]proto.Request, items) + +// // batchCmd := getBatchCmd(cmd) + +// // bcmdResp := newRespString(batchCmd) +// // bcmdType := getCmdType(batchCmd) + +// // for i := 0; i < items; i++ { +// // // keyPos := i*step +// // // argsBegin := i*step+1 +// // // argsEnd := i*step + step -1 +// // r := newRespArrayWithCapcity(step + 1) +// // r.replace(0, bcmdResp) +// // r.replaceAll(1, slice[i*step:(i+1)*step]) + +// // req := proto.Request{Type: proto.CacheTypeRedis} +// // req.WithProto(&Command{ +// // respObj: r, +// // cmdType: bcmdType, +// // batchStep: defaultBatchStep, +// // }) +// // resps[i] = req +// // } + +// // response := &proto.Response{ +// // Type: proto.CacheTypeRedis, +// // } +// // response.WithProto(newRResponse(mergeType, nil)) +// // return resps, response +// // } + +// // RResponse is the redis response protocol type. +// type RResponse struct { +// respObj *resp + +// mergeType MergeType +// } + +// func newRResponse(mtype MergeType, robj *resp) *RResponse { +// return &RResponse{ +// mergeType: mtype, +// respObj: robj, +// } +// } + +// // Merge impl the proto.Merge interface +// func (rr *RResponse) Merge(subs []proto.Request) { +// switch rr.mergeType { +// case MergeTypeBasic: +// srr, ok := subs[0].Resp.Proto().(*RResponse) +// if !ok { +// // TOOD(wayslog): log it +// return +// } +// rr.respObj = srr.respObj +// case MergeTypeJoin: +// rr.mergeJoin(subs) +// case MergeTypeCount: +// rr.mergeCount(subs) +// case MergeTypeOk: +// rr.mergeOk(subs) +// } +// } + +// func (rr *RResponse) mergeJoin(subs []proto.Request) { +// if rr.respObj == nil { +// rr.respObj = newRespArrayWithCapcity(len(subs)) +// } +// if rr.respObj.isNull() { +// rr.respObj.array = make([]*resp, len(subs)) +// } +// for idx, sub := range subs { +// srr, ok := sub.Resp.Proto().(*RResponse) +// if !ok { +// // TODO(wayslog): log it +// continue +// } +// rr.respObj.replace(idx, srr.respObj) +// } +// } + +// // IsRedirect check if response type is Redis Error +// // and payload was prefix with "ASK" && "MOVED" +// func (rr *RResponse) IsRedirect() bool { +// if rr.respObj.rtype != respError { +// return false +// } +// if rr.respObj.data == nil { +// return false +// } + +// return bytes.HasPrefix(rr.respObj.data, movedBytes) || +// bytes.HasPrefix(rr.respObj.data, askBytes) +// } + +// // RedirectTriple will check and send back by is +// // first return variable which was called as redirectType maybe return ASK or MOVED +// // second is the slot of redirect +// // third is the redirect addr +// // last is the error when parse the redirect body +// func (rr *RResponse) RedirectTriple() (redirect string, slot int, addr string, err error) { +// fields := strings.Fields(string(rr.respObj.data)) +// if len(fields) != 3 { +// err = ErrRedirectBadFormat +// return +// } +// redirect = fields[0] +// addr = fields[2] +// ival, parseErr := strconv.Atoi(fields[1]) + +// slot = ival +// err = parseErr +// return +// } + +// func (rr *RResponse) mergeCount(subs []proto.Request) { +// count := 0 +// for _, sub := range subs { +// if err := sub.Resp.Err(); err != nil { +// // TODO(wayslog): log it +// continue +// } +// ssr, ok := sub.Resp.Proto().(*RResponse) +// if !ok { +// continue +// } +// ival, err := strconv.Atoi(string(ssr.respObj.data)) +// if err != nil { +// continue +// } +// count += ival +// } +// rr.respObj = newRespInt(count) +// } + +// func (rr *RResponse) mergeOk(subs []proto.Request) { +// for _, sub := range subs { +// if err := sub.Resp.Err(); err != nil { +// // TODO(wayslog): set as bad response +// return +// } +// } +// rr.respObj = newRespString("OK") +// } From ac8ddf386a8f75368300f919b1060186cae2fd5a Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 10 Jul 2018 17:07:33 +0800 Subject: [PATCH 02/87] half of redis feature: need Merger interface --- proto/redis/cmd.go | 110 +++++++++++++++++++++++++++++++++++++++++++ proto/redis/proxy.go | 69 ++++++++++++++++++++++++--- proto/redis/reply.go | 1 + proto/redis/resp.go | 77 ++++++++++++++---------------- proto/redis/sub.go | 20 +++----- proto/redis/types.go | 95 ------------------------------------- 6 files changed, 217 insertions(+), 155 deletions(-) create mode 100644 proto/redis/cmd.go create mode 100644 proto/redis/reply.go diff --git a/proto/redis/cmd.go b/proto/redis/cmd.go new file mode 100644 index 00000000..7a95d5bc --- /dev/null +++ b/proto/redis/cmd.go @@ -0,0 +1,110 @@ +package redis + +import ( + "errors" + "strings" + // "crc" +) + +var ( + crlfBytes = []byte{'\r', '\n'} + lfByte = byte('\n') + movedBytes = []byte("MOVED") + askBytes = []byte("ASK") +) + +var ( + robjGet = newRespBulk([]byte("get")) + + cmdMSetBytes = []byte("MSET") + cmdMGetBytes = []byte("MGET") + cmdDelBytes = []byte("DEL") + cmdExistsBytes = []byte("EXITS") +) + +// errors +var ( + ErrProxyFail = errors.New("fail to send proxy") + ErrRequestBadFormat = errors.New("redis must be a RESP array") + ErrRedirectBadFormat = errors.New("bad format of MOVED or ASK") +) + +// const values +const ( + SlotCount = 16384 + SlotShiled = 0x3fff +) + +// Command is the type of a complete redis command +type Command struct { + respObj *resp + mergeType MergeType + reply *resp +} + +// NewCommand will create new command by given args +// example: +// NewCommand("GET", "mykey") +// NewCommand("MGET", "mykey", "yourkey") +// func NewCommand(cmd string, args ...string) *Command { +// respObj := newRespArrayWithCapcity(len(args) + 1) +// respObj.replace(0, newRespBulk([]byte(cmd))) +// maxLen := len(args) + 1 +// for i := 1; i < maxLen; i++ { +// respObj.replace(i, newRespBulk([]byte(args[i-1]))) +// } +// return newCommand(respObj) +// } + +func newCommand(robj *resp) *Command { + r := &Command{respObj: robj} + r.mergeType = getMergeType(robj.nth(0).data) + return r +} + +func newCommandWithMergeType(robj *resp, mtype MergeType) *Command { + return &Command{respObj: robj, mergeType: mtype} +} + +// Slot will caculate the redis crc and return the slot value +func (rr *Command) Slot() int { + // TODO:CRC16 + // keyData := rr.respObj.nth(1).data + + // // support HashTag + // idx := bytes.IndexByte(keyData, '{') + // if idx != -1 { + // eidx := bytes.IndexByte(keyData, '}') + // if eidx > idx { + // // matched + // keyData = keyData[idx+1 : eidx] + // } + // } + // crcVal := crc.Crc16(string(keyData)) + // return int(crcVal) & SlotShiled + return 0 +} + +// CmdString get the cmd +func (c *Command) CmdString() string { + return strings.ToUpper(c.respObj.nth(0).String()) +} + +// Cmd get the cmd +func (c *Command) Cmd() []byte { + return c.respObj.nth(0).data +} + +// Key impl the proto.protoRequest and get the Key of redis +func (c *Command) Key() []byte { + return c.respObj.nth(1).data +} + +// Put the resource back to pool +func (c *Command) Put() { +} + +// Resp return the response bytes of resp +func (c *Command) Resp() []byte { + return []byte{} +} diff --git a/proto/redis/proxy.go b/proto/redis/proxy.go index 7437d18e..537c89c6 100644 --- a/proto/redis/proxy.go +++ b/proto/redis/proxy.go @@ -1,16 +1,34 @@ package redis import ( + stderrs "errors" "overlord/lib/bufio" "overlord/lib/conv" + libnet "overlord/lib/net" "overlord/proto" + + "github.com/pkg/errors" +) + +var ( + ErrBadAssert = stderrs.New("bad assert for redis") ) type redisConn struct { - br bufio.Reader + br *bufio.Reader + bw *bufio.Writer + completed bool +} - bw bufio.Writer +// NewProxyConn creates new redis Encoder and Decoder. +func NewProxyConn(conn *libnet.Conn) proto.ProxyConn { + r := &redisConn{ + br: bufio.NewReader(conn, bufio.Get(1024)), + bw: bufio.NewWriter(conn), + completed: true, + } + return r } func (rc *redisConn) Decode(msgs []*proto.Message) ([]*proto.Message, error) { @@ -128,11 +146,50 @@ func (rc *redisConn) decodeArray(size int, lineSize int) (*resp, error) { return robj, nil } -func (rc *redisConn) Encode(msg *proto.Message) error { - return nil -} - func decodeInt(data []byte) (int, error) { i, err := conv.Btoi(data) return int(i), err } + +func (rc *redisConn) Encode(msg *proto.Message) (err error) { + if err = rc.encode(msg); err != nil { + err = errors.Wrap(err, "Redis Encoder encode") + return + } + + if err = rc.bw.Flush(); err != nil { + err = errors.Wrap(err, "Redis Encoder flush response") + } + return +} + +func (rc *redisConn) encode(msg *proto.Message) (err error) { + if err = msg.Err(); err != nil { + return rc.encodeError(err) + } + + // TODO: need to change message response way + // replyList := msg.Response() + // cmd, ok := msg.Request().(*Command) + // if !ok { + // err = errors.Wrwap(ErrBadAssert, "Redis encode type assert") + // } + + return nil +} + +func (rc *redisConn) mergeReply() (n int, data [][]byte) { + return +} + +func (rc *redisConn) encodeReply(reply *resp) error { + return nil +} + +func (rc *redisConn) encodeError(err error) error { + se := errors.Cause(err).Error() + _ = rc.bw.Write([]byte{respError}) + _ = rc.bw.WriteString(se) + _ = rc.bw.Write(crlfBytes) + return nil +} diff --git a/proto/redis/reply.go b/proto/redis/reply.go new file mode 100644 index 00000000..65a229e1 --- /dev/null +++ b/proto/redis/reply.go @@ -0,0 +1 @@ +package redis diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 7bdffe50..cfad64e2 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -1,6 +1,9 @@ package redis -import "strconv" +import ( + "strconv" + "sync" +) // respType is the type of redis resp type respType = byte @@ -14,6 +17,14 @@ const ( respArray respType = '*' ) +var ( + respPool = sync.Pool{ + New: func() interface{} { + return &resp{} + }, + } +) + // resp is a redis resp protocol item. type resp struct { rtype respType @@ -23,61 +34,45 @@ type resp struct { array []*resp } +func newRespInt(val int) *resp { + s := strconv.Itoa(val) + return newRespPlain(respInt, []byte(s)) +} + func newRespBulk(data []byte) *resp { - return &resp{ - rtype: respBulk, - data: data, - array: nil, - } + return newRespPlain(respBulk, data) } func newRespPlain(rtype respType, data []byte) *resp { - return &resp{ - rtype: rtype, - data: data, - array: nil, - } + robj := respPool.Get().(*resp) + robj.rtype = rtype + robj.data = data + robj.array = nil + return robj } -func newRespString(val string) *resp { - return &resp{ - rtype: respString, - data: []byte(val), - array: nil, - } +func newRespString(val []byte) *resp { + return newRespPlain(respString, val) } func newRespNull(rtype respType) *resp { - return &resp{ - rtype: rtype, - data: nil, - array: nil, - } + return newRespPlain(rtype, nil) } func newRespArray(resps []*resp) *resp { - return &resp{ - rtype: respArray, - data: nil, - array: resps, - } + robj := respPool.Get().(*resp) + robj.rtype = respArray + robj.data = nil + robj.array = resps + return robj } func newRespArrayWithCapcity(length int) *resp { - return &resp{ - rtype: respArray, - data: nil, - array: make([]*resp, length), - } -} - -func newRespInt(val int) *resp { - s := strconv.Itoa(val) - return &resp{ - rtype: respInt, - data: []byte(s), - array: nil, - } + robj := respPool.Get().(*resp) + robj.rtype = respArray + robj.data = nil + robj.array = make([]*resp, length) + return robj } func (r *resp) nth(pos int) *resp { diff --git a/proto/redis/sub.go b/proto/redis/sub.go index 543b68e6..32df1dae 100644 --- a/proto/redis/sub.go +++ b/proto/redis/sub.go @@ -4,21 +4,12 @@ import "errors" import "bytes" const ( - parityBit int = 0x0000000000000001 + parityBit int = 1 ) // errors var ( - ErrBadCommandSize = errors.New("wrong command format") -) - -var ( - robjGet = newRespBulk([]byte("get")) - - cmdMSetBytes = []byte("MSET") - cmdMGetBytes = []byte("MGET") - cmdDelBytes = []byte("DEL") - cmdExistsBytes = []byte("EXITS") + ErrBadCommandSize = errors.New("wrong Mset arguments number") ) func isEven(v int) bool { @@ -26,7 +17,10 @@ func isEven(v int) bool { } func isComplex(cmd []byte) bool { - return bytes.Equal(cmd, cmdMSetBytes) || bytes.Equal(cmd, cmdMGetBytes) || bytes.Equal(cmd, cmdDelBytes) || bytes.Equal(cmd, cmdExistsBytes) + return bytes.Equal(cmd, cmdMSetBytes) || + bytes.Equal(cmd, cmdMGetBytes) || + bytes.Equal(cmd, cmdDelBytes) || + bytes.Equal(cmd, cmdExistsBytes) } func newSubCmd(robj *resp) ([]*Command, error) { @@ -48,7 +42,7 @@ func subCmdMset(robj *resp) ([]*Command, error) { cmdObj.replace(0, robjGet) cmdObj.replace(1, robj.nth(i*2+1)) cmdObj.replace(2, robj.nth(i*2+2)) - cmds[i] = newCommand(cmdObj) + cmds[i] = newCommandWithMergeType(cmdObj, MergeTypeOk) } return cmds, nil } diff --git a/proto/redis/types.go b/proto/redis/types.go index 22c67f92..27c5b239 100644 --- a/proto/redis/types.go +++ b/proto/redis/types.go @@ -1,100 +1,5 @@ package redis -import ( - "errors" - "strings" - // "crc" -) - -var ( - crlfBytes = []byte{'\r', '\n'} - lfByte = byte('\n') - movedBytes = []byte("MOVED") - askBytes = []byte("ASK") -) - -// errors -var ( - ErrProxyFail = errors.New("fail to send proxy") - ErrRequestBadFormat = errors.New("redis must be a RESP array") - ErrRedirectBadFormat = errors.New("bad format of MOVED or ASK") -) - -// const values -const ( - SlotCount = 16384 - SlotShiled = 0x3fff -) - -// Command is the type of a complete redis command -type Command struct { - respObj *resp - mergeType MergeType -} - -// NewCommand will create new command by given args -// example: -// NewCommand("GET", "mykey") -// NewCommand("MGET", "mykey", "yourkey") -// func NewCommand(cmd string, args ...string) *Command { -// respObj := newRespArrayWithCapcity(len(args) + 1) -// respObj.replace(0, newRespBulk([]byte(cmd))) -// maxLen := len(args) + 1 -// for i := 1; i < maxLen; i++ { -// respObj.replace(i, newRespBulk([]byte(args[i-1]))) -// } -// return newCommand(respObj) -// } - -func newCommand(robj *resp) *Command { - r := &Command{respObj: robj} - r.mergeType = getMergeType(robj.nth(0).data) - return r -} - -// // Slot will caculate the redis crc and return the slot value -// func (rr *Command) Slot() int { -// keyData := rr.respObj.nth(1).data - -// // support HashTag -// idx := bytes.IndexByte(keyData, '{') -// if idx != -1 { -// eidx := bytes.IndexByte(keyData, '}') -// if eidx > idx { -// // matched -// keyData = keyData[idx+1 : eidx] -// } -// } -// // TODO: crc16 -// // crcVal := crc.Crc16(string(keyData)) -// // return int(crcVal) & SlotShiled -// return 0 -// } - -// CmdString get the cmd -func (c *Command) CmdString() string { - return strings.ToUpper(c.respObj.nth(0).String()) -} - -// Cmd get the cmd -func (c *Command) Cmd() []byte { - return c.respObj.nth(0).data -} - -// Key impl the proto.protoRequest and get the Key of redis -func (c *Command) Key() []byte { - return c.respObj.nth(1).data -} - -// Put the resource back to pool -func (c *Command) Put() { -} - -// Resp return the response bytes of resp -func (c *Command) Resp() []byte { - return []byte{} -} - // // IsBatch impl the proto.protoRequest and check if the command is batchable // func (rr *Command) IsBatch() bool { // return rr.batchStep != defaultBatchStep From b09b6b125ce42cf3cceae1ae6af2659737d65cab Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 10 Jul 2018 17:10:07 +0800 Subject: [PATCH 03/87] add merge interface --- proto/memcache/merge.go | 41 ++++++++++++++++++++++++++++++++++++ proto/memcache/proxy_conn.go | 3 ++- proto/message.go | 16 ++++++++++---- proto/types.go | 5 +++++ 4 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 proto/memcache/merge.go diff --git a/proto/memcache/merge.go b/proto/memcache/merge.go new file mode 100644 index 00000000..3ee934c4 --- /dev/null +++ b/proto/memcache/merge.go @@ -0,0 +1,41 @@ +package memcache + +import ( + "bytes" + "overlord/proto" +) + +// merger is the merge caller, but it never store any state. +type merger struct{} + +// NewMerger will create new mc merger +func NewMerger() proto.Merger { + return &merger{} +} + +// Merge the response and set the response into subResps +func (r *merger) Merge(m *proto.Message) error { + mcr, ok := m.Request().(*MCRequest) + if !ok { + m.AddSubResps(serverErrorPrefixBytes, []byte(ErrAssertMsg.Error()), crlfBytes) + return nil + } + + _, withValue := withValueTypes[mcr.rTp] + trimEnd := withValue && m.IsBatch() + for _, sub := range m.Subs() { + submcr := sub.Request().(*MCRequest) + bs := submcr.data + if trimEnd { + bs = bytes.TrimSuffix(submcr.data, endBytes) + } + if len(bs) == 0 { + continue + } + m.AddSubResps(bs) + } + if trimEnd { + m.AddSubResps(endBytes) + } + return nil +} diff --git a/proto/memcache/proxy_conn.go b/proto/memcache/proxy_conn.go index 8c370b68..7978cbe1 100644 --- a/proto/memcache/proxy_conn.go +++ b/proto/memcache/proxy_conn.go @@ -20,7 +20,8 @@ const ( ) var ( - serverErrorBytes = []byte(serverErrorPrefix) + serverErrorBytes = []byte(serverErrorPrefix) + serverErrorPrefixBytes = []byte("SERVER_ERROR ") ) type proxyConn struct { diff --git a/proto/message.go b/proto/message.go index aac336db..ac28065e 100644 --- a/proto/message.go +++ b/proto/message.go @@ -48,10 +48,10 @@ func PutMsg(msg *Message) { type Message struct { Type CacheType - req []Request - reqn int - subs []*Message - + req []Request + reqn int + subs []*Message + subResps [][]byte // Start Time, Write Time, ReadTime, EndTime st, wt, rt, et time.Time err error @@ -194,6 +194,14 @@ func (m *Message) Batch() []*Message { return m.subs[:slen] } +func (m *Message) AddSubResps(items ...[]byte) { + m.subResps = append(m.subResps, items...) +} + +func (m *Message) Subs() []*Message { + return m.subs[:m.reqn] +} + // Err returns error. func (m *Message) Err() error { return m.err diff --git a/proto/types.go b/proto/types.go index 6261cb55..dd2426ae 100644 --- a/proto/types.go +++ b/proto/types.go @@ -20,6 +20,11 @@ const ( CacheTypeRedis CacheType = "redis" ) +// Merger will merge the Request with special protocol +type Merger interface { + Merge(*Message) error +} + // Request request interface. type Request interface { CmdString() string From b29ce32d1a9c42247421fe8d8d1d3f03a694dd84 Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 10 Jul 2018 19:26:37 +0800 Subject: [PATCH 04/87] add merge of request functions and tests --- .gitignore | 1 + proto/memcache/merge.go | 16 ++++++---------- proto/memcache/proxy_conn.go | 26 +++----------------------- proto/message.go | 7 +++++++ 4 files changed, 17 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index 303a81ea..cb7b05b3 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ .glide/ cmd/proxy/proxy +coverage.txt diff --git a/proto/memcache/merge.go b/proto/memcache/merge.go index 3ee934c4..be318d78 100644 --- a/proto/memcache/merge.go +++ b/proto/memcache/merge.go @@ -5,24 +5,20 @@ import ( "overlord/proto" ) -// merger is the merge caller, but it never store any state. -type merger struct{} - -// NewMerger will create new mc merger -func NewMerger() proto.Merger { - return &merger{} -} - // Merge the response and set the response into subResps -func (r *merger) Merge(m *proto.Message) error { +func (*proxyConn) Merge(m *proto.Message) error { mcr, ok := m.Request().(*MCRequest) if !ok { m.AddSubResps(serverErrorPrefixBytes, []byte(ErrAssertMsg.Error()), crlfBytes) return nil } + if !m.IsBatch() { + m.AddSubResps(mcr.data) + return nil + } _, withValue := withValueTypes[mcr.rTp] - trimEnd := withValue && m.IsBatch() + trimEnd := withValue for _, sub := range m.Subs() { submcr := sub.Request().(*MCRequest) bs := submcr.data diff --git a/proto/memcache/proxy_conn.go b/proto/memcache/proxy_conn.go index 7978cbe1..9ed83597 100644 --- a/proto/memcache/proxy_conn.go +++ b/proto/memcache/proxy_conn.go @@ -350,29 +350,9 @@ func (p *proxyConn) Encode(m *proto.Message) (err error) { _ = p.bw.Write([]byte(se)) _ = p.bw.Write(crlfBytes) } else { - var bs []byte - reqs := m.Requests() - for _, req := range reqs { - mcr, ok := req.(*MCRequest) - if !ok { - _ = p.bw.Write(serverErrorBytes) - _ = p.bw.Write([]byte(ErrAssertReq.Error())) - _ = p.bw.Write(crlfBytes) - } else { - _, ok := withValueTypes[mcr.rTp] - if ok && m.IsBatch() { - bs = bytes.TrimSuffix(mcr.data, endBytes) - } else { - bs = mcr.data - } - if len(bs) == 0 { - continue - } - _ = p.bw.Write(bs) - } - } - if m.IsBatch() { - _ = p.bw.Write(endBytes) + _ = p.Merge(m) + for _, item := range m.SubResps() { + _ = p.bw.Write(item) } } if err = p.bw.Flush(); err != nil { diff --git a/proto/message.go b/proto/message.go index ac28065e..98381772 100644 --- a/proto/message.go +++ b/proto/message.go @@ -194,14 +194,21 @@ func (m *Message) Batch() []*Message { return m.subs[:slen] } +// AddSubResps appends all items into subResps func (m *Message) AddSubResps(items ...[]byte) { m.subResps = append(m.subResps, items...) } +// Subs returns all the sub messages. func (m *Message) Subs() []*Message { return m.subs[:m.reqn] } +// SubResps returns the response bytes buffer. +func (m *Message) SubResps() [][]byte { + return m.subResps +} + // Err returns error. func (m *Message) Err() error { return m.err From b0e52930301553139153065616af8734b160729c Mon Sep 17 00:00:00 2001 From: wayslog Date: Wed, 11 Jul 2018 17:15:09 +0800 Subject: [PATCH 05/87] add ProxyConn of redis --- lib/hashkit/ketama.go | 2 +- proto/redis/cmd.go | 11 +- proto/redis/proxy.go | 217 +++++++++++++++----------------- proto/redis/redis.go | 1 + proto/redis/resp.go | 12 ++ proto/redis/resp_conn.go | 258 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 378 insertions(+), 123 deletions(-) create mode 100644 proto/redis/resp_conn.go diff --git a/lib/hashkit/ketama.go b/lib/hashkit/ketama.go index 303cdd6a..c59f18be 100644 --- a/lib/hashkit/ketama.go +++ b/lib/hashkit/ketama.go @@ -40,7 +40,7 @@ type HashRing struct { } // Ketama new a hash ring with ketama consistency. -// Default hash: sha1 +// Default hash: fnv1a64 func Ketama() (h *HashRing) { h = new(HashRing) h.hash = NewFnv1a64().fnv1a64 diff --git a/proto/redis/cmd.go b/proto/redis/cmd.go index 7a95d5bc..ee0605af 100644 --- a/proto/redis/cmd.go +++ b/proto/redis/cmd.go @@ -4,6 +4,7 @@ import ( "errors" "strings" // "crc" + "bytes" ) var ( @@ -97,7 +98,13 @@ func (c *Command) Cmd() []byte { // Key impl the proto.protoRequest and get the Key of redis func (c *Command) Key() []byte { - return c.respObj.nth(1).data + var data = c.respObj.nth(1).data + var pos int + if c.respObj.rtype == respBulk { + pos = bytes.Index(data, crlfBytes) + 2 + } + // pos is never empty + return data[pos:] } // Put the resource back to pool @@ -106,5 +113,5 @@ func (c *Command) Put() { // Resp return the response bytes of resp func (c *Command) Resp() []byte { - return []byte{} + return nil } diff --git a/proto/redis/proxy.go b/proto/redis/proxy.go index 537c89c6..7868c068 100644 --- a/proto/redis/proxy.go +++ b/proto/redis/proxy.go @@ -2,7 +2,6 @@ package redis import ( stderrs "errors" - "overlord/lib/bufio" "overlord/lib/conv" libnet "overlord/lib/net" "overlord/proto" @@ -10,60 +9,45 @@ import ( "github.com/pkg/errors" ) +// errors var ( ErrBadAssert = stderrs.New("bad assert for redis") + ErrBadCount = stderrs.New("bad count number") ) -type redisConn struct { - br *bufio.Reader - bw *bufio.Writer - - completed bool +type proxyConn struct { + rc *respConn } // NewProxyConn creates new redis Encoder and Decoder. func NewProxyConn(conn *libnet.Conn) proto.ProxyConn { - r := &redisConn{ - br: bufio.NewReader(conn, bufio.Get(1024)), - bw: bufio.NewWriter(conn), - completed: true, + r := &proxyConn{ + rc: newRespConn(conn), } return r } -func (rc *redisConn) Decode(msgs []*proto.Message) ([]*proto.Message, error) { - var err error - if rc.completed { - err = rc.br.Read() - if err != nil { - return nil, err - } +func (pc *proxyConn) Decode(msgs []*proto.Message) ([]*proto.Message, error) { + var ( + rs []*resp + err error + ) + rs, err = pc.rc.decodeMax(len(msgs)) + if err != nil { + return msgs, err } - for i := range msgs { - rc.completed = false - // set msg type + for i, r := range rs { msgs[i].Type = proto.CacheTypeRedis - // decode - err = rc.decode(msgs[i]) - if err == bufio.ErrBufferFull { - rc.completed = true - msgs[i].Reset() - return msgs[:i], nil - } else if err != nil { + msgs[i].MarkStart() + err = pc.decodeToMsg(r, msgs[i]) + if err != nil { msgs[i].Reset() - return msgs[:i], err } - msgs[i].MarkStart() } - - return msgs, nil + return msgs[:len(rs)], nil } -func (rc *redisConn) decode(msg *proto.Message) (err error) { - robj, err := rc.decodeResp() - if err != nil { - return - } +func (pc *proxyConn) decodeToMsg(robj *resp, msg *proto.Message) (err error) { if isComplex(robj.nth(0).data) { cmds, inerr := newSubCmd(robj) if inerr != nil { @@ -79,117 +63,110 @@ func (rc *redisConn) decode(msg *proto.Message) (err error) { return } -func (rc *redisConn) decodeResp() (robj *resp, err error) { - var ( - line []byte - size int - ) - line, err = rc.br.ReadLine() - if err != nil { - return nil, err +func (pc *proxyConn) Encode(msg *proto.Message) (err error) { + if err = pc.encode(msg); err != nil { + err = errors.Wrap(err, "Redis Encoder encode") + return } - rtype := line[0] - switch rtype { - case respString, respInt, respError: - // decocde use one line to parse - robj = rc.decodePlain(rtype, line) - case respBulk: - // decode bulkString - size, err = decodeInt(line[1 : len(line)-2]) - if err != nil { - return - } - robj, err = rc.decodeBulk(size, len(line)) - case respArray: - size, err = decodeInt(line[1 : len(line)-2]) - if err != nil { - return - } - robj, err = rc.decodeArray(size, len(line)) + if err = pc.rc.Flush(); err != nil { + err = errors.Wrap(err, "Redis Encoder flush response") } return } -func (rc *redisConn) decodePlain(rtype byte, line []byte) *resp { - return newRespPlain(rtype, line[0:len(line)-2]) +func (pc *proxyConn) encode(msg *proto.Message) error { + if err := msg.Err(); err != nil { + return pc.encodeError(err) + } + reply, err := pc.merge(msg) + if err != nil { + return pc.encodeError(err) + } + return pc.rc.encode(reply) } -func (rc *redisConn) decodeBulk(size, lineSize int) (*resp, error) { - if size == -1 { - return newRespNull(respBulk), nil +func (pc *proxyConn) merge(msg *proto.Message) (*resp, error) { + cmd, ok := msg.Request().(*Command) + if !ok { + return nil, ErrBadAssert } - data, err := rc.br.ReadExact(size + 2) - if err == bufio.ErrBufferFull { - rc.br.Advance(-(lineSize + size + 2)) - } else if err != nil { - return nil, err + + if !msg.IsBatch() { + return cmd.reply, nil } - return newRespBulk(data[:len(data)-2]), nil -} -func (rc *redisConn) decodeArray(size int, lineSize int) (*resp, error) { - if size == -1 { - return newRespNull(respArray), nil + mtype, err := pc.getBatchMergeType(msg) + if err != nil { + return nil, err } - robj := newRespArrayWithCapcity(size) - mark := rc.br.Mark() - for i := 0; i < size; i++ { - sub, err := rc.decodeResp() - if err != nil { - rc.br.AdvanceTo(mark) - rc.br.Advance(lineSize) - return nil, err - } - robj.replace(i, sub) + + switch mtype { + case MergeTypeJoin: + return pc.mergeJoin(msg) + case MergeTypeOk: + return pc.mergeOk(msg) + case MergeTypeCount: + return pc.mergeCount(msg) + case MergeTypeBasic: + fallthrough + default: + panic("unreachable path") } - return robj, nil } -func decodeInt(data []byte) (int, error) { - i, err := conv.Btoi(data) - return int(i), err +func (pc *proxyConn) mergeOk(msg *proto.Message) (*resp, error) { + return newRespString(okBytes), nil } -func (rc *redisConn) Encode(msg *proto.Message) (err error) { - if err = rc.encode(msg); err != nil { - err = errors.Wrap(err, "Redis Encoder encode") - return - } - - if err = rc.bw.Flush(); err != nil { - err = errors.Wrap(err, "Redis Encoder flush response") +func (pc *proxyConn) mergeCount(msg *proto.Message) (reply *resp, err error) { + var sum = 0 + for _, sub := range msg.Subs() { + if sub.Err() != nil { + continue + } + subcmd, ok := sub.Request().(*Command) + if !ok { + return nil, ErrBadAssert + } + ival, err := conv.Btoi(subcmd.reply.data) + if err != nil { + return nil, ErrBadCount + } + sum += int(ival) } return } -func (rc *redisConn) encode(msg *proto.Message) (err error) { - if err = msg.Err(); err != nil { - return rc.encodeError(err) +func (pc *proxyConn) mergeJoin(msg *proto.Message) (reply *resp, err error) { + reply = newRespArrayWithCapcity(len(msg.Subs())) + for i, sub := range msg.Subs() { + subcmd, ok := sub.Request().(*Command) + if !ok { + err = pc.encodeError(ErrBadAssert) + if err != nil { + return + } + continue + } + reply.replace(i, subcmd.reply) } - // TODO: need to change message response way - // replyList := msg.Response() - // cmd, ok := msg.Request().(*Command) - // if !ok { - // err = errors.Wrwap(ErrBadAssert, "Redis encode type assert") - // } - - return nil -} - -func (rc *redisConn) mergeReply() (n int, data [][]byte) { return } -func (rc *redisConn) encodeReply(reply *resp) error { - return nil +func (pc *proxyConn) getBatchMergeType(msg *proto.Message) (mtype MergeType, err error) { + cmd, ok := msg.Subs()[0].Request().(*Command) + if !ok { + err = ErrBadAssert + return + } + mtype = cmd.mergeType + return } -func (rc *redisConn) encodeError(err error) error { +func (pc *proxyConn) encodeError(err error) error { se := errors.Cause(err).Error() - _ = rc.bw.Write([]byte{respError}) - _ = rc.bw.WriteString(se) - _ = rc.bw.Write(crlfBytes) - return nil + robj := newRespPlain(respError, []byte(se)) + return pc.rc.encode(robj) } diff --git a/proto/redis/redis.go b/proto/redis/redis.go index e49d87bc..ab5f7151 100644 --- a/proto/redis/redis.go +++ b/proto/redis/redis.go @@ -179,6 +179,7 @@ func getMergeType(cmd []byte) MergeType { if bytes.Equal(cmd, cmdMSetBytes) { return MergeTypeOk } + if bytes.Equal(cmd, cmdExistsBytes) || bytes.Equal(cmd, cmdDelBytes) { return MergeTypeCount } diff --git a/proto/redis/resp.go b/proto/redis/resp.go index cfad64e2..67987efb 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -17,6 +17,18 @@ const ( respArray respType = '*' ) +var ( + respStringBytes = []byte("+") + respErrorBytes = []byte("-") + respIntBytes = []byte(":") + respBulkBytes = []byte("$") + respArrayBytes = []byte("*") + + respNullBytes = []byte("-1\r\n") + + okBytes = []byte("OK") +) + var ( respPool = sync.Pool{ New: func() interface{} { diff --git a/proto/redis/resp_conn.go b/proto/redis/resp_conn.go new file mode 100644 index 00000000..b3b632b2 --- /dev/null +++ b/proto/redis/resp_conn.go @@ -0,0 +1,258 @@ +package redis + +import ( + "overlord/lib/bufio" + "overlord/lib/conv" + libnet "overlord/lib/net" +) + +// respConn will encode and decode resp object to socket +type respConn struct { + br *bufio.Reader + bw *bufio.Writer + + completed bool +} + +// newRespConn will create new resp object Conn +func newRespConn(conn *libnet.Conn) *respConn { + r := &respConn{ + br: bufio.NewReader(conn, bufio.Get(1024)), + bw: bufio.NewWriter(conn), + completed: true, + } + return r +} + +// decodeMax will parse all the resp objects and keep the reuse reference until +// next call of this function. +func (rc *respConn) decodeMax(max int) (resps []*resp, err error) { + var ( + robj *resp + ) + + if rc.completed { + err = rc.br.Read() + if err != nil { + return nil, err + } + rc.completed = false + } + + for i := 0; i < max; i++ { + robj, err = rc.decodeResp() + if err == bufio.ErrBufferFull { + rc.completed = true + err = nil + return + } else if err != nil { + return + } + resps = append(resps, robj) + } + return +} + +// decodecount will copy the buffer and copy the buffer into MsgBatch +func (rc *respConn) decodecount(n int) (resps []*resp, err error) { + var ( + robj *resp + begin = rc.br.Mark() + now = rc.br.Mark() + i = 0 + ) + + for { + // advance the r position to begin to avoid Read fill buffer + rc.br.AdvanceTo(begin) + err = rc.br.Read() + if err != nil { + return + } + rc.br.AdvanceTo(now) + + for { + if i == n { + return + } + + robj, err = rc.decodeResp() + if err == bufio.ErrBufferFull { + break + } + if err != nil { + return + } + resps = append(resps, robj) + now = rc.br.Mark() + i++ + } + } +} + +func (rc *respConn) decodeResp() (robj *resp, err error) { + var ( + line []byte + size int + ) + line, err = rc.br.ReadLine() + if err != nil { + return nil, err + } + + rtype := line[0] + switch rtype { + case respString, respInt, respError: + // decocde use one line to parse + robj = rc.decodePlain(rtype, line) + case respBulk: + // decode bulkString + size, err = decodeInt(line[1 : len(line)-2]) + if err != nil { + return + } + robj, err = rc.decodeBulk(size, len(line)) + case respArray: + size, err = decodeInt(line[1 : len(line)-2]) + if err != nil { + return + } + robj, err = rc.decodeArray(size, len(line)) + } + return +} + +func (rc *respConn) decodePlain(rtype byte, line []byte) *resp { + return newRespPlain(rtype, line[0:len(line)-2]) +} + +func (rc *respConn) decodeBulk(size, lineSize int) (*resp, error) { + if size == -1 { + return newRespNull(respBulk), nil + } + data, err := rc.br.ReadExact(size + 2) + if err == bufio.ErrBufferFull { + rc.br.Advance(-(lineSize + size + 2)) + } else if err != nil { + return nil, err + } + return newRespBulk(data[:len(data)-2]), nil +} + +func (rc *respConn) decodeArray(size int, lineSize int) (*resp, error) { + if size == -1 { + return newRespNull(respArray), nil + } + robj := newRespArrayWithCapcity(size) + mark := rc.br.Mark() + for i := 0; i < size; i++ { + sub, err := rc.decodeResp() + if err != nil { + rc.br.AdvanceTo(mark) + rc.br.Advance(lineSize) + return nil, err + } + robj.replace(i, sub) + } + return robj, nil +} + +func decodeInt(data []byte) (int, error) { + i, err := conv.Btoi(data) + return int(i), err +} + +func (rc *respConn) encode(robj *resp) error { + switch robj.rtype { + case respInt: + return rc.encodeInt(robj) + case respError: + return rc.encodeError(robj) + case respString: + return rc.encodeString(robj) + case respBulk: + return rc.encodeBulk(robj) + case respArray: + return rc.encodeArray(robj) + } + return nil +} + +// Flush was used to writev to flush. +func (rc *respConn) Flush() error { + return rc.bw.Flush() +} + +func (rc *respConn) encodeInt(robj *resp) (err error) { + return rc.encodePlain(respIntBytes, robj) +} + +func (rc *respConn) encodeError(robj *resp) (err error) { + return rc.encodePlain(respErrorBytes, robj) +} + +func (rc *respConn) encodeString(robj *resp) (err error) { + return rc.encodePlain(respStringBytes, robj) +} + +func (rc *respConn) encodePlain(rtypeBytes []byte, robj *resp) (err error) { + err = rc.bw.Write(rtypeBytes) + if err != nil { + return + } + + err = rc.bw.Write(robj.data) + if err != nil { + return + } + err = rc.bw.Write(crlfBytes) + return +} + +func (rc *respConn) encodeBulk(robj *resp) (err error) { + // NOTICE: we need not to convert robj.Len() as int + // due number has been writen into data + err = rc.bw.Write(respBulkBytes) + if err != nil { + return + } + if robj.isNull() { + err = rc.bw.Write(respNullBytes) + return + } + + err = rc.bw.Write(robj.data) + if err != nil { + return + } + + err = rc.bw.Write(crlfBytes) + return +} + +func (rc *respConn) encodeArray(robj *resp) (err error) { + err = rc.bw.Write(respArrayBytes) + if err != nil { + return + } + + if robj.isNull() { + err = rc.bw.Write(respNullBytes) + return + } + // output size + err = rc.bw.Write(robj.data) + if err != nil { + return + } + err = rc.bw.Write(crlfBytes) + + for _, item := range robj.slice() { + err = rc.encode(item) + if err != nil { + return + } + } + + return +} From 0ddd09b9ea1bc5ab95d46242979f9ad2fd6828cc Mon Sep 17 00:00:00 2001 From: wayslog Date: Wed, 11 Jul 2018 18:40:19 +0800 Subject: [PATCH 06/87] add node_conn for redis --- proto/redis/node_conn.go | 80 +++++++++++++++++++++++++ proto/redis/pinger.go | 14 +++++ proto/redis/{proxy.go => proxy_conn.go} | 0 proto/redis/resp_conn.go | 4 +- proto/types.go | 5 -- 5 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 proto/redis/node_conn.go create mode 100644 proto/redis/pinger.go rename proto/redis/{proxy.go => proxy_conn.go} (100%) diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go new file mode 100644 index 00000000..0f1d4f65 --- /dev/null +++ b/proto/redis/node_conn.go @@ -0,0 +1,80 @@ +package redis + +import ( + libnet "overlord/lib/net" + "overlord/proto" + "sync/atomic" +) + +const ( + closed = uint32(0) + opened = uint32(1) +) + +type nodeConn struct { + rc *respConn + conn *libnet.Conn + p *pinger + state uint32 +} + +// NewNodeConn create the node conn from proxy to redis +func NewNodeConn(conn *libnet.Conn) proto.NodeConn { + return &nodeConn{ + rc: newRespConn(conn), + conn: conn, + p: newPinger(conn), + state: closed, + } +} + +func (nc *nodeConn) WriteBatch(mb *proto.MsgBatch) error { + for _, m := range mb.Msgs() { + err := nc.write(m) + if err != nil { + m.DoneWithError(err) + return err + } + } + err := nc.rc.Flush() + return err +} + +func (nc *nodeConn) write(m *proto.Message) error { + cmd, ok := m.Request().(*Command) + if !ok { + m.DoneWithError(ErrBadAssert) + return ErrBadAssert + } + return nc.rc.encode(cmd.respObj) +} + +func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) error { + nc.rc.br.ResetBuffer(mb.Buffer()) + defer nc.rc.br.ResetBuffer(nil) + + count := mb.Count() + resps, err := nc.rc.decodeCount(count) + if err != nil { + return err + } + for i, msg := range mb.Msgs() { + cmd, ok := msg.Request().(*Command) + if !ok { + return ErrBadAssert + } + cmd.reply = resps[i] + } + return nil +} + +func (nc *nodeConn) Ping() error { + return nc.p.ping() +} + +func (nc *nodeConn) Close() error { + if atomic.CompareAndSwapUint32(&nc.state, opened, closed) { + return nc.conn.Close() + } + return nil +} diff --git a/proto/redis/pinger.go b/proto/redis/pinger.go new file mode 100644 index 00000000..94aa4944 --- /dev/null +++ b/proto/redis/pinger.go @@ -0,0 +1,14 @@ +package redis + +import libnet "overlord/lib/net" + +type pinger struct { +} + +func newPinger(conn *libnet.Conn) *pinger { + return &pinger{} +} + +func (p *pinger) ping() (err error) { + return +} diff --git a/proto/redis/proxy.go b/proto/redis/proxy_conn.go similarity index 100% rename from proto/redis/proxy.go rename to proto/redis/proxy_conn.go diff --git a/proto/redis/resp_conn.go b/proto/redis/resp_conn.go index b3b632b2..5732e10e 100644 --- a/proto/redis/resp_conn.go +++ b/proto/redis/resp_conn.go @@ -53,8 +53,8 @@ func (rc *respConn) decodeMax(max int) (resps []*resp, err error) { return } -// decodecount will copy the buffer and copy the buffer into MsgBatch -func (rc *respConn) decodecount(n int) (resps []*resp, err error) { +// decodeCount will trying to parse the buffer until meet the count. +func (rc *respConn) decodeCount(n int) (resps []*resp, err error) { var ( robj *resp begin = rc.br.Mark() diff --git a/proto/types.go b/proto/types.go index dd2426ae..6261cb55 100644 --- a/proto/types.go +++ b/proto/types.go @@ -20,11 +20,6 @@ const ( CacheTypeRedis CacheType = "redis" ) -// Merger will merge the Request with special protocol -type Merger interface { - Merge(*Message) error -} - // Request request interface. type Request interface { CmdString() string From 6f301980ed4180854ee18a831759421e01f7eb19 Mon Sep 17 00:00:00 2001 From: wayslog Date: Thu, 12 Jul 2018 12:09:06 +0800 Subject: [PATCH 07/87] add ping interface --- proto/redis/cmd.go | 18 ++++++++--------- proto/redis/pinger.go | 47 +++++++++++++++++++++++++++++++++++++++++-- proto/redis/reply.go | 1 - 3 files changed, 54 insertions(+), 12 deletions(-) delete mode 100644 proto/redis/reply.go diff --git a/proto/redis/cmd.go b/proto/redis/cmd.go index ee0605af..0e81e4b0 100644 --- a/proto/redis/cmd.go +++ b/proto/redis/cmd.go @@ -47,15 +47,15 @@ type Command struct { // example: // NewCommand("GET", "mykey") // NewCommand("MGET", "mykey", "yourkey") -// func NewCommand(cmd string, args ...string) *Command { -// respObj := newRespArrayWithCapcity(len(args) + 1) -// respObj.replace(0, newRespBulk([]byte(cmd))) -// maxLen := len(args) + 1 -// for i := 1; i < maxLen; i++ { -// respObj.replace(i, newRespBulk([]byte(args[i-1]))) -// } -// return newCommand(respObj) -// } +func NewCommand(cmd string, args ...string) *Command { + respObj := newRespArrayWithCapcity(len(args) + 1) + respObj.replace(0, newRespBulk([]byte(cmd))) + maxLen := len(args) + 1 + for i := 1; i < maxLen; i++ { + respObj.replace(i, newRespBulk([]byte(args[i-1]))) + } + return newCommand(respObj) +} func newCommand(robj *resp) *Command { r := &Command{respObj: robj} diff --git a/proto/redis/pinger.go b/proto/redis/pinger.go index 94aa4944..c81d031f 100644 --- a/proto/redis/pinger.go +++ b/proto/redis/pinger.go @@ -1,14 +1,57 @@ package redis -import libnet "overlord/lib/net" +import ( + "bytes" + "errors" + "overlord/lib/bufio" + libnet "overlord/lib/net" + "sync/atomic" +) + +// errors +var ( + ErrPingClosed = errors.New("ping interface has been closed") + ErrBadPong = errors.New("pong response payload is bad") +) + +var ( + pingBytes = []byte("*1\r\n$4\r\nPING\r\n") + pongBytes = []byte("+PONG\r\n") +) type pinger struct { + conn *libnet.Conn + + br *bufio.Reader + bw *bufio.Writer + + state uint32 } func newPinger(conn *libnet.Conn) *pinger { - return &pinger{} + return &pinger{ + conn: conn, + br: bufio.NewReader(conn, bufio.Get(64)), + bw: bufio.NewWriter(conn), + } } func (p *pinger) ping() (err error) { + if atomic.LoadUint32(&p.state) == closed { + err = ErrPingClosed + return + } + err = p.bw.Write(pingBytes) + if err != nil { + return + } + data, err := p.br.ReadUntil(lfByte) + if err != nil { + return + } + if bytes.Equal(data, pongBytes) { + err = ErrBadPong + return + } return } diff --git a/proto/redis/reply.go b/proto/redis/reply.go deleted file mode 100644 index 65a229e1..00000000 --- a/proto/redis/reply.go +++ /dev/null @@ -1 +0,0 @@ -package redis From 660ccd32a8f757b41de080227f14c76608c8dd76 Mon Sep 17 00:00:00 2001 From: wayslog Date: Thu, 12 Jul 2018 18:50:38 +0800 Subject: [PATCH 08/87] add crc16 and hash Ring interface --- lib/hashkit/crc16.go | 48 ++++++++ lib/hashkit/hash.go | 38 +++++++ lib/hashkit/ketama.go | 20 +++- lib/hashkit/rediscluster.go | 124 +++++++++++++++++++++ proto/memcache/node_conn_test.go | 8 +- proto/redis/cmd.go | 38 ++++++- proto/redis/types.go | 184 ------------------------------- 7 files changed, 264 insertions(+), 196 deletions(-) create mode 100644 lib/hashkit/crc16.go create mode 100644 lib/hashkit/hash.go create mode 100644 lib/hashkit/rediscluster.go delete mode 100644 proto/redis/types.go diff --git a/lib/hashkit/crc16.go b/lib/hashkit/crc16.go new file mode 100644 index 00000000..28efc5ff --- /dev/null +++ b/lib/hashkit/crc16.go @@ -0,0 +1,48 @@ +package hashkit + +// CRC16 implementation according to CCITT standards. +// Copyright 2001-2010 Georges Menie (www.menie.org) +// Copyright 2013 The Go Authors. All rights reserved. +// http://redis.io/topics/cluster-spec#appendix-a-crc16-reference-implementation-in-ansi-c +var crc16tab = [256]uint16{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, +} + +// Crc16 caculate key crc to divide slot of redis +func Crc16(key []byte) (crc uint16) { + for i := 0; i < len(key); i++ { + crc = (crc << 8) ^ crc16tab[(byte(crc>>8)^key[i])&0x00ff] + } + return +} diff --git a/lib/hashkit/hash.go b/lib/hashkit/hash.go new file mode 100644 index 00000000..7e8666d8 --- /dev/null +++ b/lib/hashkit/hash.go @@ -0,0 +1,38 @@ +package hashkit + +const ( + HashMethodFnv1a = "fnv1a_64" +) + +// Ring is a abstraction +type Ring interface { + // AddNode will add node into this ring + // if the ring is ketma hash args contains only one. + AddNode(node string, args ...int) + + // DelNode will remove node. + DelNode(node string) + + // UpdateSlot will update single one slot due to redis move/ask + UpdateSlot(node string, slot int) + + GetNode(key []byte) (string, bool) + + Init(masters []string, slots ...[]int) +} + +// NewRing will create new and need init method. +func NewRing(des, method string) Ring { + if des == "redis_cluster" { + return newRedisClusterRing() + } + + var hash func([]byte) uint + switch method { + case HashMethodFnv1a: + fallthrough + default: + hash = NewFnv1a64().fnv1a64 + } + return newMCRing(hash) +} diff --git a/lib/hashkit/ketama.go b/lib/hashkit/ketama.go index c59f18be..12d3e900 100644 --- a/lib/hashkit/ketama.go +++ b/lib/hashkit/ketama.go @@ -47,6 +47,11 @@ func Ketama() (h *HashRing) { return } +// newMCRing is the ring of mc +func newMCRing(hash func([]byte) uint) Ring { + return NewRingWithHash(hash) +} + // NewRingWithHash new a hash ring with a hash func. func NewRingWithHash(hash func([]byte) uint) (h *HashRing) { h = Ketama() @@ -54,8 +59,13 @@ func NewRingWithHash(hash func([]byte) uint) (h *HashRing) { return } +// UpdateSlot fake impl +func (h *HashRing) UpdateSlot(node string, slot int) { +} + // Init init hash ring with nodes. -func (h *HashRing) Init(nodes []string, spots []int) { +func (h *HashRing) Init(nodes []string, sl ...[]int) { + spots := sl[0] if len(nodes) != len(spots) { panic("nodes length not equal spots length") } @@ -110,12 +120,13 @@ func (h *HashRing) ketamaHash(key string, kl, alignment int) (v uint) { // AddNode a new node to the hash ring. // n: name of the server // s: multiplier for default number of ticks (useful when one cache node has more resources, like RAM, than another) -func (h *HashRing) AddNode(node string, spot int) { +func (h *HashRing) AddNode(node string, args ...int) { var ( tmpNode []string tmpSpot []int exitst bool ) + spot := args[0] h.lock.Lock() for i, nd := range h.nodes { tmpNode = append(tmpNode, nd) @@ -163,6 +174,11 @@ func (h *HashRing) DelNode(n string) { // Hash returns result node. func (h *HashRing) Hash(key []byte) (string, bool) { + return h.GetNode(key) +} + +// GetNode returns result node by given key. +func (h *HashRing) GetNode(key []byte) (string, bool) { ts, ok := h.ticks.Load().(*tickArray) if !ok || ts.length == 0 { return "", false diff --git a/lib/hashkit/rediscluster.go b/lib/hashkit/rediscluster.go new file mode 100644 index 00000000..cdd42832 --- /dev/null +++ b/lib/hashkit/rediscluster.go @@ -0,0 +1,124 @@ +package hashkit + +import ( + "errors" + "sync" +) + +const musk = 0x3fff + +// error +var ( + ErrWrongSlaveArgs = errors.New("arguments a invalid") + ErrMasterNotExists = errors.New("master didn't exists") +) + +// slotsMap is the struct of redis clusters slotsMap +type slotsMap struct { + // slaves/searchIndex has the same length of masters + masters []string + searchIndex [][]int + + slots []int + + lock sync.Mutex +} + +// newRedisClusterRing will create new redis clusters. +func newRedisClusterRing() Ring { + s := &slotsMap{ + slots: make([]int, musk+1), + lock: sync.Mutex{}, + } + // s.Init(masters, slots...) + return s +} + +func (s *slotsMap) Init(masters []string, args ...[]int) { + s.lock.Lock() + s.masters = masters + for i := range s.masters { + for _, slot := range args[i] { + s.slots[slot] = i + } + } + s.lock.Unlock() +} + +func (s *slotsMap) find(n string) int { + return findStringSlice(s.masters, n) +} + +// AddNode will add node into this ring +// if the ring is ketma hash args contains only one. +func (s *slotsMap) AddNode(node string, args ...int) { + s.lock.Lock() + idx := s.find(node) + if idx == -1 { + idx = len(s.masters) + s.masters = append(s.masters, node) + s.searchIndex = append(s.searchIndex, make([]int, 0)) + } + + for _, slot := range args { + s.slots[slot] = idx + s.searchIndex[idx] = append(s.searchIndex[idx], slot) + } + s.lock.Unlock() +} + +// DelNode will remove node. +func (s *slotsMap) DelNode(node string) { + s.lock.Lock() + idx := s.find(node) + if idx == -1 { + s.lock.Unlock() + return + } + + for i, ival := range s.slots { + if ival == idx { + s.slots[i] = -1 + } + } + s.searchIndex[idx] = s.searchIndex[idx][:0] + s.lock.Unlock() +} + +// UpdateSlot will update single one slot due to redis move/ask +func (s *slotsMap) UpdateSlot(node string, slot int) { + s.lock.Lock() + idx := s.find(node) + + if idx == -1 { + idx = len(s.masters) + s.masters = append(s.masters, node) + } + + s.slots[slot] = idx +} + +func (s *slotsMap) Hash(key []byte) int { + return int(Crc16(key) & musk) +} + +func (s *slotsMap) GetNode(key []byte) (string, bool) { + s.lock.Lock() + defer s.lock.Unlock() + slot := Crc16(key) & musk + idx := s.slots[slot] + if idx == -1 { + return "", false + } + + return s.masters[idx], true +} + +func findStringSlice(slice []string, item string) int { + for i := range slice { + if slice[i] == item { + return i + } + } + return -1 +} diff --git a/proto/memcache/node_conn_test.go b/proto/memcache/node_conn_test.go index 1d45c2f3..6739016d 100644 --- a/proto/memcache/node_conn_test.go +++ b/proto/memcache/node_conn_test.go @@ -140,10 +140,6 @@ func (*mockReq) Key() []byte { return []byte{} } -func (*mockReq) Resp() []byte { - return nil -} - func (*mockReq) Put() { } @@ -205,9 +201,9 @@ func TestNodeConnReadOk(t *testing.T) { batch.AddMsg(req) err := nc.ReadBatch(batch) assert.NoError(t, err) - mcr, ok := req.Request().(*MCRequest) - assert.Equal(t, true, ok) + assert.True(t, ok) + assert.NotNil(t, mcr) assert.Equal(t, tt.except, string(mcr.data)) }) diff --git a/proto/redis/cmd.go b/proto/redis/cmd.go index 0e81e4b0..ff82d8e8 100644 --- a/proto/redis/cmd.go +++ b/proto/redis/cmd.go @@ -2,6 +2,7 @@ package redis import ( "errors" + "strconv" "strings" // "crc" "bytes" @@ -68,7 +69,7 @@ func newCommandWithMergeType(robj *resp, mtype MergeType) *Command { } // Slot will caculate the redis crc and return the slot value -func (rr *Command) Slot() int { +func (c *Command) Slot() int { // TODO:CRC16 // keyData := rr.respObj.nth(1).data @@ -111,7 +112,36 @@ func (c *Command) Key() []byte { func (c *Command) Put() { } -// Resp return the response bytes of resp -func (c *Command) Resp() []byte { - return nil +// IsRedirect check if response type is Redis Error +// and payload was prefix with "ASK" && "MOVED" +func (c *Command) IsRedirect() bool { + if c.respObj.rtype != respError { + return false + } + if c.respObj.data == nil { + return false + } + + return bytes.HasPrefix(c.respObj.data, movedBytes) || + bytes.HasPrefix(c.respObj.data, askBytes) +} + +// RedirectTriple will check and send back by is +// first return variable which was called as redirectType maybe return ASK or MOVED +// second is the slot of redirect +// third is the redirect addr +// last is the error when parse the redirect body +func (c *Command) RedirectTriple() (redirect string, slot int, addr string, err error) { + fields := strings.Fields(string(c.respObj.data)) + if len(fields) != 3 { + err = ErrRedirectBadFormat + return + } + redirect = fields[0] + addr = fields[2] + ival, parseErr := strconv.Atoi(fields[1]) + + slot = ival + err = parseErr + return } diff --git a/proto/redis/types.go b/proto/redis/types.go deleted file mode 100644 index 27c5b239..00000000 --- a/proto/redis/types.go +++ /dev/null @@ -1,184 +0,0 @@ -package redis - -// // IsBatch impl the proto.protoRequest and check if the command is batchable -// func (rr *Command) IsBatch() bool { -// return rr.batchStep != defaultBatchStep -// } - -// // // Batch impl the proto.protoRequest and split the command into divided part. -// // func (rr *Command) Batch() ([]proto.Request, *proto.Response) { -// // if rr.batchStep == defaultBatchStep { -// // // batch but only split one -// // return rr.batchOne() -// // } - -// // return rr.batchByStep(rr.batchStep) -// // } - -// // func (rr *Command) batchOne() ([]proto.Request, *proto.Response) { -// // reqs := []proto.Request{ -// // proto.Request{ -// // Type: proto.CacheTypeRedis, -// // }, -// // } -// // reqs[0].WithProto(rr) -// // response := &proto.Response{ -// // Type: proto.CacheTypeRedis, -// // } -// // return reqs, response -// // } - -// // func (rr *Command) batchByStep(step int) ([]proto.Request, *proto.Response) { -// // // NEEDTEST(wayslog): we assume that the request is full. - -// // // trim cmd -// // cmd := rr.Cmd() -// // mergeType := getMergeType(cmd) - -// // slice := rr.respObj.slice()[1:] - -// // items := (rr.respObj.Len() - 1) / step -// // resps := make([]proto.Request, items) - -// // batchCmd := getBatchCmd(cmd) - -// // bcmdResp := newRespString(batchCmd) -// // bcmdType := getCmdType(batchCmd) - -// // for i := 0; i < items; i++ { -// // // keyPos := i*step -// // // argsBegin := i*step+1 -// // // argsEnd := i*step + step -1 -// // r := newRespArrayWithCapcity(step + 1) -// // r.replace(0, bcmdResp) -// // r.replaceAll(1, slice[i*step:(i+1)*step]) - -// // req := proto.Request{Type: proto.CacheTypeRedis} -// // req.WithProto(&Command{ -// // respObj: r, -// // cmdType: bcmdType, -// // batchStep: defaultBatchStep, -// // }) -// // resps[i] = req -// // } - -// // response := &proto.Response{ -// // Type: proto.CacheTypeRedis, -// // } -// // response.WithProto(newRResponse(mergeType, nil)) -// // return resps, response -// // } - -// // RResponse is the redis response protocol type. -// type RResponse struct { -// respObj *resp - -// mergeType MergeType -// } - -// func newRResponse(mtype MergeType, robj *resp) *RResponse { -// return &RResponse{ -// mergeType: mtype, -// respObj: robj, -// } -// } - -// // Merge impl the proto.Merge interface -// func (rr *RResponse) Merge(subs []proto.Request) { -// switch rr.mergeType { -// case MergeTypeBasic: -// srr, ok := subs[0].Resp.Proto().(*RResponse) -// if !ok { -// // TOOD(wayslog): log it -// return -// } -// rr.respObj = srr.respObj -// case MergeTypeJoin: -// rr.mergeJoin(subs) -// case MergeTypeCount: -// rr.mergeCount(subs) -// case MergeTypeOk: -// rr.mergeOk(subs) -// } -// } - -// func (rr *RResponse) mergeJoin(subs []proto.Request) { -// if rr.respObj == nil { -// rr.respObj = newRespArrayWithCapcity(len(subs)) -// } -// if rr.respObj.isNull() { -// rr.respObj.array = make([]*resp, len(subs)) -// } -// for idx, sub := range subs { -// srr, ok := sub.Resp.Proto().(*RResponse) -// if !ok { -// // TODO(wayslog): log it -// continue -// } -// rr.respObj.replace(idx, srr.respObj) -// } -// } - -// // IsRedirect check if response type is Redis Error -// // and payload was prefix with "ASK" && "MOVED" -// func (rr *RResponse) IsRedirect() bool { -// if rr.respObj.rtype != respError { -// return false -// } -// if rr.respObj.data == nil { -// return false -// } - -// return bytes.HasPrefix(rr.respObj.data, movedBytes) || -// bytes.HasPrefix(rr.respObj.data, askBytes) -// } - -// // RedirectTriple will check and send back by is -// // first return variable which was called as redirectType maybe return ASK or MOVED -// // second is the slot of redirect -// // third is the redirect addr -// // last is the error when parse the redirect body -// func (rr *RResponse) RedirectTriple() (redirect string, slot int, addr string, err error) { -// fields := strings.Fields(string(rr.respObj.data)) -// if len(fields) != 3 { -// err = ErrRedirectBadFormat -// return -// } -// redirect = fields[0] -// addr = fields[2] -// ival, parseErr := strconv.Atoi(fields[1]) - -// slot = ival -// err = parseErr -// return -// } - -// func (rr *RResponse) mergeCount(subs []proto.Request) { -// count := 0 -// for _, sub := range subs { -// if err := sub.Resp.Err(); err != nil { -// // TODO(wayslog): log it -// continue -// } -// ssr, ok := sub.Resp.Proto().(*RResponse) -// if !ok { -// continue -// } -// ival, err := strconv.Atoi(string(ssr.respObj.data)) -// if err != nil { -// continue -// } -// count += ival -// } -// rr.respObj = newRespInt(count) -// } - -// func (rr *RResponse) mergeOk(subs []proto.Request) { -// for _, sub := range subs { -// if err := sub.Resp.Err(); err != nil { -// // TODO(wayslog): set as bad response -// return -// } -// } -// rr.respObj = newRespString("OK") -// } From 37a0c151d980fe6922b21697f3e72b08577bb7ff Mon Sep 17 00:00:00 2001 From: wayslog Date: Thu, 12 Jul 2018 19:21:25 +0800 Subject: [PATCH 09/87] add cluster and handler init bootstrap call --- lib/hashkit/rediscluster.go | 3 ++- proto/redis/node_conn.go | 28 +++++++++++++++++++--------- proxy/cluster.go | 9 +++++---- proxy/handler.go | 3 ++- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/lib/hashkit/rediscluster.go b/lib/hashkit/rediscluster.go index cdd42832..0aba27d9 100644 --- a/lib/hashkit/rediscluster.go +++ b/lib/hashkit/rediscluster.go @@ -36,7 +36,8 @@ func newRedisClusterRing() Ring { func (s *slotsMap) Init(masters []string, args ...[]int) { s.lock.Lock() - s.masters = masters + s.masters = make([]string, len(masters)) + copy(s.masters, masters) for i := range s.masters { for _, slot := range args[i] { s.slots[slot] = i diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index 0f1d4f65..2d344e96 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -4,6 +4,7 @@ import ( libnet "overlord/lib/net" "overlord/proto" "sync/atomic" + "time" ) const ( @@ -12,19 +13,28 @@ const ( ) type nodeConn struct { - rc *respConn - conn *libnet.Conn - p *pinger - state uint32 + cluster string + addr string + conn *libnet.Conn + rc *respConn + p *pinger + state uint32 } // NewNodeConn create the node conn from proxy to redis -func NewNodeConn(conn *libnet.Conn) proto.NodeConn { +func NewNodeConn(cluster, addr string, dialTimeout, readTimeout, writeTimeout time.Duration) (nc proto.NodeConn) { + conn := libnet.DialWithTimeout(addr, dialTimeout, readTimeout, writeTimeout) + return newNodeConn(cluster, addr, conn) +} + +func newNodeConn(cluster, addr string, conn *libnet.Conn) proto.NodeConn { return &nodeConn{ - rc: newRespConn(conn), - conn: conn, - p: newPinger(conn), - state: closed, + cluster: cluster, + addr: addr, + rc: newRespConn(conn), + conn: conn, + p: newPinger(conn), + state: closed, } } diff --git a/proxy/cluster.go b/proxy/cluster.go index 01a76b9f..2a1be256 100644 --- a/proxy/cluster.go +++ b/proxy/cluster.go @@ -17,6 +17,7 @@ import ( "overlord/proto" "overlord/proto/memcache" mcbin "overlord/proto/memcache/binary" + "overlord/proto/redis" "github.com/pkg/errors" ) @@ -63,7 +64,7 @@ type Cluster struct { hashTag []byte - ring *hashkit.HashRing + ring hashkit.Ring alias bool nodeMap map[string]int @@ -88,7 +89,7 @@ func NewCluster(ctx context.Context, cc *ClusterConfig) (c *Cluster) { } c.alias = alias - ring := hashkit.Ketama() + ring := hashkit.NewRing(cc.HashDistribution, cc.HashMethod) if c.alias { ring.Init(ans, ws) } else { @@ -269,7 +270,7 @@ func (c *Cluster) hash(key []byte) (node string, ok bool) { if len(realKey) == 0 { realKey = key } - node, ok = c.ring.Hash(realKey) + node, ok = c.ring.GetNode(realKey) return } @@ -333,7 +334,7 @@ func newNodeConn(cc *ClusterConfig, addr string) proto.NodeConn { case proto.CacheTypeMemcacheBinary: return mcbin.NewNodeConn(cc.Name, addr, dto, rto, wto) case proto.CacheTypeRedis: - // TODO(felix): support redis + return redis.NewNodeConn(cc.Name, addr, dto, rto, wto) default: panic(proto.ErrNoSupportCacheType) } diff --git a/proxy/handler.go b/proxy/handler.go index e071bbc9..722fd6c3 100644 --- a/proxy/handler.go +++ b/proxy/handler.go @@ -13,6 +13,7 @@ import ( "overlord/proto" "overlord/proto/memcache" mcbin "overlord/proto/memcache/binary" + "overlord/proto/redis" ) const ( @@ -61,7 +62,7 @@ func NewHandler(ctx context.Context, c *Config, conn net.Conn, cluster *Cluster) case proto.CacheTypeMemcacheBinary: h.pc = mcbin.NewProxyConn(h.conn) case proto.CacheTypeRedis: - // TODO(felix): support redis. + h.pc = redis.NewProxyConn(h.conn) default: panic(proto.ErrNoSupportCacheType) } From b8e065fa9f803f1aeb7b6b9987be1dbcab1926f7 Mon Sep 17 00:00:00 2001 From: wayslog Date: Thu, 12 Jul 2018 20:01:00 +0800 Subject: [PATCH 10/87] remove unused code and fixed bugs --- proto/redis/cmd.go | 19 ------ proto/redis/redis.go | 156 ------------------------------------------- proto/redis/resp.go | 8 --- proxy/cluster.go | 1 - 4 files changed, 184 deletions(-) diff --git a/proto/redis/cmd.go b/proto/redis/cmd.go index ff82d8e8..52d4f2d6 100644 --- a/proto/redis/cmd.go +++ b/proto/redis/cmd.go @@ -68,25 +68,6 @@ func newCommandWithMergeType(robj *resp, mtype MergeType) *Command { return &Command{respObj: robj, mergeType: mtype} } -// Slot will caculate the redis crc and return the slot value -func (c *Command) Slot() int { - // TODO:CRC16 - // keyData := rr.respObj.nth(1).data - - // // support HashTag - // idx := bytes.IndexByte(keyData, '{') - // if idx != -1 { - // eidx := bytes.IndexByte(keyData, '}') - // if eidx > idx { - // // matched - // keyData = keyData[idx+1 : eidx] - // } - // } - // crcVal := crc.Crc16(string(keyData)) - // return int(crcVal) & SlotShiled - return 0 -} - // CmdString get the cmd func (c *Command) CmdString() string { return strings.ToUpper(c.respObj.nth(0).String()) diff --git a/proto/redis/redis.go b/proto/redis/redis.go index ab5f7151..5bad7e19 100644 --- a/proto/redis/redis.go +++ b/proto/redis/redis.go @@ -4,162 +4,6 @@ import ( "bytes" ) -// Command type -// MSET split with command change command as SET -// EXISTS will return an existed count -// DELETE will return delete count -// CLUSTER NODES will return an mock response of cluster - -// CmdType is the type of proxy -type CmdType = uint8 - -// command types for read/write spliting -const ( - CmdTypeRead uint8 = iota - CmdTypeWrite - CmdTypeNotSupport - CmdTypeCtl -) - -var cmdTypeMap = map[string]CmdType{ - "DEL": CmdTypeWrite, - "DUMP": CmdTypeRead, - "EXISTS": CmdTypeRead, - "EXPIRE": CmdTypeWrite, - "EXPIREAT": CmdTypeWrite, - "KEYS": CmdTypeNotSupport, - "MIGRATE": CmdTypeNotSupport, - "MOVE": CmdTypeNotSupport, - "OBJECT": CmdTypeNotSupport, - "PERSIST": CmdTypeWrite, - "PEXPIRE": CmdTypeWrite, - "PEXPIREAT": CmdTypeWrite, - "PTTL": CmdTypeRead, - "RANDOMKEY": CmdTypeNotSupport, - "RENAME": CmdTypeNotSupport, - "RENAMENX": CmdTypeNotSupport, - "RESTORE": CmdTypeWrite, - "SCAN": CmdTypeNotSupport, - "SORT": CmdTypeWrite, - "TTL": CmdTypeRead, - "TYPE": CmdTypeRead, - "WAIT": CmdTypeNotSupport, - "APPEND": CmdTypeWrite, - "BITCOUNT": CmdTypeRead, - "BITOP": CmdTypeNotSupport, - "BITPOS": CmdTypeRead, - "DECR": CmdTypeWrite, - "DECRBY": CmdTypeWrite, - "GET": CmdTypeRead, - "GETBIT": CmdTypeRead, - "GETRANGE": CmdTypeRead, - "GETSET": CmdTypeWrite, - "INCR": CmdTypeWrite, - "INCRBY": CmdTypeWrite, - "INCRBYFLOAT": CmdTypeWrite, - "MGET": CmdTypeRead, - "MSET": CmdTypeWrite, - "MSETNX": CmdTypeNotSupport, - "PSETEX": CmdTypeWrite, - "SET": CmdTypeWrite, - "SETBIT": CmdTypeWrite, - "SETEX": CmdTypeWrite, - "SETNX": CmdTypeWrite, - "SETRANGE": CmdTypeWrite, - "STRLEN": CmdTypeRead, - "HDEL": CmdTypeWrite, - "HEXISTS": CmdTypeRead, - "HGET": CmdTypeRead, - "HGETALL": CmdTypeRead, - "HINCRBY": CmdTypeWrite, - "HINCRBYFLOAT": CmdTypeWrite, - "HKEYS": CmdTypeRead, - "HLEN": CmdTypeRead, - "HMGET": CmdTypeRead, - "HMSET": CmdTypeWrite, - "HSET": CmdTypeWrite, - "HSETNX": CmdTypeWrite, - "HSTRLEN": CmdTypeRead, - "HVALS": CmdTypeRead, - "HSCAN": CmdTypeRead, - "BLPOP": CmdTypeNotSupport, - "BRPOP": CmdTypeNotSupport, - "BRPOPLPUSH": CmdTypeNotSupport, - "LINDEX": CmdTypeRead, - "LINSERT": CmdTypeWrite, - "LLEN": CmdTypeRead, - "LPOP": CmdTypeWrite, - "LPUSH": CmdTypeWrite, - "LPUSHX": CmdTypeWrite, - "LRANGE": CmdTypeRead, - "LREM": CmdTypeWrite, - "LSET": CmdTypeWrite, - "LTRIM": CmdTypeWrite, - "RPOP": CmdTypeWrite, - "RPOPLPUSH": CmdTypeWrite, - "RPUSH": CmdTypeWrite, - "RPUSHX": CmdTypeWrite, - "SADD": CmdTypeWrite, - "SCARD": CmdTypeRead, - "SDIFF": CmdTypeRead, - "SDIFFSTORE": CmdTypeWrite, - "SINTER": CmdTypeRead, - "SINTERSTORE": CmdTypeWrite, - "SISMEMBER": CmdTypeRead, - "SMEMBERS": CmdTypeRead, - "SMOVE": CmdTypeWrite, - "SPOP": CmdTypeWrite, - "SRANDMEMBER": CmdTypeRead, - "SREM": CmdTypeWrite, - "SUNION": CmdTypeRead, - "SUNIONSTORE": CmdTypeWrite, - "SSCAN": CmdTypeRead, - "ZADD": CmdTypeWrite, - "ZCARD": CmdTypeRead, - "ZCOUNT": CmdTypeRead, - "ZINCRBY": CmdTypeWrite, - "ZINTERSTORE": CmdTypeWrite, - "ZLEXCOUNT": CmdTypeRead, - "ZRANGE": CmdTypeRead, - "ZRANGEBYLEX": CmdTypeRead, - "ZRANGEBYSCORE": CmdTypeRead, - "ZRANK": CmdTypeRead, - "ZREM": CmdTypeWrite, - "ZREMRANGEBYLEX": CmdTypeWrite, - "ZREMRANGEBYRANK": CmdTypeWrite, - "ZREMRANGEBYSCORE": CmdTypeWrite, - "ZREVRANGE": CmdTypeRead, - "ZREVRANGEBYLEX": CmdTypeRead, - "ZREVRANGEBYSCORE": CmdTypeRead, - "ZREVRANK": CmdTypeRead, - "ZSCORE": CmdTypeRead, - "ZUNIONSTORE": CmdTypeWrite, - "ZSCAN": CmdTypeRead, - "PFADD": CmdTypeWrite, - "PFCOUNT": CmdTypeRead, - "PFMERGE": CmdTypeWrite, - "EVAL": CmdTypeWrite, - "EVALSHA": CmdTypeNotSupport, - - "PING": CmdTypeRead, - "AUTH": CmdTypeNotSupport, - "ECHO": CmdTypeNotSupport, - "INFO": CmdTypeNotSupport, - "PROXY": CmdTypeNotSupport, - "SLOWLOG": CmdTypeNotSupport, - "QUIT": CmdTypeNotSupport, - "SELECT": CmdTypeNotSupport, - "TIME": CmdTypeNotSupport, - "CONFIG": CmdTypeNotSupport, -} - -func getCmdType(cmd string) CmdType { - if ctype, ok := cmdTypeMap[cmd]; ok { - return ctype - } - return CmdTypeNotSupport -} - // MergeType is used to decript the merge operation. type MergeType = uint8 diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 67987efb..5f257427 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -117,11 +117,3 @@ func (r *resp) slice() []*resp { func (r *resp) Len() int { return len(r.array) } - -func (r *resp) String() string { - if r.rtype == respString || r.rtype == respBulk { - return string(r.data) - } - // TODO(wayslog): 实现其他的命令的 string - return "" -} diff --git a/proxy/cluster.go b/proxy/cluster.go index 2a1be256..b584669c 100644 --- a/proxy/cluster.go +++ b/proxy/cluster.go @@ -338,5 +338,4 @@ func newNodeConn(cc *ClusterConfig, addr string) proto.NodeConn { default: panic(proto.ErrNoSupportCacheType) } - return nil } From bd803ac425730caf211b227f5d9285c7d26c87d0 Mon Sep 17 00:00:00 2001 From: wayslog Date: Fri, 13 Jul 2018 19:54:38 +0800 Subject: [PATCH 11/87] add a lot's of test cases --- proto/memcache/mc.go | 1 + proto/memcache/mc_test.go | 58 ++++++++++++++ proto/memcache/pinger_test.go | 55 ------------- proto/redis/cmd.go | 30 ++++--- proto/redis/cmd_test.go | 28 +++++++ proto/redis/node_conn_test.go | 88 +++++++++++++++++++++ proto/redis/pinger.go | 16 +++- proto/redis/pinger_test.go | 30 +++++++ proto/redis/proxy_conn.go | 1 + proto/redis/proxy_conn_test.go | 31 ++++++++ proto/redis/redis_test.go | 67 ++++++++++++++++ proto/redis/resp.go | 10 ++- proto/redis/resp_conn.go | 48 +++++++----- proto/redis/resp_conn_test.go | 139 +++++++++++++++++++++++++++++++++ proto/redis/sub.go | 10 +-- 15 files changed, 515 insertions(+), 97 deletions(-) create mode 100644 proto/memcache/mc.go create mode 100644 proto/memcache/mc_test.go create mode 100644 proto/redis/cmd_test.go create mode 100644 proto/redis/node_conn_test.go create mode 100644 proto/redis/pinger_test.go create mode 100644 proto/redis/proxy_conn_test.go create mode 100644 proto/redis/redis_test.go create mode 100644 proto/redis/resp_conn_test.go diff --git a/proto/memcache/mc.go b/proto/memcache/mc.go new file mode 100644 index 00000000..89883a21 --- /dev/null +++ b/proto/memcache/mc.go @@ -0,0 +1 @@ +package memcache diff --git a/proto/memcache/mc_test.go b/proto/memcache/mc_test.go new file mode 100644 index 00000000..b52d2c54 --- /dev/null +++ b/proto/memcache/mc_test.go @@ -0,0 +1,58 @@ +package memcache + +import ( + "bytes" + "net" + libnet "overlord/lib/net" + "time" +) + +type mockAddr string + +func (m mockAddr) Network() string { + return "tcp" +} +func (m mockAddr) String() string { + return string(m) +} + +type mockConn struct { + rbuf *bytes.Buffer + wbuf *bytes.Buffer + addr mockAddr +} + +func (m *mockConn) Read(b []byte) (n int, err error) { + return m.rbuf.Read(b) +} +func (m *mockConn) Write(b []byte) (n int, err error) { + return m.wbuf.Write(b) +} + +// writeBuffers impl the net.buffersWriter to support writev +func (m *mockConn) writeBuffers(buf *net.Buffers) (int64, error) { + return buf.WriteTo(m.wbuf) +} + +func (m *mockConn) Close() error { return nil } +func (m *mockConn) LocalAddr() net.Addr { return m.addr } +func (m *mockConn) RemoteAddr() net.Addr { return m.addr } + +func (m *mockConn) SetDeadline(t time.Time) error { return nil } +func (m *mockConn) SetReadDeadline(t time.Time) error { return nil } +func (m *mockConn) SetWriteDeadline(t time.Time) error { return nil } + +// _createConn is useful tools for handler test +func _createConn(data []byte) *libnet.Conn { + return _createRepeatConn(data, 1) +} + +func _createRepeatConn(data []byte, r int) *libnet.Conn { + mconn := &mockConn{ + addr: "127.0.0.1:12345", + rbuf: bytes.NewBuffer(bytes.Repeat(data, r)), + wbuf: new(bytes.Buffer), + } + conn := libnet.NewConn(mconn, time.Second, time.Second) + return conn +} diff --git a/proto/memcache/pinger_test.go b/proto/memcache/pinger_test.go index 0206d577..966bf8f2 100644 --- a/proto/memcache/pinger_test.go +++ b/proto/memcache/pinger_test.go @@ -1,68 +1,13 @@ package memcache import ( - "bytes" "io" - "net" "testing" - "time" - - libnet "overlord/lib/net" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) -type mockAddr string - -func (m mockAddr) Network() string { - return "tcp" -} -func (m mockAddr) String() string { - return string(m) -} - -type mockConn struct { - rbuf *bytes.Buffer - wbuf *bytes.Buffer - addr mockAddr -} - -func (m *mockConn) Read(b []byte) (n int, err error) { - return m.rbuf.Read(b) -} -func (m *mockConn) Write(b []byte) (n int, err error) { - return m.wbuf.Write(b) -} - -// writeBuffers impl the net.buffersWriter to support writev -func (m *mockConn) writeBuffers(buf *net.Buffers) (int64, error) { - return buf.WriteTo(m.wbuf) -} - -func (m *mockConn) Close() error { return nil } -func (m *mockConn) LocalAddr() net.Addr { return m.addr } -func (m *mockConn) RemoteAddr() net.Addr { return m.addr } - -func (m *mockConn) SetDeadline(t time.Time) error { return nil } -func (m *mockConn) SetReadDeadline(t time.Time) error { return nil } -func (m *mockConn) SetWriteDeadline(t time.Time) error { return nil } - -// _createConn is useful tools for handler test -func _createConn(data []byte) *libnet.Conn { - return _createRepeatConn(data, 1) -} - -func _createRepeatConn(data []byte, r int) *libnet.Conn { - mconn := &mockConn{ - addr: "127.0.0.1:12345", - rbuf: bytes.NewBuffer(bytes.Repeat(data, r)), - wbuf: new(bytes.Buffer), - } - conn := libnet.NewConn(mconn, time.Second, time.Second) - return conn -} - func TestPingerPingOk(t *testing.T) { conn := _createConn(pong) pinger := newMCPinger(conn) diff --git a/proto/redis/cmd.go b/proto/redis/cmd.go index 52d4f2d6..3eadd825 100644 --- a/proto/redis/cmd.go +++ b/proto/redis/cmd.go @@ -2,6 +2,7 @@ package redis import ( "errors" + "fmt" "strconv" "strings" // "crc" @@ -18,10 +19,10 @@ var ( var ( robjGet = newRespBulk([]byte("get")) - cmdMSetBytes = []byte("MSET") - cmdMGetBytes = []byte("MGET") - cmdDelBytes = []byte("DEL") - cmdExistsBytes = []byte("EXITS") + cmdMSetBytes = []byte("4\r\nMSET") + cmdMGetBytes = []byte("4\r\nMGET") + cmdDelBytes = []byte("3\r\nDEL") + cmdExistsBytes = []byte("5\r\nEXITS") ) // errors @@ -47,14 +48,17 @@ type Command struct { // NewCommand will create new command by given args // example: // NewCommand("GET", "mykey") -// NewCommand("MGET", "mykey", "yourkey") +// NewCommand("CLUSTER", "NODES") func NewCommand(cmd string, args ...string) *Command { respObj := newRespArrayWithCapcity(len(args) + 1) respObj.replace(0, newRespBulk([]byte(cmd))) maxLen := len(args) + 1 for i := 1; i < maxLen; i++ { - respObj.replace(i, newRespBulk([]byte(args[i-1]))) + data := args[i-1] + line := fmt.Sprintf("%d\r\n%s", len(data), data) + respObj.replace(i, newRespBulk([]byte(line))) } + respObj.data = []byte(strconv.Itoa(len(args) + 1)) return newCommand(respObj) } @@ -82,10 +86,10 @@ func (c *Command) Cmd() []byte { func (c *Command) Key() []byte { var data = c.respObj.nth(1).data var pos int - if c.respObj.rtype == respBulk { + if c.respObj.nth(1).rtype == respBulk { pos = bytes.Index(data, crlfBytes) + 2 } - // pos is never empty + // pos is never lower than 0 return data[pos:] } @@ -96,15 +100,15 @@ func (c *Command) Put() { // IsRedirect check if response type is Redis Error // and payload was prefix with "ASK" && "MOVED" func (c *Command) IsRedirect() bool { - if c.respObj.rtype != respError { + if c.reply.rtype != respError { return false } - if c.respObj.data == nil { + if c.reply.data == nil { return false } - return bytes.HasPrefix(c.respObj.data, movedBytes) || - bytes.HasPrefix(c.respObj.data, askBytes) + return bytes.HasPrefix(c.reply.data, movedBytes) || + bytes.HasPrefix(c.reply.data, askBytes) } // RedirectTriple will check and send back by is @@ -113,7 +117,7 @@ func (c *Command) IsRedirect() bool { // third is the redirect addr // last is the error when parse the redirect body func (c *Command) RedirectTriple() (redirect string, slot int, addr string, err error) { - fields := strings.Fields(string(c.respObj.data)) + fields := strings.Fields(string(c.reply.data)) if len(fields) != 3 { err = ErrRedirectBadFormat return diff --git a/proto/redis/cmd_test.go b/proto/redis/cmd_test.go new file mode 100644 index 00000000..7bb98887 --- /dev/null +++ b/proto/redis/cmd_test.go @@ -0,0 +1,28 @@ +package redis + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCommandNewCommand(t *testing.T) { + cmd := NewCommand("GET", "a") + assert.Equal(t, MergeTypeBasic, cmd.mergeType) + assert.Equal(t, 2, cmd.respObj.Len()) + + assert.Equal(t, "GET", cmd.CmdString()) + assert.Equal(t, "GET", string(cmd.Cmd())) + assert.Equal(t, "a", string(cmd.Key())) +} + +func TestCommandRedirect(t *testing.T) { + cmd := NewCommand("GET", "BAKA") + cmd.reply = newRespPlain(respError, []byte("ASK 1024 127.0.0.1:2048")) + assert.True(t, cmd.IsRedirect()) + r, slot, addr, err := cmd.RedirectTriple() + assert.NoError(t, err) + assert.Equal(t, "ASK", r) + assert.Equal(t, 1024, slot) + assert.Equal(t, "127.0.0.1:2048", addr) +} diff --git a/proto/redis/node_conn_test.go b/proto/redis/node_conn_test.go new file mode 100644 index 00000000..64a5c7b0 --- /dev/null +++ b/proto/redis/node_conn_test.go @@ -0,0 +1,88 @@ +package redis + +import ( + "io" + "overlord/proto" + "testing" + + "github.com/stretchr/testify/assert" +) + +type mockCmd struct { +} + +func (*mockCmd) CmdString() string { + return "" +} + +func (*mockCmd) Cmd() []byte { + return []byte("") +} + +func (*mockCmd) Key() []byte { + return []byte{} +} + +func (*mockCmd) Put() { +} + +func TestNodeConnWriteBatchOk(t *testing.T) { + nc := newNodeConn("baka", "127.0.0.1:12345", _createConn(nil)) + mb := proto.NewMsgBatch() + msg := proto.GetMsg() + msg.WithRequest(NewCommand("GET", "A")) + mb.AddMsg(msg) + err := nc.WriteBatch(mb) + assert.NoError(t, err) +} + +func TestNodeConnWriteBadAssert(t *testing.T) { + nc := newNodeConn("baka", "127.0.0.1:12345", _createConn(nil)) + mb := proto.NewMsgBatch() + msg := proto.GetMsg() + msg.WithRequest(&mockCmd{}) + mb.AddMsg(msg) + + err := nc.WriteBatch(mb) + assert.Error(t, err) + assert.Equal(t, ErrBadAssert, err) +} + +func TestReadBatchOk(t *testing.T) { + data := ":1\r\n" + nc := newNodeConn("baka", "127.0.0.1:12345", _createConn([]byte(data))) + mb := proto.NewMsgBatch() + msg := proto.GetMsg() + msg.WithRequest(NewCommand("SET", "baka", "miao")) + mb.AddMsg(msg) + err := nc.ReadBatch(mb) + assert.NoError(t, err) +} + +func TestReadBatchWithBadAssert(t *testing.T) { + nc := newNodeConn("baka", "127.0.0.1:12345", _createConn([]byte(":123\r\n"))) + mb := proto.NewMsgBatch() + msg := proto.GetMsg() + msg.WithRequest(&mockCmd{}) + mb.AddMsg(msg) + err := nc.ReadBatch(mb) + assert.Error(t, err) + assert.Equal(t, ErrBadAssert, err) +} + +func TestReadBatchWithNilError(t *testing.T) { + nc := newNodeConn("baka", "127.0.0.1:12345", _createConn(nil)) + mb := proto.NewMsgBatch() + msg := proto.GetMsg() + msg.WithRequest(&mockCmd{}) + mb.AddMsg(msg) + err := nc.ReadBatch(mb) + assert.Error(t, err) + assert.Equal(t, io.EOF, err) +} + +func TestPingOk(t *testing.T) { + nc := newNodeConn("baka", "127.0.0.1:12345", _createRepeatConn(pongBytes, 100)) + err := nc.Ping() + assert.NoError(t, err) +} diff --git a/proto/redis/pinger.go b/proto/redis/pinger.go index c81d031f..f7ff0c7d 100644 --- a/proto/redis/pinger.go +++ b/proto/redis/pinger.go @@ -30,9 +30,10 @@ type pinger struct { func newPinger(conn *libnet.Conn) *pinger { return &pinger{ - conn: conn, - br: bufio.NewReader(conn, bufio.Get(64)), - bw: bufio.NewWriter(conn), + conn: conn, + br: bufio.NewReader(conn, bufio.Get(64)), + bw: bufio.NewWriter(conn), + state: opened, } } @@ -49,9 +50,16 @@ func (p *pinger) ping() (err error) { if err != nil { return } - if bytes.Equal(data, pongBytes) { + if !bytes.Equal(data, pongBytes) { err = ErrBadPong return } return } + +func (p *pinger) Close() error { + if atomic.CompareAndSwapUint32(&p.state, opened, closed) { + return p.conn.Close() + } + return nil +} diff --git a/proto/redis/pinger_test.go b/proto/redis/pinger_test.go new file mode 100644 index 00000000..65ccd5d7 --- /dev/null +++ b/proto/redis/pinger_test.go @@ -0,0 +1,30 @@ +package redis + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPingerPingOk(t *testing.T) { + conn := _createRepeatConn(pongBytes, 10) + p := newPinger(conn) + err := p.ping() + assert.NoError(t, err) +} + +func TestPingerClosed(t *testing.T) { + conn := _createRepeatConn(pongBytes, 10) + p := newPinger(conn) + assert.NoError(t, p.Close()) + err := p.ping() + assert.Error(t, err) + assert.NoError(t, p.Close()) +} + +func TestPingerWrongResp(t *testing.T) { + conn := _createRepeatConn([]byte("-Error:badping\r\n"), 10) + p := newPinger(conn) + err := p.ping() + assert.Error(t, err) +} diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 7868c068..67432ced 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -135,6 +135,7 @@ func (pc *proxyConn) mergeCount(msg *proto.Message) (reply *resp, err error) { } sum += int(ival) } + reply = newRespInt(sum) return } diff --git a/proto/redis/proxy_conn_test.go b/proto/redis/proxy_conn_test.go new file mode 100644 index 00000000..d03961ef --- /dev/null +++ b/proto/redis/proxy_conn_test.go @@ -0,0 +1,31 @@ +package redis + +import ( + "overlord/proto" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecodeBasicOk(t *testing.T) { + msgs := proto.GetMsgSlice(16) + data := "*2\r\n$3\r\nGET\r\n$4\r\nbaka\r\n" + conn := _createConn([]byte(data)) + pc := NewProxyConn(conn) + + nmsgs, err := pc.Decode(msgs) + assert.NoError(t, err) + assert.Len(t, nmsgs, 1) +} + +func TestDecodeComplexOk(t *testing.T) { + msgs := proto.GetMsgSlice(16) + data := "*3\r\n$4\r\nMGET\r\n$4\r\nbaka\r\n$4\r\nkaba\r\n" + conn := _createConn([]byte(data)) + pc := NewProxyConn(conn) + + nmsgs, err := pc.Decode(msgs) + assert.NoError(t, err) + assert.Len(t, nmsgs, 1) + assert.Len(t, nmsgs[0].Batch(), 2) +} diff --git a/proto/redis/redis_test.go b/proto/redis/redis_test.go new file mode 100644 index 00000000..53942988 --- /dev/null +++ b/proto/redis/redis_test.go @@ -0,0 +1,67 @@ +package redis + +import ( + "bytes" + "net" + libnet "overlord/lib/net" + "time" +) + +type mockAddr string + +func (m mockAddr) Network() string { + return "tcp" +} +func (m mockAddr) String() string { + return string(m) +} + +type mockConn struct { + rbuf *bytes.Buffer + wbuf *bytes.Buffer + addr mockAddr +} + +func (m *mockConn) Read(b []byte) (n int, err error) { + return m.rbuf.Read(b) +} +func (m *mockConn) Write(b []byte) (n int, err error) { + return m.wbuf.Write(b) +} + +// writeBuffers impl the net.buffersWriter to support writev +func (m *mockConn) writeBuffers(buf *net.Buffers) (int64, error) { + return buf.WriteTo(m.wbuf) +} + +func (m *mockConn) Close() error { return nil } +func (m *mockConn) LocalAddr() net.Addr { return m.addr } +func (m *mockConn) RemoteAddr() net.Addr { return m.addr } + +func (m *mockConn) SetDeadline(t time.Time) error { return nil } +func (m *mockConn) SetReadDeadline(t time.Time) error { return nil } +func (m *mockConn) SetWriteDeadline(t time.Time) error { return nil } + +// _createConn is useful tools for handler test +func _createConn(data []byte) *libnet.Conn { + return _createRepeatConn(data, 1) +} + +func _createRepeatConn(data []byte, r int) *libnet.Conn { + mconn := &mockConn{ + addr: "127.0.0.1:12345", + rbuf: bytes.NewBuffer(bytes.Repeat(data, r)), + wbuf: new(bytes.Buffer), + } + conn := libnet.NewConn(mconn, time.Second, time.Second) + return conn +} + +func _createDownStreamConn() (*libnet.Conn, *bytes.Buffer) { + buf := new(bytes.Buffer) + mconn := &mockConn{ + addr: "127.0.0.1:12345", + wbuf: buf, + } + return libnet.NewConn(mconn, time.Second, time.Second), buf +} diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 5f257427..d2246c8a 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -74,7 +74,7 @@ func newRespNull(rtype respType) *resp { func newRespArray(resps []*resp) *resp { robj := respPool.Get().(*resp) robj.rtype = respArray - robj.data = nil + robj.data = []byte(strconv.Itoa(len(resps))) robj.array = resps return robj } @@ -117,3 +117,11 @@ func (r *resp) slice() []*resp { func (r *resp) Len() int { return len(r.array) } + +func (r *resp) String() string { + if r.rtype == respArray { + return "" + } + + return string(r.data) +} diff --git a/proto/redis/resp_conn.go b/proto/redis/resp_conn.go index 5732e10e..c58bb8b4 100644 --- a/proto/redis/resp_conn.go +++ b/proto/redis/resp_conn.go @@ -93,7 +93,6 @@ func (rc *respConn) decodeCount(n int) (resps []*resp, err error) { func (rc *respConn) decodeResp() (robj *resp, err error) { var ( line []byte - size int ) line, err = rc.br.ReadLine() if err != nil { @@ -104,46 +103,58 @@ func (rc *respConn) decodeResp() (robj *resp, err error) { switch rtype { case respString, respInt, respError: // decocde use one line to parse - robj = rc.decodePlain(rtype, line) + robj = rc.decodePlain(line) case respBulk: // decode bulkString - size, err = decodeInt(line[1 : len(line)-2]) - if err != nil { - return - } - robj, err = rc.decodeBulk(size, len(line)) + // fmt.Printf("line:%s\n", strconv.Quote(string(line))) + robj, err = rc.decodeBulk(line) case respArray: - size, err = decodeInt(line[1 : len(line)-2]) - if err != nil { - return - } - robj, err = rc.decodeArray(size, len(line)) + robj, err = rc.decodeArray(line) } return } -func (rc *respConn) decodePlain(rtype byte, line []byte) *resp { - return newRespPlain(rtype, line[0:len(line)-2]) +func (rc *respConn) decodePlain(line []byte) *resp { + return newRespPlain(line[0], line[1:len(line)-2]) } -func (rc *respConn) decodeBulk(size, lineSize int) (*resp, error) { +func (rc *respConn) decodeBulk(line []byte) (*resp, error) { + lineSize := len(line) + sizeBytes := line[1 : lineSize-2] + // fmt.Printf("size:%s\n", strconv.Quote(string(sizeBytes))) + size, err := decodeInt(sizeBytes) + if err != nil { + return nil, err + } + if size == -1 { return newRespNull(respBulk), nil } - data, err := rc.br.ReadExact(size + 2) + + rc.br.Advance(-(lineSize - 1)) + fullDataSize := lineSize - 1 + size + 2 + data, err := rc.br.ReadExact(fullDataSize) + // fmt.Printf("data:%s\n", strconv.Quote(string(data))) if err == bufio.ErrBufferFull { - rc.br.Advance(-(lineSize + size + 2)) + rc.br.Advance(-1) + return nil, err } else if err != nil { return nil, err } return newRespBulk(data[:len(data)-2]), nil } -func (rc *respConn) decodeArray(size int, lineSize int) (*resp, error) { +func (rc *respConn) decodeArray(line []byte) (*resp, error) { + lineSize := len(line) + size, err := decodeInt(line[1 : lineSize-2]) + if err != nil { + return nil, err + } if size == -1 { return newRespNull(respArray), nil } robj := newRespArrayWithCapcity(size) + robj.data = line[1 : lineSize-2] mark := rc.br.Mark() for i := 0; i < size; i++ { sub, err := rc.decodeResp() @@ -253,6 +264,5 @@ func (rc *respConn) encodeArray(robj *resp) (err error) { return } } - return } diff --git a/proto/redis/resp_conn_test.go b/proto/redis/resp_conn_test.go new file mode 100644 index 00000000..70c33b09 --- /dev/null +++ b/proto/redis/resp_conn_test.go @@ -0,0 +1,139 @@ +package redis + +import ( + "io" + "overlord/lib/bufio" + "testing" + + "github.com/stretchr/testify/assert" +) + +func _createRespConn(data []byte) *respConn { + conn := _createConn(data) + return newRespConn(conn) +} + +func _runDecodeResps(t *testing.T, line string, expect string, rtype respType, eErr error) { + rc := _createRespConn([]byte(line)) + err := rc.br.Read() + if assert.NoError(t, err) { + robj, err := rc.decodeResp() + if eErr == nil { + if assert.NoError(t, err) { + if expect == "" { + assert.Nil(t, robj.data) + } else { + assert.Equal(t, expect, string(robj.data)) + } + assert.Equal(t, rtype, robj.rtype) + } + } else { + if assert.Error(t, err) { + assert.Equal(t, eErr, err) + } + } + + } +} + +func TestDecodeResp(t *testing.T) { + tslist := []struct { + Name string + Input string + Expect string + Rtype respType + EErr error + }{ + {"RespStringOk", "+Bilibili 干杯 - ( ゜- ゜)つロ\r\n", "Bilibili 干杯 - ( ゜- ゜)つロ", respString, nil}, + {"RespStringWithLF", "+Bilibili\n 干杯 - ( ゜- ゜)つロ\r\n", "Bilibili\n 干杯 - ( ゜- ゜)つロ", respString, nil}, + {"RespErrorOk", "-Bilibili 干杯 - ( ゜- ゜)つロ\r\n", "Bilibili 干杯 - ( ゜- ゜)つロ", respError, nil}, + {"RespIntOk", ":10\r\n", "10", respInt, nil}, + // {"RespIntWrongNumber", ":a@#\r\n", "", respInt, nil}, // now it's can't be checked + {"RespBulkOk", "$35\r\nBilibili 干杯 - ( ゜- ゜)つロ\r\n", "35\r\nBilibili 干杯 - ( ゜- ゜)つロ", respBulk, nil}, + {"RespBulkNullOk", "$-1\r\n", "", respBulk, nil}, + {"RespBulkWrongSizeError", "$37\r\nBilibili 干杯 - ( ゜- ゜)つロ\r\n", "", respBulk, bufio.ErrBufferFull}, + + {"RespArrayOk", "*3\r\n$2\r\nab\r\n+baka lv9\r\n-ServerError:deepn dark fantasy\r\n", "3", respArray, nil}, + {"RespArrayNotFull", "*3\r\n$30000\r\nab\r\n+baka lv9\r\n-ServerError:deepn dark fantasy\r\n", "", respArray, bufio.ErrBufferFull}, + {"RespArrayNullOk", "*-1\r\n", "", respArray, nil}, + } + for _, tt := range tslist { + t.Run(tt.Name, func(t *testing.T) { + _runDecodeResps(t, tt.Input, tt.Expect, tt.Rtype, tt.EErr) + }) + } +} + +func TestDecodeMaxReachMaxOk(t *testing.T) { + line := []byte("$1\r\na\r\n+my name is van\r\n-baka error\r\n") + rc := _createRespConn([]byte(line)) + rs, err := rc.decodeMax(2) + assert.NoError(t, err) + assert.Len(t, rs, 2) + assert.Equal(t, respBulk, rs[0].rtype) + assert.Equal(t, respString, rs[1].rtype) +} + +func TestDecodeMaxReachFullOk(t *testing.T) { + line := []byte("$1\r\na\r\n+my name is") + rc := _createRespConn([]byte(line)) + rs, err := rc.decodeMax(2) + assert.NoError(t, err) + assert.Len(t, rs, 1) + assert.Equal(t, respBulk, rs[0].rtype) +} + +func TestDecodeCountOk(t *testing.T) { + line := []byte("$1\r\na\r\n+my name is\r\n") + rc := _createRespConn([]byte(line)) + rs, err := rc.decodeCount(2) + assert.NoError(t, err) + assert.Len(t, rs, 2) + assert.Equal(t, respBulk, rs[0].rtype) + assert.Equal(t, respString, rs[1].rtype) +} + +func TestDecodeCountNotFull(t *testing.T) { + line := []byte("$1\r\na\r\n+my name is\r\n") + rc := _createRespConn([]byte(line)) + _, err := rc.decodeCount(3) + assert.Error(t, err) + assert.Equal(t, io.EOF, err) +} + +func TestEncodeResp(t *testing.T) { + ts := []struct { + Name string + Robj *resp + Expect string + }{ + {Name: "IntOk", Robj: newRespInt(1024), Expect: ":1024\r\n"}, + {Name: "StringOk", Robj: newRespString([]byte("baka")), Expect: "+baka\r\n"}, + {Name: "ErrorOk", Robj: newRespPlain(respError, []byte("kaba")), Expect: "-kaba\r\n"}, + + {Name: "BulkOk", Robj: newRespBulk([]byte("4\r\nkaba")), Expect: "$4\r\nkaba\r\n"}, + {Name: "BulkNullOk", Robj: newRespNull(respBulk), Expect: "$-1\r\n"}, + + {Name: "ArrayNullOk", Robj: newRespNull(respArray), Expect: "*-1\r\n"}, + {Name: "ArrayOk", + Robj: newRespArray([]*resp{newRespBulk([]byte("2\r\nka")), newRespString([]byte("baka"))}), + Expect: "*2\r\n$2\r\nka\r\n+baka\r\n"}, + } + + for _, tt := range ts { + t.Run(tt.Name, func(t *testing.T) { + sock, buf := _createDownStreamConn() + conn := newRespConn(sock) + // err := conn.encode(newRespInt(1024)) + err := conn.encode(tt.Robj) + assert.NoError(t, err) + err = conn.Flush() + assert.NoError(t, err) + data := make([]byte, 1024) + n, err := buf.Read(data) + assert.NoError(t, err) + assert.Equal(t, tt.Expect, string(data[:n])) + }) + + } +} diff --git a/proto/redis/sub.go b/proto/redis/sub.go index 32df1dae..5aa077fb 100644 --- a/proto/redis/sub.go +++ b/proto/redis/sub.go @@ -1,7 +1,9 @@ package redis -import "errors" -import "bytes" +import ( + "bytes" + "errors" +) const ( parityBit int = 1 @@ -50,9 +52,7 @@ func subCmdMset(robj *resp) ([]*Command, error) { func subCmdByKeys(robj *resp) ([]*Command, error) { cmds := make([]*Command, robj.Len()-1) for i, sub := range robj.slice()[1:] { - cmdObj := newRespArrayWithCapcity(2) - cmdObj.replace(0, robj.nth(0)) - cmdObj.replace(1, sub) + cmdObj := newRespArray([]*resp{robj.nth(0), sub}) cmds[i] = newCommand(cmdObj) } return cmds, nil From 53b69e775ae31cbe1a41e33f16f6c1dbebf1ff97 Mon Sep 17 00:00:00 2001 From: wayslog Date: Mon, 16 Jul 2018 14:11:46 +0800 Subject: [PATCH 12/87] add Encode test cases. --- proto/redis/cmd.go | 3 +- proto/redis/proxy_conn.go | 7 +++- proto/redis/proxy_conn_test.go | 73 ++++++++++++++++++++++++++++++++++ proto/redis/redis.go | 4 +- proto/redis/sub.go | 7 +++- 5 files changed, 89 insertions(+), 5 deletions(-) diff --git a/proto/redis/cmd.go b/proto/redis/cmd.go index 3eadd825..b69b90cd 100644 --- a/proto/redis/cmd.go +++ b/proto/redis/cmd.go @@ -17,10 +17,11 @@ var ( ) var ( - robjGet = newRespBulk([]byte("get")) + robjGet = newRespBulk([]byte("3\r\nGET")) cmdMSetBytes = []byte("4\r\nMSET") cmdMGetBytes = []byte("4\r\nMGET") + cmdGetBytes = []byte("3\r\nGET") cmdDelBytes = []byte("3\r\nDEL") cmdExistsBytes = []byte("5\r\nEXITS") ) diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 67432ced..70fa9d18 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -6,6 +6,8 @@ import ( libnet "overlord/lib/net" "overlord/proto" + "strconv" + "github.com/pkg/errors" ) @@ -141,7 +143,8 @@ func (pc *proxyConn) mergeCount(msg *proto.Message) (reply *resp, err error) { func (pc *proxyConn) mergeJoin(msg *proto.Message) (reply *resp, err error) { reply = newRespArrayWithCapcity(len(msg.Subs())) - for i, sub := range msg.Subs() { + subs := msg.Subs() + for i, sub := range subs { subcmd, ok := sub.Request().(*Command) if !ok { err = pc.encodeError(ErrBadAssert) @@ -152,7 +155,7 @@ func (pc *proxyConn) mergeJoin(msg *proto.Message) (reply *resp, err error) { } reply.replace(i, subcmd.reply) } - + reply.data = []byte(strconv.Itoa(len(subs))) return } diff --git a/proto/redis/proxy_conn_test.go b/proto/redis/proxy_conn_test.go index d03961ef..06030d57 100644 --- a/proto/redis/proxy_conn_test.go +++ b/proto/redis/proxy_conn_test.go @@ -29,3 +29,76 @@ func TestDecodeComplexOk(t *testing.T) { assert.Len(t, nmsgs, 1) assert.Len(t, nmsgs[0].Batch(), 2) } + +func TestEncodeCmdOk(t *testing.T) { + + ts := []struct { + Name string + Reps []*resp + Obj *resp + Expect string + }{ + { + Name: "MergeJoinOk", + Reps: []*resp{newRespBulk([]byte("3\r\nabc")), newRespNull(respBulk)}, + Obj: newRespArray([]*resp{newRespBulk([]byte("4\r\nMGET")), newRespBulk([]byte("3\r\nABC")), newRespBulk([]byte("3\r\nxnc"))}), + Expect: "*2\r\n$3\r\nabc\r\n$-1\r\n", + }, + { + Name: "MergeCountOk", + Reps: []*resp{newRespInt(1), newRespInt(1), newRespInt(0)}, + Obj: newRespArray( + []*resp{ + newRespBulk([]byte("3\r\nDEL")), + newRespBulk([]byte("1\r\na")), + newRespBulk([]byte("2\r\nab")), + newRespBulk([]byte("3\r\nabc")), + }), + Expect: ":2\r\n", + }, + { + Name: "MergeCountOk", + Reps: []*resp{newRespString([]byte("OK")), newRespString([]byte("OK"))}, + Obj: newRespArray( + []*resp{ + newRespBulk([]byte("4\r\nMSET")), + newRespBulk([]byte("1\r\na")), + newRespBulk([]byte("2\r\nab")), + newRespBulk([]byte("3\r\nabc")), + newRespBulk([]byte("4\r\nabcd")), + }), + Expect: "+OK\r\n", + }, + } + for _, tt := range ts { + t.Run(tt.Name, func(t *testing.T) { + rs := tt.Reps + msg := proto.GetMsg() + co := tt.Obj + if isComplex(co.nth(0).data) { + cmds, err := newSubCmd(co) + if assert.NoError(t, err) { + for i, cmd := range cmds { + cmd.reply = rs[i] + msg.WithRequest(cmd) + } + msg.Batch() + } + } else { + cmd := newCommand(co) + cmd.reply = rs[0] + msg.WithRequest(cmd) + } + data := make([]byte, 2048) + conn, buf := _createDownStreamConn() + pc := NewProxyConn(conn) + err := pc.Encode(msg) + if assert.NoError(t, err) { + size, _ := buf.Read(data) + assert.NoError(t, err) + assert.Equal(t, tt.Expect, string(data[:size])) + } + }) + } + +} diff --git a/proto/redis/redis.go b/proto/redis/redis.go index 5bad7e19..796d1a05 100644 --- a/proto/redis/redis.go +++ b/proto/redis/redis.go @@ -16,10 +16,12 @@ const ( ) func getMergeType(cmd []byte) MergeType { + // fmt.Println("mtype :", strconv.Quote(string(cmd))) // TODO: impl with tire tree to search quickly - if bytes.Equal(cmd, cmdMGetBytes) { + if bytes.Equal(cmd, cmdMGetBytes) || bytes.Equal(cmd, cmdGetBytes) { return MergeTypeJoin } + if bytes.Equal(cmd, cmdMSetBytes) { return MergeTypeOk } diff --git a/proto/redis/sub.go b/proto/redis/sub.go index 5aa077fb..6f414600 100644 --- a/proto/redis/sub.go +++ b/proto/redis/sub.go @@ -52,7 +52,12 @@ func subCmdMset(robj *resp) ([]*Command, error) { func subCmdByKeys(robj *resp) ([]*Command, error) { cmds := make([]*Command, robj.Len()-1) for i, sub := range robj.slice()[1:] { - cmdObj := newRespArray([]*resp{robj.nth(0), sub}) + var cmdObj *resp + if bytes.Equal(robj.nth(0).data, cmdMGetBytes) { + cmdObj = newRespArray([]*resp{robjGet, sub}) + } else { + cmdObj = newRespArray([]*resp{robj.nth(0), sub}) + } cmds[i] = newCommand(cmdObj) } return cmds, nil From e6a1f20e9ad208aba52fcb383373e01a88823445 Mon Sep 17 00:00:00 2001 From: wayslog Date: Mon, 16 Jul 2018 18:13:57 +0800 Subject: [PATCH 13/87] fixed of redis cluster integration --- cmd/proxy/proxy-cluster-example.toml | 8 +- lib/bufio/io.go | 1 + lib/hashkit/hash.go | 1 + proto/memcache/node_conn.go | 13 ++ proto/redis/cmd.go | 18 +- proto/redis/node_conn.go | 39 ++++ proto/redis/proxy_conn.go | 32 ++- proto/redis/resp.go | 25 ++- proto/redis/resp_conn.go | 2 +- proto/redis/slots.go | 291 +++++++++++++++++++++++++++ proto/redis/slots_test.go | 160 +++++++++++++++ proto/redis/sub.go | 3 +- proto/types.go | 2 + proxy/cluster.go | 31 ++- proxy/handler.go | 2 +- 15 files changed, 596 insertions(+), 32 deletions(-) create mode 100644 proto/redis/slots.go create mode 100644 proto/redis/slots_test.go diff --git a/cmd/proxy/proxy-cluster-example.toml b/cmd/proxy/proxy-cluster-example.toml index 6ca2c199..df5069ef 100644 --- a/cmd/proxy/proxy-cluster-example.toml +++ b/cmd/proxy/proxy-cluster-example.toml @@ -2,13 +2,13 @@ # This be used to specify the name of cache cluster. name = "test-cluster" # The name of the hash function. Possible values are: sha1. -hash_method = "sha1" +hash_method = "crc16" # The key distribution mode. Possible values are: ketama. -hash_distribution = "ketama" +hash_distribution = "redis_cluster" # A two character string that specifies the part of the key used for hashing. Eg "{}". hash_tag = "" # cache type: memcache | redis -cache_type = "memcache" +cache_type = "redis" # proxy listen proto: tcp | unix listen_proto = "tcp" # proxy listen addr: tcp addr | unix sock path @@ -29,5 +29,5 @@ ping_fail_limit = 3 ping_auto_eject = true # A list of server address, port and weight (name:port:weight or ip:port:weight) for this server pool. Also you can use alias name like: ip:port:weight alias. servers = [ - "127.0.0.1:11211:10", + "127.0.0.1:7000:1", ] diff --git a/lib/bufio/io.go b/lib/bufio/io.go index 52b8239f..1686be05 100644 --- a/lib/bufio/io.go +++ b/lib/bufio/io.go @@ -242,6 +242,7 @@ func (w *Writer) Flush() error { if len(w.bufs) == 0 { return nil } + // fmt.Println("buf:", w.bufs[:w.cursor]) w.bufsp = net.Buffers(w.bufs[:w.cursor]) _, err := w.wr.Writev(&w.bufsp) if err != nil { diff --git a/lib/hashkit/hash.go b/lib/hashkit/hash.go index 7e8666d8..fa5b48fd 100644 --- a/lib/hashkit/hash.go +++ b/lib/hashkit/hash.go @@ -1,5 +1,6 @@ package hashkit +// constants defines const ( HashMethodFnv1a = "fnv1a_64" ) diff --git a/proto/memcache/node_conn.go b/proto/memcache/node_conn.go index ad2e359a..770f0db3 100644 --- a/proto/memcache/node_conn.go +++ b/proto/memcache/node_conn.go @@ -11,6 +11,8 @@ import ( "overlord/lib/prom" "overlord/proto" + stderr "errors" + "github.com/pkg/errors" ) @@ -19,6 +21,11 @@ const ( handlerClosed = int32(1) ) +// errors +var ( + ErrNotImpl = stderr.New("i am groot") +) + type nodeConn struct { cluster string addr string @@ -206,3 +213,9 @@ func (n *nodeConn) Close() error { func (n *nodeConn) Closed() bool { return atomic.LoadInt32(&n.closed) == handlerClosed } + +// FetchSlots was not supported in mc. +func (nc *nodeConn) FetchSlots() (ndoes []string, slots [][]int, err error) { + err = ErrNotImpl + return +} diff --git a/proto/redis/cmd.go b/proto/redis/cmd.go index b69b90cd..59351846 100644 --- a/proto/redis/cmd.go +++ b/proto/redis/cmd.go @@ -17,13 +17,15 @@ var ( ) var ( - robjGet = newRespBulk([]byte("3\r\nGET")) - - cmdMSetBytes = []byte("4\r\nMSET") - cmdMGetBytes = []byte("4\r\nMGET") - cmdGetBytes = []byte("3\r\nGET") - cmdDelBytes = []byte("3\r\nDEL") - cmdExistsBytes = []byte("5\r\nEXITS") + robjGet = newRespBulk([]byte("3\r\nGET")) + robjMSet = newRespBulk([]byte("4\r\nMSET")) + + cmdMSetLenBytes = []byte("3") + cmdMSetBytes = []byte("4\r\nMSET") + cmdMGetBytes = []byte("4\r\nMGET") + cmdGetBytes = []byte("3\r\nGET") + cmdDelBytes = []byte("3\r\nDEL") + cmdExistsBytes = []byte("5\r\nEXITS") ) // errors @@ -75,7 +77,7 @@ func newCommandWithMergeType(robj *resp, mtype MergeType) *Command { // CmdString get the cmd func (c *Command) CmdString() string { - return strings.ToUpper(c.respObj.nth(0).String()) + return strings.ToUpper(string(c.respObj.nth(0).data)) } // Cmd get the cmd diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index 2d344e96..68adbc78 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -88,3 +88,42 @@ func (nc *nodeConn) Close() error { } return nil } + +var ( + robjCluterNodes = newRespArray([]*resp{ + newRespBulk([]byte("7\r\nCLUSTER")), + newRespBulk([]byte("5\r\nNODES")), + }) +) + +func (nc *nodeConn) FetchSlots() (nodes []string, slots [][]int, err error) { + err = nc.rc.encode(robjCluterNodes) + if err != nil { + return + } + err = nc.rc.Flush() + if err != nil { + return + } + rs, err := nc.rc.decodeCount(1) + if err != nil { + return + } + robj := rs[0] + ns, err := ParseSlots(robj) + if err != nil { + return + } + + cns := ns.GetNodes() + nodes = make([]string, 0) + slots = make([][]int, 0) + for _, node := range cns { + if node.Role() == roleMaster { + nodes = append(nodes, node.Addr()) + slots = append(slots, node.Slots()) + } + } + + return +} diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 70fa9d18..74555a92 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -30,20 +30,18 @@ func NewProxyConn(conn *libnet.Conn) proto.ProxyConn { } func (pc *proxyConn) Decode(msgs []*proto.Message) ([]*proto.Message, error) { - var ( - rs []*resp - err error - ) - rs, err = pc.rc.decodeMax(len(msgs)) + rs, err := pc.rc.decodeMax(len(msgs)) if err != nil { return msgs, err } for i, r := range rs { msgs[i].Type = proto.CacheTypeRedis msgs[i].MarkStart() + err = pc.decodeToMsg(r, msgs[i]) if err != nil { msgs[i].Reset() + return nil, err } } return msgs[:len(rs)], nil @@ -57,14 +55,28 @@ func (pc *proxyConn) decodeToMsg(robj *resp, msg *proto.Message) (err error) { return } for _, cmd := range cmds { - msg.WithRequest(cmd) + pc.withReq(msg, cmd) + // msg.WithRequest(cmd) } } else { - msg.WithRequest(newCommand(robj)) + pc.withReq(msg, newCommand(robj)) + // msg.WithRequest(newCommand(robj)) } return } +func (pc *proxyConn) withReq(m *proto.Message, cmd *Command) { + req := m.NextReq() + if req == nil { + m.WithRequest(cmd) + } else { + reqCmd := req.(*Command) + reqCmd.respObj = cmd.respObj + reqCmd.mergeType = cmd.mergeType + reqCmd.reply = cmd.reply + } +} + func (pc *proxyConn) Encode(msg *proto.Message) (err error) { if err = pc.encode(msg); err != nil { err = errors.Wrap(err, "Redis Encoder encode") @@ -118,6 +130,12 @@ func (pc *proxyConn) merge(msg *proto.Message) (*resp, error) { } func (pc *proxyConn) mergeOk(msg *proto.Message) (*resp, error) { + for _, sub := range msg.Subs() { + if err := sub.Err(); err != nil { + cmd := sub.Request().(*Command) + return newRespPlain(respError, cmd.reply.data), nil + } + } return newRespString(okBytes), nil } diff --git a/proto/redis/resp.go b/proto/redis/resp.go index d2246c8a..02fb0a9c 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -1,7 +1,9 @@ package redis import ( + "bytes" "strconv" + "strings" "sync" ) @@ -118,10 +120,29 @@ func (r *resp) Len() int { return len(r.array) } +// String was only for debug func (r *resp) String() string { if r.rtype == respArray { - return "" + var sb strings.Builder + sb.Write([]byte("[")) + for _, sub := range r.array[:len(r.array)-1] { + sb.WriteString(sub.String()) + sb.WriteString(", ") + } + sb.WriteString(r.array[len(r.array)-1].String()) + sb.Write([]byte("]")) + sb.WriteString("\n") + return sb.String() } - return string(r.data) + return strconv.Quote(string(r.data)) +} + +func (r *resp) bytes() []byte { + var data = r.data + var pos int + if r.rtype == respBulk { + pos = bytes.Index(data, crlfBytes) + 2 + } + return data[pos:] } diff --git a/proto/redis/resp_conn.go b/proto/redis/resp_conn.go index c58bb8b4..3a08086b 100644 --- a/proto/redis/resp_conn.go +++ b/proto/redis/resp_conn.go @@ -34,7 +34,7 @@ func (rc *respConn) decodeMax(max int) (resps []*resp, err error) { if rc.completed { err = rc.br.Read() if err != nil { - return nil, err + return } rc.completed = false } diff --git a/proto/redis/slots.go b/proto/redis/slots.go new file mode 100644 index 00000000..4b92458b --- /dev/null +++ b/proto/redis/slots.go @@ -0,0 +1,291 @@ +package redis + +import ( + "bufio" + "bytes" + "errors" + "io" + "strconv" + "strings" +) + +// errors +var ( + ErrAbsentField = errors.New("node fields is absent") + ErrEmptyNodeLine = errors.New("empty line of cluster nodes") + ErrBadReplyType = errors.New("bad reply type") +) +var ( + roleMaster = "master" + roleSlave = "slave" +) + +// Slots is the container of all slots. +type Slots interface { + GetSlots() []string + GetSlaveSlots() [][]string +} + +// Nodes is the container caontains Nodes. +type Nodes interface { + GetNodes() []Node + GetNodeByAddr(addr string) (Node, bool) +} + +// NodeSlots is the export interface of CLUSTER NODES. +type NodeSlots interface { + Slots + Nodes +} + +// ParseSlots must be call as "CLSUTER NODES" response +func ParseSlots(robj *resp) (NodeSlots, error) { + if respBulk != robj.rtype { + return nil, ErrBadReplyType + } + + return parseSlots(robj.bytes()) +} + +func parseSlots(data []byte) (NodeSlots, error) { + br := bufio.NewReader(bytes.NewBuffer(data)) + lines := []string{} + for { + // NOTICE: we assume that each line is not longer + // than 65535. + token, _, err := br.ReadLine() + if err != nil && err != io.EOF { + return nil, err + } + if len(token) != 0 { + lines = append(lines, string(token)) + } + if err == io.EOF { + break + } + } + nodes := make(map[string]Node) + slots := make([]string, SlotCount) + slaveSlots := make([][]string, SlotCount) + masterIDMap := make(map[string]Node) + // full fill master slots + for _, line := range lines { + node, err := parseNode(line) + if err != nil { + return nil, err + } + nodes[node.Addr()] = node + subSlots := node.Slots() + if node.Role() != roleMaster { + continue + } + masterIDMap[node.ID] = node + for _, slot := range subSlots { + slots[slot] = node.Addr() + } + } + // full fill slave slots + for _, node := range nodes { + if node.Role() != roleSlave { + continue + } + if mn, ok := masterIDMap[node.SlaveOf()]; ok { + for _, slot := range mn.Slots() { + if slaveSlots[slot] == nil { + slaveSlots[slot] = []string{} + } + slaveSlots[slot] = append(slaveSlots[slot], node.Addr()) + } + } + } + return &nodeSlots{nodes: nodes, slots: slots, slaveSlots: slaveSlots}, nil +} + +type nodeSlots struct { + nodes map[string]Node + slaveSlots [][]string + slots []string +} + +func (ns *nodeSlots) GetSlots() []string { + return ns.slots +} + +func (ns *nodeSlots) GetSlaveSlots() [][]string { + return ns.slaveSlots +} + +func (ns *nodeSlots) GetNodes() []Node { + nodes := make([]Node, len(ns.nodes)) + idx := 0 + for _, val := range ns.nodes { + nodes[idx] = val + idx++ + } + return nodes +} + +func (ns *nodeSlots) GetNodeByAddr(addr string) (Node, bool) { + if n, ok := ns.nodes[addr]; ok { + return n, true + } + return nil, false +} + +type node struct { + // 有别于 runID + ID string + addr string + // optional port + gossipAddr string + // Role is the special flag + role string + flags []string + slaveOf string + pingSent int + pongRecv int + configEpoch int + linkState string + slots []int +} + +func parseNode(line string) (*node, error) { + if len(strings.TrimSpace(line)) == 0 { + return nil, ErrEmptyNodeLine + } + fields := strings.Fields(line) + if len(fields) < 8 { + return nil, ErrAbsentField + } + n := &node{} + i := 0 + n.setID(fields[i]) + i++ + n.setAddr(fields[i]) + i++ + n.setFlags(fields[i]) + i++ + n.setSlaveOf(fields[i]) + i++ + n.setPingSent(fields[i]) + i++ + n.setPongRecv(fields[i]) + i++ + n.setConfigEpoch(fields[i]) + i++ + n.setLinkState(fields[i]) + i++ + n.setSlots(fields[i:]...) + // i++ + return n, nil +} +func (n *node) setID(val string) { + n.ID = strings.TrimSpace(val) +} +func (n *node) setAddr(val string) { + trimed := strings.TrimSpace(val) + // adaptor with 4.x + splited := strings.Split(trimed, "@") + n.addr = splited[0] + if len(splited) == 2 { + asp := strings.Split(n.addr, ":") + n.gossipAddr = asp[0] + splited[1] + } +} +func (n *node) setFlags(val string) { + flags := strings.Split(val, ",") + n.flags = flags + if strings.Contains(val, roleMaster) { + n.role = roleMaster + } else if strings.Contains(val, "slave") { + n.role = roleSlave + } +} +func (n *node) setSlaveOf(val string) { + n.slaveOf = val +} +func (n *node) setPingSent(val string) { + ival, err := strconv.Atoi(val) + if err != nil { + n.pingSent = 0 + } + n.pingSent = ival +} +func (n *node) setPongRecv(val string) { + ival, err := strconv.Atoi(val) + if err != nil { + n.pongRecv = 0 + } + n.pongRecv = ival +} +func (n *node) setConfigEpoch(val string) { + ival, err := strconv.Atoi(val) + if err != nil { + n.configEpoch = 0 + } + n.configEpoch = ival +} +func (n *node) setLinkState(val string) { + n.linkState = val +} +func (n *node) setSlots(vals ...string) { + slots := []int{} + for _, val := range vals { + subslots, ok := parseSlotField(val) + if ok { + slots = append(slots, subslots...) + } + } + //sort.IntSlice(slots).Sort() + n.slots = slots +} +func parseSlotField(val string) ([]int, bool) { + if len(val) == 0 || val == "-" { + return nil, false + } + vsp := strings.SplitN(val, "-", 2) + begin, err := strconv.Atoi(vsp[0]) + if err != nil { + return nil, false + } + if len(vsp) == 1 { + return []int{begin}, true + } + end, err := strconv.Atoi(vsp[1]) + if err != nil { + return nil, false + } + if end < begin { + return nil, false + } + slots := []int{} + for i := begin; i <= end; i++ { + slots = append(slots, i) + } + //sort.IntSlice(slots).Sort() + return slots, true +} +func (n *node) Addr() string { + return n.addr +} +func (n *node) Role() string { + return n.role +} +func (n *node) SlaveOf() string { + return n.slaveOf +} +func (n *node) Flags() []string { + return n.flags +} +func (n *node) Slots() []int { + return n.slots +} + +// Node is the interface of single redis node. +type Node interface { + Addr() string + Role() string + SlaveOf() string + Flags() []string + Slots() []int +} diff --git a/proto/redis/slots_test.go b/proto/redis/slots_test.go new file mode 100644 index 00000000..1708cc2c --- /dev/null +++ b/proto/redis/slots_test.go @@ -0,0 +1,160 @@ +package redis + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseSlotFieldEmptyLineOk(t *testing.T) { + slots, ok := parseSlotField("") + assert.Nil(t, slots) + assert.False(t, ok) + slots, ok = parseSlotField("-") + assert.Nil(t, slots) + assert.False(t, ok) +} +func TestParseSlotSingleValueOk(t *testing.T) { + slots, ok := parseSlotField("1024") + assert.True(t, ok) + assert.Len(t, slots, 1) + assert.Equal(t, 1024, slots[0]) +} + +func TestParseSlotSingleValueBadNumber(t *testing.T) { + slots, ok := parseSlotField("@boynextdoor") + assert.False(t, ok) + assert.Nil(t, slots) +} + +func TestParseSlotRangeSecondBadNumber(t *testing.T) { + slots, ok := parseSlotField("1024-@boynextdoor") + assert.False(t, ok) + assert.Nil(t, slots) +} + +func TestParseSlotRangeBadRange(t *testing.T) { + slots, ok := parseSlotField("1024-12") + assert.False(t, ok) + assert.Nil(t, slots) +} + +func TestParseSlotRangeButOneValue(t *testing.T) { + slots, ok := parseSlotField("1024-1024") + assert.True(t, ok) + assert.Len(t, slots, 1) + assert.Equal(t, 1024, slots[0]) +} + +func TestParseSlotRangeOk(t *testing.T) { + slots, ok := parseSlotField("12-1222") + assert.True(t, ok) + assert.Len(t, slots, 1222-12+1) + assert.Equal(t, 12, slots[0]) + assert.Equal(t, 1222, slots[len(slots)-1]) +} + +func TestNodeSetOk(t *testing.T) { + n := &node{} + cID := "3f76d4dca41307bea25e8f69a3545594479dc7a9" + n.setID(cID) + assert.Equal(t, cID, n.ID) + addr := "127.0.0.1:1024" + n.setAddr(addr) + assert.Equal(t, addr, n.Addr()) + addrWithGossip := addr + "@11024" + n.setAddr(addrWithGossip) + assert.Equal(t, addr, n.Addr()) + flagLists := []string{ + "mark,myself,master", + "mark,slave", + "mark,myself,slave", + "mark,faild", + } + for _, val := range flagLists { + t.Run("TestNodeSetWithFlags"+val, func(t *testing.T) { + n.setFlags(val) + assert.Contains(t, n.Flags(), "mark") + }) + } + masterAddr := "127.0.0.1:7788" + n.setSlaveOf(masterAddr) + assert.Equal(t, masterAddr, n.SlaveOf()) + // get or null + n.setPingSent("1024") + assert.Equal(t, 1024, n.pingSent) + n.setPingSent("-") + assert.Equal(t, 0, n.pingSent) + n.setPongRecv("1024") + assert.Equal(t, 1024, n.pongRecv) + n.setPongRecv("-") + assert.Equal(t, 0, n.pongRecv) + n.setConfigEpoch("1024") + assert.Equal(t, 1024, n.configEpoch) + n.setConfigEpoch("-") + assert.Equal(t, 0, n.configEpoch) + link := "zelda" + n.setLinkState(link) + assert.Equal(t, link, n.linkState) + slots := []struct { + name string + s []string + i []int + }{ + {"RangeOk", []string{"1", "1024"}, []int{1, 1024}}, + {"RangeFail", []string{"1", "@", "1024"}, []int{1, 1024}}, + } + for _, slot := range slots { + t.Run("TestNodeSetSlots"+slot.name, func(t *testing.T) { + n.setSlots(slot.s...) + assert.Equal(t, slot.i, n.Slots()) + }) + } +} + +func TestParseNodeOk(t *testing.T) { + slaveStr := "f17c3861b919c58b06584a0778c4f60913cf213c 172.17.0.2:7005@17005 slave 91240f5f82621d91d55b02d3bc1dcd1852dc42dd 0 1528251710522 6 connected" + n, err := parseNode(slaveStr) + assert.NoError(t, err) + assert.NotNil(t, n) + masterStr := "91240f5f82621d91d55b02d3bc1dcd1852dc42dd 172.17.0.2:7002@17002 master - 0 1528251710832 3 connected 10923-16383" + n, err = parseNode(masterStr) + assert.NoError(t, err) + assert.NotNil(t, n) + _, err = parseNode("") + assert.Error(t, err) + assert.Equal(t, ErrEmptyNodeLine, err) + _, err = parseNode("91240f5f82621d91d55b02d3bc1dcd1852dc42dd 172.17.0.2:7002@17002 10923-16383") + assert.Error(t, err) + assert.Equal(t, ErrAbsentField, err) +} + +var clusterNodesData = []byte(` +3f76d4dca41307bea25e8f69a3545594479dc7a9 172.17.0.2:7004@17004 slave ec433a34a97e09fc9c22dd4b4a301e2bca6602e0 0 1528252310916 5 connected +f17c3861b919c58b06584a0778c4f60913cf213c 172.17.0.2:7005@17005 slave 91240f5f82621d91d55b02d3bc1dcd1852dc42dd 0 1528252309896 6 connected +91240f5f82621d91d55b02d3bc1dcd1852dc42dd 172.17.0.2:7002@17002 master - 0 1528252310000 3 connected 10923-16383 +ec433a34a97e09fc9c22dd4b4a301e2bca6602e0 172.17.0.2:7001@17001 master - 0 1528252310606 2 connected 5461-10922 +a063bbdc2c4abdc60e09fdf1934dc8c8fb2d69df 172.17.0.2:7003@17003 slave a8f85c7b9a2e2cd24dda7a60f34fd889b61c9c00 0 1528252310506 4 connected +a8f85c7b9a2e2cd24dda7a60f34fd889b61c9c00 172.17.0.2:7000@17000 myself,master - 0 1528252310000 1 connected 0-5460 +`) + +func TestParseSlotsOk(t *testing.T) { + s, err := parseSlots(clusterNodesData) + assert.NoError(t, err) + assert.Len(t, s.GetSlots(), SlotCount) + assert.Len(t, s.GetSlaveSlots(), SlotCount) + assert.Len(t, s.GetNodes(), 6) + n, ok := s.GetNodeByAddr("172.17.0.2:7000") + assert.True(t, ok) + assert.NotNil(t, n) + n, ok = s.GetNodeByAddr("AddrNotExists") + assert.False(t, ok) + assert.Nil(t, n) +} + +func TestParseSlotsFromReplyOk(t *testing.T) { + rs := newRespString([]byte(clusterNodesData)) + ns, err := ParseSlots(rs) + assert.NoError(t, err) + assert.NotNil(t, ns) +} diff --git a/proto/redis/sub.go b/proto/redis/sub.go index 6f414600..36466b0c 100644 --- a/proto/redis/sub.go +++ b/proto/redis/sub.go @@ -41,9 +41,10 @@ func subCmdMset(robj *resp) ([]*Command, error) { cmds := make([]*Command, mid) for i := 0; i < mid; i++ { cmdObj := newRespArrayWithCapcity(3) - cmdObj.replace(0, robjGet) + cmdObj.replace(0, robjMSet) cmdObj.replace(1, robj.nth(i*2+1)) cmdObj.replace(2, robj.nth(i*2+2)) + cmdObj.data = cmdMSetLenBytes cmds[i] = newCommandWithMergeType(cmdObj, MergeTypeOk) } return cmds, nil diff --git a/proto/types.go b/proto/types.go index 6261cb55..3dd11bf6 100644 --- a/proto/types.go +++ b/proto/types.go @@ -41,4 +41,6 @@ type NodeConn interface { Ping() error Close() error + + FetchSlots() (nodes []string, slots [][]int, err error) } diff --git a/proxy/cluster.go b/proxy/cluster.go index b584669c..41bca986 100644 --- a/proxy/cluster.go +++ b/proxy/cluster.go @@ -4,6 +4,7 @@ import ( "bytes" "context" errs "errors" + "fmt" "net" "strings" "sync" @@ -31,7 +32,7 @@ var ( type pinger struct { ping proto.NodeConn node string - weight int + weight []int failure int retries int @@ -71,6 +72,8 @@ type Cluster struct { aliasMap map[string]int nodeChan map[int]*batchChanel + syncConn proto.NodeConn + lock sync.Mutex closed bool } @@ -90,10 +93,22 @@ func NewCluster(ctx context.Context, cc *ClusterConfig) (c *Cluster) { c.alias = alias ring := hashkit.NewRing(cc.HashDistribution, cc.HashMethod) - if c.alias { - ring.Init(ans, ws) - } else { - ring.Init(addrs, ws) + + if cc.CacheType == proto.CacheTypeMemcache { + if c.alias { + ring.Init(ans, ws) + } else { + ring.Init(addrs, ws) + } + } else if cc.CacheType == proto.CacheTypeRedis { + sc := newNodeConn(cc, addrs[0]) + nodes, slots, err := sc.FetchSlots() + fmt.Println(nodes, len(nodes)) + if err != nil { + panic(err) + } + addrs = nodes + ring.Init(nodes, slots...) } nodeChan := make(map[int]*batchChanel) @@ -115,7 +130,7 @@ func NewCluster(ctx context.Context, cc *ClusterConfig) (c *Cluster) { c.nodeMap = nodeMap c.ring = ring if c.cc.PingAutoEject { - go c.startPinger(c.cc, addrs, ws) + // go c.startPinger(c.cc, addrs, ws) } return } @@ -216,7 +231,7 @@ func (c *Cluster) processReadBatch(r proto.NodeConn, mb *proto.MsgBatch) error { return err } -func (c *Cluster) startPinger(cc *ClusterConfig, addrs []string, ws []int) { +func (c *Cluster) startPinger(cc *ClusterConfig, addrs []string, ws [][]int) { for idx, addr := range addrs { w := ws[idx] nc := newNodeConn(cc, addr) @@ -239,7 +254,7 @@ func (c *Cluster) processPing(p *pinger) { } else { p.failure = 0 if del { - c.ring.AddNode(p.node, p.weight) + c.ring.AddNode(p.node, p.weight...) del = false } } diff --git a/proxy/handler.go b/proxy/handler.go index 722fd6c3..1a8a1019 100644 --- a/proxy/handler.go +++ b/proxy/handler.go @@ -80,7 +80,7 @@ func (h *Handler) Handle() { func (h *Handler) handle() { var ( messages = proto.GetMsgSlice(defaultConcurrent) - mbatch = proto.NewMsgBatchSlice(len(h.cluster.cc.Servers)) + mbatch = proto.NewMsgBatchSlice(len(h.cluster.nodeMap)) msgs []*proto.Message err error ) From c1abc0fec1febbfe5b7fb82644a0c7ac28b82bae Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 17 Jul 2018 10:51:01 +0800 Subject: [PATCH 14/87] remove 1.9.x support for using strings.Builder --- .travis.yml | 1 - proto/redis/proxy_conn.go | 2 -- proto/redis/slots_test.go | 2 +- proxy/cluster.go | 13 +++++++++---- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 37bd95e5..b4c7162b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: go go: - - 1.9.x - 1.10.x go_import_path: overlord diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 74555a92..2add04d8 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -56,11 +56,9 @@ func (pc *proxyConn) decodeToMsg(robj *resp, msg *proto.Message) (err error) { } for _, cmd := range cmds { pc.withReq(msg, cmd) - // msg.WithRequest(cmd) } } else { pc.withReq(msg, newCommand(robj)) - // msg.WithRequest(newCommand(robj)) } return } diff --git a/proto/redis/slots_test.go b/proto/redis/slots_test.go index 1708cc2c..34e23844 100644 --- a/proto/redis/slots_test.go +++ b/proto/redis/slots_test.go @@ -153,7 +153,7 @@ func TestParseSlotsOk(t *testing.T) { } func TestParseSlotsFromReplyOk(t *testing.T) { - rs := newRespString([]byte(clusterNodesData)) + rs := newRespBulk([]byte(clusterNodesData)) ns, err := ParseSlots(rs) assert.NoError(t, err) assert.NotNil(t, ns) diff --git a/proxy/cluster.go b/proxy/cluster.go index 41bca986..906228e3 100644 --- a/proxy/cluster.go +++ b/proxy/cluster.go @@ -4,7 +4,6 @@ import ( "bytes" "context" errs "errors" - "fmt" "net" "strings" "sync" @@ -92,23 +91,29 @@ func NewCluster(ctx context.Context, cc *ClusterConfig) (c *Cluster) { } c.alias = alias + var wlist [][]int ring := hashkit.NewRing(cc.HashDistribution, cc.HashMethod) - if cc.CacheType == proto.CacheTypeMemcache { if c.alias { ring.Init(ans, ws) } else { ring.Init(addrs, ws) } + wlist = make([][]int, len(ws)) + for i, w := range ws { + wlist[i] = []int{w} + } } else if cc.CacheType == proto.CacheTypeRedis { sc := newNodeConn(cc, addrs[0]) nodes, slots, err := sc.FetchSlots() - fmt.Println(nodes, len(nodes)) + // fmt.Println(nodes, len(nodes)) if err != nil { panic(err) } addrs = nodes ring.Init(nodes, slots...) + c.syncConn = sc + wlist = slots } nodeChan := make(map[int]*batchChanel) @@ -130,7 +135,7 @@ func NewCluster(ctx context.Context, cc *ClusterConfig) (c *Cluster) { c.nodeMap = nodeMap c.ring = ring if c.cc.PingAutoEject { - // go c.startPinger(c.cc, addrs, ws) + go c.startPinger(c.cc, addrs, wlist) } return } From 1f5257d7781bd847d8c6fe08261a13615c1860b0 Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 17 Jul 2018 16:21:30 +0800 Subject: [PATCH 15/87] fixed of advance bugs and add redis example --- cmd/proxy/redis.toml | 35 +++++++++++++++++++++++++++++++++++ lib/hashkit/rediscluster.go | 1 + proto/redis/node_conn.go | 2 ++ proto/redis/pinger.go | 4 ++++ proto/redis/resp_conn.go | 3 ++- proxy/cluster.go | 1 + 6 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 cmd/proxy/redis.toml diff --git a/cmd/proxy/redis.toml b/cmd/proxy/redis.toml new file mode 100644 index 00000000..a03a5c13 --- /dev/null +++ b/cmd/proxy/redis.toml @@ -0,0 +1,35 @@ +[[clusters]] +# This be used to specify the name of cache cluster. +name = "redis-cluster" +# The name of the hash function. Possible values are: sha1. +hash_method = "crc16" +# Thhe key distribution mode. Possible values are: ketama. +has_distribution = "redis_cluster" +# A two character string that specifies the part of the key used for hashing. Eg "{}". +hash_tag = "" +# cache type: memcache | redis +cache_type = "redis" +# proxy listen proto: tcp | unix +listen_proto = "tcp" +# proxy listen addr: tcp addr | unix sock path +listen_addr = "0.0.0.0:26379" +# Authenticate to the Redis server on connect. +redis_auth = "" +# The dial timeout value in msec that we wait for to establish a connection to the server. By default, we wait indefinitely. +dial_timeout = 1000 +# The read timeout value in msec that we wait for to receive a response from a server. By default, we wait indefinitely. +read_timeout = 1000 +# The write timeout value in msec that we wait for to write a response to a server. By default, we wait indefinitely. +write_timeout = 1000 +# The number of connections that can be opened to each server. By default, we open at most 1 server connection. +node_connections = 10 +# The number of consecutive failures on a server that would lead to it being temporarily ejected when auto_eject is set to true. Defaults to 3. +ping_fail_limit = 3 +# A boolean value that controls if server should be ejected temporarily when it fails consecutively ping_fail_limit times. +ping_auto_eject = true +# A list of server address, port and weight (name:port:weight or ip:port:weight) for this server pool. Also you can use alias name like: ip:port:weight alias. +servers = [ + "172.22.15.29:7000:1", + "172.22.15.29:7001:1", + "172.22.15.29:7002:1", +] \ No newline at end of file diff --git a/lib/hashkit/rediscluster.go b/lib/hashkit/rediscluster.go index 0aba27d9..2d5c09b2 100644 --- a/lib/hashkit/rediscluster.go +++ b/lib/hashkit/rediscluster.go @@ -42,6 +42,7 @@ func (s *slotsMap) Init(masters []string, args ...[]int) { for _, slot := range args[i] { s.slots[slot] = i } + s.searchIndex[i] = args[i] } s.lock.Unlock() } diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index 68adbc78..2275f064 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -45,6 +45,7 @@ func (nc *nodeConn) WriteBatch(mb *proto.MsgBatch) error { m.DoneWithError(err) return err } + m.MarkWrite() } err := nc.rc.Flush() return err @@ -74,6 +75,7 @@ func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) error { return ErrBadAssert } cmd.reply = resps[i] + m.MarkRead() } return nil } diff --git a/proto/redis/pinger.go b/proto/redis/pinger.go index f7ff0c7d..13cc8cd5 100644 --- a/proto/redis/pinger.go +++ b/proto/redis/pinger.go @@ -46,6 +46,10 @@ func (p *pinger) ping() (err error) { if err != nil { return } + err = p.bw.Flush() + if err != nil { + return err + } data, err := p.br.ReadUntil(lfByte) if err != nil { return diff --git a/proto/redis/resp_conn.go b/proto/redis/resp_conn.go index 3a08086b..aa6bb445 100644 --- a/proto/redis/resp_conn.go +++ b/proto/redis/resp_conn.go @@ -48,6 +48,7 @@ func (rc *respConn) decodeMax(max int) (resps []*resp, err error) { } else if err != nil { return } + resps = append(resps, robj) } return @@ -160,7 +161,7 @@ func (rc *respConn) decodeArray(line []byte) (*resp, error) { sub, err := rc.decodeResp() if err != nil { rc.br.AdvanceTo(mark) - rc.br.Advance(lineSize) + rc.br.Advance(-lineSize) return nil, err } robj.replace(i, sub) diff --git a/proxy/cluster.go b/proxy/cluster.go index 906228e3..01caf908 100644 --- a/proxy/cluster.go +++ b/proxy/cluster.go @@ -256,6 +256,7 @@ func (c *Cluster) processPing(p *pinger) { if err := p.ping.Ping(); err != nil { p.failure++ p.retries = 0 + log.Warnf("node ping fail:%d times with err:%v", p.failure, err) } else { p.failure = 0 if del { From 273999ef36772391fe0cf34cfc4ea1ac378d6e51 Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 17 Jul 2018 16:31:05 +0800 Subject: [PATCH 16/87] make ci happy --- proto/memcache/node_conn.go | 3 +-- proto/redis/node_conn.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/proto/memcache/node_conn.go b/proto/memcache/node_conn.go index 770f0db3..dab19e9d 100644 --- a/proto/memcache/node_conn.go +++ b/proto/memcache/node_conn.go @@ -142,7 +142,6 @@ func (n *nodeConn) ReadBatch(mb *proto.MsgBatch) (err error) { for { size, err = n.fillMCRequest(mcr, n.br.Buffer().Bytes()[cursor:]) if err == bufio.ErrBufferFull { - err = nil break } else if err != nil { return @@ -215,7 +214,7 @@ func (n *nodeConn) Closed() bool { } // FetchSlots was not supported in mc. -func (nc *nodeConn) FetchSlots() (ndoes []string, slots [][]int, err error) { +func (n *nodeConn) FetchSlots() (ndoes []string, slots [][]int, err error) { err = ErrNotImpl return } diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index 2275f064..678347fc 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -75,7 +75,7 @@ func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) error { return ErrBadAssert } cmd.reply = resps[i] - m.MarkRead() + msg.MarkRead() } return nil } From 5cc46b572434fa10d391fbb6e1c5d7be61cb5122 Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 17 Jul 2018 17:36:31 +0800 Subject: [PATCH 17/87] add hashkit test cases --- lib/hashkit/crc16_test.go | 12 +++++ lib/hashkit/hash.go | 2 +- lib/hashkit/hash_test.go | 15 +++++++ lib/hashkit/ketama.go | 9 +--- lib/hashkit/rediscluster.go | 14 +++--- lib/hashkit/rediscluster_test.go | 75 ++++++++++++++++++++++++++++++++ 6 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 lib/hashkit/crc16_test.go create mode 100644 lib/hashkit/hash_test.go create mode 100644 lib/hashkit/rediscluster_test.go diff --git a/lib/hashkit/crc16_test.go b/lib/hashkit/crc16_test.go new file mode 100644 index 00000000..1927b0f7 --- /dev/null +++ b/lib/hashkit/crc16_test.go @@ -0,0 +1,12 @@ +package hashkit + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCrcCheckOk(t *testing.T) { + assert.Equal(t, uint16(0x31C3), Crc16([]byte("123456789"))) + assert.Equal(t, uint16(21847), Crc16([]byte{83, 153, 134, 118, 229, 214, 244, 75, 140, 37, 215, 215})) +} diff --git a/lib/hashkit/hash.go b/lib/hashkit/hash.go index fa5b48fd..756ea6b1 100644 --- a/lib/hashkit/hash.go +++ b/lib/hashkit/hash.go @@ -35,5 +35,5 @@ func NewRing(des, method string) Ring { default: hash = NewFnv1a64().fnv1a64 } - return newMCRing(hash) + return newRingWithHash(hash) } diff --git a/lib/hashkit/hash_test.go b/lib/hashkit/hash_test.go new file mode 100644 index 00000000..205ea895 --- /dev/null +++ b/lib/hashkit/hash_test.go @@ -0,0 +1,15 @@ +package hashkit + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewRingOk(t *testing.T) { + ring := NewRing("redis_cluster", "crc16") + assert.NotNil(t, ring) + + ring = NewRing("ketama", "fnv1a_64") + assert.NotNil(t, ring) +} diff --git a/lib/hashkit/ketama.go b/lib/hashkit/ketama.go index 12d3e900..af0b2aaa 100644 --- a/lib/hashkit/ketama.go +++ b/lib/hashkit/ketama.go @@ -47,13 +47,8 @@ func Ketama() (h *HashRing) { return } -// newMCRing is the ring of mc -func newMCRing(hash func([]byte) uint) Ring { - return NewRingWithHash(hash) -} - -// NewRingWithHash new a hash ring with a hash func. -func NewRingWithHash(hash func([]byte) uint) (h *HashRing) { +// newRingWithHash new a hash ring with a hash func. +func newRingWithHash(hash func([]byte) uint) (h *HashRing) { h = Ketama() h.hash = hash return diff --git a/lib/hashkit/rediscluster.go b/lib/hashkit/rediscluster.go index 2d5c09b2..7b77e635 100644 --- a/lib/hashkit/rediscluster.go +++ b/lib/hashkit/rediscluster.go @@ -1,18 +1,11 @@ package hashkit import ( - "errors" "sync" ) const musk = 0x3fff -// error -var ( - ErrWrongSlaveArgs = errors.New("arguments a invalid") - ErrMasterNotExists = errors.New("master didn't exists") -) - // slotsMap is the struct of redis clusters slotsMap type slotsMap struct { // slaves/searchIndex has the same length of masters @@ -37,6 +30,7 @@ func newRedisClusterRing() Ring { func (s *slotsMap) Init(masters []string, args ...[]int) { s.lock.Lock() s.masters = make([]string, len(masters)) + s.searchIndex = make([][]int, len(masters)) copy(s.masters, masters) for i := range s.masters { for _, slot := range args[i] { @@ -63,6 +57,12 @@ func (s *slotsMap) AddNode(node string, args ...int) { } for _, slot := range args { + // TODO: how to remove oriVal search index quickly + // If not, duplicate index will occur only when get with ordered add. + // oriVal := s.slots[slot] + // if oriVal != -1 { + // si := s.searchIndex[oriVal] + // } s.slots[slot] = idx s.searchIndex[idx] = append(s.searchIndex[idx], slot) } diff --git a/lib/hashkit/rediscluster_test.go b/lib/hashkit/rediscluster_test.go new file mode 100644 index 00000000..a1e1016e --- /dev/null +++ b/lib/hashkit/rediscluster_test.go @@ -0,0 +1,75 @@ +package hashkit + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func _createSlotsMap(n int) *slotsMap { + r := newRedisClusterRing() + addrs := make([]string, n) + for i := 0; i < n; i++ { + addrs[i] = fmt.Sprintf("127.0.0.1:70%02d", i) + } + rg := (musk + 1) / n + slots := make([][]int, n) + for i := 0; i < n; i++ { + iSlots := make([]int, 0) + start, end := i*rg, (i+1)*rg + for j := start; j < end; j++ { + iSlots = append(iSlots, j) + } + slots[i] = iSlots + } + for i := n * rg; i < musk+1; i++ { + slots[n-1] = append(slots[n-1], i) + } + // fmt.Println(addrs, slots) + r.Init(addrs, slots...) + return r.(*slotsMap) +} + +func TestSlotsMapAddNodeOk(t *testing.T) { + sm := _createSlotsMap(10) + sm.AddNode("127.0.0.2:7000", []int{0, 1, 2}...) + // the 11th + assert.Equal(t, "127.0.0.2:7000", sm.masters[10]) + assert.Equal(t, 10, sm.slots[0]) + assert.Equal(t, 10, sm.slots[1]) + assert.Equal(t, 10, sm.slots[2]) +} + +func TestSlotsMapDelNodeOk(t *testing.T) { + sm := _createSlotsMap(10) + sm.DelNode("127.0.0.1:7000") + // the 11th + assert.Equal(t, "127.0.0.1:7000", sm.masters[0]) + assert.Equal(t, -1, sm.slots[0]) + assert.Len(t, sm.searchIndex[0], 0) +} + +func TestSlotsMapUpdateSlot(t *testing.T) { + sm := _createSlotsMap(10) + sm.UpdateSlot("127.0.0.2:7000", 1) + + assert.Equal(t, "127.0.0.2:7000", sm.masters[10]) + assert.Equal(t, 10, sm.slots[1]) +} + +func TestSlotsMapHashOk(t *testing.T) { + sm := _createSlotsMap(10) + crc := sm.Hash([]byte("abcdefg")) + // assert.True(t, ok) + assert.Equal(t, 13912, crc) +} + +func TestGetNodeOk(t *testing.T) { + sm := _createSlotsMap(10) + // t.Log(sm.masters) + // t.Log(sm.slots) + node, ok := sm.GetNode([]byte("abcdefg")) + assert.True(t, ok) + assert.Equal(t, "127.0.0.1:7008", node) +} From 0b7a4a3f8e1449f48ed0c4f1b746b5fb5f88ac9f Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 17 Jul 2018 18:45:00 +0800 Subject: [PATCH 18/87] fixed of merge bugs of mc_binary --- codecov.sh | 2 +- proto/memcache/binary/node_conn.go | 12 ++++++++++++ proto/memcache/merge.go | 2 +- proxy/cluster.go | 4 +++- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/codecov.sh b/codecov.sh index 74ad8b01..74519774 100755 --- a/codecov.sh +++ b/codecov.sh @@ -9,4 +9,4 @@ for d in $(go list ./... | grep -v vendor | grep -v cmd); do cat profile.out >> coverage.txt rm profile.out fi -done \ No newline at end of file +done diff --git a/proto/memcache/binary/node_conn.go b/proto/memcache/binary/node_conn.go index 10ad0092..3bafdfdd 100644 --- a/proto/memcache/binary/node_conn.go +++ b/proto/memcache/binary/node_conn.go @@ -3,6 +3,7 @@ package binary import ( "bytes" "encoding/binary" + stderr "errors" "io" "sync/atomic" "time" @@ -20,6 +21,11 @@ const ( handlerClosed = int32(1) ) +// errors +var ( + ErrNotImpl = stderr.New("i am groot") +) + type nodeConn struct { cluster string addr string @@ -202,3 +208,9 @@ func (n *nodeConn) Close() error { func (n *nodeConn) Closed() bool { return atomic.LoadInt32(&n.closed) == handlerClosed } + +// FetchSlots was not supported in mc. +func (n *nodeConn) FetchSlots() (ndoes []string, slots [][]int, err error) { + err = ErrNotImpl + return +} diff --git a/proto/memcache/merge.go b/proto/memcache/merge.go index be318d78..fd06cde0 100644 --- a/proto/memcache/merge.go +++ b/proto/memcache/merge.go @@ -9,7 +9,7 @@ import ( func (*proxyConn) Merge(m *proto.Message) error { mcr, ok := m.Request().(*MCRequest) if !ok { - m.AddSubResps(serverErrorPrefixBytes, []byte(ErrAssertMsg.Error()), crlfBytes) + m.AddSubResps(serverErrorPrefixBytes, []byte(ErrAssertReq.Error()), crlfBytes) return nil } if !m.IsBatch() { diff --git a/proxy/cluster.go b/proxy/cluster.go index 01caf908..4aaf3f09 100644 --- a/proxy/cluster.go +++ b/proxy/cluster.go @@ -93,7 +93,7 @@ func NewCluster(ctx context.Context, cc *ClusterConfig) (c *Cluster) { var wlist [][]int ring := hashkit.NewRing(cc.HashDistribution, cc.HashMethod) - if cc.CacheType == proto.CacheTypeMemcache { + if cc.CacheType == proto.CacheTypeMemcache || cc.CacheType == proto.CacheTypeMemcacheBinary { if c.alias { ring.Init(ans, ws) } else { @@ -114,6 +114,8 @@ func NewCluster(ctx context.Context, cc *ClusterConfig) (c *Cluster) { ring.Init(nodes, slots...) c.syncConn = sc wlist = slots + } else { + panic("unsupported protocol") } nodeChan := make(map[int]*batchChanel) From 18d4434b6ce65517eab0241e74728b382bd041a2 Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 17 Jul 2018 19:26:39 +0800 Subject: [PATCH 19/87] fixed of subResps reset --- cmd/proxy/proxy-cluster-example.toml | 8 ++++---- proto/memcache/node_conn.go | 1 - proto/message.go | 1 + proxy/proxy_test.go | 12 ++++++++---- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/cmd/proxy/proxy-cluster-example.toml b/cmd/proxy/proxy-cluster-example.toml index df5069ef..28e318cb 100644 --- a/cmd/proxy/proxy-cluster-example.toml +++ b/cmd/proxy/proxy-cluster-example.toml @@ -2,13 +2,13 @@ # This be used to specify the name of cache cluster. name = "test-cluster" # The name of the hash function. Possible values are: sha1. -hash_method = "crc16" +hash_method = "fnv1a_64" # The key distribution mode. Possible values are: ketama. -hash_distribution = "redis_cluster" +hash_distribution = "ketama" # A two character string that specifies the part of the key used for hashing. Eg "{}". hash_tag = "" # cache type: memcache | redis -cache_type = "redis" +cache_type = "memcache" # proxy listen proto: tcp | unix listen_proto = "tcp" # proxy listen addr: tcp addr | unix sock path @@ -29,5 +29,5 @@ ping_fail_limit = 3 ping_auto_eject = true # A list of server address, port and weight (name:port:weight or ip:port:weight) for this server pool. Also you can use alias name like: ip:port:weight alias. servers = [ - "127.0.0.1:7000:1", + "127.0.0.1:11211:1", ] diff --git a/proto/memcache/node_conn.go b/proto/memcache/node_conn.go index dab19e9d..f2782e17 100644 --- a/proto/memcache/node_conn.go +++ b/proto/memcache/node_conn.go @@ -194,7 +194,6 @@ func (n *nodeConn) fillMCRequest(mcr *MCRequest, data []byte) (size int, err err if len(data) < size { return 0, bufio.ErrBufferFull } - mcr.data = data[:size] return } diff --git a/proto/message.go b/proto/message.go index 98381772..f619d1a2 100644 --- a/proto/message.go +++ b/proto/message.go @@ -69,6 +69,7 @@ func (m *Message) Reset() { m.reqn = 0 m.st, m.wt, m.rt, m.et = defaultTime, defaultTime, defaultTime, defaultTime m.err = nil + m.subResps = m.subResps[:0] } // Clear will clean the msg diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index 9827706b..c1a0bb1e 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -207,7 +207,7 @@ func testCmdBin(t testing.TB, cmds ...[]byte) { } func TestProxyFull(t *testing.T) { - for i := 0; i < 100; i++ { + for i := 0; i < 10; i++ { testCmd(t, cmds[0], cmds[1], cmds[2], cmds[10], cmds[11]) testCmdBin(t, cmdBins[0], cmdBins[1]) } @@ -231,7 +231,7 @@ func TestProxyWithAssert(t *testing.T) { {Name: "MultiCmdGetOk", Line: 6, Cmd: "gets a_11\r\ngets a_11\r\n", Except: []string{"VALUE a_11 0 1", "\r\n1\r\n", "END\r\n"}}, } - for i := 0; i < 100; i++ { + for i := 0; i < 10; i++ { conn, err := net.DialTimeout("tcp", "127.0.0.1:21211", time.Second) if err != nil { t.Errorf("net dial error:%v", err) @@ -251,8 +251,12 @@ func TestProxyWithAssert(t *testing.T) { buf = append(buf, data...) } sb := string(buf) - for _, except := range tt.Except { - assert.Contains(t, sb, except, "CMD:%s", tt.Cmd) + if len(tt.Except) == 1 { + assert.Equal(t, sb, tt.Except[0], "CMD:%s", tt.Cmd) + } else { + for _, except := range tt.Except { + assert.Contains(t, sb, except, "CMD:%s", tt.Cmd) + } } }) } From ba75bb9702668d7e722df2e6f4ab566014530c8e Mon Sep 17 00:00:00 2001 From: WaySLOG Date: Wed, 18 Jul 2018 10:40:31 +0800 Subject: [PATCH 20/87] Update redis.toml --- cmd/proxy/redis.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/proxy/redis.toml b/cmd/proxy/redis.toml index a03a5c13..23d7a2aa 100644 --- a/cmd/proxy/redis.toml +++ b/cmd/proxy/redis.toml @@ -4,7 +4,7 @@ name = "redis-cluster" # The name of the hash function. Possible values are: sha1. hash_method = "crc16" # Thhe key distribution mode. Possible values are: ketama. -has_distribution = "redis_cluster" +hash_distribution = "redis_cluster" # A two character string that specifies the part of the key used for hashing. Eg "{}". hash_tag = "" # cache type: memcache | redis @@ -32,4 +32,4 @@ servers = [ "172.22.15.29:7000:1", "172.22.15.29:7001:1", "172.22.15.29:7002:1", -] \ No newline at end of file +] From fd7c5a565f01aac0182ef71da977056447a0eab0 Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Wed, 18 Jul 2018 22:56:29 +0800 Subject: [PATCH 21/87] fix redis server protocol name --- proto/redis/cmd.go | 10 +++++----- proto/redis/node_conn.go | 8 ++++---- proto/redis/proxy_conn.go | 12 ++++++------ proto/redis/proxy_conn_test.go | 30 +++++++++++++++--------------- proto/redis/resp.go | 24 ++++++++++++------------ proto/redis/resp_conn.go | 24 ++++++++++++------------ proto/redis/resp_conn_test.go | 22 +++++++++++----------- proto/redis/slots_test.go | 2 +- proto/redis/sub.go | 6 +++--- 9 files changed, 69 insertions(+), 69 deletions(-) diff --git a/proto/redis/cmd.go b/proto/redis/cmd.go index 59351846..10637e43 100644 --- a/proto/redis/cmd.go +++ b/proto/redis/cmd.go @@ -17,8 +17,8 @@ var ( ) var ( - robjGet = newRespBulk([]byte("3\r\nGET")) - robjMSet = newRespBulk([]byte("4\r\nMSET")) + robjGet = newRESPBulk([]byte("3\r\nGET")) + robjMSet = newRESPBulk([]byte("4\r\nMSET")) cmdMSetLenBytes = []byte("3") cmdMSetBytes = []byte("4\r\nMSET") @@ -53,13 +53,13 @@ type Command struct { // NewCommand("GET", "mykey") // NewCommand("CLUSTER", "NODES") func NewCommand(cmd string, args ...string) *Command { - respObj := newRespArrayWithCapcity(len(args) + 1) - respObj.replace(0, newRespBulk([]byte(cmd))) + respObj := newRESPArrayWithCapcity(len(args) + 1) + respObj.replace(0, newRESPBulk([]byte(cmd))) maxLen := len(args) + 1 for i := 1; i < maxLen; i++ { data := args[i-1] line := fmt.Sprintf("%d\r\n%s", len(data), data) - respObj.replace(i, newRespBulk([]byte(line))) + respObj.replace(i, newRESPBulk([]byte(line))) } respObj.data = []byte(strconv.Itoa(len(args) + 1)) return newCommand(respObj) diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index 678347fc..7e5c82ea 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -31,7 +31,7 @@ func newNodeConn(cluster, addr string, conn *libnet.Conn) proto.NodeConn { return &nodeConn{ cluster: cluster, addr: addr, - rc: newRespConn(conn), + rc: newRESPConn(conn), conn: conn, p: newPinger(conn), state: closed, @@ -92,9 +92,9 @@ func (nc *nodeConn) Close() error { } var ( - robjCluterNodes = newRespArray([]*resp{ - newRespBulk([]byte("7\r\nCLUSTER")), - newRespBulk([]byte("5\r\nNODES")), + robjCluterNodes = newRESPArray([]*resp{ + newRESPBulk([]byte("7\r\nCLUSTER")), + newRESPBulk([]byte("5\r\nNODES")), }) ) diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 2add04d8..1473273c 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -24,7 +24,7 @@ type proxyConn struct { // NewProxyConn creates new redis Encoder and Decoder. func NewProxyConn(conn *libnet.Conn) proto.ProxyConn { r := &proxyConn{ - rc: newRespConn(conn), + rc: newRESPConn(conn), } return r } @@ -131,10 +131,10 @@ func (pc *proxyConn) mergeOk(msg *proto.Message) (*resp, error) { for _, sub := range msg.Subs() { if err := sub.Err(); err != nil { cmd := sub.Request().(*Command) - return newRespPlain(respError, cmd.reply.data), nil + return newRESPPlain(respError, cmd.reply.data), nil } } - return newRespString(okBytes), nil + return newRESPString(okBytes), nil } func (pc *proxyConn) mergeCount(msg *proto.Message) (reply *resp, err error) { @@ -153,12 +153,12 @@ func (pc *proxyConn) mergeCount(msg *proto.Message) (reply *resp, err error) { } sum += int(ival) } - reply = newRespInt(sum) + reply = newRESPInt(sum) return } func (pc *proxyConn) mergeJoin(msg *proto.Message) (reply *resp, err error) { - reply = newRespArrayWithCapcity(len(msg.Subs())) + reply = newRESPArrayWithCapcity(len(msg.Subs())) subs := msg.Subs() for i, sub := range subs { subcmd, ok := sub.Request().(*Command) @@ -187,6 +187,6 @@ func (pc *proxyConn) getBatchMergeType(msg *proto.Message) (mtype MergeType, err func (pc *proxyConn) encodeError(err error) error { se := errors.Cause(err).Error() - robj := newRespPlain(respError, []byte(se)) + robj := newRESPPlain(respError, []byte(se)) return pc.rc.encode(robj) } diff --git a/proto/redis/proxy_conn_test.go b/proto/redis/proxy_conn_test.go index 06030d57..834f412c 100644 --- a/proto/redis/proxy_conn_test.go +++ b/proto/redis/proxy_conn_test.go @@ -40,32 +40,32 @@ func TestEncodeCmdOk(t *testing.T) { }{ { Name: "MergeJoinOk", - Reps: []*resp{newRespBulk([]byte("3\r\nabc")), newRespNull(respBulk)}, - Obj: newRespArray([]*resp{newRespBulk([]byte("4\r\nMGET")), newRespBulk([]byte("3\r\nABC")), newRespBulk([]byte("3\r\nxnc"))}), + Reps: []*resp{newRESPBulk([]byte("3\r\nabc")), newRESPNull(respBulk)}, + Obj: newRESPArray([]*resp{newRESPBulk([]byte("4\r\nMGET")), newRESPBulk([]byte("3\r\nABC")), newRESPBulk([]byte("3\r\nxnc"))}), Expect: "*2\r\n$3\r\nabc\r\n$-1\r\n", }, { Name: "MergeCountOk", - Reps: []*resp{newRespInt(1), newRespInt(1), newRespInt(0)}, - Obj: newRespArray( + Reps: []*resp{newRESPInt(1), newRESPInt(1), newRESPInt(0)}, + Obj: newRESPArray( []*resp{ - newRespBulk([]byte("3\r\nDEL")), - newRespBulk([]byte("1\r\na")), - newRespBulk([]byte("2\r\nab")), - newRespBulk([]byte("3\r\nabc")), + newRESPBulk([]byte("3\r\nDEL")), + newRESPBulk([]byte("1\r\na")), + newRESPBulk([]byte("2\r\nab")), + newRESPBulk([]byte("3\r\nabc")), }), Expect: ":2\r\n", }, { Name: "MergeCountOk", - Reps: []*resp{newRespString([]byte("OK")), newRespString([]byte("OK"))}, - Obj: newRespArray( + Reps: []*resp{newRESPString([]byte("OK")), newRESPString([]byte("OK"))}, + Obj: newRESPArray( []*resp{ - newRespBulk([]byte("4\r\nMSET")), - newRespBulk([]byte("1\r\na")), - newRespBulk([]byte("2\r\nab")), - newRespBulk([]byte("3\r\nabc")), - newRespBulk([]byte("4\r\nabcd")), + newRESPBulk([]byte("4\r\nMSET")), + newRESPBulk([]byte("1\r\na")), + newRESPBulk([]byte("2\r\nab")), + newRESPBulk([]byte("3\r\nabc")), + newRESPBulk([]byte("4\r\nabcd")), }), Expect: "+OK\r\n", }, diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 02fb0a9c..73ca6606 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -39,7 +39,7 @@ var ( } ) -// resp is a redis resp protocol item. +// resp is a redis server protocol item. type resp struct { rtype respType // in Bulk this is the size field @@ -48,16 +48,16 @@ type resp struct { array []*resp } -func newRespInt(val int) *resp { +func newRESPInt(val int) *resp { s := strconv.Itoa(val) - return newRespPlain(respInt, []byte(s)) + return newRESPPlain(respInt, []byte(s)) } -func newRespBulk(data []byte) *resp { - return newRespPlain(respBulk, data) +func newRESPBulk(data []byte) *resp { + return newRESPPlain(respBulk, data) } -func newRespPlain(rtype respType, data []byte) *resp { +func newRESPPlain(rtype respType, data []byte) *resp { robj := respPool.Get().(*resp) robj.rtype = rtype robj.data = data @@ -65,15 +65,15 @@ func newRespPlain(rtype respType, data []byte) *resp { return robj } -func newRespString(val []byte) *resp { - return newRespPlain(respString, val) +func newRESPString(val []byte) *resp { + return newRESPPlain(respString, val) } -func newRespNull(rtype respType) *resp { - return newRespPlain(rtype, nil) +func newRESPNull(rtype respType) *resp { + return newRESPPlain(rtype, nil) } -func newRespArray(resps []*resp) *resp { +func newRESPArray(resps []*resp) *resp { robj := respPool.Get().(*resp) robj.rtype = respArray robj.data = []byte(strconv.Itoa(len(resps))) @@ -81,7 +81,7 @@ func newRespArray(resps []*resp) *resp { return robj } -func newRespArrayWithCapcity(length int) *resp { +func newRESPArrayWithCapcity(length int) *resp { robj := respPool.Get().(*resp) robj.rtype = respArray robj.data = nil diff --git a/proto/redis/resp_conn.go b/proto/redis/resp_conn.go index aa6bb445..ae84f741 100644 --- a/proto/redis/resp_conn.go +++ b/proto/redis/resp_conn.go @@ -6,7 +6,7 @@ import ( libnet "overlord/lib/net" ) -// respConn will encode and decode resp object to socket +// respConn will encode and decode res object to socket type respConn struct { br *bufio.Reader bw *bufio.Writer @@ -14,8 +14,8 @@ type respConn struct { completed bool } -// newRespConn will create new resp object Conn -func newRespConn(conn *libnet.Conn) *respConn { +// newRespConn will create new redis server protocol object Conn +func newRESPConn(conn *libnet.Conn) *respConn { r := &respConn{ br: bufio.NewReader(conn, bufio.Get(1024)), bw: bufio.NewWriter(conn), @@ -40,7 +40,7 @@ func (rc *respConn) decodeMax(max int) (resps []*resp, err error) { } for i := 0; i < max; i++ { - robj, err = rc.decodeResp() + robj, err = rc.decodeRESP() if err == bufio.ErrBufferFull { rc.completed = true err = nil @@ -77,7 +77,7 @@ func (rc *respConn) decodeCount(n int) (resps []*resp, err error) { return } - robj, err = rc.decodeResp() + robj, err = rc.decodeRESP() if err == bufio.ErrBufferFull { break } @@ -91,7 +91,7 @@ func (rc *respConn) decodeCount(n int) (resps []*resp, err error) { } } -func (rc *respConn) decodeResp() (robj *resp, err error) { +func (rc *respConn) decodeRESP() (robj *resp, err error) { var ( line []byte ) @@ -116,7 +116,7 @@ func (rc *respConn) decodeResp() (robj *resp, err error) { } func (rc *respConn) decodePlain(line []byte) *resp { - return newRespPlain(line[0], line[1:len(line)-2]) + return newRESPPlain(line[0], line[1:len(line)-2]) } func (rc *respConn) decodeBulk(line []byte) (*resp, error) { @@ -129,7 +129,7 @@ func (rc *respConn) decodeBulk(line []byte) (*resp, error) { } if size == -1 { - return newRespNull(respBulk), nil + return newRESPNull(respBulk), nil } rc.br.Advance(-(lineSize - 1)) @@ -142,7 +142,7 @@ func (rc *respConn) decodeBulk(line []byte) (*resp, error) { } else if err != nil { return nil, err } - return newRespBulk(data[:len(data)-2]), nil + return newRESPBulk(data[:len(data)-2]), nil } func (rc *respConn) decodeArray(line []byte) (*resp, error) { @@ -152,13 +152,13 @@ func (rc *respConn) decodeArray(line []byte) (*resp, error) { return nil, err } if size == -1 { - return newRespNull(respArray), nil + return newRESPNull(respArray), nil } - robj := newRespArrayWithCapcity(size) + robj := newRESPArrayWithCapcity(size) robj.data = line[1 : lineSize-2] mark := rc.br.Mark() for i := 0; i < size; i++ { - sub, err := rc.decodeResp() + sub, err := rc.decodeRESP() if err != nil { rc.br.AdvanceTo(mark) rc.br.Advance(-lineSize) diff --git a/proto/redis/resp_conn_test.go b/proto/redis/resp_conn_test.go index 70c33b09..10bfe6f5 100644 --- a/proto/redis/resp_conn_test.go +++ b/proto/redis/resp_conn_test.go @@ -10,14 +10,14 @@ import ( func _createRespConn(data []byte) *respConn { conn := _createConn(data) - return newRespConn(conn) + return newRESPConn(conn) } func _runDecodeResps(t *testing.T, line string, expect string, rtype respType, eErr error) { rc := _createRespConn([]byte(line)) err := rc.br.Read() if assert.NoError(t, err) { - robj, err := rc.decodeResp() + robj, err := rc.decodeRESP() if eErr == nil { if assert.NoError(t, err) { if expect == "" { @@ -36,7 +36,7 @@ func _runDecodeResps(t *testing.T, line string, expect string, rtype respType, e } } -func TestDecodeResp(t *testing.T) { +func TestDecodeRESP(t *testing.T) { tslist := []struct { Name string Input string @@ -107,23 +107,23 @@ func TestEncodeResp(t *testing.T) { Robj *resp Expect string }{ - {Name: "IntOk", Robj: newRespInt(1024), Expect: ":1024\r\n"}, - {Name: "StringOk", Robj: newRespString([]byte("baka")), Expect: "+baka\r\n"}, - {Name: "ErrorOk", Robj: newRespPlain(respError, []byte("kaba")), Expect: "-kaba\r\n"}, + {Name: "IntOk", Robj: newRESPInt(1024), Expect: ":1024\r\n"}, + {Name: "StringOk", Robj: newRESPString([]byte("baka")), Expect: "+baka\r\n"}, + {Name: "ErrorOk", Robj: newRESPPlain(respError, []byte("kaba")), Expect: "-kaba\r\n"}, - {Name: "BulkOk", Robj: newRespBulk([]byte("4\r\nkaba")), Expect: "$4\r\nkaba\r\n"}, - {Name: "BulkNullOk", Robj: newRespNull(respBulk), Expect: "$-1\r\n"}, + {Name: "BulkOk", Robj: newRESPBulk([]byte("4\r\nkaba")), Expect: "$4\r\nkaba\r\n"}, + {Name: "BulkNullOk", Robj: newRESPNull(respBulk), Expect: "$-1\r\n"}, - {Name: "ArrayNullOk", Robj: newRespNull(respArray), Expect: "*-1\r\n"}, + {Name: "ArrayNullOk", Robj: newRESPNull(respArray), Expect: "*-1\r\n"}, {Name: "ArrayOk", - Robj: newRespArray([]*resp{newRespBulk([]byte("2\r\nka")), newRespString([]byte("baka"))}), + Robj: newRESPArray([]*resp{newRESPBulk([]byte("2\r\nka")), newRESPString([]byte("baka"))}), Expect: "*2\r\n$2\r\nka\r\n+baka\r\n"}, } for _, tt := range ts { t.Run(tt.Name, func(t *testing.T) { sock, buf := _createDownStreamConn() - conn := newRespConn(sock) + conn := newRESPConn(sock) // err := conn.encode(newRespInt(1024)) err := conn.encode(tt.Robj) assert.NoError(t, err) diff --git a/proto/redis/slots_test.go b/proto/redis/slots_test.go index 34e23844..82491b16 100644 --- a/proto/redis/slots_test.go +++ b/proto/redis/slots_test.go @@ -153,7 +153,7 @@ func TestParseSlotsOk(t *testing.T) { } func TestParseSlotsFromReplyOk(t *testing.T) { - rs := newRespBulk([]byte(clusterNodesData)) + rs := newRESPBulk([]byte(clusterNodesData)) ns, err := ParseSlots(rs) assert.NoError(t, err) assert.NotNil(t, ns) diff --git a/proto/redis/sub.go b/proto/redis/sub.go index 36466b0c..6c5050dd 100644 --- a/proto/redis/sub.go +++ b/proto/redis/sub.go @@ -40,7 +40,7 @@ func subCmdMset(robj *resp) ([]*Command, error) { mid := robj.Len() / 2 cmds := make([]*Command, mid) for i := 0; i < mid; i++ { - cmdObj := newRespArrayWithCapcity(3) + cmdObj := newRESPArrayWithCapcity(3) cmdObj.replace(0, robjMSet) cmdObj.replace(1, robj.nth(i*2+1)) cmdObj.replace(2, robj.nth(i*2+2)) @@ -55,9 +55,9 @@ func subCmdByKeys(robj *resp) ([]*Command, error) { for i, sub := range robj.slice()[1:] { var cmdObj *resp if bytes.Equal(robj.nth(0).data, cmdMGetBytes) { - cmdObj = newRespArray([]*resp{robjGet, sub}) + cmdObj = newRESPArray([]*resp{robjGet, sub}) } else { - cmdObj = newRespArray([]*resp{robj.nth(0), sub}) + cmdObj = newRESPArray([]*resp{robj.nth(0), sub}) } cmds[i] = newCommand(cmdObj) } From fca9361543067c6aec7f8609ad3da26e4c613693 Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Fri, 20 Jul 2018 13:12:38 +0800 Subject: [PATCH 22/87] resp obj reuse --- proto/redis/cmd.go | 9 +++-- proto/redis/cmd_test.go | 2 +- proto/redis/node_conn.go | 18 +++++---- proto/redis/node_conn_test.go | 2 +- proto/redis/proxy_conn.go | 28 +++++++++----- proto/redis/proxy_conn_test.go | 3 +- proto/redis/resp.go | 71 +++++++++++++++++++++++++++------- proto/redis/resp_conn.go | 71 ++++++++++++++++++---------------- proto/redis/resp_conn_test.go | 19 ++++++--- proto/redis/sub.go | 4 +- 10 files changed, 150 insertions(+), 77 deletions(-) diff --git a/proto/redis/cmd.go b/proto/redis/cmd.go index 10637e43..ceb3f45b 100644 --- a/proto/redis/cmd.go +++ b/proto/redis/cmd.go @@ -53,13 +53,15 @@ type Command struct { // NewCommand("GET", "mykey") // NewCommand("CLUSTER", "NODES") func NewCommand(cmd string, args ...string) *Command { - respObj := newRESPArrayWithCapcity(len(args) + 1) - respObj.replace(0, newRESPBulk([]byte(cmd))) + respObj := respPool.Get().(*resp) + respObj.next().setBulk([]byte(cmd)) + // respObj := newRESPArrayWithCapcity(len(args) + 1) + // respObj.replace(0, newRESPBulk([]byte(cmd))) maxLen := len(args) + 1 for i := 1; i < maxLen; i++ { data := args[i-1] line := fmt.Sprintf("%d\r\n%s", len(data), data) - respObj.replace(i, newRESPBulk([]byte(line))) + respObj.next().setBulk([]byte(line)) } respObj.data = []byte(strconv.Itoa(len(args) + 1)) return newCommand(respObj) @@ -68,6 +70,7 @@ func NewCommand(cmd string, args ...string) *Command { func newCommand(robj *resp) *Command { r := &Command{respObj: robj} r.mergeType = getMergeType(robj.nth(0).data) + r.reply = &resp{} return r } diff --git a/proto/redis/cmd_test.go b/proto/redis/cmd_test.go index 7bb98887..904b5a61 100644 --- a/proto/redis/cmd_test.go +++ b/proto/redis/cmd_test.go @@ -18,7 +18,7 @@ func TestCommandNewCommand(t *testing.T) { func TestCommandRedirect(t *testing.T) { cmd := NewCommand("GET", "BAKA") - cmd.reply = newRespPlain(respError, []byte("ASK 1024 127.0.0.1:2048")) + cmd.reply = newRESPPlain(respError, []byte("ASK 1024 127.0.0.1:2048")) assert.True(t, cmd.IsRedirect()) r, slot, addr, err := cmd.RedirectTriple() assert.NoError(t, err) diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index 7e5c82ea..17421d84 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -63,18 +63,19 @@ func (nc *nodeConn) write(m *proto.Message) error { func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) error { nc.rc.br.ResetBuffer(mb.Buffer()) defer nc.rc.br.ResetBuffer(nil) - - count := mb.Count() - resps, err := nc.rc.decodeCount(count) - if err != nil { - return err - } + resps := make([]*resp, len(mb.Msgs())) for i, msg := range mb.Msgs() { cmd, ok := msg.Request().(*Command) if !ok { return ErrBadAssert } - cmd.reply = resps[i] + resps[i] = cmd.reply + } + err := nc.rc.decodeCount(resps) + if err != nil { + return err + } + for _, msg := range mb.Msgs() { msg.MarkRead() } return nil @@ -107,7 +108,8 @@ func (nc *nodeConn) FetchSlots() (nodes []string, slots [][]int, err error) { if err != nil { return } - rs, err := nc.rc.decodeCount(1) + rs := []*resp{&resp{}} + err = nc.rc.decodeCount(rs) if err != nil { return } diff --git a/proto/redis/node_conn_test.go b/proto/redis/node_conn_test.go index 64a5c7b0..98d48f7a 100644 --- a/proto/redis/node_conn_test.go +++ b/proto/redis/node_conn_test.go @@ -74,7 +74,7 @@ func TestReadBatchWithNilError(t *testing.T) { nc := newNodeConn("baka", "127.0.0.1:12345", _createConn(nil)) mb := proto.NewMsgBatch() msg := proto.GetMsg() - msg.WithRequest(&mockCmd{}) + msg.WithRequest(&Command{}) mb.AddMsg(msg) err := nc.ReadBatch(mb) assert.Error(t, err) diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 1473273c..f6505b36 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -5,7 +5,6 @@ import ( "overlord/lib/conv" libnet "overlord/lib/net" "overlord/proto" - "strconv" "github.com/pkg/errors" @@ -30,7 +29,7 @@ func NewProxyConn(conn *libnet.Conn) proto.ProxyConn { } func (pc *proxyConn) Decode(msgs []*proto.Message) ([]*proto.Message, error) { - rs, err := pc.rc.decodeMax(len(msgs)) + rs, err := pc.rc.decodeMax(msgs) if err != nil { return msgs, err } @@ -80,7 +79,6 @@ func (pc *proxyConn) Encode(msg *proto.Message) (err error) { err = errors.Wrap(err, "Redis Encoder encode") return } - if err = pc.rc.Flush(); err != nil { err = errors.Wrap(err, "Redis Encoder flush response") } @@ -127,14 +125,21 @@ func (pc *proxyConn) merge(msg *proto.Message) (*resp, error) { } } -func (pc *proxyConn) mergeOk(msg *proto.Message) (*resp, error) { - for _, sub := range msg.Subs() { - if err := sub.Err(); err != nil { +func (pc *proxyConn) mergeOk(msg *proto.Message) (robj *resp, err error) { + for i, sub := range msg.Subs() { + if err = sub.Err(); err != nil { cmd := sub.Request().(*Command) - return newRESPPlain(respError, cmd.reply.data), nil + robj = cmd.reply + robj.setPlain(respError, cmd.reply.data) + return + } + if i == len(msg.Subs())-1 { + cmd := sub.Request().(*Command) + robj = cmd.reply + robj.setString(okBytes) } } - return newRESPString(okBytes), nil + return } func (pc *proxyConn) mergeCount(msg *proto.Message) (reply *resp, err error) { @@ -158,7 +163,11 @@ func (pc *proxyConn) mergeCount(msg *proto.Message) (reply *resp, err error) { } func (pc *proxyConn) mergeJoin(msg *proto.Message) (reply *resp, err error) { - reply = newRESPArrayWithCapcity(len(msg.Subs())) + // TODO(LINTANGHUI):reuse reply + reply = &resp{} + reply.rtype = respArray + reply.arrayn = 0 + // reply = newRESPArrayWithCapcity(len(msg.Subs())) subs := msg.Subs() for i, sub := range subs { subcmd, ok := sub.Request().(*Command) @@ -169,6 +178,7 @@ func (pc *proxyConn) mergeJoin(msg *proto.Message) (reply *resp, err error) { } continue } + subcmd.reply.arrayn = 0 reply.replace(i, subcmd.reply) } reply.data = []byte(strconv.Itoa(len(subs))) diff --git a/proto/redis/proxy_conn_test.go b/proto/redis/proxy_conn_test.go index 834f412c..34d68a80 100644 --- a/proto/redis/proxy_conn_test.go +++ b/proto/redis/proxy_conn_test.go @@ -73,13 +73,14 @@ func TestEncodeCmdOk(t *testing.T) { for _, tt := range ts { t.Run(tt.Name, func(t *testing.T) { rs := tt.Reps - msg := proto.GetMsg() + msg := proto.NewMessage() co := tt.Obj if isComplex(co.nth(0).data) { cmds, err := newSubCmd(co) if assert.NoError(t, err) { for i, cmd := range cmds { cmd.reply = rs[i] + // fmt.Printf("cmd %+v\n", cmd) msg.WithRequest(cmd) } msg.Batch() diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 73ca6606..0a183ec8 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -46,6 +46,8 @@ type resp struct { // in array this is the count field data []byte array []*resp + // in order to reuse array.use arrayn to mark current obj. + arrayn int } func newRESPInt(val int) *resp { @@ -53,10 +55,19 @@ func newRESPInt(val int) *resp { return newRESPPlain(respInt, []byte(s)) } +func (r *resp)setInt(val int) { + s := strconv.Itoa(val) + r.setPlain(respInt, []byte(s)) +} + func newRESPBulk(data []byte) *resp { return newRESPPlain(respBulk, data) } +func (r *resp)setBulk(data []byte) { + r.setPlain(respBulk, data) +} + func newRESPPlain(rtype respType, data []byte) *resp { robj := respPool.Get().(*resp) robj.rtype = rtype @@ -65,37 +76,65 @@ func newRESPPlain(rtype respType, data []byte) *resp { return robj } +func (r *resp)setPlain(rtype respType, data []byte) { + r.rtype = rtype + r.data = data + r.arrayn=0 +} + func newRESPString(val []byte) *resp { return newRESPPlain(respString, val) } +func (r *resp)setString(val []byte) { + r.setPlain(respString, val) +} + func newRESPNull(rtype respType) *resp { return newRESPPlain(rtype, nil) } +func (r *resp)setNull(rtype respType) { + r.setPlain(rtype, nil) +} + func newRESPArray(resps []*resp) *resp { robj := respPool.Get().(*resp) robj.rtype = respArray robj.data = []byte(strconv.Itoa(len(resps))) robj.array = resps + robj.arrayn = len(resps) return robj } -func newRESPArrayWithCapcity(length int) *resp { - robj := respPool.Get().(*resp) - robj.rtype = respArray - robj.data = nil - robj.array = make([]*resp, length) - return robj +func (r *resp)setArray(resps []*resp) { + r.rtype = respArray + r.data = []byte(strconv.Itoa(len(resps))) + r.array = resps + r.arrayn = len(resps) } - +// func newRESPArray(resps []*resp) *resp { +// return +// } func (r *resp) nth(pos int) *resp { return r.array[pos] } +func (r *resp)next()*resp { + if r.arrayn < len(r.array) { + robj:= r.array[r.arrayn] + r.arrayn++ + return robj + } else { + robj := respPool.Get().(*resp) + r.array = append(r.array,robj) + r.arrayn++ + return robj + } +} func (r *resp) isNull() bool { if r.rtype == respArray { - return r.array == nil + return r.arrayn == 0 } if r.rtype == respBulk { return r.data == nil @@ -108,16 +147,22 @@ func (r *resp) replaceAll(begin int, newers []*resp) { } func (r *resp) replace(pos int, newer *resp) { - r.array[pos] = newer + if pos Date: Fri, 20 Jul 2018 13:15:40 +0800 Subject: [PATCH 23/87] check nil before assert --- proto/redis/resp_conn.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/proto/redis/resp_conn.go b/proto/redis/resp_conn.go index 1e95c201..0b706210 100644 --- a/proto/redis/resp_conn.go +++ b/proto/redis/resp_conn.go @@ -38,11 +38,11 @@ func (rc *respConn) decodeMax(msgs []*proto.Message) (resps []*resp, err error) for _, msg := range msgs { var robj *resp - subcmd, ok := msg.Request().(*Command) - if !ok { + req := msg.Request() + if req == nil { robj = respPool.Get().(*resp) } else { - robj = subcmd.respObj + robj = req.(*Command).respObj } err = rc.decodeRESP(robj) if err == bufio.ErrBufferFull { @@ -52,7 +52,6 @@ func (rc *respConn) decodeMax(msgs []*proto.Message) (resps []*resp, err error) } else if err != nil { return } - resps = append(resps, robj) } return From c52b45e0348154fbe5f1bbe3041973a00583fe66 Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Mon, 23 Jul 2018 10:12:47 +0800 Subject: [PATCH 24/87] gofmt --- proto/redis/proxy_conn.go | 2 +- proto/redis/resp.go | 33 +++++++++++++++++---------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index f6505b36..c15a5e37 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -163,7 +163,7 @@ func (pc *proxyConn) mergeCount(msg *proto.Message) (reply *resp, err error) { } func (pc *proxyConn) mergeJoin(msg *proto.Message) (reply *resp, err error) { - // TODO(LINTANGHUI):reuse reply + // TODO (LINTANGHUI):reuse reply reply = &resp{} reply.rtype = respArray reply.arrayn = 0 diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 0a183ec8..2497942f 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -47,7 +47,7 @@ type resp struct { data []byte array []*resp // in order to reuse array.use arrayn to mark current obj. - arrayn int + arrayn int } func newRESPInt(val int) *resp { @@ -55,7 +55,7 @@ func newRESPInt(val int) *resp { return newRESPPlain(respInt, []byte(s)) } -func (r *resp)setInt(val int) { +func (r *resp) setInt(val int) { s := strconv.Itoa(val) r.setPlain(respInt, []byte(s)) } @@ -64,7 +64,7 @@ func newRESPBulk(data []byte) *resp { return newRESPPlain(respBulk, data) } -func (r *resp)setBulk(data []byte) { +func (r *resp) setBulk(data []byte) { r.setPlain(respBulk, data) } @@ -76,17 +76,17 @@ func newRESPPlain(rtype respType, data []byte) *resp { return robj } -func (r *resp)setPlain(rtype respType, data []byte) { +func (r *resp) setPlain(rtype respType, data []byte) { r.rtype = rtype r.data = data - r.arrayn=0 + r.arrayn = 0 } func newRESPString(val []byte) *resp { return newRESPPlain(respString, val) } -func (r *resp)setString(val []byte) { +func (r *resp) setString(val []byte) { r.setPlain(respString, val) } @@ -94,8 +94,8 @@ func newRESPNull(rtype respType) *resp { return newRESPPlain(rtype, nil) } -func (r *resp)setNull(rtype respType) { - r.setPlain(rtype, nil) +func (r *resp) setNull(rtype respType) { + r.setPlain(rtype, nil) } func newRESPArray(resps []*resp) *resp { @@ -107,27 +107,28 @@ func newRESPArray(resps []*resp) *resp { return robj } -func (r *resp)setArray(resps []*resp) { +func (r *resp) setArray(resps []*resp) { r.rtype = respArray r.data = []byte(strconv.Itoa(len(resps))) r.array = resps r.arrayn = len(resps) } + // func newRESPArray(resps []*resp) *resp { -// return +// return // } func (r *resp) nth(pos int) *resp { return r.array[pos] } -func (r *resp)next()*resp { +func (r *resp) next() *resp { if r.arrayn < len(r.array) { - robj:= r.array[r.arrayn] + robj := r.array[r.arrayn] r.arrayn++ return robj } else { robj := respPool.Get().(*resp) - r.array = append(r.array,robj) + r.array = append(r.array, robj) r.arrayn++ return robj } @@ -147,10 +148,10 @@ func (r *resp) replaceAll(begin int, newers []*resp) { } func (r *resp) replace(pos int, newer *resp) { - if pos Date: Mon, 23 Jul 2018 18:32:02 +0800 Subject: [PATCH 25/87] merge into subresps --- proto/message.go | 5 ++ proto/redis/node_conn.go | 20 +++---- proto/redis/proxy_conn.go | 78 ++++++++++++------------ proto/redis/resp.go | 98 +++++++++++++++++++++++++++--- proto/redis/resp_conn.go | 109 +++++++--------------------------- proto/redis/resp_conn_test.go | 2 +- proto/types.go | 5 ++ 7 files changed, 166 insertions(+), 151 deletions(-) diff --git a/proto/message.go b/proto/message.go index f619d1a2..10387e4f 100644 --- a/proto/message.go +++ b/proto/message.go @@ -200,6 +200,11 @@ func (m *Message) AddSubResps(items ...[]byte) { m.subResps = append(m.subResps, items...) } +func (m *Message) Write(p []byte) error { + m.AddSubResps(p) + return nil +} + // Subs returns all the sub messages. func (m *Message) Subs() []*Message { return m.subs[:m.reqn] diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index 17421d84..558a4d74 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -57,25 +57,20 @@ func (nc *nodeConn) write(m *proto.Message) error { m.DoneWithError(ErrBadAssert) return ErrBadAssert } - return nc.rc.encode(cmd.respObj) + return cmd.respObj.encode(nc.rc.bw) } -func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) error { +func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) (err error) { nc.rc.br.ResetBuffer(mb.Buffer()) defer nc.rc.br.ResetBuffer(nil) - resps := make([]*resp, len(mb.Msgs())) - for i, msg := range mb.Msgs() { + for _, msg := range mb.Msgs() { cmd, ok := msg.Request().(*Command) if !ok { return ErrBadAssert } - resps[i] = cmd.reply - } - err := nc.rc.decodeCount(resps) - if err != nil { - return err - } - for _, msg := range mb.Msgs() { + if err = nc.rc.decodeOne(cmd.reply); err != nil { + return + } msg.MarkRead() } return nil @@ -100,7 +95,7 @@ var ( ) func (nc *nodeConn) FetchSlots() (nodes []string, slots [][]int, err error) { - err = nc.rc.encode(robjCluterNodes) + robjCluterNodes.encode(nc.rc.bw) if err != nil { return } @@ -128,6 +123,5 @@ func (nc *nodeConn) FetchSlots() (nodes []string, slots [][]int, err error) { slots = append(slots, node.Slots()) } } - return } diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index c15a5e37..4f3e69cd 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -75,42 +75,34 @@ func (pc *proxyConn) withReq(m *proto.Message, cmd *Command) { } func (pc *proxyConn) Encode(msg *proto.Message) (err error) { - if err = pc.encode(msg); err != nil { - err = errors.Wrap(err, "Redis Encoder encode") - return - } - if err = pc.rc.Flush(); err != nil { - err = errors.Wrap(err, "Redis Encoder flush response") - } - return -} - -func (pc *proxyConn) encode(msg *proto.Message) error { if err := msg.Err(); err != nil { return pc.encodeError(err) } - reply, err := pc.merge(msg) + err = pc.merge(msg) if err != nil { return pc.encodeError(err) } - return pc.rc.encode(reply) + for _, item := range msg.SubResps() { + _ = pc.rc.bw.Write(item) + } + if err = pc.rc.Flush(); err != nil { + err = errors.Wrap(err, "Redis Encoder flush response") + } + return } -func (pc *proxyConn) merge(msg *proto.Message) (*resp, error) { +func (pc *proxyConn) merge(msg *proto.Message) error { cmd, ok := msg.Request().(*Command) if !ok { - return nil, ErrBadAssert + return ErrBadAssert } - if !msg.IsBatch() { - return cmd.reply, nil + return cmd.reply.encode(msg) } - mtype, err := pc.getBatchMergeType(msg) if err != nil { - return nil, err + return err } - switch mtype { case MergeTypeJoin: return pc.mergeJoin(msg) @@ -125,24 +117,25 @@ func (pc *proxyConn) merge(msg *proto.Message) (*resp, error) { } } -func (pc *proxyConn) mergeOk(msg *proto.Message) (robj *resp, err error) { +func (pc *proxyConn) mergeOk(msg *proto.Message) (err error) { for i, sub := range msg.Subs() { if err = sub.Err(); err != nil { cmd := sub.Request().(*Command) - robj = cmd.reply - robj.setPlain(respError, cmd.reply.data) + msg.Write(respErrorBytes) + msg.Write(cmd.reply.data) + msg.Write(crlfBytes) return } if i == len(msg.Subs())-1 { - cmd := sub.Request().(*Command) - robj = cmd.reply - robj.setString(okBytes) + msg.Write(respStringBytes) + msg.Write(okBytes) + msg.Write(crlfBytes) } } return } -func (pc *proxyConn) mergeCount(msg *proto.Message) (reply *resp, err error) { +func (pc *proxyConn) mergeCount(msg *proto.Message) (err error) { var sum = 0 for _, sub := range msg.Subs() { if sub.Err() != nil { @@ -150,38 +143,41 @@ func (pc *proxyConn) mergeCount(msg *proto.Message) (reply *resp, err error) { } subcmd, ok := sub.Request().(*Command) if !ok { - return nil, ErrBadAssert + return ErrBadAssert } ival, err := conv.Btoi(subcmd.reply.data) if err != nil { - return nil, ErrBadCount + return ErrBadCount } sum += int(ival) } - reply = newRESPInt(sum) + msg.Write(respIntBytes) + msg.Write([]byte(strconv.Itoa(sum))) + msg.Write(crlfBytes) return } -func (pc *proxyConn) mergeJoin(msg *proto.Message) (reply *resp, err error) { +func (pc *proxyConn) mergeJoin(msg *proto.Message) (err error) { // TODO (LINTANGHUI):reuse reply - reply = &resp{} - reply.rtype = respArray - reply.arrayn = 0 - // reply = newRESPArrayWithCapcity(len(msg.Subs())) subs := msg.Subs() - for i, sub := range subs { + msg.Write(respArrayBytes) + if len(subs) == 0 { + msg.Write(respNullBytes) + return + } + msg.Write([]byte(strconv.Itoa(len(subs)))) + msg.Write(crlfBytes) + for _, sub := range subs { subcmd, ok := sub.Request().(*Command) if !ok { err = pc.encodeError(ErrBadAssert) if err != nil { return } - continue + return ErrBadAssert } - subcmd.reply.arrayn = 0 - reply.replace(i, subcmd.reply) + subcmd.reply.encode(msg) } - reply.data = []byte(strconv.Itoa(len(subs))) return } @@ -198,5 +194,5 @@ func (pc *proxyConn) getBatchMergeType(msg *proto.Message) (mtype MergeType, err func (pc *proxyConn) encodeError(err error) error { se := errors.Cause(err).Error() robj := newRESPPlain(respError, []byte(se)) - return pc.rc.encode(robj) + return robj.encode(pc.rc.bw) } diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 2497942f..e11afc3c 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -2,6 +2,7 @@ package redis import ( "bytes" + "overlord/proto" "strconv" "strings" "sync" @@ -114,9 +115,6 @@ func (r *resp) setArray(resps []*resp) { r.arrayn = len(resps) } -// func newRESPArray(resps []*resp) *resp { -// return -// } func (r *resp) nth(pos int) *resp { return r.array[pos] } @@ -133,6 +131,7 @@ func (r *resp) next() *resp { return robj } } + func (r *resp) isNull() bool { if r.rtype == respArray { return r.arrayn == 0 @@ -143,10 +142,6 @@ func (r *resp) isNull() bool { return false } -func (r *resp) replaceAll(begin int, newers []*resp) { - copy(r.array[begin:], newers) -} - func (r *resp) replace(pos int, newer *resp) { if pos < len(r.array) { r.array[pos] = newer @@ -192,3 +187,92 @@ func (r *resp) bytes() []byte { } return data[pos:] } + +func (r *resp) encode(w proto.Writer) error { + switch r.rtype { + case respInt: + return r.encodeInt(w) + case respError: + return r.encodeError(w) + case respString: + return r.encodeString(w) + case respBulk: + return r.encodeBulk(w) + case respArray: + return r.encodeArray(w) + } + return nil +} + +func (r *resp) encodeError(w proto.Writer) (err error) { + return r.encodePlain(respErrorBytes, w) +} + +func (r *resp) encodeInt(w proto.Writer) (err error) { + return r.encodePlain(respIntBytes, w) + return + +} + +func (r *resp) encodeString(w proto.Writer) (err error) { + return r.encodePlain(respStringBytes, w) +} + +func (r *resp) encodePlain(rtypeBytes []byte, w proto.Writer) (err error) { + err = w.Write(rtypeBytes) + if err != nil { + return + } + err = w.Write(r.data) + if err != nil { + return + } + err = w.Write(crlfBytes) + return +} + +func (r *resp) encodeBulk(w proto.Writer) (err error) { + // NOTICE: we need not to convert robj.Len() as int + // due number has been writen into data + err = w.Write(respBulkBytes) + if err != nil { + return + } + if r.isNull() { + err = w.Write(respNullBytes) + return + } + + err = w.Write(r.data) + if err != nil { + return + } + err = w.Write(crlfBytes) + return +} + +func (r *resp) encodeArray(w proto.Writer) (err error) { + err = w.Write(respArrayBytes) + if err != nil { + return + } + + if r.isNull() { + err = w.Write(respNullBytes) + return + } + // output size + err = w.Write(r.data) + if err != nil { + return + } + err = w.Write(crlfBytes) + + for _, item := range r.slice() { + item.encode(w) + if err != nil { + return + } + } + return +} diff --git a/proto/redis/resp_conn.go b/proto/redis/resp_conn.go index 0b706210..19c3d0fb 100644 --- a/proto/redis/resp_conn.go +++ b/proto/redis/resp_conn.go @@ -93,6 +93,26 @@ func (rc *respConn) decodeCount(robjs []*resp) (err error) { } } +// decodeOne will trying to parse the buffer until get one complete resp.. +func (rc *respConn) decodeOne(robj *resp) (err error) { + var ( + begin = rc.br.Mark() + ) + for { + // advance the r position to begin to avoid Read fill buffer + rc.br.AdvanceTo(begin) + err = rc.br.Read() + if err != nil { + return + } + err = rc.decodeRESP(robj) + if err == bufio.ErrBufferFull { + continue + } + return + } +} + func (rc *respConn) decodeRESP(robj *resp) (err error) { var ( line []byte @@ -176,96 +196,7 @@ func decodeInt(data []byte) (int, error) { return int(i), err } -func (rc *respConn) encode(robj *resp) error { - switch robj.rtype { - case respInt: - return rc.encodeInt(robj) - case respError: - return rc.encodeError(robj) - case respString: - return rc.encodeString(robj) - case respBulk: - return rc.encodeBulk(robj) - case respArray: - return rc.encodeArray(robj) - } - return nil -} - // Flush was used to writev to flush. func (rc *respConn) Flush() error { return rc.bw.Flush() } - -func (rc *respConn) encodeInt(robj *resp) (err error) { - return rc.encodePlain(respIntBytes, robj) -} - -func (rc *respConn) encodeError(robj *resp) (err error) { - return rc.encodePlain(respErrorBytes, robj) -} - -func (rc *respConn) encodeString(robj *resp) (err error) { - return rc.encodePlain(respStringBytes, robj) -} - -func (rc *respConn) encodePlain(rtypeBytes []byte, robj *resp) (err error) { - err = rc.bw.Write(rtypeBytes) - if err != nil { - return - } - - err = rc.bw.Write(robj.data) - if err != nil { - return - } - err = rc.bw.Write(crlfBytes) - return -} - -func (rc *respConn) encodeBulk(robj *resp) (err error) { - // NOTICE: we need not to convert robj.Len() as int - // due number has been writen into data - err = rc.bw.Write(respBulkBytes) - if err != nil { - return - } - if robj.isNull() { - err = rc.bw.Write(respNullBytes) - return - } - - err = rc.bw.Write(robj.data) - if err != nil { - return - } - - err = rc.bw.Write(crlfBytes) - return -} - -func (rc *respConn) encodeArray(robj *resp) (err error) { - err = rc.bw.Write(respArrayBytes) - if err != nil { - return - } - - if robj.isNull() { - err = rc.bw.Write(respNullBytes) - return - } - // output size - err = rc.bw.Write(robj.data) - if err != nil { - return - } - err = rc.bw.Write(crlfBytes) - - for _, item := range robj.slice() { - err = rc.encode(item) - if err != nil { - return - } - } - return -} diff --git a/proto/redis/resp_conn_test.go b/proto/redis/resp_conn_test.go index 94969921..9300248a 100644 --- a/proto/redis/resp_conn_test.go +++ b/proto/redis/resp_conn_test.go @@ -132,7 +132,7 @@ func TestEncodeResp(t *testing.T) { sock, buf := _createDownStreamConn() conn := newRESPConn(sock) // err := conn.encode(newRespInt(1024)) - err := conn.encode(tt.Robj) + err := tt.Robj.encode(conn.bw) assert.NoError(t, err) err = conn.Flush() assert.NoError(t, err) diff --git a/proto/types.go b/proto/types.go index 3dd11bf6..85b4669d 100644 --- a/proto/types.go +++ b/proto/types.go @@ -44,3 +44,8 @@ type NodeConn interface { FetchSlots() (nodes []string, slots [][]int, err error) } + +// Writer writer without err. +type Writer interface { + Write([]byte) error +} From 20a5203c3ac4c9a06ffb91811570024ebb57700c Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Mon, 23 Jul 2018 19:36:16 +0800 Subject: [PATCH 26/87] fix decode --- proto/redis/proxy_conn.go | 33 ++++++++------------------- proto/redis/resp.go | 29 +++++++++++++++++++++++- proto/redis/resp_conn.go | 14 ++++++++---- proto/redis/resp_conn_test.go | 42 +++++++++++++++++------------------ 4 files changed, 68 insertions(+), 50 deletions(-) diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 4f3e69cd..0dc821fd 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -29,21 +29,7 @@ func NewProxyConn(conn *libnet.Conn) proto.ProxyConn { } func (pc *proxyConn) Decode(msgs []*proto.Message) ([]*proto.Message, error) { - rs, err := pc.rc.decodeMax(msgs) - if err != nil { - return msgs, err - } - for i, r := range rs { - msgs[i].Type = proto.CacheTypeRedis - msgs[i].MarkStart() - - err = pc.decodeToMsg(r, msgs[i]) - if err != nil { - msgs[i].Reset() - return nil, err - } - } - return msgs[:len(rs)], nil + return pc.rc.decodeMax(msgs) } func (pc *proxyConn) decodeToMsg(robj *resp, msg *proto.Message) (err error) { @@ -83,7 +69,7 @@ func (pc *proxyConn) Encode(msg *proto.Message) (err error) { return pc.encodeError(err) } for _, item := range msg.SubResps() { - _ = pc.rc.bw.Write(item) + pc.rc.bw.Write(item) } if err = pc.rc.Flush(); err != nil { err = errors.Wrap(err, "Redis Encoder flush response") @@ -118,7 +104,7 @@ func (pc *proxyConn) merge(msg *proto.Message) error { } func (pc *proxyConn) mergeOk(msg *proto.Message) (err error) { - for i, sub := range msg.Subs() { + for _, sub := range msg.Subs() { if err = sub.Err(); err != nil { cmd := sub.Request().(*Command) msg.Write(respErrorBytes) @@ -126,12 +112,10 @@ func (pc *proxyConn) mergeOk(msg *proto.Message) (err error) { msg.Write(crlfBytes) return } - if i == len(msg.Subs())-1 { - msg.Write(respStringBytes) - msg.Write(okBytes) - msg.Write(crlfBytes) - } } + msg.Write(respStringBytes) + msg.Write(okBytes) + msg.Write(crlfBytes) return } @@ -193,6 +177,7 @@ func (pc *proxyConn) getBatchMergeType(msg *proto.Message) (mtype MergeType, err func (pc *proxyConn) encodeError(err error) error { se := errors.Cause(err).Error() - robj := newRESPPlain(respError, []byte(se)) - return robj.encode(pc.rc.bw) + pc.rc.bw.Write(respErrorBytes) + pc.rc.bw.Write([]byte(se)) + return pc.rc.bw.Write(crlfBytes) } diff --git a/proto/redis/resp.go b/proto/redis/resp.go index e11afc3c..9e6a2247 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -210,7 +210,6 @@ func (r *resp) encodeError(w proto.Writer) (err error) { func (r *resp) encodeInt(w proto.Writer) (err error) { return r.encodePlain(respIntBytes, w) - return } @@ -276,3 +275,31 @@ func (r *resp) encodeArray(w proto.Writer) (err error) { } return } + +func (r *resp) decode(msg *proto.Message) (err error) { + if isComplex(r.nth(0).data) { + cmds, inerr := newSubCmd(r) + if inerr != nil { + err = inerr + return + } + for _, cmd := range cmds { + withReq(msg, cmd) + } + } else { + withReq(msg, newCommand(r)) + } + return +} + +func withReq(m *proto.Message, cmd *Command) { + req := m.NextReq() + if req == nil { + m.WithRequest(cmd) + } else { + reqCmd := req.(*Command) + reqCmd.respObj = cmd.respObj + reqCmd.mergeType = cmd.mergeType + reqCmd.reply = cmd.reply + } +} diff --git a/proto/redis/resp_conn.go b/proto/redis/resp_conn.go index 19c3d0fb..9936b0ed 100644 --- a/proto/redis/resp_conn.go +++ b/proto/redis/resp_conn.go @@ -27,7 +27,7 @@ func newRESPConn(conn *libnet.Conn) *respConn { // decodeMax will parse all the resp objects and keep the reuse reference until // next call of this function. -func (rc *respConn) decodeMax(msgs []*proto.Message) (resps []*resp, err error) { +func (rc *respConn) decodeMax(msgs []*proto.Message) (resps []*proto.Message, err error) { if rc.completed { err = rc.br.Read() if err != nil { @@ -36,7 +36,7 @@ func (rc *respConn) decodeMax(msgs []*proto.Message) (resps []*resp, err error) rc.completed = false } - for _, msg := range msgs { + for i, msg := range msgs { var robj *resp req := msg.Request() if req == nil { @@ -48,11 +48,17 @@ func (rc *respConn) decodeMax(msgs []*proto.Message) (resps []*resp, err error) if err == bufio.ErrBufferFull { rc.completed = true err = nil - return + return msgs[:i], nil } else if err != nil { return } - resps = append(resps, robj) + msg.Type = proto.CacheTypeRedis + msg.MarkStart() + err = robj.decode(msg) + if err != nil { + msg.Reset() + return + } } return } diff --git a/proto/redis/resp_conn_test.go b/proto/redis/resp_conn_test.go index 9300248a..fdb2c621 100644 --- a/proto/redis/resp_conn_test.go +++ b/proto/redis/resp_conn_test.go @@ -5,7 +5,6 @@ import ( "testing" "overlord/lib/bufio" - "overlord/proto" "github.com/stretchr/testify/assert" ) @@ -67,26 +66,27 @@ func TestDecodeRESP(t *testing.T) { } } -func TestDecodeMaxReachMaxOk(t *testing.T) { - line := []byte("$1\r\na\r\n+my name is van\r\n-baka error\r\n") - rc := _createRespConn([]byte(line)) - msgs := []*proto.Message{proto.NewMessage(), proto.NewMessage()} - rs, err := rc.decodeMax(msgs) - assert.NoError(t, err) - assert.Len(t, rs, 2) - assert.Equal(t, respBulk, rs[0].rtype) - assert.Equal(t, respString, rs[1].rtype) -} - -func TestDecodeMaxReachFullOk(t *testing.T) { - line := []byte("$1\r\na\r\n+my name is") - rc := _createRespConn([]byte(line)) - msgs := []*proto.Message{proto.NewMessage(), proto.NewMessage()} - rs, err := rc.decodeMax(msgs) - assert.NoError(t, err) - assert.Len(t, rs, 1) - assert.Equal(t, respBulk, rs[0].rtype) -} +// func TestDecodeMaxReachMaxOk(t *testing.T) { +// line := []byte("$1\r\na\r\n") +// rc := _createRespConn([]byte(line)) +// msgs := []*proto.Message{proto.NewMessage(), proto.NewMessage()} +// rs, err := rc.decodeMax(msgs) +// assert.NoError(t, err) +// assert.Len(t, rs, 2) + +// assert.Equal(t, respBulk, rs[0].Request().(*Command).respObj.rtype) +// assert.Equal(t, respString, rs[1].Request().(*Command).respObj.rtype) +// } + +// func TestDecodeMaxReachFullOk(t *testing.T) { +// line := []byte("$1\r\na\r\n+my name is") +// rc := _createRespConn([]byte(line)) +// msgs := []*proto.Message{proto.NewMessage(), proto.NewMessage()} +// rs, err := rc.decodeMax(msgs) +// assert.NoError(t, err) +// assert.Len(t, rs, 1) +// assert.Equal(t, respBulk, rs[0].Request().(*Command).respObj.rtype) +// } func TestDecodeCountOk(t *testing.T) { line := []byte("$1\r\na\r\n+my name is\r\n") From e96a33b366e7f07b78be8428ac45a4fc351ea611 Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Mon, 23 Jul 2018 22:24:11 +0800 Subject: [PATCH 27/87] reuse command --- proto/redis/cmd.go | 7 +++ proto/redis/proxy_conn.go | 30 +-------- proto/redis/proxy_conn_test.go | 7 +-- proto/redis/resp.go | 18 ++---- proto/redis/resp_conn.go | 13 ++-- proto/redis/resp_conn_test.go | 43 ++++++------- proto/redis/sub.go | 110 +++++++++++++++++++++++++-------- 7 files changed, 131 insertions(+), 97 deletions(-) diff --git a/proto/redis/cmd.go b/proto/redis/cmd.go index ceb3f45b..531f1d12 100644 --- a/proto/redis/cmd.go +++ b/proto/redis/cmd.go @@ -5,6 +5,7 @@ import ( "fmt" "strconv" "strings" + // "crc" "bytes" ) @@ -74,6 +75,12 @@ func newCommand(robj *resp) *Command { return r } +func (c *Command) setRESP(robj *resp) { + c.respObj = robj + c.mergeType = getMergeType(robj.nth(0).data) + c.reply = &resp{} +} + func newCommandWithMergeType(robj *resp, mtype MergeType) *Command { return &Command{respObj: robj, mergeType: mtype} } diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 0dc821fd..ef5a2ba5 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -29,35 +29,7 @@ func NewProxyConn(conn *libnet.Conn) proto.ProxyConn { } func (pc *proxyConn) Decode(msgs []*proto.Message) ([]*proto.Message, error) { - return pc.rc.decodeMax(msgs) -} - -func (pc *proxyConn) decodeToMsg(robj *resp, msg *proto.Message) (err error) { - if isComplex(robj.nth(0).data) { - cmds, inerr := newSubCmd(robj) - if inerr != nil { - err = inerr - return - } - for _, cmd := range cmds { - pc.withReq(msg, cmd) - } - } else { - pc.withReq(msg, newCommand(robj)) - } - return -} - -func (pc *proxyConn) withReq(m *proto.Message, cmd *Command) { - req := m.NextReq() - if req == nil { - m.WithRequest(cmd) - } else { - reqCmd := req.(*Command) - reqCmd.respObj = cmd.respObj - reqCmd.mergeType = cmd.mergeType - reqCmd.reply = cmd.reply - } + return pc.rc.decodeMsg(msgs) } func (pc *proxyConn) Encode(msg *proto.Message) (err error) { diff --git a/proto/redis/proxy_conn_test.go b/proto/redis/proxy_conn_test.go index 34d68a80..d494e53c 100644 --- a/proto/redis/proxy_conn_test.go +++ b/proto/redis/proxy_conn_test.go @@ -76,12 +76,11 @@ func TestEncodeCmdOk(t *testing.T) { msg := proto.NewMessage() co := tt.Obj if isComplex(co.nth(0).data) { - cmds, err := newSubCmd(co) + err := newSubCmd(msg, co) if assert.NoError(t, err) { - for i, cmd := range cmds { + for i, req := range msg.Requests() { + cmd := req.(*Command) cmd.reply = rs[i] - // fmt.Printf("cmd %+v\n", cmd) - msg.WithRequest(cmd) } msg.Batch() } diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 9e6a2247..167c203f 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -278,28 +278,22 @@ func (r *resp) encodeArray(w proto.Writer) (err error) { func (r *resp) decode(msg *proto.Message) (err error) { if isComplex(r.nth(0).data) { - cmds, inerr := newSubCmd(r) - if inerr != nil { - err = inerr + err = newSubCmd(msg, r) + if err != nil { return } - for _, cmd := range cmds { - withReq(msg, cmd) - } } else { - withReq(msg, newCommand(r)) + withReq(msg, r) } return } -func withReq(m *proto.Message, cmd *Command) { +func withReq(m *proto.Message, robj *resp) { req := m.NextReq() if req == nil { - m.WithRequest(cmd) + m.WithRequest(newCommand(robj)) } else { reqCmd := req.(*Command) - reqCmd.respObj = cmd.respObj - reqCmd.mergeType = cmd.mergeType - reqCmd.reply = cmd.reply + reqCmd.setRESP(robj) } } diff --git a/proto/redis/resp_conn.go b/proto/redis/resp_conn.go index 9936b0ed..0f924ed5 100644 --- a/proto/redis/resp_conn.go +++ b/proto/redis/resp_conn.go @@ -25,13 +25,14 @@ func newRESPConn(conn *libnet.Conn) *respConn { return r } -// decodeMax will parse all the resp objects and keep the reuse reference until +// decodeMsg will parse all the req resp objects into message and keep the reuse reference until // next call of this function. -func (rc *respConn) decodeMax(msgs []*proto.Message) (resps []*proto.Message, err error) { +func (rc *respConn) decodeMsg(msgs []*proto.Message) ([]*proto.Message, error) { + var err error if rc.completed { err = rc.br.Read() if err != nil { - return + return nil, err } rc.completed = false } @@ -50,17 +51,17 @@ func (rc *respConn) decodeMax(msgs []*proto.Message) (resps []*proto.Message, er err = nil return msgs[:i], nil } else if err != nil { - return + return nil, err } msg.Type = proto.CacheTypeRedis msg.MarkStart() err = robj.decode(msg) if err != nil { msg.Reset() - return + return nil, err } } - return + return msgs, nil } // decodeCount will trying to parse the buffer until meet the count. diff --git a/proto/redis/resp_conn_test.go b/proto/redis/resp_conn_test.go index fdb2c621..4a3c82eb 100644 --- a/proto/redis/resp_conn_test.go +++ b/proto/redis/resp_conn_test.go @@ -5,6 +5,7 @@ import ( "testing" "overlord/lib/bufio" + "overlord/proto" "github.com/stretchr/testify/assert" ) @@ -66,27 +67,27 @@ func TestDecodeRESP(t *testing.T) { } } -// func TestDecodeMaxReachMaxOk(t *testing.T) { -// line := []byte("$1\r\na\r\n") -// rc := _createRespConn([]byte(line)) -// msgs := []*proto.Message{proto.NewMessage(), proto.NewMessage()} -// rs, err := rc.decodeMax(msgs) -// assert.NoError(t, err) -// assert.Len(t, rs, 2) - -// assert.Equal(t, respBulk, rs[0].Request().(*Command).respObj.rtype) -// assert.Equal(t, respString, rs[1].Request().(*Command).respObj.rtype) -// } - -// func TestDecodeMaxReachFullOk(t *testing.T) { -// line := []byte("$1\r\na\r\n+my name is") -// rc := _createRespConn([]byte(line)) -// msgs := []*proto.Message{proto.NewMessage(), proto.NewMessage()} -// rs, err := rc.decodeMax(msgs) -// assert.NoError(t, err) -// assert.Len(t, rs, 1) -// assert.Equal(t, respBulk, rs[0].Request().(*Command).respObj.rtype) -// } +func TestDecodeMaxReachMaxOk(t *testing.T) { + line := []byte("*2\r\n$3\r\nget\r\n$1\r\na\r\n*2\r\n$3\r\nget\r\n$1\r\na\r\n") + rc := _createRespConn([]byte(line)) + msgs := []*proto.Message{proto.NewMessage(), proto.NewMessage()} + rs, err := rc.decodeMsg(msgs) + assert.NoError(t, err) + assert.Len(t, rs, 2) + + assert.Equal(t, respArray, rs[0].Request().(*Command).respObj.rtype) + assert.Equal(t, 2, rs[1].Request().(*Command).respObj.Len()) +} + +func TestDecodeMaxReachFullOk(t *testing.T) { + line := []byte("*2\r\n$3\r\nget\r\n$1\r\na\r\n*2\r\n$3\r\nget\r\n$1") + rc := _createRespConn([]byte(line)) + msgs := []*proto.Message{proto.NewMessage(), proto.NewMessage()} + rs, err := rc.decodeMsg(msgs) + assert.NoError(t, err) + assert.Len(t, rs, 1) + assert.Equal(t, respArray, rs[0].Request().(*Command).respObj.rtype) +} func TestDecodeCountOk(t *testing.T) { line := []byte("$1\r\na\r\n+my name is\r\n") diff --git a/proto/redis/sub.go b/proto/redis/sub.go index d51cdd22..53d5eef7 100644 --- a/proto/redis/sub.go +++ b/proto/redis/sub.go @@ -3,6 +3,8 @@ package redis import ( "bytes" "errors" + + "overlord/proto" ) const ( @@ -25,43 +27,101 @@ func isComplex(cmd []byte) bool { bytes.Equal(cmd, cmdExistsBytes) } -func newSubCmd(robj *resp) ([]*Command, error) { +func newSubCmd(msg *proto.Message, robj *resp) error { if bytes.Equal(robj.nth(0).data, cmdMSetBytes) { - return subCmdMset(robj) + return cmdMset(msg, robj) } - return subCmdByKeys(robj) + return cmdByKeys(msg, robj) } -func subCmdMset(robj *resp) ([]*Command, error) { +// func subCmdMset(robj *resp) ([]*Command, error) { +// if !isEven(robj.Len() - 1) { +// return nil, ErrBadCommandSize +// } + +// mid := robj.Len() / 2 +// cmds := make([]*Command, mid) +// for i := 0; i < mid; i++ { +// cmdObj := respPool.Get().(*resp) +// cmdObj.rtype = respArray +// cmdObj.data = nil +// cmdObj.replace(0, robjMSet) +// cmdObj.replace(1, robj.nth(i*2+1)) +// cmdObj.replace(2, robj.nth(i*2+2)) +// cmdObj.data = cmdMSetLenBytes +// cmds[i] = newCommandWithMergeType(cmdObj, MergeTypeOk) +// } +// return cmds, nil +// } + +func cmdMset(msg *proto.Message, robj *resp) error { if !isEven(robj.Len() - 1) { - return nil, ErrBadCommandSize + return ErrBadCommandSize } - mid := robj.Len() / 2 - cmds := make([]*Command, mid) for i := 0; i < mid; i++ { - cmdObj := respPool.Get().(*resp) - cmdObj.rtype = respArray - cmdObj.data = nil - cmdObj.replace(0, robjMSet) - cmdObj.replace(1, robj.nth(i*2+1)) - cmdObj.replace(2, robj.nth(i*2+2)) - cmdObj.data = cmdMSetLenBytes - cmds[i] = newCommandWithMergeType(cmdObj, MergeTypeOk) + req := msg.NextReq() + if req == nil { + cmdObj := respPool.Get().(*resp) + cmdObj.rtype = respArray + cmdObj.data = nil + cmdObj.replace(0, robjMSet) + cmdObj.replace(1, robj.nth(i*2+1)) + cmdObj.replace(2, robj.nth(i*2+2)) + cmdObj.data = cmdMSetLenBytes + cmd := newCommandWithMergeType(cmdObj, MergeTypeOk) + msg.WithRequest(cmd) + } else { + reqCmd := req.(*Command) + reqCmd.mergeType = MergeTypeOk + cmdObj := reqCmd.respObj + cmdObj.rtype = respArray + cmdObj.data = nil + cmdObj.replace(0, robjMSet) + cmdObj.replace(1, robj.nth(i*2+1)) + cmdObj.replace(2, robj.nth(i*2+2)) + cmdObj.data = cmdMSetLenBytes + } } - return cmds, nil + return nil } -func subCmdByKeys(robj *resp) ([]*Command, error) { - cmds := make([]*Command, robj.Len()-1) - for i, sub := range robj.slice()[1:] { - var cmdObj *resp - if bytes.Equal(robj.nth(0).data, cmdMGetBytes) { - cmdObj = newRESPArray([]*resp{robjGet, sub}) +// func subCmdByKeys(robj *resp) ([]*Command, error) { +// cmds := make([]*Command, robj.Len()-1) +// for i, sub := range robj.slice()[1:] { +// var cmdObj *resp +// if bytes.Equal(robj.nth(0).data, cmdMGetBytes) { +// cmdObj = newRESPArray([]*resp{robjGet, sub}) +// } else { +// cmdObj = newRESPArray([]*resp{robj.nth(0), sub}) +// } +// cmds[i] = newCommand(cmdObj) +// } +// return cmds, nil +// } + +func cmdByKeys(msg *proto.Message, robj *resp) error { + var cmdObj *resp + for _, sub := range robj.slice()[1:] { + req := msg.NextReq() + if req == nil { + if bytes.Equal(robj.nth(0).data, cmdMGetBytes) { + cmdObj = newRESPArray([]*resp{robjGet, sub}) + } else { + cmdObj = newRESPArray([]*resp{robj.nth(0), sub}) + } + cmd := newCommand(cmdObj) + msg.WithRequest(cmd) } else { - cmdObj = newRESPArray([]*resp{robj.nth(0), sub}) + reqCmd := req.(*Command) + cmdObj := reqCmd.respObj + if bytes.Equal(robj.nth(0).data, cmdMGetBytes) { + cmdObj.setArray([]*resp{robjGet, sub}) + } else { + cmdObj.setArray([]*resp{robj.nth(0), sub}) + } + reqCmd.mergeType = getMergeType(cmdObj.nth(0).data) } - cmds[i] = newCommand(cmdObj) } - return cmds, nil + return nil } From c9400db37896a5bba7402d04e8e2a01fb539401b Mon Sep 17 00:00:00 2001 From: WaySLOG Date: Tue, 24 Jul 2018 10:53:11 +0800 Subject: [PATCH 28/87] Update cmd.go --- proto/redis/cmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto/redis/cmd.go b/proto/redis/cmd.go index 531f1d12..0b8b3fbe 100644 --- a/proto/redis/cmd.go +++ b/proto/redis/cmd.go @@ -26,7 +26,7 @@ var ( cmdMGetBytes = []byte("4\r\nMGET") cmdGetBytes = []byte("3\r\nGET") cmdDelBytes = []byte("3\r\nDEL") - cmdExistsBytes = []byte("5\r\nEXITS") + cmdExistsBytes = []byte("6\r\nEXISTS") ) // errors From 3406725869fcbe490f33653f65a0d1a9d98f5ad5 Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Tue, 24 Jul 2018 10:54:56 +0800 Subject: [PATCH 29/87] add reuse command test --- proto/redis/cmd_test.go | 4 ++++ proto/redis/proxy_conn.go | 4 ++-- proto/redis/proxy_conn_test.go | 24 +++++++++++++++++++++--- proto/redis/resp.go | 8 ++++++++ proto/redis/resp_conn.go | 3 ++- proto/redis/resp_conn_test.go | 21 +++++++++++++++++++-- 6 files changed, 56 insertions(+), 8 deletions(-) diff --git a/proto/redis/cmd_test.go b/proto/redis/cmd_test.go index 904b5a61..b45b3933 100644 --- a/proto/redis/cmd_test.go +++ b/proto/redis/cmd_test.go @@ -25,4 +25,8 @@ func TestCommandRedirect(t *testing.T) { assert.Equal(t, "ASK", r) assert.Equal(t, 1024, slot) assert.Equal(t, "127.0.0.1:2048", addr) + cmd.reply = newRESPPlain(respError, []byte("ERROR")) + assert.False(t, cmd.IsRedirect()) + _, _, _, err = cmd.RedirectTriple() + assert.Error(t, err) } diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index ef5a2ba5..96d417f4 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -114,7 +114,6 @@ func (pc *proxyConn) mergeCount(msg *proto.Message) (err error) { } func (pc *proxyConn) mergeJoin(msg *proto.Message) (err error) { - // TODO (LINTANGHUI):reuse reply subs := msg.Subs() msg.Write(respArrayBytes) if len(subs) == 0 { @@ -151,5 +150,6 @@ func (pc *proxyConn) encodeError(err error) error { se := errors.Cause(err).Error() pc.rc.bw.Write(respErrorBytes) pc.rc.bw.Write([]byte(se)) - return pc.rc.bw.Write(crlfBytes) + pc.rc.bw.Write(crlfBytes) + return pc.rc.bw.Flush() } diff --git a/proto/redis/proxy_conn_test.go b/proto/redis/proxy_conn_test.go index d494e53c..634c5094 100644 --- a/proto/redis/proxy_conn_test.go +++ b/proto/redis/proxy_conn_test.go @@ -1,6 +1,7 @@ package redis import ( + "fmt" "overlord/proto" "testing" @@ -20,13 +21,19 @@ func TestDecodeBasicOk(t *testing.T) { func TestDecodeComplexOk(t *testing.T) { msgs := proto.GetMsgSlice(16) - data := "*3\r\n$4\r\nMGET\r\n$4\r\nbaka\r\n$4\r\nkaba\r\n" + data := "*3\r\n$4\r\nMGET\r\n$4\r\nbaka\r\n$4\r\nkaba\r\n*5\r\n$4\r\nMSET\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nb\r\n$1\r\nc\r\n*3\r\n$4\r\nMGET\r\n$4\r\nbaka\r\n$4\r\nkaba\r\n" conn := _createConn([]byte(data)) pc := NewProxyConn(conn) - + // test reuse command + msgs[1].WithRequest(NewCommand("get", "a")) + msgs[1].WithRequest(NewCommand("get", "a")) + msgs[1].Reset() + msgs[2].WithRequest(NewCommand("get", "a")) + msgs[2].WithRequest(NewCommand("get", "a")) + msgs[2].Reset() nmsgs, err := pc.Decode(msgs) assert.NoError(t, err) - assert.Len(t, nmsgs, 1) + assert.Len(t, nmsgs, 3) assert.Len(t, nmsgs[0].Batch(), 2) } @@ -102,3 +109,14 @@ func TestEncodeCmdOk(t *testing.T) { } } +func TestEncodeErr(t *testing.T) { + data := make([]byte, 2048) + conn, buf := _createDownStreamConn() + pc := NewProxyConn(conn) + msg := proto.NewMessage() + msg.DoneWithError(fmt.Errorf("ERR msg err")) + err := pc.Encode(msg) + assert.NoError(t, err) + size, _ := buf.Read(data) + assert.Equal(t, "-ERR msg err\r\n", string(data[:size])) +} diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 167c203f..619d5ff8 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -51,6 +51,14 @@ type resp struct { arrayn int } +func (r *resp) reset() { + r.data = r.data[:0] + r.arrayn = 0 + for _, ar := range r.array { + ar.reset() + } +} + func newRESPInt(val int) *resp { s := strconv.Itoa(val) return newRESPPlain(respInt, []byte(s)) diff --git a/proto/redis/resp_conn.go b/proto/redis/resp_conn.go index 0f924ed5..dae4c230 100644 --- a/proto/redis/resp_conn.go +++ b/proto/redis/resp_conn.go @@ -44,11 +44,12 @@ func (rc *respConn) decodeMsg(msgs []*proto.Message) ([]*proto.Message, error) { robj = respPool.Get().(*resp) } else { robj = req.(*Command).respObj + // reset metadata before reuse it. + robj.reset() } err = rc.decodeRESP(robj) if err == bufio.ErrBufferFull { rc.completed = true - err = nil return msgs[:i], nil } else if err != nil { return nil, err diff --git a/proto/redis/resp_conn_test.go b/proto/redis/resp_conn_test.go index 4a3c82eb..64122b44 100644 --- a/proto/redis/resp_conn_test.go +++ b/proto/redis/resp_conn_test.go @@ -67,7 +67,7 @@ func TestDecodeRESP(t *testing.T) { } } -func TestDecodeMaxReachMaxOk(t *testing.T) { +func TestDecodeMsgReachMaxOk(t *testing.T) { line := []byte("*2\r\n$3\r\nget\r\n$1\r\na\r\n*2\r\n$3\r\nget\r\n$1\r\na\r\n") rc := _createRespConn([]byte(line)) msgs := []*proto.Message{proto.NewMessage(), proto.NewMessage()} @@ -79,7 +79,7 @@ func TestDecodeMaxReachMaxOk(t *testing.T) { assert.Equal(t, 2, rs[1].Request().(*Command).respObj.Len()) } -func TestDecodeMaxReachFullOk(t *testing.T) { +func TestDecodeMsgReachFullOk(t *testing.T) { line := []byte("*2\r\n$3\r\nget\r\n$1\r\na\r\n*2\r\n$3\r\nget\r\n$1") rc := _createRespConn([]byte(line)) msgs := []*proto.Message{proto.NewMessage(), proto.NewMessage()} @@ -89,6 +89,23 @@ func TestDecodeMaxReachFullOk(t *testing.T) { assert.Equal(t, respArray, rs[0].Request().(*Command).respObj.rtype) } +func TestDecodeMsgReuseRESP(t *testing.T) { + line := []byte("*2\r\n$3\r\nget\r\n$1\r\na\r\n*2\r\n$3\r\nget\r\n$1\r\na\r\n") + rc := _createRespConn([]byte(line)) + msg0 := proto.NewMessage() + msg0.WithRequest(NewCommand("get", "a")) + msg0.Reset() + msg1 := proto.NewMessage() + msg1.WithRequest(NewCommand("get", "a")) + msg1.Reset() + msgs := []*proto.Message{msg0, msg1} + rs, err := rc.decodeMsg(msgs) + assert.NoError(t, err) + assert.Len(t, rs, 2) + + assert.Equal(t, respArray, rs[0].Request().(*Command).respObj.rtype) + assert.Equal(t, 2, rs[1].Request().(*Command).respObj.Len()) +} func TestDecodeCountOk(t *testing.T) { line := []byte("$1\r\na\r\n+my name is\r\n") rc := _createRespConn([]byte(line)) From 68a8659bf719125c9d185aef89b5efa16fbfeac2 Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Tue, 24 Jul 2018 11:24:15 +0800 Subject: [PATCH 30/87] rename command & del subpres --- proto/memcache/merge.go | 16 ++++++++---- proto/memcache/proxy_conn.go | 3 --- proto/message.go | 23 +++------------- proto/redis/cmd.go | 36 ++++++++++++------------- proto/redis/cmd_test.go | 8 +++--- proto/redis/node_conn.go | 4 +-- proto/redis/node_conn_test.go | 6 ++--- proto/redis/proxy_conn.go | 37 ++++++++++++-------------- proto/redis/proxy_conn_test.go | 12 ++++----- proto/redis/redis.go | 34 ------------------------ proto/redis/resp.go | 48 +++++++++++++++++++++++++++------- proto/redis/resp_conn.go | 2 +- proto/redis/resp_conn_test.go | 14 +++++----- proto/redis/sub.go | 26 +++++++++--------- proto/types.go | 5 ---- 15 files changed, 124 insertions(+), 150 deletions(-) delete mode 100644 proto/redis/redis.go diff --git a/proto/memcache/merge.go b/proto/memcache/merge.go index fd06cde0..8a33e520 100644 --- a/proto/memcache/merge.go +++ b/proto/memcache/merge.go @@ -6,14 +6,18 @@ import ( ) // Merge the response and set the response into subResps -func (*proxyConn) Merge(m *proto.Message) error { +func (p *proxyConn) Merge(m *proto.Message) error { mcr, ok := m.Request().(*MCRequest) if !ok { - m.AddSubResps(serverErrorPrefixBytes, []byte(ErrAssertReq.Error()), crlfBytes) + p.bw.Write(serverErrorPrefixBytes) + p.bw.Write([]byte(ErrAssertReq.Error())) + p.bw.Write(crlfBytes) + // m.AddSubResps(serverErrorPrefixBytes, []byte(ErrAssertReq.Error()), crlfBytes) return nil } if !m.IsBatch() { - m.AddSubResps(mcr.data) + p.bw.Write(mcr.data) + // m.AddSubResps(mcr.data) return nil } @@ -28,10 +32,12 @@ func (*proxyConn) Merge(m *proto.Message) error { if len(bs) == 0 { continue } - m.AddSubResps(bs) + p.bw.Write(bs) + // m.AddSubResps(bs) } if trimEnd { - m.AddSubResps(endBytes) + p.bw.Write(endBytes) + // m.AddSubResps(endBytes) } return nil } diff --git a/proto/memcache/proxy_conn.go b/proto/memcache/proxy_conn.go index 9ed83597..adc19e44 100644 --- a/proto/memcache/proxy_conn.go +++ b/proto/memcache/proxy_conn.go @@ -351,9 +351,6 @@ func (p *proxyConn) Encode(m *proto.Message) (err error) { _ = p.bw.Write(crlfBytes) } else { _ = p.Merge(m) - for _, item := range m.SubResps() { - _ = p.bw.Write(item) - } } if err = p.bw.Flush(); err != nil { err = errors.Wrap(err, "MC Encoder encode response flush bytes") diff --git a/proto/message.go b/proto/message.go index 10387e4f..2c02ba7c 100644 --- a/proto/message.go +++ b/proto/message.go @@ -48,10 +48,9 @@ func PutMsg(msg *Message) { type Message struct { Type CacheType - req []Request - reqn int - subs []*Message - subResps [][]byte + req []Request + reqn int + subs []*Message // Start Time, Write Time, ReadTime, EndTime st, wt, rt, et time.Time err error @@ -69,7 +68,6 @@ func (m *Message) Reset() { m.reqn = 0 m.st, m.wt, m.rt, m.et = defaultTime, defaultTime, defaultTime, defaultTime m.err = nil - m.subResps = m.subResps[:0] } // Clear will clean the msg @@ -195,26 +193,11 @@ func (m *Message) Batch() []*Message { return m.subs[:slen] } -// AddSubResps appends all items into subResps -func (m *Message) AddSubResps(items ...[]byte) { - m.subResps = append(m.subResps, items...) -} - -func (m *Message) Write(p []byte) error { - m.AddSubResps(p) - return nil -} - // Subs returns all the sub messages. func (m *Message) Subs() []*Message { return m.subs[:m.reqn] } -// SubResps returns the response bytes buffer. -func (m *Message) SubResps() [][]byte { - return m.subResps -} - // Err returns error. func (m *Message) Err() error { return m.err diff --git a/proto/redis/cmd.go b/proto/redis/cmd.go index 0b8b3fbe..ba023344 100644 --- a/proto/redis/cmd.go +++ b/proto/redis/cmd.go @@ -42,18 +42,18 @@ const ( SlotShiled = 0x3fff ) -// Command is the type of a complete redis command -type Command struct { +// Request is the type of a complete redis command +type Request struct { respObj *resp mergeType MergeType reply *resp } -// NewCommand will create new command by given args +// NewRequest will create new command by given args // example: -// NewCommand("GET", "mykey") -// NewCommand("CLUSTER", "NODES") -func NewCommand(cmd string, args ...string) *Command { +// NewRequest("GET", "mykey") +// NewRequest("CLUSTER", "NODES") +func NewRequest(cmd string, args ...string) *Request { respObj := respPool.Get().(*resp) respObj.next().setBulk([]byte(cmd)) // respObj := newRESPArrayWithCapcity(len(args) + 1) @@ -65,38 +65,38 @@ func NewCommand(cmd string, args ...string) *Command { respObj.next().setBulk([]byte(line)) } respObj.data = []byte(strconv.Itoa(len(args) + 1)) - return newCommand(respObj) + return newRequest(respObj) } -func newCommand(robj *resp) *Command { - r := &Command{respObj: robj} +func newRequest(robj *resp) *Request { + r := &Request{respObj: robj} r.mergeType = getMergeType(robj.nth(0).data) r.reply = &resp{} return r } -func (c *Command) setRESP(robj *resp) { +func (c *Request) setRESP(robj *resp) { c.respObj = robj c.mergeType = getMergeType(robj.nth(0).data) c.reply = &resp{} } -func newCommandWithMergeType(robj *resp, mtype MergeType) *Command { - return &Command{respObj: robj, mergeType: mtype} +func newRequestWithMergeType(robj *resp, mtype MergeType) *Request { + return &Request{respObj: robj, mergeType: mtype} } // CmdString get the cmd -func (c *Command) CmdString() string { +func (c *Request) CmdString() string { return strings.ToUpper(string(c.respObj.nth(0).data)) } // Cmd get the cmd -func (c *Command) Cmd() []byte { +func (c *Request) Cmd() []byte { return c.respObj.nth(0).data } // Key impl the proto.protoRequest and get the Key of redis -func (c *Command) Key() []byte { +func (c *Request) Key() []byte { var data = c.respObj.nth(1).data var pos int if c.respObj.nth(1).rtype == respBulk { @@ -107,12 +107,12 @@ func (c *Command) Key() []byte { } // Put the resource back to pool -func (c *Command) Put() { +func (c *Request) Put() { } // IsRedirect check if response type is Redis Error // and payload was prefix with "ASK" && "MOVED" -func (c *Command) IsRedirect() bool { +func (c *Request) IsRedirect() bool { if c.reply.rtype != respError { return false } @@ -129,7 +129,7 @@ func (c *Command) IsRedirect() bool { // second is the slot of redirect // third is the redirect addr // last is the error when parse the redirect body -func (c *Command) RedirectTriple() (redirect string, slot int, addr string, err error) { +func (c *Request) RedirectTriple() (redirect string, slot int, addr string, err error) { fields := strings.Fields(string(c.reply.data)) if len(fields) != 3 { err = ErrRedirectBadFormat diff --git a/proto/redis/cmd_test.go b/proto/redis/cmd_test.go index b45b3933..6aec2aea 100644 --- a/proto/redis/cmd_test.go +++ b/proto/redis/cmd_test.go @@ -6,8 +6,8 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCommandNewCommand(t *testing.T) { - cmd := NewCommand("GET", "a") +func TestRequestNewRequest(t *testing.T) { + cmd := NewRequest("GET", "a") assert.Equal(t, MergeTypeBasic, cmd.mergeType) assert.Equal(t, 2, cmd.respObj.Len()) @@ -16,8 +16,8 @@ func TestCommandNewCommand(t *testing.T) { assert.Equal(t, "a", string(cmd.Key())) } -func TestCommandRedirect(t *testing.T) { - cmd := NewCommand("GET", "BAKA") +func TestRequestRedirect(t *testing.T) { + cmd := NewRequest("GET", "BAKA") cmd.reply = newRESPPlain(respError, []byte("ASK 1024 127.0.0.1:2048")) assert.True(t, cmd.IsRedirect()) r, slot, addr, err := cmd.RedirectTriple() diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index 558a4d74..30e31e3f 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -52,7 +52,7 @@ func (nc *nodeConn) WriteBatch(mb *proto.MsgBatch) error { } func (nc *nodeConn) write(m *proto.Message) error { - cmd, ok := m.Request().(*Command) + cmd, ok := m.Request().(*Request) if !ok { m.DoneWithError(ErrBadAssert) return ErrBadAssert @@ -64,7 +64,7 @@ func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) (err error) { nc.rc.br.ResetBuffer(mb.Buffer()) defer nc.rc.br.ResetBuffer(nil) for _, msg := range mb.Msgs() { - cmd, ok := msg.Request().(*Command) + cmd, ok := msg.Request().(*Request) if !ok { return ErrBadAssert } diff --git a/proto/redis/node_conn_test.go b/proto/redis/node_conn_test.go index 98d48f7a..1cdd34d3 100644 --- a/proto/redis/node_conn_test.go +++ b/proto/redis/node_conn_test.go @@ -30,7 +30,7 @@ func TestNodeConnWriteBatchOk(t *testing.T) { nc := newNodeConn("baka", "127.0.0.1:12345", _createConn(nil)) mb := proto.NewMsgBatch() msg := proto.GetMsg() - msg.WithRequest(NewCommand("GET", "A")) + msg.WithRequest(NewRequest("GET", "A")) mb.AddMsg(msg) err := nc.WriteBatch(mb) assert.NoError(t, err) @@ -53,7 +53,7 @@ func TestReadBatchOk(t *testing.T) { nc := newNodeConn("baka", "127.0.0.1:12345", _createConn([]byte(data))) mb := proto.NewMsgBatch() msg := proto.GetMsg() - msg.WithRequest(NewCommand("SET", "baka", "miao")) + msg.WithRequest(NewRequest("SET", "baka", "miao")) mb.AddMsg(msg) err := nc.ReadBatch(mb) assert.NoError(t, err) @@ -74,7 +74,7 @@ func TestReadBatchWithNilError(t *testing.T) { nc := newNodeConn("baka", "127.0.0.1:12345", _createConn(nil)) mb := proto.NewMsgBatch() msg := proto.GetMsg() - msg.WithRequest(&Command{}) + msg.WithRequest(&Request{}) mb.AddMsg(msg) err := nc.ReadBatch(mb) assert.Error(t, err) diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 96d417f4..16869c05 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -40,9 +40,6 @@ func (pc *proxyConn) Encode(msg *proto.Message) (err error) { if err != nil { return pc.encodeError(err) } - for _, item := range msg.SubResps() { - pc.rc.bw.Write(item) - } if err = pc.rc.Flush(); err != nil { err = errors.Wrap(err, "Redis Encoder flush response") } @@ -50,12 +47,12 @@ func (pc *proxyConn) Encode(msg *proto.Message) (err error) { } func (pc *proxyConn) merge(msg *proto.Message) error { - cmd, ok := msg.Request().(*Command) + cmd, ok := msg.Request().(*Request) if !ok { return ErrBadAssert } if !msg.IsBatch() { - return cmd.reply.encode(msg) + return cmd.reply.encode(pc.rc.bw) } mtype, err := pc.getBatchMergeType(msg) if err != nil { @@ -78,16 +75,16 @@ func (pc *proxyConn) merge(msg *proto.Message) error { func (pc *proxyConn) mergeOk(msg *proto.Message) (err error) { for _, sub := range msg.Subs() { if err = sub.Err(); err != nil { - cmd := sub.Request().(*Command) + cmd := sub.Request().(*Request) msg.Write(respErrorBytes) msg.Write(cmd.reply.data) msg.Write(crlfBytes) return } } - msg.Write(respStringBytes) - msg.Write(okBytes) - msg.Write(crlfBytes) + pc.rc.bw.Write(respStringBytes) + pc.rc.bw.Write(okBytes) + pc.rc.bw.Write(crlfBytes) return } @@ -97,7 +94,7 @@ func (pc *proxyConn) mergeCount(msg *proto.Message) (err error) { if sub.Err() != nil { continue } - subcmd, ok := sub.Request().(*Command) + subcmd, ok := sub.Request().(*Request) if !ok { return ErrBadAssert } @@ -107,23 +104,23 @@ func (pc *proxyConn) mergeCount(msg *proto.Message) (err error) { } sum += int(ival) } - msg.Write(respIntBytes) - msg.Write([]byte(strconv.Itoa(sum))) - msg.Write(crlfBytes) + pc.rc.bw.Write(respIntBytes) + pc.rc.bw.Write([]byte(strconv.Itoa(sum))) + pc.rc.bw.Write(crlfBytes) return } func (pc *proxyConn) mergeJoin(msg *proto.Message) (err error) { subs := msg.Subs() - msg.Write(respArrayBytes) + pc.rc.bw.Write(respArrayBytes) if len(subs) == 0 { - msg.Write(respNullBytes) + pc.rc.bw.Write(respNullBytes) return } - msg.Write([]byte(strconv.Itoa(len(subs)))) - msg.Write(crlfBytes) + pc.rc.bw.Write([]byte(strconv.Itoa(len(subs)))) + pc.rc.bw.Write(crlfBytes) for _, sub := range subs { - subcmd, ok := sub.Request().(*Command) + subcmd, ok := sub.Request().(*Request) if !ok { err = pc.encodeError(ErrBadAssert) if err != nil { @@ -131,13 +128,13 @@ func (pc *proxyConn) mergeJoin(msg *proto.Message) (err error) { } return ErrBadAssert } - subcmd.reply.encode(msg) + subcmd.reply.encode(pc.rc.bw) } return } func (pc *proxyConn) getBatchMergeType(msg *proto.Message) (mtype MergeType, err error) { - cmd, ok := msg.Subs()[0].Request().(*Command) + cmd, ok := msg.Subs()[0].Request().(*Request) if !ok { err = ErrBadAssert return diff --git a/proto/redis/proxy_conn_test.go b/proto/redis/proxy_conn_test.go index 634c5094..328a39c3 100644 --- a/proto/redis/proxy_conn_test.go +++ b/proto/redis/proxy_conn_test.go @@ -25,11 +25,11 @@ func TestDecodeComplexOk(t *testing.T) { conn := _createConn([]byte(data)) pc := NewProxyConn(conn) // test reuse command - msgs[1].WithRequest(NewCommand("get", "a")) - msgs[1].WithRequest(NewCommand("get", "a")) + msgs[1].WithRequest(NewRequest("get", "a")) + msgs[1].WithRequest(NewRequest("get", "a")) msgs[1].Reset() - msgs[2].WithRequest(NewCommand("get", "a")) - msgs[2].WithRequest(NewCommand("get", "a")) + msgs[2].WithRequest(NewRequest("get", "a")) + msgs[2].WithRequest(NewRequest("get", "a")) msgs[2].Reset() nmsgs, err := pc.Decode(msgs) assert.NoError(t, err) @@ -86,13 +86,13 @@ func TestEncodeCmdOk(t *testing.T) { err := newSubCmd(msg, co) if assert.NoError(t, err) { for i, req := range msg.Requests() { - cmd := req.(*Command) + cmd := req.(*Request) cmd.reply = rs[i] } msg.Batch() } } else { - cmd := newCommand(co) + cmd := newRequest(co) cmd.reply = rs[0] msg.WithRequest(cmd) } diff --git a/proto/redis/redis.go b/proto/redis/redis.go deleted file mode 100644 index 796d1a05..00000000 --- a/proto/redis/redis.go +++ /dev/null @@ -1,34 +0,0 @@ -package redis - -import ( - "bytes" -) - -// MergeType is used to decript the merge operation. -type MergeType = uint8 - -// merge types -const ( - MergeTypeCount MergeType = iota - MergeTypeOk - MergeTypeJoin - MergeTypeBasic -) - -func getMergeType(cmd []byte) MergeType { - // fmt.Println("mtype :", strconv.Quote(string(cmd))) - // TODO: impl with tire tree to search quickly - if bytes.Equal(cmd, cmdMGetBytes) || bytes.Equal(cmd, cmdGetBytes) { - return MergeTypeJoin - } - - if bytes.Equal(cmd, cmdMSetBytes) { - return MergeTypeOk - } - - if bytes.Equal(cmd, cmdExistsBytes) || bytes.Equal(cmd, cmdDelBytes) { - return MergeTypeCount - } - - return MergeTypeBasic -} diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 619d5ff8..254fcff6 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -2,6 +2,7 @@ package redis import ( "bytes" + "overlord/lib/bufio" "overlord/proto" "strconv" "strings" @@ -196,7 +197,7 @@ func (r *resp) bytes() []byte { return data[pos:] } -func (r *resp) encode(w proto.Writer) error { +func (r *resp) encode(w *bufio.Writer) error { switch r.rtype { case respInt: return r.encodeInt(w) @@ -212,20 +213,20 @@ func (r *resp) encode(w proto.Writer) error { return nil } -func (r *resp) encodeError(w proto.Writer) (err error) { +func (r *resp) encodeError(w *bufio.Writer) (err error) { return r.encodePlain(respErrorBytes, w) } -func (r *resp) encodeInt(w proto.Writer) (err error) { +func (r *resp) encodeInt(w *bufio.Writer) (err error) { return r.encodePlain(respIntBytes, w) } -func (r *resp) encodeString(w proto.Writer) (err error) { +func (r *resp) encodeString(w *bufio.Writer) (err error) { return r.encodePlain(respStringBytes, w) } -func (r *resp) encodePlain(rtypeBytes []byte, w proto.Writer) (err error) { +func (r *resp) encodePlain(rtypeBytes []byte, w *bufio.Writer) (err error) { err = w.Write(rtypeBytes) if err != nil { return @@ -238,7 +239,7 @@ func (r *resp) encodePlain(rtypeBytes []byte, w proto.Writer) (err error) { return } -func (r *resp) encodeBulk(w proto.Writer) (err error) { +func (r *resp) encodeBulk(w *bufio.Writer) (err error) { // NOTICE: we need not to convert robj.Len() as int // due number has been writen into data err = w.Write(respBulkBytes) @@ -258,7 +259,7 @@ func (r *resp) encodeBulk(w proto.Writer) (err error) { return } -func (r *resp) encodeArray(w proto.Writer) (err error) { +func (r *resp) encodeArray(w *bufio.Writer) (err error) { err = w.Write(respArrayBytes) if err != nil { return @@ -299,9 +300,38 @@ func (r *resp) decode(msg *proto.Message) (err error) { func withReq(m *proto.Message, robj *resp) { req := m.NextReq() if req == nil { - m.WithRequest(newCommand(robj)) + m.WithRequest(newRequest(robj)) } else { - reqCmd := req.(*Command) + reqCmd := req.(*Request) reqCmd.setRESP(robj) } } + +// MergeType is used to decript the merge operation. +type MergeType = uint8 + +// merge types +const ( + MergeTypeCount MergeType = iota + MergeTypeOk + MergeTypeJoin + MergeTypeBasic +) + +func getMergeType(cmd []byte) MergeType { + // fmt.Println("mtype :", strconv.Quote(string(cmd))) + // TODO: impl with tire tree to search quickly + if bytes.Equal(cmd, cmdMGetBytes) || bytes.Equal(cmd, cmdGetBytes) { + return MergeTypeJoin + } + + if bytes.Equal(cmd, cmdMSetBytes) { + return MergeTypeOk + } + + if bytes.Equal(cmd, cmdExistsBytes) || bytes.Equal(cmd, cmdDelBytes) { + return MergeTypeCount + } + + return MergeTypeBasic +} diff --git a/proto/redis/resp_conn.go b/proto/redis/resp_conn.go index dae4c230..05a0887e 100644 --- a/proto/redis/resp_conn.go +++ b/proto/redis/resp_conn.go @@ -43,7 +43,7 @@ func (rc *respConn) decodeMsg(msgs []*proto.Message) ([]*proto.Message, error) { if req == nil { robj = respPool.Get().(*resp) } else { - robj = req.(*Command).respObj + robj = req.(*Request).respObj // reset metadata before reuse it. robj.reset() } diff --git a/proto/redis/resp_conn_test.go b/proto/redis/resp_conn_test.go index 64122b44..18e8980f 100644 --- a/proto/redis/resp_conn_test.go +++ b/proto/redis/resp_conn_test.go @@ -75,8 +75,8 @@ func TestDecodeMsgReachMaxOk(t *testing.T) { assert.NoError(t, err) assert.Len(t, rs, 2) - assert.Equal(t, respArray, rs[0].Request().(*Command).respObj.rtype) - assert.Equal(t, 2, rs[1].Request().(*Command).respObj.Len()) + assert.Equal(t, respArray, rs[0].Request().(*Request).respObj.rtype) + assert.Equal(t, 2, rs[1].Request().(*Request).respObj.Len()) } func TestDecodeMsgReachFullOk(t *testing.T) { @@ -86,25 +86,25 @@ func TestDecodeMsgReachFullOk(t *testing.T) { rs, err := rc.decodeMsg(msgs) assert.NoError(t, err) assert.Len(t, rs, 1) - assert.Equal(t, respArray, rs[0].Request().(*Command).respObj.rtype) + assert.Equal(t, respArray, rs[0].Request().(*Request).respObj.rtype) } func TestDecodeMsgReuseRESP(t *testing.T) { line := []byte("*2\r\n$3\r\nget\r\n$1\r\na\r\n*2\r\n$3\r\nget\r\n$1\r\na\r\n") rc := _createRespConn([]byte(line)) msg0 := proto.NewMessage() - msg0.WithRequest(NewCommand("get", "a")) + msg0.WithRequest(NewRequest("get", "a")) msg0.Reset() msg1 := proto.NewMessage() - msg1.WithRequest(NewCommand("get", "a")) + msg1.WithRequest(NewRequest("get", "a")) msg1.Reset() msgs := []*proto.Message{msg0, msg1} rs, err := rc.decodeMsg(msgs) assert.NoError(t, err) assert.Len(t, rs, 2) - assert.Equal(t, respArray, rs[0].Request().(*Command).respObj.rtype) - assert.Equal(t, 2, rs[1].Request().(*Command).respObj.Len()) + assert.Equal(t, respArray, rs[0].Request().(*Request).respObj.rtype) + assert.Equal(t, 2, rs[1].Request().(*Request).respObj.Len()) } func TestDecodeCountOk(t *testing.T) { line := []byte("$1\r\na\r\n+my name is\r\n") diff --git a/proto/redis/sub.go b/proto/redis/sub.go index 53d5eef7..80b11154 100644 --- a/proto/redis/sub.go +++ b/proto/redis/sub.go @@ -13,7 +13,7 @@ const ( // errors var ( - ErrBadCommandSize = errors.New("wrong Mset arguments number") + ErrBadRequestSize = errors.New("wrong Mset arguments number") ) func isEven(v int) bool { @@ -34,13 +34,13 @@ func newSubCmd(msg *proto.Message, robj *resp) error { return cmdByKeys(msg, robj) } -// func subCmdMset(robj *resp) ([]*Command, error) { +// func subCmdMset(robj *resp) ([]*Request, error) { // if !isEven(robj.Len() - 1) { -// return nil, ErrBadCommandSize +// return nil, ErrBadRequestSize // } // mid := robj.Len() / 2 -// cmds := make([]*Command, mid) +// cmds := make([]*Request, mid) // for i := 0; i < mid; i++ { // cmdObj := respPool.Get().(*resp) // cmdObj.rtype = respArray @@ -49,14 +49,14 @@ func newSubCmd(msg *proto.Message, robj *resp) error { // cmdObj.replace(1, robj.nth(i*2+1)) // cmdObj.replace(2, robj.nth(i*2+2)) // cmdObj.data = cmdMSetLenBytes -// cmds[i] = newCommandWithMergeType(cmdObj, MergeTypeOk) +// cmds[i] = newRequestWithMergeType(cmdObj, MergeTypeOk) // } // return cmds, nil // } func cmdMset(msg *proto.Message, robj *resp) error { if !isEven(robj.Len() - 1) { - return ErrBadCommandSize + return ErrBadRequestSize } mid := robj.Len() / 2 for i := 0; i < mid; i++ { @@ -69,10 +69,10 @@ func cmdMset(msg *proto.Message, robj *resp) error { cmdObj.replace(1, robj.nth(i*2+1)) cmdObj.replace(2, robj.nth(i*2+2)) cmdObj.data = cmdMSetLenBytes - cmd := newCommandWithMergeType(cmdObj, MergeTypeOk) + cmd := newRequestWithMergeType(cmdObj, MergeTypeOk) msg.WithRequest(cmd) } else { - reqCmd := req.(*Command) + reqCmd := req.(*Request) reqCmd.mergeType = MergeTypeOk cmdObj := reqCmd.respObj cmdObj.rtype = respArray @@ -86,8 +86,8 @@ func cmdMset(msg *proto.Message, robj *resp) error { return nil } -// func subCmdByKeys(robj *resp) ([]*Command, error) { -// cmds := make([]*Command, robj.Len()-1) +// func subCmdByKeys(robj *resp) ([]*Request, error) { +// cmds := make([]*Request, robj.Len()-1) // for i, sub := range robj.slice()[1:] { // var cmdObj *resp // if bytes.Equal(robj.nth(0).data, cmdMGetBytes) { @@ -95,7 +95,7 @@ func cmdMset(msg *proto.Message, robj *resp) error { // } else { // cmdObj = newRESPArray([]*resp{robj.nth(0), sub}) // } -// cmds[i] = newCommand(cmdObj) +// cmds[i] = newRequest(cmdObj) // } // return cmds, nil // } @@ -110,10 +110,10 @@ func cmdByKeys(msg *proto.Message, robj *resp) error { } else { cmdObj = newRESPArray([]*resp{robj.nth(0), sub}) } - cmd := newCommand(cmdObj) + cmd := newRequest(cmdObj) msg.WithRequest(cmd) } else { - reqCmd := req.(*Command) + reqCmd := req.(*Request) cmdObj := reqCmd.respObj if bytes.Equal(robj.nth(0).data, cmdMGetBytes) { cmdObj.setArray([]*resp{robjGet, sub}) diff --git a/proto/types.go b/proto/types.go index 85b4669d..3dd11bf6 100644 --- a/proto/types.go +++ b/proto/types.go @@ -44,8 +44,3 @@ type NodeConn interface { FetchSlots() (nodes []string, slots [][]int, err error) } - -// Writer writer without err. -type Writer interface { - Write([]byte) error -} From d3d7ffc9ce89a0564ef25e7d11143d23bae5f2d9 Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Tue, 24 Jul 2018 11:32:56 +0800 Subject: [PATCH 31/87] del subresp --- proto/redis/proxy_conn.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 16869c05..5f5ea9be 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -76,9 +76,9 @@ func (pc *proxyConn) mergeOk(msg *proto.Message) (err error) { for _, sub := range msg.Subs() { if err = sub.Err(); err != nil { cmd := sub.Request().(*Request) - msg.Write(respErrorBytes) - msg.Write(cmd.reply.data) - msg.Write(crlfBytes) + pc.rc.bw.Write(respErrorBytes) + pc.rc.bw.Write(cmd.reply.data) + pc.rc.bw.Write(crlfBytes) return } } From 69d39dbeb32050f833b3715e4f3dc5ba6026628a Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Tue, 24 Jul 2018 14:40:04 +0800 Subject: [PATCH 32/87] del redis cluster --- cmd/proxy/proxy-cluster-example.toml | 36 +++- lib/hashkit/crc16.go | 48 ----- lib/hashkit/crc16_test.go | 12 -- lib/hashkit/hash.go | 23 +-- lib/hashkit/rediscluster.go | 126 ------------ lib/hashkit/rediscluster_test.go | 75 ------- proto/redis/cmd.go | 145 ------------- proto/redis/cmd_test.go | 32 --- proto/redis/node_conn.go | 40 +--- proto/redis/node_conn_test.go | 2 +- proto/redis/resp.go | 3 +- proto/redis/slots.go | 291 --------------------------- proto/redis/slots_test.go | 160 --------------- proto/types.go | 2 - proxy/cluster.go | 15 +- 15 files changed, 48 insertions(+), 962 deletions(-) delete mode 100644 lib/hashkit/crc16.go delete mode 100644 lib/hashkit/crc16_test.go delete mode 100644 lib/hashkit/rediscluster.go delete mode 100644 lib/hashkit/rediscluster_test.go delete mode 100644 proto/redis/cmd.go delete mode 100644 proto/redis/cmd_test.go delete mode 100644 proto/redis/slots.go delete mode 100644 proto/redis/slots_test.go diff --git a/cmd/proxy/proxy-cluster-example.toml b/cmd/proxy/proxy-cluster-example.toml index 28e318cb..59fbc04d 100644 --- a/cmd/proxy/proxy-cluster-example.toml +++ b/cmd/proxy/proxy-cluster-example.toml @@ -7,7 +7,7 @@ hash_method = "fnv1a_64" hash_distribution = "ketama" # A two character string that specifies the part of the key used for hashing. Eg "{}". hash_tag = "" -# cache type: memcache | redis +# cache type: memcache | memcache_binary |redis cache_type = "memcache" # proxy listen proto: tcp | unix listen_proto = "tcp" @@ -31,3 +31,37 @@ ping_auto_eject = true servers = [ "127.0.0.1:11211:1", ] + +[[clusters]] +# This be used to specify the name of cache cluster. +name = "redis" +# The name of the hash function. Possible values are: sha1. +hash_method = "fnv1a_64" +# The key distribution mode. Possible values are: ketama. +hash_distribution = "ketama" +# A two character string that specifies the part of the key used for hashing. Eg "{}". +hash_tag = "" +# cache type: memcache | redis +cache_type = "redis" +# proxy listen proto: tcp | unix +listen_proto = "tcp" +# proxy listen addr: tcp addr | unix sock path +listen_addr = "0.0.0.0:22211" +# Authenticate to the Redis server on connect. +redis_auth = "" +# The dial timeout value in msec that we wait for to establish a connection to the server. By default, we wait indefinitely. +dial_timeout = 1000 +# The read timeout value in msec that we wait for to receive a response from a server. By default, we wait indefinitely. +read_timeout = 1000 +# The write timeout value in msec that we wait for to write a response to a server. By default, we wait indefinitely. +write_timeout = 1000 +# The number of connections that can be opened to each server. By default, we open at most 1 server connection. +node_connections = 2 +# The number of consecutive failures on a server that would lead to it being temporarily ejected when auto_eject is set to true. Defaults to 3. +ping_fail_limit = 3 +# A boolean value that controls if server should be ejected temporarily when it fails consecutively ping_fail_limit times. +ping_auto_eject = true +# A list of server address, port and weight (name:port:weight or ip:port:weight) for this server pool. Also you can use alias name like: ip:port:weight alias. +servers = [ + "127.0.0.1:6379:1", +] \ No newline at end of file diff --git a/lib/hashkit/crc16.go b/lib/hashkit/crc16.go deleted file mode 100644 index 28efc5ff..00000000 --- a/lib/hashkit/crc16.go +++ /dev/null @@ -1,48 +0,0 @@ -package hashkit - -// CRC16 implementation according to CCITT standards. -// Copyright 2001-2010 Georges Menie (www.menie.org) -// Copyright 2013 The Go Authors. All rights reserved. -// http://redis.io/topics/cluster-spec#appendix-a-crc16-reference-implementation-in-ansi-c -var crc16tab = [256]uint16{ - 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, - 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, - 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, - 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, - 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, - 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, - 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, - 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, - 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, - 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, - 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, - 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, - 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, - 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, - 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, - 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, - 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, - 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, - 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, - 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, - 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, - 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, - 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, - 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, - 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, - 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, - 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, - 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, - 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, - 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, - 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, - 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, -} - -// Crc16 caculate key crc to divide slot of redis -func Crc16(key []byte) (crc uint16) { - for i := 0; i < len(key); i++ { - crc = (crc << 8) ^ crc16tab[(byte(crc>>8)^key[i])&0x00ff] - } - return -} diff --git a/lib/hashkit/crc16_test.go b/lib/hashkit/crc16_test.go deleted file mode 100644 index 1927b0f7..00000000 --- a/lib/hashkit/crc16_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package hashkit - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCrcCheckOk(t *testing.T) { - assert.Equal(t, uint16(0x31C3), Crc16([]byte("123456789"))) - assert.Equal(t, uint16(21847), Crc16([]byte{83, 153, 134, 118, 229, 214, 244, 75, 140, 37, 215, 215})) -} diff --git a/lib/hashkit/hash.go b/lib/hashkit/hash.go index 756ea6b1..6fbd2e7d 100644 --- a/lib/hashkit/hash.go +++ b/lib/hashkit/hash.go @@ -5,29 +5,8 @@ const ( HashMethodFnv1a = "fnv1a_64" ) -// Ring is a abstraction -type Ring interface { - // AddNode will add node into this ring - // if the ring is ketma hash args contains only one. - AddNode(node string, args ...int) - - // DelNode will remove node. - DelNode(node string) - - // UpdateSlot will update single one slot due to redis move/ask - UpdateSlot(node string, slot int) - - GetNode(key []byte) (string, bool) - - Init(masters []string, slots ...[]int) -} - // NewRing will create new and need init method. -func NewRing(des, method string) Ring { - if des == "redis_cluster" { - return newRedisClusterRing() - } - +func NewRing(des, method string) *HashRing { var hash func([]byte) uint switch method { case HashMethodFnv1a: diff --git a/lib/hashkit/rediscluster.go b/lib/hashkit/rediscluster.go deleted file mode 100644 index 7b77e635..00000000 --- a/lib/hashkit/rediscluster.go +++ /dev/null @@ -1,126 +0,0 @@ -package hashkit - -import ( - "sync" -) - -const musk = 0x3fff - -// slotsMap is the struct of redis clusters slotsMap -type slotsMap struct { - // slaves/searchIndex has the same length of masters - masters []string - searchIndex [][]int - - slots []int - - lock sync.Mutex -} - -// newRedisClusterRing will create new redis clusters. -func newRedisClusterRing() Ring { - s := &slotsMap{ - slots: make([]int, musk+1), - lock: sync.Mutex{}, - } - // s.Init(masters, slots...) - return s -} - -func (s *slotsMap) Init(masters []string, args ...[]int) { - s.lock.Lock() - s.masters = make([]string, len(masters)) - s.searchIndex = make([][]int, len(masters)) - copy(s.masters, masters) - for i := range s.masters { - for _, slot := range args[i] { - s.slots[slot] = i - } - s.searchIndex[i] = args[i] - } - s.lock.Unlock() -} - -func (s *slotsMap) find(n string) int { - return findStringSlice(s.masters, n) -} - -// AddNode will add node into this ring -// if the ring is ketma hash args contains only one. -func (s *slotsMap) AddNode(node string, args ...int) { - s.lock.Lock() - idx := s.find(node) - if idx == -1 { - idx = len(s.masters) - s.masters = append(s.masters, node) - s.searchIndex = append(s.searchIndex, make([]int, 0)) - } - - for _, slot := range args { - // TODO: how to remove oriVal search index quickly - // If not, duplicate index will occur only when get with ordered add. - // oriVal := s.slots[slot] - // if oriVal != -1 { - // si := s.searchIndex[oriVal] - // } - s.slots[slot] = idx - s.searchIndex[idx] = append(s.searchIndex[idx], slot) - } - s.lock.Unlock() -} - -// DelNode will remove node. -func (s *slotsMap) DelNode(node string) { - s.lock.Lock() - idx := s.find(node) - if idx == -1 { - s.lock.Unlock() - return - } - - for i, ival := range s.slots { - if ival == idx { - s.slots[i] = -1 - } - } - s.searchIndex[idx] = s.searchIndex[idx][:0] - s.lock.Unlock() -} - -// UpdateSlot will update single one slot due to redis move/ask -func (s *slotsMap) UpdateSlot(node string, slot int) { - s.lock.Lock() - idx := s.find(node) - - if idx == -1 { - idx = len(s.masters) - s.masters = append(s.masters, node) - } - - s.slots[slot] = idx -} - -func (s *slotsMap) Hash(key []byte) int { - return int(Crc16(key) & musk) -} - -func (s *slotsMap) GetNode(key []byte) (string, bool) { - s.lock.Lock() - defer s.lock.Unlock() - slot := Crc16(key) & musk - idx := s.slots[slot] - if idx == -1 { - return "", false - } - - return s.masters[idx], true -} - -func findStringSlice(slice []string, item string) int { - for i := range slice { - if slice[i] == item { - return i - } - } - return -1 -} diff --git a/lib/hashkit/rediscluster_test.go b/lib/hashkit/rediscluster_test.go deleted file mode 100644 index a1e1016e..00000000 --- a/lib/hashkit/rediscluster_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package hashkit - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func _createSlotsMap(n int) *slotsMap { - r := newRedisClusterRing() - addrs := make([]string, n) - for i := 0; i < n; i++ { - addrs[i] = fmt.Sprintf("127.0.0.1:70%02d", i) - } - rg := (musk + 1) / n - slots := make([][]int, n) - for i := 0; i < n; i++ { - iSlots := make([]int, 0) - start, end := i*rg, (i+1)*rg - for j := start; j < end; j++ { - iSlots = append(iSlots, j) - } - slots[i] = iSlots - } - for i := n * rg; i < musk+1; i++ { - slots[n-1] = append(slots[n-1], i) - } - // fmt.Println(addrs, slots) - r.Init(addrs, slots...) - return r.(*slotsMap) -} - -func TestSlotsMapAddNodeOk(t *testing.T) { - sm := _createSlotsMap(10) - sm.AddNode("127.0.0.2:7000", []int{0, 1, 2}...) - // the 11th - assert.Equal(t, "127.0.0.2:7000", sm.masters[10]) - assert.Equal(t, 10, sm.slots[0]) - assert.Equal(t, 10, sm.slots[1]) - assert.Equal(t, 10, sm.slots[2]) -} - -func TestSlotsMapDelNodeOk(t *testing.T) { - sm := _createSlotsMap(10) - sm.DelNode("127.0.0.1:7000") - // the 11th - assert.Equal(t, "127.0.0.1:7000", sm.masters[0]) - assert.Equal(t, -1, sm.slots[0]) - assert.Len(t, sm.searchIndex[0], 0) -} - -func TestSlotsMapUpdateSlot(t *testing.T) { - sm := _createSlotsMap(10) - sm.UpdateSlot("127.0.0.2:7000", 1) - - assert.Equal(t, "127.0.0.2:7000", sm.masters[10]) - assert.Equal(t, 10, sm.slots[1]) -} - -func TestSlotsMapHashOk(t *testing.T) { - sm := _createSlotsMap(10) - crc := sm.Hash([]byte("abcdefg")) - // assert.True(t, ok) - assert.Equal(t, 13912, crc) -} - -func TestGetNodeOk(t *testing.T) { - sm := _createSlotsMap(10) - // t.Log(sm.masters) - // t.Log(sm.slots) - node, ok := sm.GetNode([]byte("abcdefg")) - assert.True(t, ok) - assert.Equal(t, "127.0.0.1:7008", node) -} diff --git a/proto/redis/cmd.go b/proto/redis/cmd.go deleted file mode 100644 index ba023344..00000000 --- a/proto/redis/cmd.go +++ /dev/null @@ -1,145 +0,0 @@ -package redis - -import ( - "errors" - "fmt" - "strconv" - "strings" - - // "crc" - "bytes" -) - -var ( - crlfBytes = []byte{'\r', '\n'} - lfByte = byte('\n') - movedBytes = []byte("MOVED") - askBytes = []byte("ASK") -) - -var ( - robjGet = newRESPBulk([]byte("3\r\nGET")) - robjMSet = newRESPBulk([]byte("4\r\nMSET")) - - cmdMSetLenBytes = []byte("3") - cmdMSetBytes = []byte("4\r\nMSET") - cmdMGetBytes = []byte("4\r\nMGET") - cmdGetBytes = []byte("3\r\nGET") - cmdDelBytes = []byte("3\r\nDEL") - cmdExistsBytes = []byte("6\r\nEXISTS") -) - -// errors -var ( - ErrProxyFail = errors.New("fail to send proxy") - ErrRequestBadFormat = errors.New("redis must be a RESP array") - ErrRedirectBadFormat = errors.New("bad format of MOVED or ASK") -) - -// const values -const ( - SlotCount = 16384 - SlotShiled = 0x3fff -) - -// Request is the type of a complete redis command -type Request struct { - respObj *resp - mergeType MergeType - reply *resp -} - -// NewRequest will create new command by given args -// example: -// NewRequest("GET", "mykey") -// NewRequest("CLUSTER", "NODES") -func NewRequest(cmd string, args ...string) *Request { - respObj := respPool.Get().(*resp) - respObj.next().setBulk([]byte(cmd)) - // respObj := newRESPArrayWithCapcity(len(args) + 1) - // respObj.replace(0, newRESPBulk([]byte(cmd))) - maxLen := len(args) + 1 - for i := 1; i < maxLen; i++ { - data := args[i-1] - line := fmt.Sprintf("%d\r\n%s", len(data), data) - respObj.next().setBulk([]byte(line)) - } - respObj.data = []byte(strconv.Itoa(len(args) + 1)) - return newRequest(respObj) -} - -func newRequest(robj *resp) *Request { - r := &Request{respObj: robj} - r.mergeType = getMergeType(robj.nth(0).data) - r.reply = &resp{} - return r -} - -func (c *Request) setRESP(robj *resp) { - c.respObj = robj - c.mergeType = getMergeType(robj.nth(0).data) - c.reply = &resp{} -} - -func newRequestWithMergeType(robj *resp, mtype MergeType) *Request { - return &Request{respObj: robj, mergeType: mtype} -} - -// CmdString get the cmd -func (c *Request) CmdString() string { - return strings.ToUpper(string(c.respObj.nth(0).data)) -} - -// Cmd get the cmd -func (c *Request) Cmd() []byte { - return c.respObj.nth(0).data -} - -// Key impl the proto.protoRequest and get the Key of redis -func (c *Request) Key() []byte { - var data = c.respObj.nth(1).data - var pos int - if c.respObj.nth(1).rtype == respBulk { - pos = bytes.Index(data, crlfBytes) + 2 - } - // pos is never lower than 0 - return data[pos:] -} - -// Put the resource back to pool -func (c *Request) Put() { -} - -// IsRedirect check if response type is Redis Error -// and payload was prefix with "ASK" && "MOVED" -func (c *Request) IsRedirect() bool { - if c.reply.rtype != respError { - return false - } - if c.reply.data == nil { - return false - } - - return bytes.HasPrefix(c.reply.data, movedBytes) || - bytes.HasPrefix(c.reply.data, askBytes) -} - -// RedirectTriple will check and send back by is -// first return variable which was called as redirectType maybe return ASK or MOVED -// second is the slot of redirect -// third is the redirect addr -// last is the error when parse the redirect body -func (c *Request) RedirectTriple() (redirect string, slot int, addr string, err error) { - fields := strings.Fields(string(c.reply.data)) - if len(fields) != 3 { - err = ErrRedirectBadFormat - return - } - redirect = fields[0] - addr = fields[2] - ival, parseErr := strconv.Atoi(fields[1]) - - slot = ival - err = parseErr - return -} diff --git a/proto/redis/cmd_test.go b/proto/redis/cmd_test.go deleted file mode 100644 index 6aec2aea..00000000 --- a/proto/redis/cmd_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package redis - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestRequestNewRequest(t *testing.T) { - cmd := NewRequest("GET", "a") - assert.Equal(t, MergeTypeBasic, cmd.mergeType) - assert.Equal(t, 2, cmd.respObj.Len()) - - assert.Equal(t, "GET", cmd.CmdString()) - assert.Equal(t, "GET", string(cmd.Cmd())) - assert.Equal(t, "a", string(cmd.Key())) -} - -func TestRequestRedirect(t *testing.T) { - cmd := NewRequest("GET", "BAKA") - cmd.reply = newRESPPlain(respError, []byte("ASK 1024 127.0.0.1:2048")) - assert.True(t, cmd.IsRedirect()) - r, slot, addr, err := cmd.RedirectTriple() - assert.NoError(t, err) - assert.Equal(t, "ASK", r) - assert.Equal(t, 1024, slot) - assert.Equal(t, "127.0.0.1:2048", addr) - cmd.reply = newRESPPlain(respError, []byte("ERROR")) - assert.False(t, cmd.IsRedirect()) - _, _, _, err = cmd.RedirectTriple() - assert.Error(t, err) -} diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index 30e31e3f..f8177f07 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -1,6 +1,7 @@ package redis import ( + "bytes" libnet "overlord/lib/net" "overlord/proto" "sync/atomic" @@ -57,6 +58,9 @@ func (nc *nodeConn) write(m *proto.Message) error { m.DoneWithError(ErrBadAssert) return ErrBadAssert } + if bytes.Equal(cmd.Cmd(), commandBytes) { + return nil + } return cmd.respObj.encode(nc.rc.bw) } @@ -68,6 +72,10 @@ func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) (err error) { if !ok { return ErrBadAssert } + if bytes.Equal(cmd.Cmd(), commandBytes) { + cmd.reply = newRESPNull(respError) + continue + } if err = nc.rc.decodeOne(cmd.reply); err != nil { return } @@ -93,35 +101,3 @@ var ( newRESPBulk([]byte("5\r\nNODES")), }) ) - -func (nc *nodeConn) FetchSlots() (nodes []string, slots [][]int, err error) { - robjCluterNodes.encode(nc.rc.bw) - if err != nil { - return - } - err = nc.rc.Flush() - if err != nil { - return - } - rs := []*resp{&resp{}} - err = nc.rc.decodeCount(rs) - if err != nil { - return - } - robj := rs[0] - ns, err := ParseSlots(robj) - if err != nil { - return - } - - cns := ns.GetNodes() - nodes = make([]string, 0) - slots = make([][]int, 0) - for _, node := range cns { - if node.Role() == roleMaster { - nodes = append(nodes, node.Addr()) - slots = append(slots, node.Slots()) - } - } - return -} diff --git a/proto/redis/node_conn_test.go b/proto/redis/node_conn_test.go index 1cdd34d3..362a5c79 100644 --- a/proto/redis/node_conn_test.go +++ b/proto/redis/node_conn_test.go @@ -74,7 +74,7 @@ func TestReadBatchWithNilError(t *testing.T) { nc := newNodeConn("baka", "127.0.0.1:12345", _createConn(nil)) mb := proto.NewMsgBatch() msg := proto.GetMsg() - msg.WithRequest(&Request{}) + msg.WithRequest(NewRequest("get", "a")) mb.AddMsg(msg) err := nc.ReadBatch(mb) assert.Error(t, err) diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 254fcff6..292a7922 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -29,8 +29,7 @@ var ( respArrayBytes = []byte("*") respNullBytes = []byte("-1\r\n") - - okBytes = []byte("OK") + okBytes = []byte("OK") ) var ( diff --git a/proto/redis/slots.go b/proto/redis/slots.go deleted file mode 100644 index 4b92458b..00000000 --- a/proto/redis/slots.go +++ /dev/null @@ -1,291 +0,0 @@ -package redis - -import ( - "bufio" - "bytes" - "errors" - "io" - "strconv" - "strings" -) - -// errors -var ( - ErrAbsentField = errors.New("node fields is absent") - ErrEmptyNodeLine = errors.New("empty line of cluster nodes") - ErrBadReplyType = errors.New("bad reply type") -) -var ( - roleMaster = "master" - roleSlave = "slave" -) - -// Slots is the container of all slots. -type Slots interface { - GetSlots() []string - GetSlaveSlots() [][]string -} - -// Nodes is the container caontains Nodes. -type Nodes interface { - GetNodes() []Node - GetNodeByAddr(addr string) (Node, bool) -} - -// NodeSlots is the export interface of CLUSTER NODES. -type NodeSlots interface { - Slots - Nodes -} - -// ParseSlots must be call as "CLSUTER NODES" response -func ParseSlots(robj *resp) (NodeSlots, error) { - if respBulk != robj.rtype { - return nil, ErrBadReplyType - } - - return parseSlots(robj.bytes()) -} - -func parseSlots(data []byte) (NodeSlots, error) { - br := bufio.NewReader(bytes.NewBuffer(data)) - lines := []string{} - for { - // NOTICE: we assume that each line is not longer - // than 65535. - token, _, err := br.ReadLine() - if err != nil && err != io.EOF { - return nil, err - } - if len(token) != 0 { - lines = append(lines, string(token)) - } - if err == io.EOF { - break - } - } - nodes := make(map[string]Node) - slots := make([]string, SlotCount) - slaveSlots := make([][]string, SlotCount) - masterIDMap := make(map[string]Node) - // full fill master slots - for _, line := range lines { - node, err := parseNode(line) - if err != nil { - return nil, err - } - nodes[node.Addr()] = node - subSlots := node.Slots() - if node.Role() != roleMaster { - continue - } - masterIDMap[node.ID] = node - for _, slot := range subSlots { - slots[slot] = node.Addr() - } - } - // full fill slave slots - for _, node := range nodes { - if node.Role() != roleSlave { - continue - } - if mn, ok := masterIDMap[node.SlaveOf()]; ok { - for _, slot := range mn.Slots() { - if slaveSlots[slot] == nil { - slaveSlots[slot] = []string{} - } - slaveSlots[slot] = append(slaveSlots[slot], node.Addr()) - } - } - } - return &nodeSlots{nodes: nodes, slots: slots, slaveSlots: slaveSlots}, nil -} - -type nodeSlots struct { - nodes map[string]Node - slaveSlots [][]string - slots []string -} - -func (ns *nodeSlots) GetSlots() []string { - return ns.slots -} - -func (ns *nodeSlots) GetSlaveSlots() [][]string { - return ns.slaveSlots -} - -func (ns *nodeSlots) GetNodes() []Node { - nodes := make([]Node, len(ns.nodes)) - idx := 0 - for _, val := range ns.nodes { - nodes[idx] = val - idx++ - } - return nodes -} - -func (ns *nodeSlots) GetNodeByAddr(addr string) (Node, bool) { - if n, ok := ns.nodes[addr]; ok { - return n, true - } - return nil, false -} - -type node struct { - // 有别于 runID - ID string - addr string - // optional port - gossipAddr string - // Role is the special flag - role string - flags []string - slaveOf string - pingSent int - pongRecv int - configEpoch int - linkState string - slots []int -} - -func parseNode(line string) (*node, error) { - if len(strings.TrimSpace(line)) == 0 { - return nil, ErrEmptyNodeLine - } - fields := strings.Fields(line) - if len(fields) < 8 { - return nil, ErrAbsentField - } - n := &node{} - i := 0 - n.setID(fields[i]) - i++ - n.setAddr(fields[i]) - i++ - n.setFlags(fields[i]) - i++ - n.setSlaveOf(fields[i]) - i++ - n.setPingSent(fields[i]) - i++ - n.setPongRecv(fields[i]) - i++ - n.setConfigEpoch(fields[i]) - i++ - n.setLinkState(fields[i]) - i++ - n.setSlots(fields[i:]...) - // i++ - return n, nil -} -func (n *node) setID(val string) { - n.ID = strings.TrimSpace(val) -} -func (n *node) setAddr(val string) { - trimed := strings.TrimSpace(val) - // adaptor with 4.x - splited := strings.Split(trimed, "@") - n.addr = splited[0] - if len(splited) == 2 { - asp := strings.Split(n.addr, ":") - n.gossipAddr = asp[0] + splited[1] - } -} -func (n *node) setFlags(val string) { - flags := strings.Split(val, ",") - n.flags = flags - if strings.Contains(val, roleMaster) { - n.role = roleMaster - } else if strings.Contains(val, "slave") { - n.role = roleSlave - } -} -func (n *node) setSlaveOf(val string) { - n.slaveOf = val -} -func (n *node) setPingSent(val string) { - ival, err := strconv.Atoi(val) - if err != nil { - n.pingSent = 0 - } - n.pingSent = ival -} -func (n *node) setPongRecv(val string) { - ival, err := strconv.Atoi(val) - if err != nil { - n.pongRecv = 0 - } - n.pongRecv = ival -} -func (n *node) setConfigEpoch(val string) { - ival, err := strconv.Atoi(val) - if err != nil { - n.configEpoch = 0 - } - n.configEpoch = ival -} -func (n *node) setLinkState(val string) { - n.linkState = val -} -func (n *node) setSlots(vals ...string) { - slots := []int{} - for _, val := range vals { - subslots, ok := parseSlotField(val) - if ok { - slots = append(slots, subslots...) - } - } - //sort.IntSlice(slots).Sort() - n.slots = slots -} -func parseSlotField(val string) ([]int, bool) { - if len(val) == 0 || val == "-" { - return nil, false - } - vsp := strings.SplitN(val, "-", 2) - begin, err := strconv.Atoi(vsp[0]) - if err != nil { - return nil, false - } - if len(vsp) == 1 { - return []int{begin}, true - } - end, err := strconv.Atoi(vsp[1]) - if err != nil { - return nil, false - } - if end < begin { - return nil, false - } - slots := []int{} - for i := begin; i <= end; i++ { - slots = append(slots, i) - } - //sort.IntSlice(slots).Sort() - return slots, true -} -func (n *node) Addr() string { - return n.addr -} -func (n *node) Role() string { - return n.role -} -func (n *node) SlaveOf() string { - return n.slaveOf -} -func (n *node) Flags() []string { - return n.flags -} -func (n *node) Slots() []int { - return n.slots -} - -// Node is the interface of single redis node. -type Node interface { - Addr() string - Role() string - SlaveOf() string - Flags() []string - Slots() []int -} diff --git a/proto/redis/slots_test.go b/proto/redis/slots_test.go deleted file mode 100644 index 82491b16..00000000 --- a/proto/redis/slots_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package redis - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestParseSlotFieldEmptyLineOk(t *testing.T) { - slots, ok := parseSlotField("") - assert.Nil(t, slots) - assert.False(t, ok) - slots, ok = parseSlotField("-") - assert.Nil(t, slots) - assert.False(t, ok) -} -func TestParseSlotSingleValueOk(t *testing.T) { - slots, ok := parseSlotField("1024") - assert.True(t, ok) - assert.Len(t, slots, 1) - assert.Equal(t, 1024, slots[0]) -} - -func TestParseSlotSingleValueBadNumber(t *testing.T) { - slots, ok := parseSlotField("@boynextdoor") - assert.False(t, ok) - assert.Nil(t, slots) -} - -func TestParseSlotRangeSecondBadNumber(t *testing.T) { - slots, ok := parseSlotField("1024-@boynextdoor") - assert.False(t, ok) - assert.Nil(t, slots) -} - -func TestParseSlotRangeBadRange(t *testing.T) { - slots, ok := parseSlotField("1024-12") - assert.False(t, ok) - assert.Nil(t, slots) -} - -func TestParseSlotRangeButOneValue(t *testing.T) { - slots, ok := parseSlotField("1024-1024") - assert.True(t, ok) - assert.Len(t, slots, 1) - assert.Equal(t, 1024, slots[0]) -} - -func TestParseSlotRangeOk(t *testing.T) { - slots, ok := parseSlotField("12-1222") - assert.True(t, ok) - assert.Len(t, slots, 1222-12+1) - assert.Equal(t, 12, slots[0]) - assert.Equal(t, 1222, slots[len(slots)-1]) -} - -func TestNodeSetOk(t *testing.T) { - n := &node{} - cID := "3f76d4dca41307bea25e8f69a3545594479dc7a9" - n.setID(cID) - assert.Equal(t, cID, n.ID) - addr := "127.0.0.1:1024" - n.setAddr(addr) - assert.Equal(t, addr, n.Addr()) - addrWithGossip := addr + "@11024" - n.setAddr(addrWithGossip) - assert.Equal(t, addr, n.Addr()) - flagLists := []string{ - "mark,myself,master", - "mark,slave", - "mark,myself,slave", - "mark,faild", - } - for _, val := range flagLists { - t.Run("TestNodeSetWithFlags"+val, func(t *testing.T) { - n.setFlags(val) - assert.Contains(t, n.Flags(), "mark") - }) - } - masterAddr := "127.0.0.1:7788" - n.setSlaveOf(masterAddr) - assert.Equal(t, masterAddr, n.SlaveOf()) - // get or null - n.setPingSent("1024") - assert.Equal(t, 1024, n.pingSent) - n.setPingSent("-") - assert.Equal(t, 0, n.pingSent) - n.setPongRecv("1024") - assert.Equal(t, 1024, n.pongRecv) - n.setPongRecv("-") - assert.Equal(t, 0, n.pongRecv) - n.setConfigEpoch("1024") - assert.Equal(t, 1024, n.configEpoch) - n.setConfigEpoch("-") - assert.Equal(t, 0, n.configEpoch) - link := "zelda" - n.setLinkState(link) - assert.Equal(t, link, n.linkState) - slots := []struct { - name string - s []string - i []int - }{ - {"RangeOk", []string{"1", "1024"}, []int{1, 1024}}, - {"RangeFail", []string{"1", "@", "1024"}, []int{1, 1024}}, - } - for _, slot := range slots { - t.Run("TestNodeSetSlots"+slot.name, func(t *testing.T) { - n.setSlots(slot.s...) - assert.Equal(t, slot.i, n.Slots()) - }) - } -} - -func TestParseNodeOk(t *testing.T) { - slaveStr := "f17c3861b919c58b06584a0778c4f60913cf213c 172.17.0.2:7005@17005 slave 91240f5f82621d91d55b02d3bc1dcd1852dc42dd 0 1528251710522 6 connected" - n, err := parseNode(slaveStr) - assert.NoError(t, err) - assert.NotNil(t, n) - masterStr := "91240f5f82621d91d55b02d3bc1dcd1852dc42dd 172.17.0.2:7002@17002 master - 0 1528251710832 3 connected 10923-16383" - n, err = parseNode(masterStr) - assert.NoError(t, err) - assert.NotNil(t, n) - _, err = parseNode("") - assert.Error(t, err) - assert.Equal(t, ErrEmptyNodeLine, err) - _, err = parseNode("91240f5f82621d91d55b02d3bc1dcd1852dc42dd 172.17.0.2:7002@17002 10923-16383") - assert.Error(t, err) - assert.Equal(t, ErrAbsentField, err) -} - -var clusterNodesData = []byte(` -3f76d4dca41307bea25e8f69a3545594479dc7a9 172.17.0.2:7004@17004 slave ec433a34a97e09fc9c22dd4b4a301e2bca6602e0 0 1528252310916 5 connected -f17c3861b919c58b06584a0778c4f60913cf213c 172.17.0.2:7005@17005 slave 91240f5f82621d91d55b02d3bc1dcd1852dc42dd 0 1528252309896 6 connected -91240f5f82621d91d55b02d3bc1dcd1852dc42dd 172.17.0.2:7002@17002 master - 0 1528252310000 3 connected 10923-16383 -ec433a34a97e09fc9c22dd4b4a301e2bca6602e0 172.17.0.2:7001@17001 master - 0 1528252310606 2 connected 5461-10922 -a063bbdc2c4abdc60e09fdf1934dc8c8fb2d69df 172.17.0.2:7003@17003 slave a8f85c7b9a2e2cd24dda7a60f34fd889b61c9c00 0 1528252310506 4 connected -a8f85c7b9a2e2cd24dda7a60f34fd889b61c9c00 172.17.0.2:7000@17000 myself,master - 0 1528252310000 1 connected 0-5460 -`) - -func TestParseSlotsOk(t *testing.T) { - s, err := parseSlots(clusterNodesData) - assert.NoError(t, err) - assert.Len(t, s.GetSlots(), SlotCount) - assert.Len(t, s.GetSlaveSlots(), SlotCount) - assert.Len(t, s.GetNodes(), 6) - n, ok := s.GetNodeByAddr("172.17.0.2:7000") - assert.True(t, ok) - assert.NotNil(t, n) - n, ok = s.GetNodeByAddr("AddrNotExists") - assert.False(t, ok) - assert.Nil(t, n) -} - -func TestParseSlotsFromReplyOk(t *testing.T) { - rs := newRESPBulk([]byte(clusterNodesData)) - ns, err := ParseSlots(rs) - assert.NoError(t, err) - assert.NotNil(t, ns) -} diff --git a/proto/types.go b/proto/types.go index 3dd11bf6..6261cb55 100644 --- a/proto/types.go +++ b/proto/types.go @@ -41,6 +41,4 @@ type NodeConn interface { Ping() error Close() error - - FetchSlots() (nodes []string, slots [][]int, err error) } diff --git a/proxy/cluster.go b/proxy/cluster.go index 4aaf3f09..c3d80db3 100644 --- a/proxy/cluster.go +++ b/proxy/cluster.go @@ -64,7 +64,7 @@ type Cluster struct { hashTag []byte - ring hashkit.Ring + ring *hashkit.HashRing alias bool nodeMap map[string]int @@ -93,7 +93,7 @@ func NewCluster(ctx context.Context, cc *ClusterConfig) (c *Cluster) { var wlist [][]int ring := hashkit.NewRing(cc.HashDistribution, cc.HashMethod) - if cc.CacheType == proto.CacheTypeMemcache || cc.CacheType == proto.CacheTypeMemcacheBinary { + if cc.CacheType == proto.CacheTypeMemcache || cc.CacheType == proto.CacheTypeMemcacheBinary || cc.CacheType == proto.CacheTypeRedis { if c.alias { ring.Init(ans, ws) } else { @@ -103,17 +103,6 @@ func NewCluster(ctx context.Context, cc *ClusterConfig) (c *Cluster) { for i, w := range ws { wlist[i] = []int{w} } - } else if cc.CacheType == proto.CacheTypeRedis { - sc := newNodeConn(cc, addrs[0]) - nodes, slots, err := sc.FetchSlots() - // fmt.Println(nodes, len(nodes)) - if err != nil { - panic(err) - } - addrs = nodes - ring.Init(nodes, slots...) - c.syncConn = sc - wlist = slots } else { panic("unsupported protocol") } From c58bcb3c4513bd11b6322997204c15c52d5db82a Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Tue, 24 Jul 2018 14:40:32 +0800 Subject: [PATCH 33/87] rename command --- proto/redis/request.go | 149 ++++++++++++++++++++++++++++++++++++ proto/redis/request_test.go | 32 ++++++++ 2 files changed, 181 insertions(+) create mode 100644 proto/redis/request.go create mode 100644 proto/redis/request_test.go diff --git a/proto/redis/request.go b/proto/redis/request.go new file mode 100644 index 00000000..71e403b3 --- /dev/null +++ b/proto/redis/request.go @@ -0,0 +1,149 @@ +package redis + +import ( + "errors" + "fmt" + "strconv" + "strings" + + // "crc" + "bytes" +) + +var ( + crlfBytes = []byte{'\r', '\n'} + lfByte = byte('\n') + movedBytes = []byte("MOVED") + askBytes = []byte("ASK") +) + +var ( + robjGet = newRESPBulk([]byte("3\r\nGET")) + robjMSet = newRESPBulk([]byte("4\r\nMSET")) + + cmdMSetLenBytes = []byte("3") + cmdMSetBytes = []byte("4\r\nMSET") + cmdMGetBytes = []byte("4\r\nMGET") + cmdGetBytes = []byte("3\r\nGET") + cmdDelBytes = []byte("3\r\nDEL") + cmdExistsBytes = []byte("6\r\nEXISTS") + commandBytes = []byte("7\r\nCOMMAND") +) + +// errors +var ( + ErrProxyFail = errors.New("fail to send proxy") + ErrRequestBadFormat = errors.New("redis must be a RESP array") + ErrRedirectBadFormat = errors.New("bad format of MOVED or ASK") +) + +// const values +const ( + SlotCount = 16384 + SlotShiled = 0x3fff +) + +// Request is the type of a complete redis command +type Request struct { + respObj *resp + mergeType MergeType + reply *resp +} + +// NewRequest will create new command by given args +// example: +// NewRequest("GET", "mykey") +// NewRequest("CLUSTER", "NODES") +func NewRequest(cmd string, args ...string) *Request { + respObj := respPool.Get().(*resp) + respObj.next().setBulk([]byte(cmd)) + // respObj := newRESPArrayWithCapcity(len(args) + 1) + // respObj.replace(0, newRESPBulk([]byte(cmd))) + maxLen := len(args) + 1 + for i := 1; i < maxLen; i++ { + data := args[i-1] + line := fmt.Sprintf("%d\r\n%s", len(data), data) + respObj.next().setBulk([]byte(line)) + } + respObj.data = []byte(strconv.Itoa(len(args) + 1)) + return newRequest(respObj) +} + +func newRequest(robj *resp) *Request { + r := &Request{respObj: robj} + r.mergeType = getMergeType(robj.nth(0).data) + r.reply = &resp{} + return r +} + +func (c *Request) setRESP(robj *resp) { + c.respObj = robj + c.mergeType = getMergeType(robj.nth(0).data) + c.reply = &resp{} +} + +func newRequestWithMergeType(robj *resp, mtype MergeType) *Request { + return &Request{respObj: robj, mergeType: mtype} +} + +// CmdString get the cmd +func (c *Request) CmdString() string { + return strings.ToUpper(string(c.respObj.nth(0).data)) +} + +// Cmd get the cmd +func (c *Request) Cmd() []byte { + return c.respObj.nth(0).data +} + +// Key impl the proto.protoRequest and get the Key of redis +func (c *Request) Key() []byte { + if len(c.respObj.array) == 1 { + return c.respObj.nth(0).data + } + var data = c.respObj.nth(1).data + var pos int + if c.respObj.nth(1).rtype == respBulk { + pos = bytes.Index(data, crlfBytes) + 2 + } + // pos is never lower than 0 + return data[pos:] +} + +// Put the resource back to pool +func (c *Request) Put() { +} + +// IsRedirect check if response type is Redis Error +// and payload was prefix with "ASK" && "MOVED" +func (c *Request) IsRedirect() bool { + if c.reply.rtype != respError { + return false + } + if c.reply.data == nil { + return false + } + + return bytes.HasPrefix(c.reply.data, movedBytes) || + bytes.HasPrefix(c.reply.data, askBytes) +} + +// RedirectTriple will check and send back by is +// first return variable which was called as redirectType maybe return ASK or MOVED +// second is the slot of redirect +// third is the redirect addr +// last is the error when parse the redirect body +func (c *Request) RedirectTriple() (redirect string, slot int, addr string, err error) { + fields := strings.Fields(string(c.reply.data)) + if len(fields) != 3 { + err = ErrRedirectBadFormat + return + } + redirect = fields[0] + addr = fields[2] + ival, parseErr := strconv.Atoi(fields[1]) + + slot = ival + err = parseErr + return +} diff --git a/proto/redis/request_test.go b/proto/redis/request_test.go new file mode 100644 index 00000000..6aec2aea --- /dev/null +++ b/proto/redis/request_test.go @@ -0,0 +1,32 @@ +package redis + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRequestNewRequest(t *testing.T) { + cmd := NewRequest("GET", "a") + assert.Equal(t, MergeTypeBasic, cmd.mergeType) + assert.Equal(t, 2, cmd.respObj.Len()) + + assert.Equal(t, "GET", cmd.CmdString()) + assert.Equal(t, "GET", string(cmd.Cmd())) + assert.Equal(t, "a", string(cmd.Key())) +} + +func TestRequestRedirect(t *testing.T) { + cmd := NewRequest("GET", "BAKA") + cmd.reply = newRESPPlain(respError, []byte("ASK 1024 127.0.0.1:2048")) + assert.True(t, cmd.IsRedirect()) + r, slot, addr, err := cmd.RedirectTriple() + assert.NoError(t, err) + assert.Equal(t, "ASK", r) + assert.Equal(t, 1024, slot) + assert.Equal(t, "127.0.0.1:2048", addr) + cmd.reply = newRESPPlain(respError, []byte("ERROR")) + assert.False(t, cmd.IsRedirect()) + _, _, _, err = cmd.RedirectTriple() + assert.Error(t, err) +} From 36f15e812f9e851b90024ea36c31aa2bcb066860 Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 24 Jul 2018 14:05:29 +0800 Subject: [PATCH 34/87] add new dependencies of crit-bit tree --- .../github.com/tatsushid/go-critbit/LICENSE | 21 + .../github.com/tatsushid/go-critbit/README.md | 105 +++++ .../tatsushid/go-critbit/critbit.go | 370 ++++++++++++++++++ vendor/vendor.json | 8 +- 4 files changed, 503 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/tatsushid/go-critbit/LICENSE create mode 100644 vendor/github.com/tatsushid/go-critbit/README.md create mode 100644 vendor/github.com/tatsushid/go-critbit/critbit.go diff --git a/vendor/github.com/tatsushid/go-critbit/LICENSE b/vendor/github.com/tatsushid/go-critbit/LICENSE new file mode 100644 index 00000000..fba1c1da --- /dev/null +++ b/vendor/github.com/tatsushid/go-critbit/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Tatsushi Demachi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/tatsushid/go-critbit/README.md b/vendor/github.com/tatsushid/go-critbit/README.md new file mode 100644 index 00000000..37911109 --- /dev/null +++ b/vendor/github.com/tatsushid/go-critbit/README.md @@ -0,0 +1,105 @@ +Crit-bit tree library written in Go +=================================== + +[![GoDoc](https://godoc.org/github.com/tatsushid/go-critbit?status.svg)][godoc] +[![Build Status](https://travis-ci.org/tatsushid/go-critbit.svg?branch=master)](https://travis-ci.org/tatsushid/go-critbit) +[![Go Report Card](https://goreportcard.com/badge/github.com/tatsushid/go-critbit)](https://goreportcard.com/report/github.com/tatsushid/go-critbit) + +This is a [Crit-bit tree](http://cr.yp.to/critbit.html) implementation written in Go by referring [the C implementation and document](https://github.com/agl/critbit). + +## Installation + +Install and update by `go get -u github.com/tatsushid/go-critbit`. + +## Usage + +```go +package main + +import ( + "github.com/tatsushid/go-critbit" +) + +func main() { + // create a tree + tr := critbit.New() + + tr.Insert([]byte("foo"), 1) + tr.Insert([]byte("bar"), 2) + tr.Insert([]byte("foobar"), 3) + + // search exact key + v, ok := tr.Get([]byte("bar")) + if !ok { + panic("should be ok") + } + if v.(int) != 2 { + panic("should be 2") + } + + // find the longest prefix of a given key + k, _, _ := tr.LongestPrefix([]byte("foozip")) + if string(k) != "foo" { + panic("should be foo") + } + + var sum int + // walk the tree with a callback function + tr.Walk(func(k []byte, v interface{}) bool { + sum += v.(int) + return false + }) + if sum != 6 { + panic("should be 6 = 1 + 2 + 3") + } +} +``` + +Please see [GoDoc][godoc] for more APIs and details. + +This also has Graphviz exporter sample. It is implemented as a part of tests. +To use it, please compile a test command by + +```shellsession +go test -c +``` + +and run the generated command like + +```shellsession +./critbit.test -random 10 -printdot > critbit.dot +``` + +You can see Graphviz image by opening the created file. + +The command has following options + +``` +-add key + add key to critbit tree. this can be used multiple times +-del key + delete key from critbit tree. this can be used multiple times +-printdot + print graphviz dot of critbit tree and exit +-random times + insert keys chosen at random up to specified times +``` + +## Notes +- [go-radix](https://github.com/armon/go-radix) is widly used tree library and its API is well organized I think. I wrote this library to have a similar API to that and used some test data patterns to make sure it returns same results as that. +- To compare performance, I took the performance test data from [An Adaptive Radix Tree Implementation in Go](https://github.com/plar/go-adaptive-radix-tree). If you are interested in it, you can see it by running + + ```shellsession + go test -bench . -benchmem + ``` + + in both cloned repositories. In my environment, this library is a bit slower but a little more memory efficient than that. +- This can handle a byte sequence with null bytes in it as same as [critbitgo](https://github.com/k-sone/critbitgo), the other Crit-bit library written in Go. + +Thanks for all these libraries! + +## License +This program is under MIT license. Please see the [LICENSE][license] file for details. + +[godoc]: http://godoc.org/github.com/tatsushid/go-critbit +[license]: https://github.com/tatsushid/go-critbit/blob/master/LICENSE diff --git a/vendor/github.com/tatsushid/go-critbit/critbit.go b/vendor/github.com/tatsushid/go-critbit/critbit.go new file mode 100644 index 00000000..570c5560 --- /dev/null +++ b/vendor/github.com/tatsushid/go-critbit/critbit.go @@ -0,0 +1,370 @@ +// Package critbit implements Crit-Bit tree for byte sequences. +// +// Crit-Bit tree [1] is fast, memory efficient and a variant of PATRICIA trie. +// This implementation can be used for byte sequences if it includes a null +// byte or not. This is based on [2] and extends it to support a null byte in a +// byte sequence. +// +// [1]: http://cr.yp.to/critbit.html (definition) +// [2]: https://github.com/agl/critbit (C implementation and document) +package critbit + +import "bytes" + +type nodeType int + +const ( + internal nodeType = iota + external +) + +type node interface { + kind() nodeType +} + +type iNode struct { + children [2]node + pos int + other uint8 +} + +func (n *iNode) kind() nodeType { return internal } + +type eNode struct { + key []byte + value interface{} +} + +func (n *eNode) kind() nodeType { return external } + +// Tree represents a critbit tree. +type Tree struct { + root node + size int +} + +// New returns an empty tree. +func New() *Tree { + return &Tree{} +} + +// Len returns a number of elements in the tree. +func (t *Tree) Len() int { + return t.size +} + +func (t *Tree) direction(k []byte, pos int, other uint8) int { + var c uint8 + if pos < len(k) { + c = k[pos] + } else if other == 0xff { + return 0 + } + return (1 + int(other|c)) >> 8 +} + +func (t *Tree) lookup(k []byte) (*eNode, *iNode) { + if t.root == nil { + return nil, nil + } + + var top *iNode + p := t.root + for { + switch n := p.(type) { + case *eNode: + return n, top + case *iNode: + if top == nil || n.pos < len(k) { + top = n + } + p = n.children[t.direction(k, n.pos, n.other)] + } + } +} + +// Get searches a given key from the tree. If the key exists in the tree, it +// returns its value and true. If not, it returns nil and false. +func (t *Tree) Get(k []byte) (interface{}, bool) { + n, _ := t.lookup(k) + if n != nil && bytes.Equal(k, n.key) { + return n.value, true + } + return nil, false +} + +func (t *Tree) findFirstDiffByte(k []byte, n *eNode) (pos int, other uint8, match bool) { + var byt, b byte + for pos = 0; pos < len(k); pos++ { + b = k[pos] + byt = 0 + if pos < len(n.key) { + byt = n.key[pos] + } + if byt != b { + return pos, byt ^ b, false + } + } + if pos < len(n.key) { + return pos, n.key[pos], false + } else if pos == len(n.key) { + return 0, 0, true + } + return pos - 1, 0, false +} + +func (t *Tree) findInsertPos(k []byte, pos int, other uint8) (*node, node) { + p := &t.root + for { + switch n := (*p).(type) { + case *eNode: + return p, n + case *iNode: + if n.pos > pos { + return p, n + } + if n.pos == pos && n.other > other { + return p, n + } + p = &n.children[t.direction(k, n.pos, n.other)] + } + } +} + +// Insert adds or updates a given key to the tree and returns its previous +// value and if anything was set or not. If there is the key in the tree, it +// adds the key and the value to the tree and returns nil and true when it +// succeeded while if not, it updates the key's value and returns its previous +// value and true when it succeeded. +func (t *Tree) Insert(k []byte, v interface{}) (interface{}, bool) { + key := append([]byte{}, k...) + + n, _ := t.lookup(k) + if n == nil { // only happens when t.root is nil + t.root = &eNode{key: key, value: v} + t.size++ + return nil, true + } + + pos, other, match := t.findFirstDiffByte(k, n) + if match { + orig := n.value + n.value = v + return orig, true + } + + other |= other >> 1 + other |= other >> 2 + other |= other >> 4 + other = ^(other &^ (other >> 1)) + di := t.direction(n.key, pos, other) + + newn := &iNode{pos: pos, other: other} + newn.children[1-di] = &eNode{key: key, value: v} + + p, child := t.findInsertPos(k, pos, other) + newn.children[di] = child + *p = newn + + t.size++ + return nil, true +} + +func (t *Tree) findDeletePos(k []byte) (*node, *eNode, int) { + if t.root == nil { + return nil, nil, 0 + } + + var di int + var q *node + p := &t.root + for { + switch n := (*p).(type) { + case *eNode: + return q, n, di + case *iNode: + di = t.direction(k, n.pos, n.other) + q = p + p = &n.children[di] + } + } +} + +// Delete removes a given key and its value from the tree. If it succeeded, it +// returns the key's previous value and true while if not, it returns nil and +// false. On an empty tree, it always fails. +func (t *Tree) Delete(k []byte) (interface{}, bool) { + q, n, di := t.findDeletePos(k) + if n == nil || !bytes.Equal(k, n.key) { + return nil, false + } + t.size-- + if q == nil { + t.root = nil + return n.value, true + } + tmp := (*q).(*iNode) + *q = tmp.children[1-di] + return n.value, true +} + +// Clear removes all elements in the tree. If it removes something, it returns +// true while the tree is empty and there is nothing to remove, it returns +// false. +func (t *Tree) Clear() bool { + if t.root != nil { + t.root = nil + t.size = 0 + return true + } + return false +} + +// Minimum searches a key from the tree in lexicographic order and returns the +// first one and its value. If it found such a key, it also returns true as the +// bool value while if not, it returns false as it. +func (t *Tree) Minimum() ([]byte, interface{}, bool) { + if t.root == nil { + return nil, nil, false + } + + p := t.root + for { + switch n := p.(type) { + case *eNode: + return n.key, n.value, true + case *iNode: + p = n.children[0] + } + } +} + +// Maximum searches a key from the tree in lexicographic order and returns the +// last one and its value. If it found such a key, it also returns true as the +// bool value while if not, it returns false as it. +func (t *Tree) Maximum() ([]byte, interface{}, bool) { + if t.root == nil { + return nil, nil, false + } + + p := t.root + for { + switch n := p.(type) { + case *eNode: + return n.key, n.value, true + case *iNode: + p = n.children[1] + } + } +} + +func (t *Tree) longestPrefix(p node, prefix []byte) ([]byte, interface{}, bool) { + if p == nil { + return nil, nil, false + } + var di int + var c uint8 + switch n := p.(type) { + case *eNode: + if bytes.HasPrefix(prefix, n.key) { + return n.key, n.value, true + } + case *iNode: + c = 0 + if n.pos < len(prefix) { + c = prefix[n.pos] + } + di = (1 + int(n.other|c)) >> 8 + + if k, v, ok := t.longestPrefix(n.children[di], prefix); ok { + return k, v, ok + } else if di == 1 { + return t.longestPrefix(n.children[0], prefix) + } + } + return nil, nil, false +} + +// LongestPrefix searches the longest key which is included in a given key and +// returns the found key and its value. For example, if there are "f", "fo", +// "foobar" in the tree and "foo" is given, it returns "fo". If it found such a +// key, it returns true as the bool value while if not, it returns false as it. +func (t *Tree) LongestPrefix(prefix []byte) ([]byte, interface{}, bool) { + return t.longestPrefix(t.root, prefix) +} + +// WalkFn is used at walking a tree. It receives a key and its value of each +// elements which a walk function gives. If it returns true, a walk function +// should be terminated at there. +type WalkFn func(k []byte, v interface{}) bool + +func (t *Tree) walk(p node, fn WalkFn) bool { + if p == nil { + return false + } + switch n := p.(type) { + case *eNode: + return fn(n.key, n.value) + case *iNode: + for i := 0; i < 2; i++ { + if t.walk(n.children[i], fn) { + return true + } + } + } + return false +} + +// Walk walks whole the tree and call a given function with each element's key +// and value. If the function returns true, the walk is terminated at there. +func (t *Tree) Walk(fn WalkFn) { + t.walk(t.root, fn) +} + +// WalkPrefix walks the tree under a given prefix and call a given function +// with each element's key and value. For example, the tree has "f", "fo", +// "foob", "foobar" and "foo" is given, it visits "foob" and "foobar" elements. +// If the function returns true, the walk is terminated at there. +func (t *Tree) WalkPrefix(prefix []byte, fn WalkFn) { + n, top := t.lookup(prefix) + if n == nil || !bytes.HasPrefix(n.key, prefix) { + return + } + wrapper := func(k []byte, v interface{}) bool { + if bytes.HasPrefix(k, prefix) { + return fn(k, v) + } + return false + } + t.walk(top, wrapper) +} + +func (t *Tree) walkPath(p node, path []byte, fn WalkFn) bool { + if p == nil { + return false + } + var di int + switch n := p.(type) { + case *eNode: + if bytes.HasPrefix(path, n.key) { + return fn(n.key, n.value) + } + case *iNode: + di = t.direction(path, n.pos, n.other) + if di == 1 { + if t.walkPath(n.children[0], path, fn) { + return true + } + } + return t.walkPath(n.children[di], path, fn) + } + return false +} + +// WalkPath walks the tree from the root up to a given key and call a given +// function with each element's key and value. For example, the tree has "f", +// "fo", "foob", "foobar" and "foo" is given, it visits "f" and "fo" elements. +// If the function returns true, the walk is terminated at there. +func (t *Tree) WalkPath(path []byte, fn WalkFn) { + t.walkPath(t.root, path, fn) +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 7f20161d..73020abb 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -109,7 +109,13 @@ "path": "github.com/stretchr/testify/assert", "revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686", "revisionTime": "2018-05-06T18:05:49Z" + }, + { + "checksumSHA1": "9yMoHF1RPGh802dGQtG/8EvS6FA=", + "path": "github.com/tatsushid/go-critbit", + "revision": "487ef94b52c147166c6c62a3c2db431cad0f329b", + "revisionTime": "2018-03-27T15:21:58Z" } ], - "rootPath": "github.com/felixhao/overlord" + "rootPath": "overlord" } From 62728da46c19f8d74e78586aadb11fe3558423aa Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 24 Jul 2018 15:14:28 +0800 Subject: [PATCH 35/87] add ping and not supported command list --- lib/conv/conv.go | 3 +- proto/redis/filter.go | 178 +++++++++++++++++++++++++++++++++++++++ proto/redis/node_conn.go | 10 ++- proto/redis/request.go | 28 +++++- proto/redis/resp.go | 5 ++ 5 files changed, 216 insertions(+), 8 deletions(-) create mode 100644 proto/redis/filter.go diff --git a/lib/conv/conv.go b/lib/conv/conv.go index 6ed1d86e..70a1c84a 100644 --- a/lib/conv/conv.go +++ b/lib/conv/conv.go @@ -74,8 +74,9 @@ const ( func UpdateToUpper(src []byte) { for i := range src { if src[i] < lowerBegin || src[i] > lowerEnd { - src[i] -= step + continue } + src[i] -= step } } diff --git a/proto/redis/filter.go b/proto/redis/filter.go new file mode 100644 index 00000000..5b818dcf --- /dev/null +++ b/proto/redis/filter.go @@ -0,0 +1,178 @@ +package redis + +import ( + "bytes" + + "github.com/tatsushid/go-critbit" +) + +// reqType is a kind for cmd which is Ctl/Read/Write/NotSupport +type reqType = int + +const ( + reqTypeCtl reqType = iota + reqTypeRead reqType = iota + reqTypeWrite reqType = iota + reqTypeNotSupport reqType = iota +) + +var reqMap *critbit.Tree + +func init() { + + tmpMap := map[string]reqType{ + "DEL": reqTypeWrite, + "DUMP": reqTypeRead, + "EXISTS": reqTypeRead, + "EXPIRE": reqTypeWrite, + "EXPIREAT": reqTypeWrite, + "KEYS": reqTypeNotSupport, + "MIGRATE": reqTypeNotSupport, + "MOVE": reqTypeNotSupport, + "OBJECT": reqTypeNotSupport, + "PERSIST": reqTypeWrite, + "PEXPIRE": reqTypeWrite, + "PEXPIREAT": reqTypeWrite, + "PTTL": reqTypeRead, + + "RANDOMKEY": reqTypeNotSupport, + "RENAME": reqTypeNotSupport, + "RENAMENX": reqTypeNotSupport, + "RESTORE": reqTypeWrite, + "SCAN": reqTypeNotSupport, + "SORT": reqTypeWrite, + "TTL": reqTypeRead, + "TYPE": reqTypeRead, + "WAIT": reqTypeNotSupport, + "APPEND": reqTypeWrite, + "BITCOUNT": reqTypeRead, + "BITOP": reqTypeNotSupport, + "BITPOS": reqTypeRead, + "DECR": reqTypeWrite, + "DECRBY": reqTypeWrite, + "GET": reqTypeRead, + "GETBIT": reqTypeRead, + "GETRANGE": reqTypeRead, + "GETSET": reqTypeWrite, + "INCR": reqTypeWrite, + "INCRBY": reqTypeWrite, + "INCRBYFLOAT": reqTypeWrite, + + "MGET": reqTypeRead, + "MSET": reqTypeWrite, + "MSETNX": reqTypeNotSupport, + "PSETEX": reqTypeWrite, + "SET": reqTypeWrite, + "SETBIT": reqTypeWrite, + "SETEX": reqTypeWrite, + "SETNX": reqTypeWrite, + "SETRANGE": reqTypeWrite, + "STRLEN": reqTypeRead, + + "HDEL": reqTypeWrite, + "HEXISTS": reqTypeRead, + "HGET": reqTypeRead, + "HGETALL": reqTypeRead, + "HINCRBY": reqTypeWrite, + "HINCRBYFLOAT": reqTypeWrite, + "HKEYS": reqTypeRead, + "HLEN": reqTypeRead, + "HMGET": reqTypeRead, + "HMSET": reqTypeWrite, + "HSET": reqTypeWrite, + "HSETNX": reqTypeWrite, + "HSTRLEN": reqTypeRead, + "HVALS": reqTypeRead, + "HSCAN": reqTypeRead, + "BLPOP": reqTypeNotSupport, + "BRPOP": reqTypeNotSupport, + "BRPOPLPUSH": reqTypeNotSupport, + + "LINDEX": reqTypeRead, + "LINSERT": reqTypeWrite, + "LLEN": reqTypeRead, + "LPOP": reqTypeWrite, + "LPUSH": reqTypeWrite, + "LPUSHX": reqTypeWrite, + "LRANGE": reqTypeRead, + "LREM": reqTypeWrite, + "LSET": reqTypeWrite, + "LTRIM": reqTypeWrite, + "RPOP": reqTypeWrite, + "RPOPLPUSH": reqTypeWrite, + "RPUSH": reqTypeWrite, + "RPUSHX": reqTypeWrite, + + "SADD": reqTypeWrite, + "SCARD": reqTypeRead, + "SDIFF": reqTypeRead, + "SDIFFSTORE": reqTypeWrite, + "SINTER": reqTypeRead, + "SINTERSTORE": reqTypeWrite, + "SISMEMBER": reqTypeRead, + "SMEMBERS": reqTypeRead, + "SMOVE": reqTypeWrite, + "SPOP": reqTypeWrite, + "SRANDMEMBER": reqTypeRead, + "SREM": reqTypeWrite, + "SUNION": reqTypeRead, + "SUNIONSTORE": reqTypeWrite, + "SSCAN": reqTypeRead, + + "ZADD": reqTypeWrite, + "ZCARD": reqTypeRead, + "ZCOUNT": reqTypeRead, + "ZINCRBY": reqTypeWrite, + "ZINTERSTORE": reqTypeWrite, + "ZLEXCOUNT": reqTypeRead, + "ZRANGE": reqTypeRead, + "ZRANGEBYLEX": reqTypeRead, + "ZRANGEBYSCORE": reqTypeRead, + "ZRANK": reqTypeRead, + "ZREM": reqTypeWrite, + "ZREMRANGEBYLEX": reqTypeWrite, + "ZREMRANGEBYRANK": reqTypeWrite, + "ZREMRANGEBYSCORE": reqTypeWrite, + "ZREVRANGE": reqTypeRead, + "ZREVRANGEBYLEX": reqTypeRead, + "ZREVRANGEBYSCORE": reqTypeRead, + "ZREVRANK": reqTypeRead, + "ZSCORE": reqTypeRead, + "ZUNIONSTORE": reqTypeWrite, + "ZSCAN": reqTypeRead, + + "PFADD": reqTypeWrite, + "PFCOUNT": reqTypeRead, + "PFMERGE": reqTypeWrite, + "EVAL": reqTypeNotSupport, + "EVALSHA": reqTypeNotSupport, + "PING": reqTypeNotSupport, + "AUTH": reqTypeNotSupport, + "ECHO": reqTypeNotSupport, + "INFO": reqTypeNotSupport, + "PROXY": reqTypeNotSupport, + "SLOWLOG": reqTypeNotSupport, + "QUIT": reqTypeNotSupport, + "SELECT": reqTypeNotSupport, + "TIME": reqTypeNotSupport, + "CONFIG": reqTypeNotSupport, + } + + reqMap = critbit.New() + for key, tp := range tmpMap { + reqMap.Insert([]byte(key), tp) + } +} + +func getReqType(cmd []byte) reqType { + idx := bytes.IndexByte(cmd, lfByte) + if idx != -1 { + cmd = cmd[idx+1:] + } + + val, ok := reqMap.Get(cmd) + if !ok { + return reqTypeNotSupport + } + return val.(int) +} diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index f8177f07..c6aeb136 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -1,7 +1,6 @@ package redis import ( - "bytes" libnet "overlord/lib/net" "overlord/proto" "sync/atomic" @@ -58,9 +57,11 @@ func (nc *nodeConn) write(m *proto.Message) error { m.DoneWithError(ErrBadAssert) return ErrBadAssert } - if bytes.Equal(cmd.Cmd(), commandBytes) { + + if cmd.rtype == reqTypeNotSupport && cmd.reply.isZero() { return nil } + return cmd.respObj.encode(nc.rc.bw) } @@ -72,10 +73,11 @@ func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) (err error) { if !ok { return ErrBadAssert } - if bytes.Equal(cmd.Cmd(), commandBytes) { - cmd.reply = newRESPNull(respError) + + if cmd.rtype == reqTypeNotSupport { continue } + if err = nc.rc.decodeOne(cmd.reply); err != nil { return } diff --git a/proto/redis/request.go b/proto/redis/request.go index 71e403b3..525de516 100644 --- a/proto/redis/request.go +++ b/proto/redis/request.go @@ -15,11 +15,15 @@ var ( lfByte = byte('\n') movedBytes = []byte("MOVED") askBytes = []byte("ASK") + + pingReqBytes = []byte("4\r\nPING") ) var ( - robjGet = newRESPBulk([]byte("3\r\nGET")) - robjMSet = newRESPBulk([]byte("4\r\nMSET")) + robjGet = newRESPBulk([]byte("3\r\nGET")) + robjMSet = newRESPBulk([]byte("4\r\nMSET")) + robjErrNotSupport = newRESPPlain(respError, []byte("Error: command not support")) + robjPong = newRESPString([]byte("PONG")) cmdMSetLenBytes = []byte("3") cmdMSetBytes = []byte("4\r\nMSET") @@ -48,6 +52,7 @@ type Request struct { respObj *resp mergeType MergeType reply *resp + rtype reqType } // NewRequest will create new command by given args @@ -72,10 +77,27 @@ func NewRequest(cmd string, args ...string) *Request { func newRequest(robj *resp) *Request { r := &Request{respObj: robj} r.mergeType = getMergeType(robj.nth(0).data) - r.reply = &resp{} + r.rtype = getReqType(robj.nth(0).data) + if !setupSpecialReply(r) { + r.reply = &resp{} + } return r } +func setupSpecialReply(req *Request) bool { + data := req.respObj.nth(0).data + if bytes.Equal(data, pingReqBytes) { + req.reply = robjPong + return true + } + + if req.rtype == reqTypeNotSupport { + req.reply = robjErrNotSupport + return true + } + return false +} + func (c *Request) setRESP(robj *resp) { c.respObj = robj c.mergeType = getMergeType(robj.nth(0).data) diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 292a7922..6d3b0b0c 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -52,6 +52,7 @@ type resp struct { } func (r *resp) reset() { + r.rtype = 0 r.data = r.data[:0] r.arrayn = 0 for _, ar := range r.array { @@ -64,6 +65,10 @@ func newRESPInt(val int) *resp { return newRESPPlain(respInt, []byte(s)) } +func (r *resp) isZero() bool { + return r.rtype == 0 +} + func (r *resp) setInt(val int) { s := strconv.Itoa(val) r.setPlain(respInt, []byte(s)) From d03af340cd45566e07d04ec9050ebea922b90273 Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 24 Jul 2018 15:22:09 +0800 Subject: [PATCH 36/87] add rtype and reply zero check for node_conn --- proto/redis/filter.go | 31 ++++++++++++++++--------------- proto/redis/node_conn.go | 11 ++--------- proto/redis/request.go | 1 - 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/proto/redis/filter.go b/proto/redis/filter.go index 5b818dcf..5cdb4b8d 100644 --- a/proto/redis/filter.go +++ b/proto/redis/filter.go @@ -141,21 +141,22 @@ func init() { "ZUNIONSTORE": reqTypeWrite, "ZSCAN": reqTypeRead, - "PFADD": reqTypeWrite, - "PFCOUNT": reqTypeRead, - "PFMERGE": reqTypeWrite, - "EVAL": reqTypeNotSupport, - "EVALSHA": reqTypeNotSupport, - "PING": reqTypeNotSupport, - "AUTH": reqTypeNotSupport, - "ECHO": reqTypeNotSupport, - "INFO": reqTypeNotSupport, - "PROXY": reqTypeNotSupport, - "SLOWLOG": reqTypeNotSupport, - "QUIT": reqTypeNotSupport, - "SELECT": reqTypeNotSupport, - "TIME": reqTypeNotSupport, - "CONFIG": reqTypeNotSupport, + "PFADD": reqTypeWrite, + "PFCOUNT": reqTypeRead, + "PFMERGE": reqTypeWrite, + "EVAL": reqTypeNotSupport, + "EVALSHA": reqTypeNotSupport, + "PING": reqTypeNotSupport, + "AUTH": reqTypeNotSupport, + "ECHO": reqTypeNotSupport, + "INFO": reqTypeNotSupport, + "PROXY": reqTypeNotSupport, + "SLOWLOG": reqTypeNotSupport, + "QUIT": reqTypeNotSupport, + "SELECT": reqTypeNotSupport, + "TIME": reqTypeNotSupport, + "CONFIG": reqTypeNotSupport, + "COMMANDS": reqTypeNotSupport, } reqMap = critbit.New() diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index c6aeb136..7afff70d 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -58,7 +58,7 @@ func (nc *nodeConn) write(m *proto.Message) error { return ErrBadAssert } - if cmd.rtype == reqTypeNotSupport && cmd.reply.isZero() { + if cmd.rtype == reqTypeNotSupport && !cmd.reply.isZero() { return nil } @@ -74,7 +74,7 @@ func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) (err error) { return ErrBadAssert } - if cmd.rtype == reqTypeNotSupport { + if cmd.rtype == reqTypeNotSupport && !cmd.reply.isZero() { continue } @@ -96,10 +96,3 @@ func (nc *nodeConn) Close() error { } return nil } - -var ( - robjCluterNodes = newRESPArray([]*resp{ - newRESPBulk([]byte("7\r\nCLUSTER")), - newRESPBulk([]byte("5\r\nNODES")), - }) -) diff --git a/proto/redis/request.go b/proto/redis/request.go index 525de516..bcd81b99 100644 --- a/proto/redis/request.go +++ b/proto/redis/request.go @@ -31,7 +31,6 @@ var ( cmdGetBytes = []byte("3\r\nGET") cmdDelBytes = []byte("3\r\nDEL") cmdExistsBytes = []byte("6\r\nEXISTS") - commandBytes = []byte("7\r\nCOMMAND") ) // errors From ddb716f1070b3e95018849a1dfbc132d83ad475d Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 24 Jul 2018 15:27:02 +0800 Subject: [PATCH 37/87] change the command as upper for test --- proto/redis/node_conn_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto/redis/node_conn_test.go b/proto/redis/node_conn_test.go index 362a5c79..dab8e51a 100644 --- a/proto/redis/node_conn_test.go +++ b/proto/redis/node_conn_test.go @@ -74,7 +74,7 @@ func TestReadBatchWithNilError(t *testing.T) { nc := newNodeConn("baka", "127.0.0.1:12345", _createConn(nil)) mb := proto.NewMsgBatch() msg := proto.GetMsg() - msg.WithRequest(NewRequest("get", "a")) + msg.WithRequest(NewRequest("GET", "a")) mb.AddMsg(msg) err := nc.ReadBatch(mb) assert.Error(t, err) From 17f73a6bdd38105809c8144fb28996ff414e1aaf Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 24 Jul 2018 15:33:52 +0800 Subject: [PATCH 38/87] change ping command as reqTypeCtl --- proto/redis/filter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto/redis/filter.go b/proto/redis/filter.go index 5cdb4b8d..e9c7d817 100644 --- a/proto/redis/filter.go +++ b/proto/redis/filter.go @@ -146,7 +146,7 @@ func init() { "PFMERGE": reqTypeWrite, "EVAL": reqTypeNotSupport, "EVALSHA": reqTypeNotSupport, - "PING": reqTypeNotSupport, + "PING": reqTypeCtl, "AUTH": reqTypeNotSupport, "ECHO": reqTypeNotSupport, "INFO": reqTypeNotSupport, From 7338eb2b61f47155257f6d2edd828d389225e4e9 Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 24 Jul 2018 15:45:42 +0800 Subject: [PATCH 39/87] remove recycle use error --- proto/redis/request.go | 14 +++++--------- proto/redis/sub.go | 8 ++++---- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/proto/redis/request.go b/proto/redis/request.go index bcd81b99..981e98b7 100644 --- a/proto/redis/request.go +++ b/proto/redis/request.go @@ -16,15 +16,11 @@ var ( movedBytes = []byte("MOVED") askBytes = []byte("ASK") - pingReqBytes = []byte("4\r\nPING") + errNotSuportBytes = []byte("Error: command not support") ) var ( - robjGet = newRESPBulk([]byte("3\r\nGET")) - robjMSet = newRESPBulk([]byte("4\r\nMSET")) - robjErrNotSupport = newRESPPlain(respError, []byte("Error: command not support")) - robjPong = newRESPString([]byte("PONG")) - + cmdPingBytes = []byte("4\r\nPING") cmdMSetLenBytes = []byte("3") cmdMSetBytes = []byte("4\r\nMSET") cmdMGetBytes = []byte("4\r\nMGET") @@ -85,13 +81,13 @@ func newRequest(robj *resp) *Request { func setupSpecialReply(req *Request) bool { data := req.respObj.nth(0).data - if bytes.Equal(data, pingReqBytes) { - req.reply = robjPong + if bytes.Equal(data, cmdPingBytes) { + req.reply = newRESPPlain(respString, pongBytes) return true } if req.rtype == reqTypeNotSupport { - req.reply = robjErrNotSupport + req.reply = newRESPPlain(respError, errNotSuportBytes) return true } return false diff --git a/proto/redis/sub.go b/proto/redis/sub.go index 80b11154..15e4da62 100644 --- a/proto/redis/sub.go +++ b/proto/redis/sub.go @@ -65,7 +65,7 @@ func cmdMset(msg *proto.Message, robj *resp) error { cmdObj := respPool.Get().(*resp) cmdObj.rtype = respArray cmdObj.data = nil - cmdObj.replace(0, robjMSet) + cmdObj.replace(0, newRESPBulk(cmdMSetBytes)) cmdObj.replace(1, robj.nth(i*2+1)) cmdObj.replace(2, robj.nth(i*2+2)) cmdObj.data = cmdMSetLenBytes @@ -77,7 +77,7 @@ func cmdMset(msg *proto.Message, robj *resp) error { cmdObj := reqCmd.respObj cmdObj.rtype = respArray cmdObj.data = nil - cmdObj.replace(0, robjMSet) + cmdObj.replace(0, newRESPBulk(cmdMSetBytes)) cmdObj.replace(1, robj.nth(i*2+1)) cmdObj.replace(2, robj.nth(i*2+2)) cmdObj.data = cmdMSetLenBytes @@ -106,7 +106,7 @@ func cmdByKeys(msg *proto.Message, robj *resp) error { req := msg.NextReq() if req == nil { if bytes.Equal(robj.nth(0).data, cmdMGetBytes) { - cmdObj = newRESPArray([]*resp{robjGet, sub}) + cmdObj = newRESPArray([]*resp{newRESPBulk(cmdGetBytes), sub}) } else { cmdObj = newRESPArray([]*resp{robj.nth(0), sub}) } @@ -116,7 +116,7 @@ func cmdByKeys(msg *proto.Message, robj *resp) error { reqCmd := req.(*Request) cmdObj := reqCmd.respObj if bytes.Equal(robj.nth(0).data, cmdMGetBytes) { - cmdObj.setArray([]*resp{robjGet, sub}) + cmdObj.setArray([]*resp{newRESPBulk(cmdGetBytes), sub}) } else { cmdObj.setArray([]*resp{robj.nth(0), sub}) } From f044f63b84cf7061c574a964d56bf924c42eebb0 Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 24 Jul 2018 16:01:25 +0800 Subject: [PATCH 40/87] add encodeReply for reqTypeCtl or reqTypeNotSupport --- proto/redis/node_conn.go | 4 ++-- proto/redis/proxy_conn.go | 21 ++++++++++++++++++++- proto/redis/request.go | 20 +------------------- proto/redis/resp.go | 4 ---- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index 7afff70d..33a92e3c 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -58,7 +58,7 @@ func (nc *nodeConn) write(m *proto.Message) error { return ErrBadAssert } - if cmd.rtype == reqTypeNotSupport && !cmd.reply.isZero() { + if cmd.rtype == reqTypeNotSupport && cmd.rtype == reqTypeCtl { return nil } @@ -74,7 +74,7 @@ func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) (err error) { return ErrBadAssert } - if cmd.rtype == reqTypeNotSupport && !cmd.reply.isZero() { + if cmd.rtype == reqTypeNotSupport && cmd.rtype == reqTypeCtl { continue } diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 5f5ea9be..0c4085ba 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -1,6 +1,7 @@ package redis import ( + "bytes" stderrs "errors" "overlord/lib/conv" libnet "overlord/lib/net" @@ -16,6 +17,11 @@ var ( ErrBadCount = stderrs.New("bad count number") ) +var ( + robjErrNotSupport = newRESPBulk([]byte("Error: command not support")) + robjPong = newRESPPlain(respString, pongBytes) +) + type proxyConn struct { rc *respConn } @@ -52,7 +58,7 @@ func (pc *proxyConn) merge(msg *proto.Message) error { return ErrBadAssert } if !msg.IsBatch() { - return cmd.reply.encode(pc.rc.bw) + return pc.encodeReply(cmd) } mtype, err := pc.getBatchMergeType(msg) if err != nil { @@ -72,6 +78,19 @@ func (pc *proxyConn) merge(msg *proto.Message) error { } } +func (pc *proxyConn) encodeReply(req *Request) error { + if req.rtype == reqTypeNotSupport { + return robjErrNotSupport.encode(pc.rc.bw) + } + + data := req.respObj.nth(0).data + if bytes.Equal(data, cmdPingBytes) { + return robjPong.encode(pc.rc.bw) + } + + return req.reply.encode(pc.rc.bw) +} + func (pc *proxyConn) mergeOk(msg *proto.Message) (err error) { for _, sub := range msg.Subs() { if err = sub.Err(); err != nil { diff --git a/proto/redis/request.go b/proto/redis/request.go index 981e98b7..5dd95896 100644 --- a/proto/redis/request.go +++ b/proto/redis/request.go @@ -15,8 +15,6 @@ var ( lfByte = byte('\n') movedBytes = []byte("MOVED") askBytes = []byte("ASK") - - errNotSuportBytes = []byte("Error: command not support") ) var ( @@ -73,26 +71,10 @@ func newRequest(robj *resp) *Request { r := &Request{respObj: robj} r.mergeType = getMergeType(robj.nth(0).data) r.rtype = getReqType(robj.nth(0).data) - if !setupSpecialReply(r) { - r.reply = &resp{} - } + r.reply = &resp{} return r } -func setupSpecialReply(req *Request) bool { - data := req.respObj.nth(0).data - if bytes.Equal(data, cmdPingBytes) { - req.reply = newRESPPlain(respString, pongBytes) - return true - } - - if req.rtype == reqTypeNotSupport { - req.reply = newRESPPlain(respError, errNotSuportBytes) - return true - } - return false -} - func (c *Request) setRESP(robj *resp) { c.respObj = robj c.mergeType = getMergeType(robj.nth(0).data) diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 6d3b0b0c..ac85213f 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -65,10 +65,6 @@ func newRESPInt(val int) *resp { return newRESPPlain(respInt, []byte(s)) } -func (r *resp) isZero() bool { - return r.rtype == 0 -} - func (r *resp) setInt(val int) { s := strconv.Itoa(val) r.setPlain(respInt, []byte(s)) From 49d4a299f088882fbff01b4575808efd14a932fa Mon Sep 17 00:00:00 2001 From: felixhao Date: Tue, 24 Jul 2018 16:03:55 +0800 Subject: [PATCH 41/87] some adjust --- cmd/proxy/proxy-cluster-example.toml | 6 ++-- cmd/proxy/redis.toml | 35 --------------------- lib/conv/conv.go | 47 ++++------------------------ lib/hashkit/ketama.go | 15 ++------- lib/hashkit/ketama_test.go | 2 +- proto/memcache/binary/node_conn.go | 12 ------- proto/memcache/mc.go | 1 - proto/memcache/merge.go | 43 ------------------------- proto/memcache/node_conn.go | 13 -------- proto/memcache/proxy_conn.go | 28 +++++++++++++++-- proto/redis/proxy_conn.go | 3 +- proto/redis/resp_conn.go | 3 -- proxy/cluster.go | 24 ++++---------- proxy/proxy.go | 20 ++++++------ 14 files changed, 54 insertions(+), 198 deletions(-) delete mode 100644 cmd/proxy/redis.toml delete mode 100644 proto/memcache/mc.go delete mode 100644 proto/memcache/merge.go diff --git a/cmd/proxy/proxy-cluster-example.toml b/cmd/proxy/proxy-cluster-example.toml index 59fbc04d..265940fb 100644 --- a/cmd/proxy/proxy-cluster-example.toml +++ b/cmd/proxy/proxy-cluster-example.toml @@ -1,6 +1,6 @@ [[clusters]] # This be used to specify the name of cache cluster. -name = "test-cluster" +name = "test-mc" # The name of the hash function. Possible values are: sha1. hash_method = "fnv1a_64" # The key distribution mode. Possible values are: ketama. @@ -34,7 +34,7 @@ servers = [ [[clusters]] # This be used to specify the name of cache cluster. -name = "redis" +name = "test-redis" # The name of the hash function. Possible values are: sha1. hash_method = "fnv1a_64" # The key distribution mode. Possible values are: ketama. @@ -46,7 +46,7 @@ cache_type = "redis" # proxy listen proto: tcp | unix listen_proto = "tcp" # proxy listen addr: tcp addr | unix sock path -listen_addr = "0.0.0.0:22211" +listen_addr = "0.0.0.0:26379" # Authenticate to the Redis server on connect. redis_auth = "" # The dial timeout value in msec that we wait for to establish a connection to the server. By default, we wait indefinitely. diff --git a/cmd/proxy/redis.toml b/cmd/proxy/redis.toml deleted file mode 100644 index 23d7a2aa..00000000 --- a/cmd/proxy/redis.toml +++ /dev/null @@ -1,35 +0,0 @@ -[[clusters]] -# This be used to specify the name of cache cluster. -name = "redis-cluster" -# The name of the hash function. Possible values are: sha1. -hash_method = "crc16" -# Thhe key distribution mode. Possible values are: ketama. -hash_distribution = "redis_cluster" -# A two character string that specifies the part of the key used for hashing. Eg "{}". -hash_tag = "" -# cache type: memcache | redis -cache_type = "redis" -# proxy listen proto: tcp | unix -listen_proto = "tcp" -# proxy listen addr: tcp addr | unix sock path -listen_addr = "0.0.0.0:26379" -# Authenticate to the Redis server on connect. -redis_auth = "" -# The dial timeout value in msec that we wait for to establish a connection to the server. By default, we wait indefinitely. -dial_timeout = 1000 -# The read timeout value in msec that we wait for to receive a response from a server. By default, we wait indefinitely. -read_timeout = 1000 -# The write timeout value in msec that we wait for to write a response to a server. By default, we wait indefinitely. -write_timeout = 1000 -# The number of connections that can be opened to each server. By default, we open at most 1 server connection. -node_connections = 10 -# The number of consecutive failures on a server that would lead to it being temporarily ejected when auto_eject is set to true. Defaults to 3. -ping_fail_limit = 3 -# A boolean value that controls if server should be ejected temporarily when it fails consecutively ping_fail_limit times. -ping_auto_eject = true -# A list of server address, port and weight (name:port:weight or ip:port:weight) for this server pool. Also you can use alias name like: ip:port:weight alias. -servers = [ - "172.22.15.29:7000:1", - "172.22.15.29:7001:1", - "172.22.15.29:7002:1", -] diff --git a/lib/conv/conv.go b/lib/conv/conv.go index 6ed1d86e..386598d3 100644 --- a/lib/conv/conv.go +++ b/lib/conv/conv.go @@ -4,27 +4,7 @@ import ( "strconv" ) -const ( - maxCmdLen = 64 -) - -var ( - charmap [256]byte -) - -func init() { - for i := range charmap { - c := byte(i) - switch { - case c >= 'A' && c <= 'Z': - charmap[i] = c + 'a' - 'A' - case c >= 'a' && c <= 'z': - charmap[i] = c - } - } -} - -// btoi returns the corresponding value i. +// Btoi returns the corresponding value i. func Btoi(b []byte) (int64, error) { if len(b) != 0 && len(b) < 10 { var neg, i = false, 0 @@ -57,35 +37,20 @@ func Btoi(b []byte) (int64, error) { // UpdateToLower will convert to lower case func UpdateToLower(src []byte) { + const step = byte('a') - byte('A') for i := range src { - if c := charmap[src[i]]; c != 0 { - src[i] = c + if src[i] >= 'A' || src[i] <= 'Z' { + src[i] += step } } } -const ( - step = byte('a') - byte('A') - lowerBegin = byte('a') - lowerEnd = byte('z') -) - // UpdateToUpper will convert to lower case func UpdateToUpper(src []byte) { + const step = byte('a') - byte('A') for i := range src { - if src[i] < lowerBegin || src[i] > lowerEnd { + if src[i] >= 'a' || src[i] <= 'z' { src[i] -= step } } } - -// ToLower returns a copy of the string s with all Unicode letters mapped to their lower case. -func ToLower(src []byte) []byte { - var lower [maxCmdLen]byte - for i := range src { - if c := charmap[src[i]]; c != 0 { - lower[i] = c - } - } - return lower[:len(src)] -} diff --git a/lib/hashkit/ketama.go b/lib/hashkit/ketama.go index af0b2aaa..3839d109 100644 --- a/lib/hashkit/ketama.go +++ b/lib/hashkit/ketama.go @@ -54,13 +54,8 @@ func newRingWithHash(hash func([]byte) uint) (h *HashRing) { return } -// UpdateSlot fake impl -func (h *HashRing) UpdateSlot(node string, slot int) { -} - // Init init hash ring with nodes. -func (h *HashRing) Init(nodes []string, sl ...[]int) { - spots := sl[0] +func (h *HashRing) Init(nodes []string, spots []int) { if len(nodes) != len(spots) { panic("nodes length not equal spots length") } @@ -115,13 +110,12 @@ func (h *HashRing) ketamaHash(key string, kl, alignment int) (v uint) { // AddNode a new node to the hash ring. // n: name of the server // s: multiplier for default number of ticks (useful when one cache node has more resources, like RAM, than another) -func (h *HashRing) AddNode(node string, args ...int) { +func (h *HashRing) AddNode(node string, spot int) { var ( tmpNode []string tmpSpot []int exitst bool ) - spot := args[0] h.lock.Lock() for i, nd := range h.nodes { tmpNode = append(tmpNode, nd) @@ -167,11 +161,6 @@ func (h *HashRing) DelNode(n string) { } } -// Hash returns result node. -func (h *HashRing) Hash(key []byte) (string, bool) { - return h.GetNode(key) -} - // GetNode returns result node by given key. func (h *HashRing) GetNode(key []byte) (string, bool) { ts, ok := h.ticks.Load().(*tickArray) diff --git a/lib/hashkit/ketama_test.go b/lib/hashkit/ketama_test.go index 4407bc0d..b9c50476 100644 --- a/lib/hashkit/ketama_test.go +++ b/lib/hashkit/ketama_test.go @@ -58,7 +58,7 @@ func testHash(t *testing.T) { for i := 0; i < 1e6; i++ { s := "test value" + strconv.FormatUint(uint64(i), 10) bs := []byte(s) - n, ok := ring.Hash(bs) + n, ok := ring.GetNode(bs) if !ok { if !delAll { t.Error("unexpected not ok???") diff --git a/proto/memcache/binary/node_conn.go b/proto/memcache/binary/node_conn.go index 3bafdfdd..10ad0092 100644 --- a/proto/memcache/binary/node_conn.go +++ b/proto/memcache/binary/node_conn.go @@ -3,7 +3,6 @@ package binary import ( "bytes" "encoding/binary" - stderr "errors" "io" "sync/atomic" "time" @@ -21,11 +20,6 @@ const ( handlerClosed = int32(1) ) -// errors -var ( - ErrNotImpl = stderr.New("i am groot") -) - type nodeConn struct { cluster string addr string @@ -208,9 +202,3 @@ func (n *nodeConn) Close() error { func (n *nodeConn) Closed() bool { return atomic.LoadInt32(&n.closed) == handlerClosed } - -// FetchSlots was not supported in mc. -func (n *nodeConn) FetchSlots() (ndoes []string, slots [][]int, err error) { - err = ErrNotImpl - return -} diff --git a/proto/memcache/mc.go b/proto/memcache/mc.go deleted file mode 100644 index 89883a21..00000000 --- a/proto/memcache/mc.go +++ /dev/null @@ -1 +0,0 @@ -package memcache diff --git a/proto/memcache/merge.go b/proto/memcache/merge.go deleted file mode 100644 index 8a33e520..00000000 --- a/proto/memcache/merge.go +++ /dev/null @@ -1,43 +0,0 @@ -package memcache - -import ( - "bytes" - "overlord/proto" -) - -// Merge the response and set the response into subResps -func (p *proxyConn) Merge(m *proto.Message) error { - mcr, ok := m.Request().(*MCRequest) - if !ok { - p.bw.Write(serverErrorPrefixBytes) - p.bw.Write([]byte(ErrAssertReq.Error())) - p.bw.Write(crlfBytes) - // m.AddSubResps(serverErrorPrefixBytes, []byte(ErrAssertReq.Error()), crlfBytes) - return nil - } - if !m.IsBatch() { - p.bw.Write(mcr.data) - // m.AddSubResps(mcr.data) - return nil - } - - _, withValue := withValueTypes[mcr.rTp] - trimEnd := withValue - for _, sub := range m.Subs() { - submcr := sub.Request().(*MCRequest) - bs := submcr.data - if trimEnd { - bs = bytes.TrimSuffix(submcr.data, endBytes) - } - if len(bs) == 0 { - continue - } - p.bw.Write(bs) - // m.AddSubResps(bs) - } - if trimEnd { - p.bw.Write(endBytes) - // m.AddSubResps(endBytes) - } - return nil -} diff --git a/proto/memcache/node_conn.go b/proto/memcache/node_conn.go index f2782e17..65cbbb09 100644 --- a/proto/memcache/node_conn.go +++ b/proto/memcache/node_conn.go @@ -11,8 +11,6 @@ import ( "overlord/lib/prom" "overlord/proto" - stderr "errors" - "github.com/pkg/errors" ) @@ -21,11 +19,6 @@ const ( handlerClosed = int32(1) ) -// errors -var ( - ErrNotImpl = stderr.New("i am groot") -) - type nodeConn struct { cluster string addr string @@ -211,9 +204,3 @@ func (n *nodeConn) Close() error { func (n *nodeConn) Closed() bool { return atomic.LoadInt32(&n.closed) == handlerClosed } - -// FetchSlots was not supported in mc. -func (n *nodeConn) FetchSlots() (ndoes []string, slots [][]int, err error) { - err = ErrNotImpl - return -} diff --git a/proto/memcache/proxy_conn.go b/proto/memcache/proxy_conn.go index adc19e44..8c370b68 100644 --- a/proto/memcache/proxy_conn.go +++ b/proto/memcache/proxy_conn.go @@ -20,8 +20,7 @@ const ( ) var ( - serverErrorBytes = []byte(serverErrorPrefix) - serverErrorPrefixBytes = []byte("SERVER_ERROR ") + serverErrorBytes = []byte(serverErrorPrefix) ) type proxyConn struct { @@ -350,7 +349,30 @@ func (p *proxyConn) Encode(m *proto.Message) (err error) { _ = p.bw.Write([]byte(se)) _ = p.bw.Write(crlfBytes) } else { - _ = p.Merge(m) + var bs []byte + reqs := m.Requests() + for _, req := range reqs { + mcr, ok := req.(*MCRequest) + if !ok { + _ = p.bw.Write(serverErrorBytes) + _ = p.bw.Write([]byte(ErrAssertReq.Error())) + _ = p.bw.Write(crlfBytes) + } else { + _, ok := withValueTypes[mcr.rTp] + if ok && m.IsBatch() { + bs = bytes.TrimSuffix(mcr.data, endBytes) + } else { + bs = mcr.data + } + if len(bs) == 0 { + continue + } + _ = p.bw.Write(bs) + } + } + if m.IsBatch() { + _ = p.bw.Write(endBytes) + } } if err = p.bw.Flush(); err != nil { err = errors.Wrap(err, "MC Encoder encode response flush bytes") diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 5f5ea9be..d88dca9e 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -2,10 +2,11 @@ package redis import ( stderrs "errors" + "strconv" + "overlord/lib/conv" libnet "overlord/lib/net" "overlord/proto" - "strconv" "github.com/pkg/errors" ) diff --git a/proto/redis/resp_conn.go b/proto/redis/resp_conn.go index 05a0887e..e00adca8 100644 --- a/proto/redis/resp_conn.go +++ b/proto/redis/resp_conn.go @@ -137,7 +137,6 @@ func (rc *respConn) decodeRESP(robj *resp) (err error) { rc.decodePlain(line, robj) case respBulk: // decode bulkString - // fmt.Printf("line:%s\n", strconv.Quote(string(line))) err = rc.decodeBulk(line, robj) case respArray: err = rc.decodeArray(line, robj) @@ -152,7 +151,6 @@ func (rc *respConn) decodePlain(line []byte, robj *resp) { func (rc *respConn) decodeBulk(line []byte, robj *resp) error { lineSize := len(line) sizeBytes := line[1 : lineSize-2] - // fmt.Printf("size:%s\n", strconv.Quote(string(sizeBytes))) size, err := decodeInt(sizeBytes) if err != nil { return err @@ -164,7 +162,6 @@ func (rc *respConn) decodeBulk(line []byte, robj *resp) error { rc.br.Advance(-(lineSize - 1)) fullDataSize := lineSize - 1 + size + 2 data, err := rc.br.ReadExact(fullDataSize) - // fmt.Printf("data:%s\n", strconv.Quote(string(data))) if err == bufio.ErrBufferFull { rc.br.Advance(-1) return err diff --git a/proxy/cluster.go b/proxy/cluster.go index c3d80db3..cb0ea0a9 100644 --- a/proxy/cluster.go +++ b/proxy/cluster.go @@ -31,7 +31,7 @@ var ( type pinger struct { ping proto.NodeConn node string - weight []int + weight int failure int retries int @@ -71,8 +71,6 @@ type Cluster struct { aliasMap map[string]int nodeChan map[int]*batchChanel - syncConn proto.NodeConn - lock sync.Mutex closed bool } @@ -90,8 +88,7 @@ func NewCluster(ctx context.Context, cc *ClusterConfig) (c *Cluster) { c.hashTag = []byte{cc.HashTag[0], cc.HashTag[1]} } c.alias = alias - - var wlist [][]int + // hash ring ring := hashkit.NewRing(cc.HashDistribution, cc.HashMethod) if cc.CacheType == proto.CacheTypeMemcache || cc.CacheType == proto.CacheTypeMemcacheBinary || cc.CacheType == proto.CacheTypeRedis { if c.alias { @@ -99,10 +96,6 @@ func NewCluster(ctx context.Context, cc *ClusterConfig) (c *Cluster) { } else { ring.Init(addrs, ws) } - wlist = make([][]int, len(ws)) - for i, w := range ws { - wlist[i] = []int{w} - } } else { panic("unsupported protocol") } @@ -110,7 +103,6 @@ func NewCluster(ctx context.Context, cc *ClusterConfig) (c *Cluster) { nodeChan := make(map[int]*batchChanel) nodeMap := make(map[string]int) aliasMap := make(map[string]int) - for i := range addrs { nbc := newBatchChanel(cc.NodeConnections) go c.processBatch(nbc, addrs[i]) @@ -126,7 +118,7 @@ func NewCluster(ctx context.Context, cc *ClusterConfig) (c *Cluster) { c.nodeMap = nodeMap c.ring = ring if c.cc.PingAutoEject { - go c.startPinger(c.cc, addrs, wlist) + go c.startPinger(c.cc, addrs, ws) } return } @@ -148,10 +140,7 @@ func (c *Cluster) calculateBatchIndex(key []byte) int { // DispatchBatch delivers all the messages to batch execute by hash func (c *Cluster) DispatchBatch(mbs []*proto.MsgBatch, slice []*proto.Message) { // TODO: dynamic update mbs by add more than configrured nodes - var ( - bidx int - ) - + var bidx int for _, msg := range slice { if msg.IsBatch() { for _, sub := range msg.Batch() { @@ -163,7 +152,6 @@ func (c *Cluster) DispatchBatch(mbs []*proto.MsgBatch, slice []*proto.Message) { mbs[bidx].AddMsg(msg) } } - c.deliver(mbs) } @@ -227,7 +215,7 @@ func (c *Cluster) processReadBatch(r proto.NodeConn, mb *proto.MsgBatch) error { return err } -func (c *Cluster) startPinger(cc *ClusterConfig, addrs []string, ws [][]int) { +func (c *Cluster) startPinger(cc *ClusterConfig, addrs []string, ws []int) { for idx, addr := range addrs { w := ws[idx] nc := newNodeConn(cc, addr) @@ -251,7 +239,7 @@ func (c *Cluster) processPing(p *pinger) { } else { p.failure = 0 if del { - c.ring.AddNode(p.node, p.weight...) + c.ring.AddNode(p.node, p.weight) del = false } } diff --git a/proxy/proxy.go b/proxy/proxy.go index 161e8525..7ba1b70f 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -12,6 +12,7 @@ import ( "overlord/proto" "overlord/proto/memcache" mcbin "overlord/proto/memcache/binary" + "overlord/proto/redis" "github.com/pkg/errors" ) @@ -88,22 +89,19 @@ func (p *Proxy) serve(cc *ClusterConfig) { if p.c.Proxy.MaxConnections > 0 { if conns := atomic.AddInt32(&p.conns, 1); conns > p.c.Proxy.MaxConnections { // cache type + var encoder proto.ProxyConn switch cc.CacheType { case proto.CacheTypeMemcache: - encoder := memcache.NewProxyConn(libnet.NewConn(conn, time.Second, time.Second)) - m := proto.ErrMessage(ErrProxyMoreMaxConns) - _ = encoder.Encode(m) - _ = conn.Close() + encoder = memcache.NewProxyConn(libnet.NewConn(conn, time.Second, time.Second)) case proto.CacheTypeMemcacheBinary: - encoder := mcbin.NewProxyConn(libnet.NewConn(conn, time.Second, time.Second)) - m := proto.ErrMessage(ErrProxyMoreMaxConns) - _ = encoder.Encode(m) - _ = conn.Close() + encoder = mcbin.NewProxyConn(libnet.NewConn(conn, time.Second, time.Second)) case proto.CacheTypeRedis: - // TODO(felix): support redis. - default: - _ = conn.Close() + encoder = redis.NewProxyConn(libnet.NewConn(conn, time.Second, time.Second)) } + if encoder != nil { + _ = encoder.Encode(proto.ErrMessage(ErrProxyMoreMaxConns)) + } + _ = conn.Close() if log.V(3) { log.Warnf("proxy reject connection count(%d) due to more than max(%d)", conns, p.c.Proxy.MaxConnections) } From 9bcec0188bf9d64a4b31287236fe5e5e60b325be Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 24 Jul 2018 16:04:17 +0800 Subject: [PATCH 42/87] fixed of suspect condition of judgement --- proto/redis/node_conn.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index 33a92e3c..f4778ec4 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -58,7 +58,7 @@ func (nc *nodeConn) write(m *proto.Message) error { return ErrBadAssert } - if cmd.rtype == reqTypeNotSupport && cmd.rtype == reqTypeCtl { + if cmd.rtype == reqTypeNotSupport || cmd.rtype == reqTypeCtl { return nil } @@ -74,7 +74,7 @@ func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) (err error) { return ErrBadAssert } - if cmd.rtype == reqTypeNotSupport && cmd.rtype == reqTypeCtl { + if cmd.rtype == reqTypeNotSupport || cmd.rtype == reqTypeCtl { continue } From e2bd0b9e2cd55aa6ea40722663470477bda5796d Mon Sep 17 00:00:00 2001 From: felixhao Date: Tue, 24 Jul 2018 16:07:36 +0800 Subject: [PATCH 43/87] changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c006d1d..98b458ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Overlord +## Version 1.2.0 +1. add redis protocol support. + ## Version 1.1.0 1. add memcache binary protocol support. 2. add conf file check From 11da885131d5bddf0c3562c3d4ae7c2596d4dca1 Mon Sep 17 00:00:00 2001 From: felixhao Date: Tue, 24 Jul 2018 16:13:08 +0800 Subject: [PATCH 44/87] rm fmt.Println --- lib/bufio/io.go | 1 - proto/redis/resp.go | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/bufio/io.go b/lib/bufio/io.go index 1686be05..52b8239f 100644 --- a/lib/bufio/io.go +++ b/lib/bufio/io.go @@ -242,7 +242,6 @@ func (w *Writer) Flush() error { if len(w.bufs) == 0 { return nil } - // fmt.Println("buf:", w.bufs[:w.cursor]) w.bufsp = net.Buffers(w.bufs[:w.cursor]) _, err := w.wr.Writev(&w.bufsp) if err != nil { diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 6d3b0b0c..c6492cc5 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -323,7 +323,6 @@ const ( ) func getMergeType(cmd []byte) MergeType { - // fmt.Println("mtype :", strconv.Quote(string(cmd))) // TODO: impl with tire tree to search quickly if bytes.Equal(cmd, cmdMGetBytes) || bytes.Equal(cmd, cmdGetBytes) { return MergeTypeJoin From 4ff7724d8c56a676e4f68b21f955d0665d03c090 Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 24 Jul 2018 17:41:58 +0800 Subject: [PATCH 45/87] change getReqType of by using Bytes.Contains --- proto/redis/filter.go | 298 +++++++++++++++++++------------------- proto/redis/proxy_conn.go | 2 +- 2 files changed, 149 insertions(+), 151 deletions(-) diff --git a/proto/redis/filter.go b/proto/redis/filter.go index e9c7d817..cacadd2a 100644 --- a/proto/redis/filter.go +++ b/proto/redis/filter.go @@ -18,162 +18,160 @@ const ( var reqMap *critbit.Tree -func init() { - - tmpMap := map[string]reqType{ - "DEL": reqTypeWrite, - "DUMP": reqTypeRead, - "EXISTS": reqTypeRead, - "EXPIRE": reqTypeWrite, - "EXPIREAT": reqTypeWrite, - "KEYS": reqTypeNotSupport, - "MIGRATE": reqTypeNotSupport, - "MOVE": reqTypeNotSupport, - "OBJECT": reqTypeNotSupport, - "PERSIST": reqTypeWrite, - "PEXPIRE": reqTypeWrite, - "PEXPIREAT": reqTypeWrite, - "PTTL": reqTypeRead, - - "RANDOMKEY": reqTypeNotSupport, - "RENAME": reqTypeNotSupport, - "RENAMENX": reqTypeNotSupport, - "RESTORE": reqTypeWrite, - "SCAN": reqTypeNotSupport, - "SORT": reqTypeWrite, - "TTL": reqTypeRead, - "TYPE": reqTypeRead, - "WAIT": reqTypeNotSupport, - "APPEND": reqTypeWrite, - "BITCOUNT": reqTypeRead, - "BITOP": reqTypeNotSupport, - "BITPOS": reqTypeRead, - "DECR": reqTypeWrite, - "DECRBY": reqTypeWrite, - "GET": reqTypeRead, - "GETBIT": reqTypeRead, - "GETRANGE": reqTypeRead, - "GETSET": reqTypeWrite, - "INCR": reqTypeWrite, - "INCRBY": reqTypeWrite, - "INCRBYFLOAT": reqTypeWrite, - - "MGET": reqTypeRead, - "MSET": reqTypeWrite, - "MSETNX": reqTypeNotSupport, - "PSETEX": reqTypeWrite, - "SET": reqTypeWrite, - "SETBIT": reqTypeWrite, - "SETEX": reqTypeWrite, - "SETNX": reqTypeWrite, - "SETRANGE": reqTypeWrite, - "STRLEN": reqTypeRead, - - "HDEL": reqTypeWrite, - "HEXISTS": reqTypeRead, - "HGET": reqTypeRead, - "HGETALL": reqTypeRead, - "HINCRBY": reqTypeWrite, - "HINCRBYFLOAT": reqTypeWrite, - "HKEYS": reqTypeRead, - "HLEN": reqTypeRead, - "HMGET": reqTypeRead, - "HMSET": reqTypeWrite, - "HSET": reqTypeWrite, - "HSETNX": reqTypeWrite, - "HSTRLEN": reqTypeRead, - "HVALS": reqTypeRead, - "HSCAN": reqTypeRead, - "BLPOP": reqTypeNotSupport, - "BRPOP": reqTypeNotSupport, - "BRPOPLPUSH": reqTypeNotSupport, - - "LINDEX": reqTypeRead, - "LINSERT": reqTypeWrite, - "LLEN": reqTypeRead, - "LPOP": reqTypeWrite, - "LPUSH": reqTypeWrite, - "LPUSHX": reqTypeWrite, - "LRANGE": reqTypeRead, - "LREM": reqTypeWrite, - "LSET": reqTypeWrite, - "LTRIM": reqTypeWrite, - "RPOP": reqTypeWrite, - "RPOPLPUSH": reqTypeWrite, - "RPUSH": reqTypeWrite, - "RPUSHX": reqTypeWrite, - - "SADD": reqTypeWrite, - "SCARD": reqTypeRead, - "SDIFF": reqTypeRead, - "SDIFFSTORE": reqTypeWrite, - "SINTER": reqTypeRead, - "SINTERSTORE": reqTypeWrite, - "SISMEMBER": reqTypeRead, - "SMEMBERS": reqTypeRead, - "SMOVE": reqTypeWrite, - "SPOP": reqTypeWrite, - "SRANDMEMBER": reqTypeRead, - "SREM": reqTypeWrite, - "SUNION": reqTypeRead, - "SUNIONSTORE": reqTypeWrite, - "SSCAN": reqTypeRead, - - "ZADD": reqTypeWrite, - "ZCARD": reqTypeRead, - "ZCOUNT": reqTypeRead, - "ZINCRBY": reqTypeWrite, - "ZINTERSTORE": reqTypeWrite, - "ZLEXCOUNT": reqTypeRead, - "ZRANGE": reqTypeRead, - "ZRANGEBYLEX": reqTypeRead, - "ZRANGEBYSCORE": reqTypeRead, - "ZRANK": reqTypeRead, - "ZREM": reqTypeWrite, - "ZREMRANGEBYLEX": reqTypeWrite, - "ZREMRANGEBYRANK": reqTypeWrite, - "ZREMRANGEBYSCORE": reqTypeWrite, - "ZREVRANGE": reqTypeRead, - "ZREVRANGEBYLEX": reqTypeRead, - "ZREVRANGEBYSCORE": reqTypeRead, - "ZREVRANK": reqTypeRead, - "ZSCORE": reqTypeRead, - "ZUNIONSTORE": reqTypeWrite, - "ZSCAN": reqTypeRead, +var ( + reqReadBytes = []byte("" + + "4\r\nDUMP" + + "6\r\nEXISTS" + + "4\r\nPTTL" + + "3\r\nTTL" + + "4\r\nTYPE" + + "8\r\nBITCOUNT" + + "6\r\nBITPOS" + + "3\r\nGET" + + "6\r\nGETBIT" + + "8\r\nGETRANGE" + + "4\r\nMGET" + + "6\r\nSTRLEN" + + "7\r\nHEXISTS" + + "4\r\nHGET" + + "7\r\nHGETALL" + + "5\r\nHKEYS" + + "4\r\nHLEN" + + "5\r\nHMGET" + + "7\r\nHSTRLEN" + + "5\r\nHVALS" + + "5\r\nHSCAN" + + "5\r\nSCARD" + + "5\r\nSDIFF" + + "6\r\nSINTER" + + "9\r\nSISMEMBER" + + "8\r\nSMEMBERS" + + "11\r\nSRANDMEMBER" + + "6\r\nSUNION" + + "5\r\nSSCAN" + + "5\r\nZCARD" + + "6\r\nZCOUNT" + + "9\r\nZLEXCOUNT" + + "6\r\nZRANGE" + + "11\r\nZRANGEBYLEX" + + "13\r\nZRANGEBYSCORE" + + "5\r\nZRANK" + + "9\r\nZREVRANGE" + + "14\r\nZREVRANGEBYLEX" + + "16\r\nZREVRANGEBYSCORE" + + "8\r\nZREVRANK" + + "6\r\nZSCORE" + + "5\r\nZSCAN" + + "6\r\nLINDEX" + + "4\r\nLLEN" + + "6\r\nLRANGE" + + "7\r\nPFCOUNT") + + reqWriteBytes = []byte("" + + "3\r\nDEL" + + "6\r\nEXPIRE" + + "8\r\nEXPIREAT" + + "7\r\nPERSIST" + + "7\r\nPEXPIRE" + + "9\r\nPEXPIREAT" + + "7\r\nRESTORE" + + "4\r\nSORT" + + "6\r\nAPPEND" + + "4\r\nDECR" + + "6\r\nDECRBY" + + "6\r\nGETSET" + + "4\r\nINCR" + + "6\r\nINCRBY" + + "11\r\nINCRBYFLOAT" + + "4\r\nMSET" + + "6\r\nPSETEX" + + "3\r\nSET" + + "6\r\nSETBIT" + + "5\r\nSETEX" + + "5\r\nSETNX" + + "8\r\nSETRANGE" + + "4\r\nHDEL" + + "7\r\nHINCRBY" + + "12\r\nHINCRBYFLOAT" + + "5\r\nHMSET" + + "4\r\nHSET" + + "6\r\nHSETNX" + + "7\r\nLINSERT" + + "4\r\nLPOP" + + "5\r\nLPUSH" + + "6\r\nLPUSHX" + + "4\r\nLREM" + + "4\r\nLSET" + + "5\r\nLTRIM" + + "4\r\nRPOP" + + "9\r\nRPOPLPUSH" + + "5\r\nRPUSH" + + "6\r\nRPUSHX" + + "4\r\nSADD" + + "10\r\nSDIFFSTORE" + + "11\r\nSINTERSTORE" + + "5\r\nSMOVE" + + "4\r\nSPOP" + + "4\r\nSREM" + + "11\r\nSUNIONSTORE" + + "4\r\nZADD" + + "7\r\nZINCRBY" + + "11\r\nZINTERSTORE" + + "4\r\nZREM" + + "14\r\nZREMRANGEBYLEX" + + "15\r\nZREMRANGEBYRANK" + + "16\r\nZREMRANGEBYSCORE" + + "11\r\nZUNIONSTORE" + + "5\r\nPFADD" + + "7\r\nPFMERGE") + + reqNotSupportBytes = []byte("" + + "6\r\nMSETNX" + + "5\r\nBLPOP" + + "5\r\nBRPOP" + + "10\r\nBRPOPLPUSH" + + "4\r\nKEYS" + + "7\r\nMIGRATE" + + "4\r\nMOVE" + + "6\r\nOBJECT" + + "9\r\nRANDOMKEY" + + "6\r\nRENAME" + + "8\r\nRENAMENX" + + "4\r\nSCAN" + + "4\r\nWAIT" + + "5\r\nBITOP" + + "4\r\nEVAL" + + "7\r\nEVALSHA" + + "4\r\nAUTH" + + "4\r\nECHO" + + "4\r\nINFO" + + "5\r\nPROXY" + + "7\r\nSLOWLOG" + + "4\r\nQUIT" + + "6\r\nSELECT" + + "4\r\nTIME" + + "6\r\nCONFIG" + + "8\r\nCOMMANDS") + + reqCtlBytes = []byte("4\r\nPING") +) - "PFADD": reqTypeWrite, - "PFCOUNT": reqTypeRead, - "PFMERGE": reqTypeWrite, - "EVAL": reqTypeNotSupport, - "EVALSHA": reqTypeNotSupport, - "PING": reqTypeCtl, - "AUTH": reqTypeNotSupport, - "ECHO": reqTypeNotSupport, - "INFO": reqTypeNotSupport, - "PROXY": reqTypeNotSupport, - "SLOWLOG": reqTypeNotSupport, - "QUIT": reqTypeNotSupport, - "SELECT": reqTypeNotSupport, - "TIME": reqTypeNotSupport, - "CONFIG": reqTypeNotSupport, - "COMMANDS": reqTypeNotSupport, +func getReqType(cmd []byte) reqType { + if bytes.Contains(reqNotSupportBytes, cmd) { + return reqTypeNotSupport } - reqMap = critbit.New() - for key, tp := range tmpMap { - reqMap.Insert([]byte(key), tp) + if bytes.Contains(reqReadBytes, cmd) { + return reqTypeRead } -} -func getReqType(cmd []byte) reqType { - idx := bytes.IndexByte(cmd, lfByte) - if idx != -1 { - cmd = cmd[idx+1:] + if bytes.Contains(reqWriteBytes, cmd) { + return reqTypeWrite } - val, ok := reqMap.Get(cmd) - if !ok { - return reqTypeNotSupport + if bytes.Contains(reqCtlBytes, cmd) { + return reqTypeCtl } - return val.(int) + + return reqTypeNotSupport } diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 0c4085ba..9031ecba 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -18,7 +18,7 @@ var ( ) var ( - robjErrNotSupport = newRESPBulk([]byte("Error: command not support")) + robjErrNotSupport = newRESPPlain(respError, []byte("Error: command not support")) robjPong = newRESPPlain(respString, pongBytes) ) From 2f43170c15d576e15b8bbfff06ddb01fd81212ed Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 24 Jul 2018 17:57:01 +0800 Subject: [PATCH 46/87] add new filter test cases make ci happy --- proto/redis/filter_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 proto/redis/filter_test.go diff --git a/proto/redis/filter_test.go b/proto/redis/filter_test.go new file mode 100644 index 00000000..eb39d3f3 --- /dev/null +++ b/proto/redis/filter_test.go @@ -0,0 +1,15 @@ +package redis + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetReqTypeOk(t *testing.T) { + assert.Equal(t, reqTypeCtl, getReqType([]byte("4\r\nPING"))) + assert.Equal(t, reqTypeRead, getReqType([]byte("3\r\nGET"))) + assert.Equal(t, reqTypeWrite, getReqType([]byte("3\r\nSET"))) + assert.Equal(t, reqTypeNotSupport, getReqType([]byte("4\r\nEVAL"))) + assert.Equal(t, reqTypeNotSupport, getReqType([]byte("baka"))) +} From 41a9d5c6e66a038febf6a18b47220143c27141ad Mon Sep 17 00:00:00 2001 From: felixhao Date: Wed, 25 Jul 2018 11:38:24 +0800 Subject: [PATCH 47/87] rm unused code --- proto/redis/filter.go | 4 -- proto/redis/pinger.go | 1 - proto/redis/redis_test.go | 3 +- proto/redis/request.go | 84 +++++++------------------------------ proto/redis/request_test.go | 15 ------- proto/redis/resp.go | 14 ++----- 6 files changed, 22 insertions(+), 99 deletions(-) diff --git a/proto/redis/filter.go b/proto/redis/filter.go index cacadd2a..4a3d22b9 100644 --- a/proto/redis/filter.go +++ b/proto/redis/filter.go @@ -2,8 +2,6 @@ package redis import ( "bytes" - - "github.com/tatsushid/go-critbit" ) // reqType is a kind for cmd which is Ctl/Read/Write/NotSupport @@ -16,8 +14,6 @@ const ( reqTypeNotSupport reqType = iota ) -var reqMap *critbit.Tree - var ( reqReadBytes = []byte("" + "4\r\nDUMP" + diff --git a/proto/redis/pinger.go b/proto/redis/pinger.go index 13cc8cd5..cf8fc31e 100644 --- a/proto/redis/pinger.go +++ b/proto/redis/pinger.go @@ -56,7 +56,6 @@ func (p *pinger) ping() (err error) { } if !bytes.Equal(data, pongBytes) { err = ErrBadPong - return } return } diff --git a/proto/redis/redis_test.go b/proto/redis/redis_test.go index 53942988..c5d678b0 100644 --- a/proto/redis/redis_test.go +++ b/proto/redis/redis_test.go @@ -3,8 +3,9 @@ package redis import ( "bytes" "net" - libnet "overlord/lib/net" "time" + + libnet "overlord/lib/net" ) type mockAddr string diff --git a/proto/redis/request.go b/proto/redis/request.go index 5dd95896..29c453a9 100644 --- a/proto/redis/request.go +++ b/proto/redis/request.go @@ -1,17 +1,12 @@ package redis import ( - "errors" - "fmt" - "strconv" - "strings" - - // "crc" "bytes" + "strings" ) var ( - crlfBytes = []byte{'\r', '\n'} + crlfBytes = []byte("\r\n") lfByte = byte('\n') movedBytes = []byte("MOVED") askBytes = []byte("ASK") @@ -27,19 +22,6 @@ var ( cmdExistsBytes = []byte("6\r\nEXISTS") ) -// errors -var ( - ErrProxyFail = errors.New("fail to send proxy") - ErrRequestBadFormat = errors.New("redis must be a RESP array") - ErrRedirectBadFormat = errors.New("bad format of MOVED or ASK") -) - -// const values -const ( - SlotCount = 16384 - SlotShiled = 0x3fff -) - // Request is the type of a complete redis command type Request struct { respObj *resp @@ -52,20 +34,20 @@ type Request struct { // example: // NewRequest("GET", "mykey") // NewRequest("CLUSTER", "NODES") -func NewRequest(cmd string, args ...string) *Request { - respObj := respPool.Get().(*resp) - respObj.next().setBulk([]byte(cmd)) - // respObj := newRESPArrayWithCapcity(len(args) + 1) - // respObj.replace(0, newRESPBulk([]byte(cmd))) - maxLen := len(args) + 1 - for i := 1; i < maxLen; i++ { - data := args[i-1] - line := fmt.Sprintf("%d\r\n%s", len(data), data) - respObj.next().setBulk([]byte(line)) - } - respObj.data = []byte(strconv.Itoa(len(args) + 1)) - return newRequest(respObj) -} +// func NewRequest(cmd string, args ...string) *Request { +// respObj := respPool.Get().(*resp) +// respObj.next().setBulk([]byte(cmd)) +// // respObj := newRESPArrayWithCapcity(len(args) + 1) +// // respObj.replace(0, newRESPBulk([]byte(cmd))) +// maxLen := len(args) + 1 +// for i := 1; i < maxLen; i++ { +// data := args[i-1] +// line := fmt.Sprintf("%d\r\n%s", len(data), data) +// respObj.next().setBulk([]byte(line)) +// } +// respObj.data = []byte(strconv.Itoa(len(args) + 1)) +// return newRequest(respObj) +// } func newRequest(robj *resp) *Request { r := &Request{respObj: robj} @@ -112,37 +94,3 @@ func (c *Request) Key() []byte { // Put the resource back to pool func (c *Request) Put() { } - -// IsRedirect check if response type is Redis Error -// and payload was prefix with "ASK" && "MOVED" -func (c *Request) IsRedirect() bool { - if c.reply.rtype != respError { - return false - } - if c.reply.data == nil { - return false - } - - return bytes.HasPrefix(c.reply.data, movedBytes) || - bytes.HasPrefix(c.reply.data, askBytes) -} - -// RedirectTriple will check and send back by is -// first return variable which was called as redirectType maybe return ASK or MOVED -// second is the slot of redirect -// third is the redirect addr -// last is the error when parse the redirect body -func (c *Request) RedirectTriple() (redirect string, slot int, addr string, err error) { - fields := strings.Fields(string(c.reply.data)) - if len(fields) != 3 { - err = ErrRedirectBadFormat - return - } - redirect = fields[0] - addr = fields[2] - ival, parseErr := strconv.Atoi(fields[1]) - - slot = ival - err = parseErr - return -} diff --git a/proto/redis/request_test.go b/proto/redis/request_test.go index 6aec2aea..c10bc426 100644 --- a/proto/redis/request_test.go +++ b/proto/redis/request_test.go @@ -15,18 +15,3 @@ func TestRequestNewRequest(t *testing.T) { assert.Equal(t, "GET", string(cmd.Cmd())) assert.Equal(t, "a", string(cmd.Key())) } - -func TestRequestRedirect(t *testing.T) { - cmd := NewRequest("GET", "BAKA") - cmd.reply = newRESPPlain(respError, []byte("ASK 1024 127.0.0.1:2048")) - assert.True(t, cmd.IsRedirect()) - r, slot, addr, err := cmd.RedirectTriple() - assert.NoError(t, err) - assert.Equal(t, "ASK", r) - assert.Equal(t, 1024, slot) - assert.Equal(t, "127.0.0.1:2048", addr) - cmd.reply = newRESPPlain(respError, []byte("ERROR")) - assert.False(t, cmd.IsRedirect()) - _, _, _, err = cmd.RedirectTriple() - assert.Error(t, err) -} diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 93857ad4..ae4fe01f 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -133,12 +133,11 @@ func (r *resp) next() *resp { robj := r.array[r.arrayn] r.arrayn++ return robj - } else { - robj := respPool.Get().(*resp) - r.array = append(r.array, robj) - r.arrayn++ - return robj } + robj := respPool.Get().(*resp) + r.array = append(r.array, robj) + r.arrayn++ + return robj } func (r *resp) isNull() bool { @@ -184,7 +183,6 @@ func (r *resp) String() string { sb.WriteString("\n") return sb.String() } - return strconv.Quote(string(r.data)) } @@ -219,7 +217,6 @@ func (r *resp) encodeError(w *bufio.Writer) (err error) { func (r *resp) encodeInt(w *bufio.Writer) (err error) { return r.encodePlain(respIntBytes, w) - } func (r *resp) encodeString(w *bufio.Writer) (err error) { @@ -250,7 +247,6 @@ func (r *resp) encodeBulk(w *bufio.Writer) (err error) { err = w.Write(respNullBytes) return } - err = w.Write(r.data) if err != nil { return @@ -264,7 +260,6 @@ func (r *resp) encodeArray(w *bufio.Writer) (err error) { if err != nil { return } - if r.isNull() { err = w.Write(respNullBytes) return @@ -275,7 +270,6 @@ func (r *resp) encodeArray(w *bufio.Writer) (err error) { return } err = w.Write(crlfBytes) - for _, item := range r.slice() { item.encode(w) if err != nil { From d92b91d6bfda37a9ffedc18ae9c3b2894ea3c290 Mon Sep 17 00:00:00 2001 From: felixhao Date: Wed, 25 Jul 2018 21:50:13 +0800 Subject: [PATCH 48/87] refactor redis protocol --- lib/bufio/io.go | 123 ++++++----- lib/bufio/io_test.go | 52 ++--- proto/memcache/binary/pinger.go | 2 +- proto/memcache/pinger.go | 3 +- proto/message.go | 5 - proto/redis/filter.go | 173 ---------------- proto/redis/filter_test.go | 15 -- proto/redis/node_conn.go | 78 ++++--- proto/redis/node_conn_test.go | 38 +++- proto/redis/pinger.go | 14 +- proto/redis/pinger_test.go | 2 + proto/redis/proxy_conn.go | 270 ++++++++++++++---------- proto/redis/proxy_conn_test.go | 249 ++++++++++++++++------ proto/redis/request.go | 168 +++++++++------ proto/redis/request_test.go | 22 +- proto/redis/resp.go | 357 +++++++++----------------------- proto/redis/resp_conn.go | 207 ------------------ proto/redis/resp_conn_test.go | 164 --------------- proto/redis/resp_test.go | 246 ++++++++++++++++++++++ proto/redis/sub.go | 127 ------------ proxy/cluster.go | 17 +- 21 files changed, 991 insertions(+), 1341 deletions(-) delete mode 100644 proto/redis/filter.go delete mode 100644 proto/redis/filter_test.go delete mode 100644 proto/redis/resp_conn.go delete mode 100644 proto/redis/resp_conn_test.go create mode 100644 proto/redis/resp_test.go delete mode 100644 proto/redis/sub.go diff --git a/lib/bufio/io.go b/lib/bufio/io.go index 52b8239f..6ef16387 100644 --- a/lib/bufio/io.go +++ b/lib/bufio/io.go @@ -46,19 +46,21 @@ func (r *Reader) fill() error { return nil } +// Advance proxy to buffer advance +func (r *Reader) Advance(n int) { + r.b.Advance(n) +} + +// Mark return buf read pos. func (r *Reader) Mark() int { return r.b.r } +// AdvanceTo reset buffer read pos. func (r *Reader) AdvanceTo(mark int) { r.Advance(mark - r.b.r) } -// Advance proxy to buffer advance -func (r *Reader) Advance(n int) { - r.b.Advance(n) -} - // Buffer will return the reference of local buffer func (r *Reader) Buffer() *Buffer { return r.b @@ -69,15 +71,12 @@ func (r *Reader) Read() error { if r.err != nil { return r.err } - if r.b.buffered() == r.b.len() { r.b.grow() } - if r.b.w == r.b.len() { r.b.shrink() } - if err := r.fill(); err != io.EOF { return err } @@ -152,32 +151,32 @@ func (r *Reader) ResetBuffer(b *Buffer) { // If ReadUntil encounters an error before finding a delimiter, // it returns all the data in the buffer and the error itself (often io.EOF). // ReadUntil returns err != nil if and only if line does not end in delim. -func (r *Reader) ReadUntil(delim byte) ([]byte, error) { - if r.err != nil { - return nil, r.err - } - for { - var index = bytes.IndexByte(r.b.buf[r.b.r:r.b.w], delim) - if index >= 0 { - limit := r.b.r + index + 1 - slice := r.b.buf[r.b.r:limit] - r.b.r = limit - return slice, nil - } - if r.b.w >= r.b.len() { - r.b.grow() - } - err := r.fill() - if err == io.EOF && r.b.buffered() > 0 { - data := r.b.buf[r.b.r:r.b.w] - r.b.r = r.b.w - return data, nil - } else if err != nil { - r.err = err - return nil, err - } - } -} +// func (r *Reader) ReadUntil(delim byte) ([]byte, error) { +// if r.err != nil { +// return nil, r.err +// } +// for { +// var index = bytes.IndexByte(r.b.buf[r.b.r:r.b.w], delim) +// if index >= 0 { +// limit := r.b.r + index + 1 +// slice := r.b.buf[r.b.r:limit] +// r.b.r = limit +// return slice, nil +// } +// if r.b.w >= r.b.len() { +// r.b.grow() +// } +// err := r.fill() +// if err == io.EOF && r.b.buffered() > 0 { +// data := r.b.buf[r.b.r:r.b.w] +// r.b.r = r.b.w +// return data, nil +// } else if err != nil { +// r.err = err +// return nil, err +// } +// } +// } // ReadFull reads exactly n bytes from r into buf. // It returns the number of bytes copied and an error if fewer bytes were read. @@ -185,34 +184,34 @@ func (r *Reader) ReadUntil(delim byte) ([]byte, error) { // If an EOF happens after reading some but not all the bytes, // ReadFull returns ErrUnexpectedEOF. // On return, n == len(buf) if and only if err == nil. -func (r *Reader) ReadFull(n int) ([]byte, error) { - if n <= 0 { - return nil, nil - } - if r.err != nil { - return nil, r.err - } - for { - if r.b.buffered() >= n { - bs := r.b.buf[r.b.r : r.b.r+n] - r.b.r += n - return bs, nil - } - maxCanRead := r.b.len() - r.b.w + r.b.buffered() - if maxCanRead < n { - r.b.grow() - } - err := r.fill() - if err == io.EOF && r.b.buffered() > 0 { - data := r.b.buf[r.b.r:r.b.w] - r.b.r = r.b.w - return data, nil - } else if err != nil { - r.err = err - return nil, err - } - } -} +// func (r *Reader) ReadFull(n int) ([]byte, error) { +// if n <= 0 { +// return nil, nil +// } +// if r.err != nil { +// return nil, r.err +// } +// for { +// if r.b.buffered() >= n { +// bs := r.b.buf[r.b.r : r.b.r+n] +// r.b.r += n +// return bs, nil +// } +// maxCanRead := r.b.len() - r.b.w + r.b.buffered() +// if maxCanRead < n { +// r.b.grow() +// } +// err := r.fill() +// if err == io.EOF && r.b.buffered() > 0 { +// data := r.b.buf[r.b.r:r.b.w] +// r.b.r = r.b.w +// return data, nil +// } else if err != nil { +// r.err = err +// return nil, err +// } +// } +// } // Writer implements buffering for an io.Writer object. // If an error occurs writing to a Writer, no more data will be diff --git a/lib/bufio/io_test.go b/lib/bufio/io_test.go index f1f825f1..140ab30b 100644 --- a/lib/bufio/io_test.go +++ b/lib/bufio/io_test.go @@ -3,12 +3,12 @@ package bufio import ( "bytes" "errors" - "io" "net" - libnet "overlord/lib/net" "testing" "time" + libnet "overlord/lib/net" + "github.com/stretchr/testify/assert" ) @@ -34,6 +34,15 @@ func TestReaderAdvance(t *testing.T) { assert.Len(t, buf.Bytes(), 502) b.Advance(-10) assert.Len(t, buf.Bytes(), 512) + + b.ReadExact(10) + m := b.Mark() + assert.Equal(t, 10, m) + + b.AdvanceTo(5) + m = b.Mark() + assert.Equal(t, 5, m) + assert.Equal(t, 5, b.Buffer().r) } func TestReaderRead(t *testing.T) { @@ -48,18 +57,6 @@ func TestReaderRead(t *testing.T) { assert.EqualError(t, err, "some error") } -func TestReaderReadUntil(t *testing.T) { - bts := _genData() - b := NewReader(bytes.NewBuffer(bts), Get(defaultBufferSize)) - data, err := b.ReadUntil(fbyte) - assert.NoError(t, err) - assert.Len(t, data, 5*3*100) - - b.err = errors.New("some error") - _, err = b.ReadUntil(fbyte) - assert.EqualError(t, err, "some error") -} - func TestReaderReadSlice(t *testing.T) { bts := _genData() @@ -73,19 +70,6 @@ func TestReaderReadSlice(t *testing.T) { assert.EqualError(t, err, "bufio: buffer full") } -func TestReaderReadFull(t *testing.T) { - bts := _genData() - - b := NewReader(bytes.NewBuffer(bts), Get(defaultBufferSize)) - data, err := b.ReadFull(1200) - assert.NoError(t, err) - assert.Len(t, data, 1200) - - b.err = errors.New("some error") - _, err = b.ReadFull(1) - assert.EqualError(t, err, "some error") -} - func TestReaderReadExact(t *testing.T) { bts := _genData() @@ -103,17 +87,23 @@ func TestReaderResetBuffer(t *testing.T) { bts := _genData() b := NewReader(bytes.NewBuffer(bts), Get(defaultBufferSize)) - _, err := b.ReadFull(1200) + err := b.Read() + assert.NoError(t, err) + + _, err = b.ReadExact(512) assert.NoError(t, err) b.ResetBuffer(Get(defaultBufferSize)) - data, err := b.ReadFull(300) + err = b.Read() + assert.NoError(t, err) + + data, err := b.ReadExact(300) assert.NoError(t, err) assert.Len(t, data, 300) - _, err = b.ReadFull(300) + _, err = b.ReadExact(300) assert.Error(t, err) - assert.Equal(t, io.EOF, err) + assert.Equal(t, ErrBufferFull, err) b.ResetBuffer(nil) buf := b.Buffer() diff --git a/proto/memcache/binary/pinger.go b/proto/memcache/binary/pinger.go index a85394a8..ba60ad9e 100644 --- a/proto/memcache/binary/pinger.go +++ b/proto/memcache/binary/pinger.go @@ -64,7 +64,7 @@ func (m *mcPinger) Ping() (err error) { err = errors.Wrap(err, "MC ping flush") return } - err = m.br.Read() + _ = m.br.Read() head, err := m.br.ReadExact(requestHeaderLen) if err != nil { err = errors.Wrap(err, "MC ping read exact") diff --git a/proto/memcache/pinger.go b/proto/memcache/pinger.go index 869a79ee..a4d9c39b 100644 --- a/proto/memcache/pinger.go +++ b/proto/memcache/pinger.go @@ -47,8 +47,9 @@ func (m *mcPinger) Ping() (err error) { err = errors.Wrap(err, "MC ping flush") return } + _ = m.br.Read() var b []byte - if b, err = m.br.ReadUntil(delim); err != nil { + if b, err = m.br.ReadLine(); err != nil { err = errors.Wrap(err, "MC ping read response") return } diff --git a/proto/message.go b/proto/message.go index 2c02ba7c..a03a71d9 100644 --- a/proto/message.go +++ b/proto/message.go @@ -193,11 +193,6 @@ func (m *Message) Batch() []*Message { return m.subs[:slen] } -// Subs returns all the sub messages. -func (m *Message) Subs() []*Message { - return m.subs[:m.reqn] -} - // Err returns error. func (m *Message) Err() error { return m.err diff --git a/proto/redis/filter.go b/proto/redis/filter.go deleted file mode 100644 index 4a3d22b9..00000000 --- a/proto/redis/filter.go +++ /dev/null @@ -1,173 +0,0 @@ -package redis - -import ( - "bytes" -) - -// reqType is a kind for cmd which is Ctl/Read/Write/NotSupport -type reqType = int - -const ( - reqTypeCtl reqType = iota - reqTypeRead reqType = iota - reqTypeWrite reqType = iota - reqTypeNotSupport reqType = iota -) - -var ( - reqReadBytes = []byte("" + - "4\r\nDUMP" + - "6\r\nEXISTS" + - "4\r\nPTTL" + - "3\r\nTTL" + - "4\r\nTYPE" + - "8\r\nBITCOUNT" + - "6\r\nBITPOS" + - "3\r\nGET" + - "6\r\nGETBIT" + - "8\r\nGETRANGE" + - "4\r\nMGET" + - "6\r\nSTRLEN" + - "7\r\nHEXISTS" + - "4\r\nHGET" + - "7\r\nHGETALL" + - "5\r\nHKEYS" + - "4\r\nHLEN" + - "5\r\nHMGET" + - "7\r\nHSTRLEN" + - "5\r\nHVALS" + - "5\r\nHSCAN" + - "5\r\nSCARD" + - "5\r\nSDIFF" + - "6\r\nSINTER" + - "9\r\nSISMEMBER" + - "8\r\nSMEMBERS" + - "11\r\nSRANDMEMBER" + - "6\r\nSUNION" + - "5\r\nSSCAN" + - "5\r\nZCARD" + - "6\r\nZCOUNT" + - "9\r\nZLEXCOUNT" + - "6\r\nZRANGE" + - "11\r\nZRANGEBYLEX" + - "13\r\nZRANGEBYSCORE" + - "5\r\nZRANK" + - "9\r\nZREVRANGE" + - "14\r\nZREVRANGEBYLEX" + - "16\r\nZREVRANGEBYSCORE" + - "8\r\nZREVRANK" + - "6\r\nZSCORE" + - "5\r\nZSCAN" + - "6\r\nLINDEX" + - "4\r\nLLEN" + - "6\r\nLRANGE" + - "7\r\nPFCOUNT") - - reqWriteBytes = []byte("" + - "3\r\nDEL" + - "6\r\nEXPIRE" + - "8\r\nEXPIREAT" + - "7\r\nPERSIST" + - "7\r\nPEXPIRE" + - "9\r\nPEXPIREAT" + - "7\r\nRESTORE" + - "4\r\nSORT" + - "6\r\nAPPEND" + - "4\r\nDECR" + - "6\r\nDECRBY" + - "6\r\nGETSET" + - "4\r\nINCR" + - "6\r\nINCRBY" + - "11\r\nINCRBYFLOAT" + - "4\r\nMSET" + - "6\r\nPSETEX" + - "3\r\nSET" + - "6\r\nSETBIT" + - "5\r\nSETEX" + - "5\r\nSETNX" + - "8\r\nSETRANGE" + - "4\r\nHDEL" + - "7\r\nHINCRBY" + - "12\r\nHINCRBYFLOAT" + - "5\r\nHMSET" + - "4\r\nHSET" + - "6\r\nHSETNX" + - "7\r\nLINSERT" + - "4\r\nLPOP" + - "5\r\nLPUSH" + - "6\r\nLPUSHX" + - "4\r\nLREM" + - "4\r\nLSET" + - "5\r\nLTRIM" + - "4\r\nRPOP" + - "9\r\nRPOPLPUSH" + - "5\r\nRPUSH" + - "6\r\nRPUSHX" + - "4\r\nSADD" + - "10\r\nSDIFFSTORE" + - "11\r\nSINTERSTORE" + - "5\r\nSMOVE" + - "4\r\nSPOP" + - "4\r\nSREM" + - "11\r\nSUNIONSTORE" + - "4\r\nZADD" + - "7\r\nZINCRBY" + - "11\r\nZINTERSTORE" + - "4\r\nZREM" + - "14\r\nZREMRANGEBYLEX" + - "15\r\nZREMRANGEBYRANK" + - "16\r\nZREMRANGEBYSCORE" + - "11\r\nZUNIONSTORE" + - "5\r\nPFADD" + - "7\r\nPFMERGE") - - reqNotSupportBytes = []byte("" + - "6\r\nMSETNX" + - "5\r\nBLPOP" + - "5\r\nBRPOP" + - "10\r\nBRPOPLPUSH" + - "4\r\nKEYS" + - "7\r\nMIGRATE" + - "4\r\nMOVE" + - "6\r\nOBJECT" + - "9\r\nRANDOMKEY" + - "6\r\nRENAME" + - "8\r\nRENAMENX" + - "4\r\nSCAN" + - "4\r\nWAIT" + - "5\r\nBITOP" + - "4\r\nEVAL" + - "7\r\nEVALSHA" + - "4\r\nAUTH" + - "4\r\nECHO" + - "4\r\nINFO" + - "5\r\nPROXY" + - "7\r\nSLOWLOG" + - "4\r\nQUIT" + - "6\r\nSELECT" + - "4\r\nTIME" + - "6\r\nCONFIG" + - "8\r\nCOMMANDS") - - reqCtlBytes = []byte("4\r\nPING") -) - -func getReqType(cmd []byte) reqType { - if bytes.Contains(reqNotSupportBytes, cmd) { - return reqTypeNotSupport - } - - if bytes.Contains(reqReadBytes, cmd) { - return reqTypeRead - } - - if bytes.Contains(reqWriteBytes, cmd) { - return reqTypeWrite - } - - if bytes.Contains(reqCtlBytes, cmd) { - return reqTypeCtl - } - - return reqTypeNotSupport -} diff --git a/proto/redis/filter_test.go b/proto/redis/filter_test.go deleted file mode 100644 index eb39d3f3..00000000 --- a/proto/redis/filter_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package redis - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetReqTypeOk(t *testing.T) { - assert.Equal(t, reqTypeCtl, getReqType([]byte("4\r\nPING"))) - assert.Equal(t, reqTypeRead, getReqType([]byte("3\r\nGET"))) - assert.Equal(t, reqTypeWrite, getReqType([]byte("3\r\nSET"))) - assert.Equal(t, reqTypeNotSupport, getReqType([]byte("4\r\nEVAL"))) - assert.Equal(t, reqTypeNotSupport, getReqType([]byte("baka"))) -} diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index f4778ec4..46afd7eb 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -1,24 +1,28 @@ package redis import ( - libnet "overlord/lib/net" - "overlord/proto" "sync/atomic" "time" + + "overlord/lib/bufio" + libnet "overlord/lib/net" + "overlord/proto" ) const ( - closed = uint32(0) - opened = uint32(1) + opened = uint32(0) + closed = uint32(1) ) type nodeConn struct { cluster string addr string conn *libnet.Conn - rc *respConn - p *pinger + bw *bufio.Writer + br *bufio.Reader state uint32 + + p *pinger } // NewNodeConn create the node conn from proxy to redis @@ -31,14 +35,14 @@ func newNodeConn(cluster, addr string, conn *libnet.Conn) proto.NodeConn { return &nodeConn{ cluster: cluster, addr: addr, - rc: newRESPConn(conn), + br: bufio.NewReader(conn, nil), + bw: bufio.NewWriter(conn), conn: conn, p: newPinger(conn), - state: closed, } } -func (nc *nodeConn) WriteBatch(mb *proto.MsgBatch) error { +func (nc *nodeConn) WriteBatch(mb *proto.MsgBatch) (err error) { for _, m := range mb.Msgs() { err := nc.write(m) if err != nil { @@ -47,52 +51,64 @@ func (nc *nodeConn) WriteBatch(mb *proto.MsgBatch) error { } m.MarkWrite() } - err := nc.rc.Flush() - return err + return nc.bw.Flush() } -func (nc *nodeConn) write(m *proto.Message) error { - cmd, ok := m.Request().(*Request) +func (nc *nodeConn) write(m *proto.Message) (err error) { + req, ok := m.Request().(*Request) if !ok { m.DoneWithError(ErrBadAssert) return ErrBadAssert } - - if cmd.rtype == reqTypeNotSupport || cmd.rtype == reqTypeCtl { + if req.notSupport() { return nil } - - return cmd.respObj.encode(nc.rc.bw) + return req.resp.encode(nc.bw) } func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) (err error) { - nc.rc.br.ResetBuffer(mb.Buffer()) - defer nc.rc.br.ResetBuffer(nil) - for _, msg := range mb.Msgs() { - cmd, ok := msg.Request().(*Request) + nc.br.ResetBuffer(mb.Buffer()) + defer nc.br.ResetBuffer(nil) + // read + if err = nc.br.Read(); err != nil { + return + } + begin := nc.br.Mark() + now := nc.br.Mark() + for i := 0; i < mb.Count(); { + m := mb.Nth(i) + req, ok := m.Request().(*Request) if !ok { return ErrBadAssert } - - if cmd.rtype == reqTypeNotSupport || cmd.rtype == reqTypeCtl { - continue + if req.notSupport() { + i++ + return } - - if err = nc.rc.decodeOne(cmd.reply); err != nil { + if err = req.reply.decode(nc.br); err == bufio.ErrBufferFull { + nc.br.AdvanceTo(begin) + if err = nc.br.Read(); err != nil { + return + } + nc.br.AdvanceTo(now) + continue + } else if err != nil { return } - msg.MarkRead() + m.MarkRead() + i++ + now = nc.br.Mark() } - return nil + return } -func (nc *nodeConn) Ping() error { +func (nc *nodeConn) Ping() (err error) { return nc.p.ping() } -func (nc *nodeConn) Close() error { +func (nc *nodeConn) Close() (err error) { if atomic.CompareAndSwapUint32(&nc.state, opened, closed) { return nc.conn.Close() } - return nil + return } diff --git a/proto/redis/node_conn_test.go b/proto/redis/node_conn_test.go index dab8e51a..9745ebaf 100644 --- a/proto/redis/node_conn_test.go +++ b/proto/redis/node_conn_test.go @@ -1,10 +1,11 @@ package redis import ( - "io" - "overlord/proto" "testing" + "overlord/lib/bufio" + "overlord/proto" + "github.com/stretchr/testify/assert" ) @@ -30,7 +31,24 @@ func TestNodeConnWriteBatchOk(t *testing.T) { nc := newNodeConn("baka", "127.0.0.1:12345", _createConn(nil)) mb := proto.NewMsgBatch() msg := proto.GetMsg() - msg.WithRequest(NewRequest("GET", "A")) + req := getReq() + req.mType = mergeTypeNo + req.resp = &resp{ + rTp: respArray, + data: []byte("2"), + array: []*resp{ + &resp{ + rTp: respBulk, + data: []byte("3\r\nGET"), + }, + &resp{ + rTp: respBulk, + data: []byte("5\r\nabcde"), + }, + }, + arrayn: 2, + } + msg.WithRequest(req) mb.AddMsg(msg) err := nc.WriteBatch(mb) assert.NoError(t, err) @@ -53,7 +71,10 @@ func TestReadBatchOk(t *testing.T) { nc := newNodeConn("baka", "127.0.0.1:12345", _createConn([]byte(data))) mb := proto.NewMsgBatch() msg := proto.GetMsg() - msg.WithRequest(NewRequest("SET", "baka", "miao")) + req := getReq() + req.mType = mergeTypeNo + req.reply = &resp{} + msg.WithRequest(req) mb.AddMsg(msg) err := nc.ReadBatch(mb) assert.NoError(t, err) @@ -74,15 +95,18 @@ func TestReadBatchWithNilError(t *testing.T) { nc := newNodeConn("baka", "127.0.0.1:12345", _createConn(nil)) mb := proto.NewMsgBatch() msg := proto.GetMsg() - msg.WithRequest(NewRequest("GET", "a")) + req := getReq() + req.mType = mergeTypeNo + req.reply = &resp{} + msg.WithRequest(req) mb.AddMsg(msg) err := nc.ReadBatch(mb) assert.Error(t, err) - assert.Equal(t, io.EOF, err) + assert.Equal(t, bufio.ErrBufferFull, err) } func TestPingOk(t *testing.T) { - nc := newNodeConn("baka", "127.0.0.1:12345", _createRepeatConn(pongBytes, 100)) + nc := newNodeConn("baka", "127.0.0.1:12345", _createRepeatConn(pongBytes, 1)) err := nc.Ping() assert.NoError(t, err) } diff --git a/proto/redis/pinger.go b/proto/redis/pinger.go index cf8fc31e..005e5709 100644 --- a/proto/redis/pinger.go +++ b/proto/redis/pinger.go @@ -3,9 +3,10 @@ package redis import ( "bytes" "errors" + "sync/atomic" + "overlord/lib/bufio" libnet "overlord/lib/net" - "sync/atomic" ) // errors @@ -42,15 +43,12 @@ func (p *pinger) ping() (err error) { err = ErrPingClosed return } - err = p.bw.Write(pingBytes) - if err != nil { - return - } - err = p.bw.Flush() - if err != nil { + _ = p.bw.Write(pingBytes) + if err = p.bw.Flush(); err != nil { return err } - data, err := p.br.ReadUntil(lfByte) + _ = p.br.Read() + data, err := p.br.ReadLine() if err != nil { return } diff --git a/proto/redis/pinger_test.go b/proto/redis/pinger_test.go index 65ccd5d7..34ba0d7c 100644 --- a/proto/redis/pinger_test.go +++ b/proto/redis/pinger_test.go @@ -19,6 +19,7 @@ func TestPingerClosed(t *testing.T) { assert.NoError(t, p.Close()) err := p.ping() assert.Error(t, err) + assert.EqualError(t, err, "ping interface has been closed") assert.NoError(t, p.Close()) } @@ -27,4 +28,5 @@ func TestPingerWrongResp(t *testing.T) { p := newPinger(conn) err := p.ping() assert.Error(t, err) + assert.EqualError(t, err, "pong response payload is bad") } diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 46da22be..80f2f051 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -2,9 +2,9 @@ package redis import ( "bytes" - stderrs "errors" "strconv" + "overlord/lib/bufio" "overlord/lib/conv" libnet "overlord/lib/net" "overlord/proto" @@ -12,161 +12,215 @@ import ( "github.com/pkg/errors" ) -// errors -var ( - ErrBadAssert = stderrs.New("bad assert for redis") - ErrBadCount = stderrs.New("bad count number") -) - -var ( - robjErrNotSupport = newRESPPlain(respError, []byte("Error: command not support")) - robjPong = newRESPPlain(respString, pongBytes) -) - type proxyConn struct { - rc *respConn + br *bufio.Reader + bw *bufio.Writer + completed bool + + resp *resp } // NewProxyConn creates new redis Encoder and Decoder. func NewProxyConn(conn *libnet.Conn) proto.ProxyConn { r := &proxyConn{ - rc: newRESPConn(conn), + br: bufio.NewReader(conn, bufio.Get(1024)), + bw: bufio.NewWriter(conn), + completed: true, + resp: &resp{}, } return r } func (pc *proxyConn) Decode(msgs []*proto.Message) ([]*proto.Message, error) { - return pc.rc.decodeMsg(msgs) + var err error + if pc.completed { + err = pc.br.Read() + if err != nil { + return nil, err + } + pc.completed = false + } + for i := range msgs { + msgs[i].Type = proto.CacheTypeRedis + // decode + if err = pc.decode(msgs[i]); err == bufio.ErrBufferFull { + pc.completed = true + return msgs[:i], nil + } else if err != nil { + return nil, err + } + msgs[i].MarkStart() + } + return msgs, nil } -func (pc *proxyConn) Encode(msg *proto.Message) (err error) { - if err := msg.Err(); err != nil { - return pc.encodeError(err) +func (pc *proxyConn) decode(m *proto.Message) (err error) { + if err = pc.resp.decode(pc.br); err != nil { + return } - err = pc.merge(msg) - if err != nil { - return pc.encodeError(err) + if pc.resp.arrayn < 1 { + return } - if err = pc.rc.Flush(); err != nil { - err = errors.Wrap(err, "Redis Encoder flush response") + cmd := pc.resp.array[0].data // NOTE: when array, first is command + if bytes.Equal(cmd, cmdMSetBytes) { + mid := pc.resp.arrayn / 2 + for i := 0; i < mid; i++ { + r := nextReq(m) + r.mType = mergeTypeOK + r.resp.reset() // NOTE: *3\r\n + r.resp.rTp = respArray + r.resp.data = arrayLenThree + // array resp: mset + nre1 := r.resp.next() // NOTE: $4\r\nMSET\r\n + nre1.reset() + nre1.rTp = respBulk + nre1.data = cmdMSetBytes + // array resp: key + nre2 := r.resp.next() // NOTE: $klen\r\nkey\r\n + nre2.reset() + nre2.rTp = pc.resp.array[i*2+1].rTp + nre2.data = pc.resp.array[i*2+1].data + // array resp: value + nre3 := r.resp.next() // NOTE: $vlen\r\nvalue\r\n + nre3.reset() + nre3.rTp = pc.resp.array[i*2+2].rTp + nre3.data = pc.resp.array[i*2+2].data + } + } else if bytes.Equal(cmd, cmdMGetBytes) { + for i := 1; i < pc.resp.arrayn; i++ { + r := nextReq(m) + r.mType = mergeTypeJoin + r.resp.reset() // NOTE: *2\r\n + r.resp.rTp = respArray + r.resp.data = arrayLenTwo + // array resp: get + nre1 := r.resp.next() // NOTE: $3\r\nGET\r\n + nre1.reset() + nre1.rTp = respBulk + nre1.data = cmdGetBytes + // array resp: key + nre2 := r.resp.next() // NOTE: $klen\r\nkey\r\n + nre2.reset() + nre2.rTp = pc.resp.array[i].rTp + nre2.data = pc.resp.array[i].data + } + } else if bytes.Equal(cmd, cmdDelBytes) || bytes.Equal(cmd, cmdExistsBytes) { + for i := 1; i < pc.resp.arrayn; i++ { + r := nextReq(m) + r.mType = mergeTypeCount + r.resp.reset() // NOTE: *2\r\n + r.resp.rTp = respArray + r.resp.data = arrayLenTwo + // array resp: get + nre1 := r.resp.next() // NOTE: $3\r\nDEL\r\n | $6\r\nEXISTS\r\n + nre1.reset() + nre1.rTp = pc.resp.array[0].rTp + nre1.data = pc.resp.array[0].data + // array resp: key + nre2 := r.resp.next() // NOTE: $klen\r\nkey\r\n + nre2.reset() + nre2.rTp = pc.resp.array[i].rTp + nre2.data = pc.resp.array[i].data + } + } else { + r := nextReq(m) + r.resp.reset() + r.resp.rTp = pc.resp.rTp + r.resp.data = pc.resp.data + for i := 0; i < pc.resp.arrayn; i++ { + nre := r.resp.next() + nre.reset() + nre.rTp = pc.resp.array[i].rTp + nre.data = pc.resp.array[i].data + } } return } -func (pc *proxyConn) merge(msg *proto.Message) error { - cmd, ok := msg.Request().(*Request) +func nextReq(m *proto.Message) *Request { + req := m.NextReq() + if req == nil { + r := getReq() + m.WithRequest(r) + return r + } + r := req.(*Request) + return r +} + +func (pc *proxyConn) Encode(m *proto.Message) (err error) { + if err = m.Err(); err != nil { + return + } + req, ok := m.Request().(*Request) if !ok { return ErrBadAssert } - if !msg.IsBatch() { - return pc.encodeReply(cmd) + if !m.IsBatch() { + err = req.reply.encode(pc.bw) + } else { + switch req.mType { + case mergeTypeOK: + err = pc.mergeOK(m) + case mergeTypeJoin: + err = pc.mergeJoin(m) + case mergeTypeCount: + err = pc.mergeCount(m) + default: + // TODO: panic??? + } } - mtype, err := pc.getBatchMergeType(msg) if err != nil { - return err - } - switch mtype { - case MergeTypeJoin: - return pc.mergeJoin(msg) - case MergeTypeOk: - return pc.mergeOk(msg) - case MergeTypeCount: - return pc.mergeCount(msg) - case MergeTypeBasic: - fallthrough - default: - panic("unreachable path") - } -} - -func (pc *proxyConn) encodeReply(req *Request) error { - if req.rtype == reqTypeNotSupport { - return robjErrNotSupport.encode(pc.rc.bw) + err = errors.Wrap(err, "Redis Encoder before flush response") + return } - - data := req.respObj.nth(0).data - if bytes.Equal(data, cmdPingBytes) { - return robjPong.encode(pc.rc.bw) + if err = pc.bw.Flush(); err != nil { + err = errors.Wrap(err, "Redis Encoder flush response") } - - return req.reply.encode(pc.rc.bw) + return } -func (pc *proxyConn) mergeOk(msg *proto.Message) (err error) { - for _, sub := range msg.Subs() { - if err = sub.Err(); err != nil { - cmd := sub.Request().(*Request) - pc.rc.bw.Write(respErrorBytes) - pc.rc.bw.Write(cmd.reply.data) - pc.rc.bw.Write(crlfBytes) - return - } - } - pc.rc.bw.Write(respStringBytes) - pc.rc.bw.Write(okBytes) - pc.rc.bw.Write(crlfBytes) +func (pc *proxyConn) mergeOK(m *proto.Message) (err error) { + _ = pc.bw.Write(respStringBytes) + _ = pc.bw.Write(okBytes) + _ = pc.bw.Write(crlfBytes) return } -func (pc *proxyConn) mergeCount(msg *proto.Message) (err error) { +func (pc *proxyConn) mergeCount(m *proto.Message) (err error) { var sum = 0 - for _, sub := range msg.Subs() { - if sub.Err() != nil { - continue - } - subcmd, ok := sub.Request().(*Request) + for _, mreq := range m.Requests() { + req, ok := mreq.(*Request) if !ok { return ErrBadAssert } - ival, err := conv.Btoi(subcmd.reply.data) + ival, err := conv.Btoi(req.reply.data) if err != nil { return ErrBadCount } sum += int(ival) } - pc.rc.bw.Write(respIntBytes) - pc.rc.bw.Write([]byte(strconv.Itoa(sum))) - pc.rc.bw.Write(crlfBytes) + _ = pc.bw.Write(respIntBytes) + _ = pc.bw.Write([]byte(strconv.Itoa(sum))) + _ = pc.bw.Write(crlfBytes) return } -func (pc *proxyConn) mergeJoin(msg *proto.Message) (err error) { - subs := msg.Subs() - pc.rc.bw.Write(respArrayBytes) - if len(subs) == 0 { - pc.rc.bw.Write(respNullBytes) +func (pc *proxyConn) mergeJoin(m *proto.Message) (err error) { + reqs := m.Requests() + _ = pc.bw.Write(respArrayBytes) + if len(reqs) == 0 { + _ = pc.bw.Write(respNullBytes) return } - pc.rc.bw.Write([]byte(strconv.Itoa(len(subs)))) - pc.rc.bw.Write(crlfBytes) - for _, sub := range subs { - subcmd, ok := sub.Request().(*Request) + _ = pc.bw.Write([]byte(strconv.Itoa(len(reqs)))) + _ = pc.bw.Write(crlfBytes) + for _, mreq := range reqs { + req, ok := mreq.(*Request) if !ok { - err = pc.encodeError(ErrBadAssert) - if err != nil { - return - } return ErrBadAssert } - subcmd.reply.encode(pc.rc.bw) + _ = req.reply.encode(pc.bw) } return } - -func (pc *proxyConn) getBatchMergeType(msg *proto.Message) (mtype MergeType, err error) { - cmd, ok := msg.Subs()[0].Request().(*Request) - if !ok { - err = ErrBadAssert - return - } - mtype = cmd.mergeType - return -} - -func (pc *proxyConn) encodeError(err error) error { - se := errors.Cause(err).Error() - pc.rc.bw.Write(respErrorBytes) - pc.rc.bw.Write([]byte(se)) - pc.rc.bw.Write(crlfBytes) - return pc.rc.bw.Flush() -} diff --git a/proto/redis/proxy_conn_test.go b/proto/redis/proxy_conn_test.go index 328a39c3..61b4d97d 100644 --- a/proto/redis/proxy_conn_test.go +++ b/proto/redis/proxy_conn_test.go @@ -1,122 +1,239 @@ package redis import ( - "fmt" - "overlord/proto" "testing" + "overlord/proto" + "github.com/stretchr/testify/assert" ) func TestDecodeBasicOk(t *testing.T) { - msgs := proto.GetMsgSlice(16) data := "*2\r\n$3\r\nGET\r\n$4\r\nbaka\r\n" conn := _createConn([]byte(data)) pc := NewProxyConn(conn) + msgs := proto.GetMsgSlice(2) nmsgs, err := pc.Decode(msgs) assert.NoError(t, err) assert.Len(t, nmsgs, 1) + + req := msgs[0].Request().(*Request) + assert.Equal(t, mergeTypeNo, req.mType) + assert.Equal(t, 2, req.resp.arrayn) + assert.Equal(t, "GET", req.CmdString()) + assert.Equal(t, []byte("GET"), req.Cmd()) + assert.Equal(t, "baka", string(req.Key())) + assert.Equal(t, []byte("2"), req.resp.data) + assert.Equal(t, []byte("3\r\nGET"), req.resp.array[0].data) + assert.Equal(t, []byte("4\r\nbaka"), req.resp.array[1].data) } func TestDecodeComplexOk(t *testing.T) { - msgs := proto.GetMsgSlice(16) - data := "*3\r\n$4\r\nMGET\r\n$4\r\nbaka\r\n$4\r\nkaba\r\n*5\r\n$4\r\nMSET\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nb\r\n$1\r\nc\r\n*3\r\n$4\r\nMGET\r\n$4\r\nbaka\r\n$4\r\nkaba\r\n" + data := "*3\r\n$4\r\nMGET\r\n$4\r\nbaka\r\n$4\r\nkaba\r\n*5\r\n$4\r\nMSET\r\n$1\r\na\r\n$1\r\nb\r\n$3\r\neee\r\n$5\r\n12345\r\n*3\r\n$4\r\nMGET\r\n$4\r\nenen\r\n$4\r\nnime\r\n*2\r\n$3\r\nGET\r\n$5\r\nabcde\r\n" conn := _createConn([]byte(data)) pc := NewProxyConn(conn) // test reuse command - msgs[1].WithRequest(NewRequest("get", "a")) - msgs[1].WithRequest(NewRequest("get", "a")) + msgs := proto.GetMsgSlice(16) + msgs[1].WithRequest(getReq()) + msgs[1].WithRequest(getReq()) msgs[1].Reset() - msgs[2].WithRequest(NewRequest("get", "a")) - msgs[2].WithRequest(NewRequest("get", "a")) + msgs[2].WithRequest(getReq()) + msgs[2].WithRequest(getReq()) + msgs[2].WithRequest(getReq()) msgs[2].Reset() + // decode nmsgs, err := pc.Decode(msgs) assert.NoError(t, err) - assert.Len(t, nmsgs, 3) + assert.Len(t, nmsgs, 4) + // MGET baka + assert.Len(t, nmsgs[0].Batch(), 2) + req := msgs[0].Requests()[0].(*Request) + assert.Equal(t, mergeTypeJoin, req.mType) + assert.Equal(t, 2, req.resp.arrayn) + assert.Equal(t, "GET", req.CmdString()) + assert.Equal(t, []byte("GET"), req.Cmd()) + assert.Equal(t, "baka", string(req.Key())) + assert.Equal(t, []byte("2"), req.resp.data) + assert.Equal(t, []byte("3\r\nGET"), req.resp.array[0].data) + assert.Equal(t, []byte("4\r\nbaka"), req.resp.array[1].data) + // MGET kaba + req = msgs[0].Requests()[1].(*Request) + assert.Equal(t, mergeTypeJoin, req.mType) + assert.Equal(t, 2, req.resp.arrayn) + assert.Equal(t, "GET", req.CmdString()) + assert.Equal(t, []byte("GET"), req.Cmd()) + assert.Equal(t, "kaba", string(req.Key())) + assert.Equal(t, []byte("2"), req.resp.data) + assert.Equal(t, []byte("3\r\nGET"), req.resp.array[0].data) + assert.Equal(t, []byte("4\r\nkaba"), req.resp.array[1].data) + // MSET a b + assert.Len(t, nmsgs[1].Batch(), 2) + req = msgs[1].Requests()[0].(*Request) + assert.Equal(t, mergeTypeOK, req.mType) + assert.Equal(t, 3, req.resp.arrayn) + assert.Equal(t, "MSET", req.CmdString()) + assert.Equal(t, []byte("MSET"), req.Cmd()) + assert.Equal(t, "a", string(req.Key())) + assert.Equal(t, []byte("3"), req.resp.data) + assert.Equal(t, []byte("4\r\nMSET"), req.resp.array[0].data) + assert.Equal(t, []byte("1\r\na"), req.resp.array[1].data) + assert.Equal(t, []byte("1\r\nb"), req.resp.array[2].data) + // MSET eee 12345 + req = msgs[1].Requests()[1].(*Request) + assert.Equal(t, mergeTypeOK, req.mType) + assert.Equal(t, 3, req.resp.arrayn) + assert.Equal(t, "MSET", req.CmdString()) + assert.Equal(t, []byte("MSET"), req.Cmd()) + assert.Equal(t, "eee", string(req.Key())) + assert.Equal(t, []byte("3"), req.resp.data) + assert.Equal(t, []byte("4\r\nMSET"), req.resp.array[0].data) + assert.Equal(t, []byte("3\r\neee"), req.resp.array[1].data) + assert.Equal(t, []byte("5\r\n12345"), req.resp.array[2].data) + // MGET enen assert.Len(t, nmsgs[0].Batch(), 2) + req = msgs[2].Requests()[0].(*Request) + assert.Equal(t, mergeTypeJoin, req.mType) + assert.Equal(t, 2, req.resp.arrayn) + assert.Equal(t, "GET", req.CmdString()) + assert.Equal(t, []byte("GET"), req.Cmd()) + assert.Equal(t, "enen", string(req.Key())) + assert.Equal(t, []byte("2"), req.resp.data) + assert.Equal(t, []byte("3\r\nGET"), req.resp.array[0].data) + assert.Equal(t, []byte("4\r\nenen"), req.resp.array[1].data) + // MGET nime + req = msgs[2].Requests()[1].(*Request) + assert.Equal(t, mergeTypeJoin, req.mType) + assert.Equal(t, 2, req.resp.arrayn) + assert.Equal(t, "GET", req.CmdString()) + assert.Equal(t, []byte("GET"), req.Cmd()) + assert.Equal(t, "nime", string(req.Key())) + assert.Equal(t, []byte("2"), req.resp.data) + assert.Equal(t, []byte("3\r\nGET"), req.resp.array[0].data) + assert.Equal(t, []byte("4\r\nnime"), req.resp.array[1].data) + // GET abcde + req = msgs[3].Requests()[0].(*Request) + assert.Equal(t, mergeTypeNo, req.mType) + assert.Equal(t, 2, req.resp.arrayn) + assert.Equal(t, "GET", req.CmdString()) + assert.Equal(t, []byte("GET"), req.Cmd()) + assert.Equal(t, "abcde", string(req.Key())) + assert.Equal(t, []byte("2"), req.resp.data) + assert.Equal(t, []byte("3\r\nGET"), req.resp.array[0].data) + assert.Equal(t, []byte("5\r\nabcde"), req.resp.array[1].data) } func TestEncodeCmdOk(t *testing.T) { - ts := []struct { Name string - Reps []*resp - Obj *resp + MType mergeType + Reply []*resp Expect string }{ { - Name: "MergeJoinOk", - Reps: []*resp{newRESPBulk([]byte("3\r\nabc")), newRESPNull(respBulk)}, - Obj: newRESPArray([]*resp{newRESPBulk([]byte("4\r\nMGET")), newRESPBulk([]byte("3\r\nABC")), newRESPBulk([]byte("3\r\nxnc"))}), - Expect: "*2\r\n$3\r\nabc\r\n$-1\r\n", + Name: "mergeStr", + MType: mergeTypeNo, + Reply: []*resp{ + &resp{ + rTp: respString, + data: []byte("123456789"), + }, + }, + Expect: "+123456789\r\n", }, { - Name: "MergeCountOk", - Reps: []*resp{newRESPInt(1), newRESPInt(1), newRESPInt(0)}, - Obj: newRESPArray( - []*resp{ - newRESPBulk([]byte("3\r\nDEL")), - newRESPBulk([]byte("1\r\na")), - newRESPBulk([]byte("2\r\nab")), - newRESPBulk([]byte("3\r\nabc")), - }), - Expect: ":2\r\n", + Name: "mergeInt", + MType: mergeTypeNo, + Reply: []*resp{ + &resp{ + rTp: respInt, + data: []byte("12"), + }, + }, + Expect: ":12\r\n", + }, + { + Name: "mergeError", + MType: mergeTypeNo, + Reply: []*resp{ + &resp{ + rTp: respError, + data: []byte("i am error"), + }, + }, + Expect: "-i am error\r\n", }, { - Name: "MergeCountOk", - Reps: []*resp{newRESPString([]byte("OK")), newRESPString([]byte("OK"))}, - Obj: newRESPArray( - []*resp{ - newRESPBulk([]byte("4\r\nMSET")), - newRESPBulk([]byte("1\r\na")), - newRESPBulk([]byte("2\r\nab")), - newRESPBulk([]byte("3\r\nabc")), - newRESPBulk([]byte("4\r\nabcd")), - }), + Name: "mergeOK", + MType: mergeTypeOK, + Reply: []*resp{ + &resp{ + rTp: respString, + data: []byte("OK"), + }, + &resp{ + rTp: respString, + data: []byte("OK"), + }, + }, Expect: "+OK\r\n", }, + { + Name: "mergeCount", + MType: mergeTypeCount, + Reply: []*resp{ + &resp{ + rTp: respInt, + data: []byte("1"), + }, + &resp{ + rTp: respInt, + data: []byte("1"), + }, + }, + Expect: ":2\r\n", + }, + { + Name: "mergeJoin", + MType: mergeTypeJoin, + Reply: []*resp{ + &resp{ + rTp: respString, + data: []byte("abc"), + }, + &resp{ + rTp: respString, + data: []byte("ooo"), + }, + &resp{ + rTp: respString, + data: []byte("mmm"), + }, + }, + Expect: "*3\r\n+abc\r\n+ooo\r\n+mmm\r\n", + }, } for _, tt := range ts { t.Run(tt.Name, func(t *testing.T) { - rs := tt.Reps msg := proto.NewMessage() - co := tt.Obj - if isComplex(co.nth(0).data) { - err := newSubCmd(msg, co) - if assert.NoError(t, err) { - for i, req := range msg.Requests() { - cmd := req.(*Request) - cmd.reply = rs[i] - } - msg.Batch() - } - } else { - cmd := newRequest(co) - cmd.reply = rs[0] - msg.WithRequest(cmd) + for _, rpl := range tt.Reply { + req := getReq() + req.mType = tt.MType + req.reply = rpl + msg.WithRequest(req) + } + if msg.IsBatch() { + msg.Batch() } - data := make([]byte, 2048) conn, buf := _createDownStreamConn() pc := NewProxyConn(conn) err := pc.Encode(msg) if assert.NoError(t, err) { - size, _ := buf.Read(data) + data := make([]byte, 2048) + size, err := buf.Read(data) assert.NoError(t, err) assert.Equal(t, tt.Expect, string(data[:size])) } }) } - -} -func TestEncodeErr(t *testing.T) { - data := make([]byte, 2048) - conn, buf := _createDownStreamConn() - pc := NewProxyConn(conn) - msg := proto.NewMessage() - msg.DoneWithError(fmt.Errorf("ERR msg err")) - err := pc.Encode(msg) - assert.NoError(t, err) - size, _ := buf.Read(data) - assert.Equal(t, "-ERR msg err\r\n", string(data[:size])) } diff --git a/proto/redis/request.go b/proto/redis/request.go index 29c453a9..d39ce29f 100644 --- a/proto/redis/request.go +++ b/proto/redis/request.go @@ -2,95 +2,145 @@ package redis import ( "bytes" - "strings" + errs "errors" + "sync" ) var ( + emptyBytes = []byte("") crlfBytes = []byte("\r\n") - lfByte = byte('\n') - movedBytes = []byte("MOVED") - askBytes = []byte("ASK") + + arrayLenTwo = []byte("2") + arrayLenThree = []byte("3") + + cmdPingBytes = []byte("4\r\nPING") + cmdMSetBytes = []byte("4\r\nMSET") + cmdMGetBytes = []byte("4\r\nMGET") + cmdGetBytes = []byte("3\r\nGET") + cmdDelBytes = []byte("3\r\nDEL") + cmdExistsBytes = []byte("6\r\nEXISTS") + + respNullBytes = []byte("-1\r\n") + okBytes = []byte("OK") + + notSupportCmdBytes = []byte("" + + "6\r\nMSETNX" + + "5\r\nBLPOP" + + "5\r\nBRPOP" + + "10\r\nBRPOPLPUSH" + + "4\r\nKEYS" + + "7\r\nMIGRATE" + + "4\r\nMOVE" + + "6\r\nOBJECT" + + "9\r\nRANDOMKEY" + + "6\r\nRENAME" + + "8\r\nRENAMENX" + + "4\r\nSCAN" + + "4\r\nWAIT" + + "5\r\nBITOP" + + "4\r\nEVAL" + + "7\r\nEVALSHA" + + "4\r\nAUTH" + + "4\r\nECHO" + + "4\r\nINFO" + + "5\r\nPROXY" + + "7\r\nSLOWLOG" + + "4\r\nQUIT" + + "6\r\nSELECT" + + "4\r\nTIME" + + "6\r\nCONFIG" + + "8\r\nCOMMANDS") ) +// errors var ( - cmdPingBytes = []byte("4\r\nPING") - cmdMSetLenBytes = []byte("3") - cmdMSetBytes = []byte("4\r\nMSET") - cmdMGetBytes = []byte("4\r\nMGET") - cmdGetBytes = []byte("3\r\nGET") - cmdDelBytes = []byte("3\r\nDEL") - cmdExistsBytes = []byte("6\r\nEXISTS") + ErrBadAssert = errs.New("bad assert for redis") + ErrBadCount = errs.New("bad count number") + ErrBadRequest = errs.New("bad request") +) + +// mergeType is used to decript the merge operation. +type mergeType = uint8 + +// merge types +const ( + mergeTypeNo mergeType = iota + mergeTypeCount + mergeTypeOK + mergeTypeJoin ) // Request is the type of a complete redis command type Request struct { - respObj *resp - mergeType MergeType - reply *resp - rtype reqType + resp *resp + reply *resp + mType mergeType } -// NewRequest will create new command by given args -// example: -// NewRequest("GET", "mykey") -// NewRequest("CLUSTER", "NODES") -// func NewRequest(cmd string, args ...string) *Request { -// respObj := respPool.Get().(*resp) -// respObj.next().setBulk([]byte(cmd)) -// // respObj := newRESPArrayWithCapcity(len(args) + 1) -// // respObj.replace(0, newRESPBulk([]byte(cmd))) -// maxLen := len(args) + 1 -// for i := 1; i < maxLen; i++ { -// data := args[i-1] -// line := fmt.Sprintf("%d\r\n%s", len(data), data) -// respObj.next().setBulk([]byte(line)) -// } -// respObj.data = []byte(strconv.Itoa(len(args) + 1)) -// return newRequest(respObj) -// } - -func newRequest(robj *resp) *Request { - r := &Request{respObj: robj} - r.mergeType = getMergeType(robj.nth(0).data) - r.rtype = getReqType(robj.nth(0).data) - r.reply = &resp{} - return r +var reqPool = &sync.Pool{ + New: func() interface{} { + return newReq() + }, } -func (c *Request) setRESP(robj *resp) { - c.respObj = robj - c.mergeType = getMergeType(robj.nth(0).data) - c.reply = &resp{} +// getReq get the msg from pool +func getReq() *Request { + return reqPool.Get().(*Request) } -func newRequestWithMergeType(robj *resp, mtype MergeType) *Request { - return &Request{respObj: robj, mergeType: mtype} +func newReq() *Request { + r := &Request{} + r.resp = &resp{} + r.reply = &resp{} + return r } // CmdString get the cmd -func (c *Request) CmdString() string { - return strings.ToUpper(string(c.respObj.nth(0).data)) +func (r *Request) CmdString() string { + return string(r.Cmd()) } // Cmd get the cmd -func (c *Request) Cmd() []byte { - return c.respObj.nth(0).data +func (r *Request) Cmd() []byte { + if r.resp.arrayn < 1 { + return emptyBytes + } + cmd := r.resp.array[0] + var pos int + if cmd.rTp == respBulk { + pos = bytes.Index(cmd.data, crlfBytes) + 2 + } + return cmd.data[pos:] } // Key impl the proto.protoRequest and get the Key of redis -func (c *Request) Key() []byte { - if len(c.respObj.array) == 1 { - return c.respObj.nth(0).data +func (r *Request) Key() []byte { + if r.resp.arrayn < 1 { + return emptyBytes } - var data = c.respObj.nth(1).data + if r.resp.arrayn == 1 { + return r.resp.array[0].data + } + k := r.resp.array[1] var pos int - if c.respObj.nth(1).rtype == respBulk { - pos = bytes.Index(data, crlfBytes) + 2 + if k.rTp == respBulk { + pos = bytes.Index(k.data, crlfBytes) + 2 } - // pos is never lower than 0 - return data[pos:] + return k.data[pos:] } // Put the resource back to pool -func (c *Request) Put() { +func (r *Request) Put() { + r.resp.reset() + r.reply.reset() + r.mType = mergeTypeNo + reqPool.Put(r) +} + +// notSupport command not support +func (r *Request) notSupport() bool { + if r.resp.arrayn < 1 { + return false + } + return bytes.Index(notSupportCmdBytes, r.resp.array[0].data) > -1 } diff --git a/proto/redis/request_test.go b/proto/redis/request_test.go index c10bc426..1d667fcc 100644 --- a/proto/redis/request_test.go +++ b/proto/redis/request_test.go @@ -3,15 +3,23 @@ package redis import ( "testing" + "overlord/lib/bufio" + "github.com/stretchr/testify/assert" ) func TestRequestNewRequest(t *testing.T) { - cmd := NewRequest("GET", "a") - assert.Equal(t, MergeTypeBasic, cmd.mergeType) - assert.Equal(t, 2, cmd.respObj.Len()) - - assert.Equal(t, "GET", cmd.CmdString()) - assert.Equal(t, "GET", string(cmd.Cmd())) - assert.Equal(t, "a", string(cmd.Key())) + var bs = []byte("*2\r\n$4\r\nLLEN\r\n$6\r\nmylist\r\n") + // conn + conn := _createConn(bs) + br := bufio.NewReader(conn, bufio.Get(1024)) + br.Read() + req := getReq() + err := req.resp.decode(br) + assert.Nil(t, err) + assert.Equal(t, mergeTypeNo, req.mType) + assert.Equal(t, 2, req.resp.arrayn) + assert.Equal(t, "LLEN", req.CmdString()) + assert.Equal(t, []byte("LLEN"), req.Cmd()) + assert.Equal(t, "mylist", string(req.Key())) } diff --git a/proto/redis/resp.go b/proto/redis/resp.go index ae4fe01f..abf24932 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -1,12 +1,8 @@ package redis import ( - "bytes" "overlord/lib/bufio" - "overlord/proto" - "strconv" - "strings" - "sync" + "overlord/lib/conv" ) // respType is the type of redis resp @@ -14,11 +10,12 @@ type respType = byte // resp type define const ( - respString respType = '+' - respError respType = '-' - respInt respType = ':' - respBulk respType = '$' - respArray respType = '*' + respUnknown respType = '0' + respString respType = '+' + respError respType = '-' + respInt respType = ':' + respBulk respType = '$' + respArray respType = '*' ) var ( @@ -27,22 +24,11 @@ var ( respIntBytes = []byte(":") respBulkBytes = []byte("$") respArrayBytes = []byte("*") - - respNullBytes = []byte("-1\r\n") - okBytes = []byte("OK") -) - -var ( - respPool = sync.Pool{ - New: func() interface{} { - return &resp{} - }, - } ) // resp is a redis server protocol item. type resp struct { - rtype respType + rTp respType // in Bulk this is the size field // in array this is the count field data []byte @@ -52,279 +38,142 @@ type resp struct { } func (r *resp) reset() { - r.rtype = 0 - r.data = r.data[:0] + r.rTp = respUnknown + r.data = nil r.arrayn = 0 for _, ar := range r.array { ar.reset() } } -func newRESPInt(val int) *resp { - s := strconv.Itoa(val) - return newRESPPlain(respInt, []byte(s)) -} - -func (r *resp) setInt(val int) { - s := strconv.Itoa(val) - r.setPlain(respInt, []byte(s)) -} - -func newRESPBulk(data []byte) *resp { - return newRESPPlain(respBulk, data) -} - -func (r *resp) setBulk(data []byte) { - r.setPlain(respBulk, data) -} - -func newRESPPlain(rtype respType, data []byte) *resp { - robj := respPool.Get().(*resp) - robj.rtype = rtype - robj.data = data - robj.array = nil - return robj -} - -func (r *resp) setPlain(rtype respType, data []byte) { - r.rtype = rtype - r.data = data - r.arrayn = 0 -} - -func newRESPString(val []byte) *resp { - return newRESPPlain(respString, val) -} - -func (r *resp) setString(val []byte) { - r.setPlain(respString, val) -} - -func newRESPNull(rtype respType) *resp { - return newRESPPlain(rtype, nil) -} - -func (r *resp) setNull(rtype respType) { - r.setPlain(rtype, nil) -} - -func newRESPArray(resps []*resp) *resp { - robj := respPool.Get().(*resp) - robj.rtype = respArray - robj.data = []byte(strconv.Itoa(len(resps))) - robj.array = resps - robj.arrayn = len(resps) - return robj -} - -func (r *resp) setArray(resps []*resp) { - r.rtype = respArray - r.data = []byte(strconv.Itoa(len(resps))) - r.array = resps - r.arrayn = len(resps) -} - -func (r *resp) nth(pos int) *resp { - return r.array[pos] -} - func (r *resp) next() *resp { if r.arrayn < len(r.array) { - robj := r.array[r.arrayn] + nr := r.array[r.arrayn] + nr.reset() r.arrayn++ - return robj + return nr } - robj := respPool.Get().(*resp) - r.array = append(r.array, robj) + nr := &resp{} + nr.reset() + r.array = append(r.array, nr) r.arrayn++ - return robj + return nr } -func (r *resp) isNull() bool { - if r.rtype == respArray { - return r.arrayn == 0 - } - if r.rtype == respBulk { - return r.data == nil +func (r *resp) decode(br *bufio.Reader) (err error) { + r.reset() + // start read + line, err := br.ReadLine() + if err != nil { + return err } - return false -} - -func (r *resp) replace(pos int, newer *resp) { - if pos < len(r.array) { - r.array[pos] = newer - r.arrayn = pos - } else { - r.array = append(r.array, newer) - r.arrayn = len(r.array) + rTp := line[0] + r.rTp = rTp + switch rTp { + case respString, respInt, respError: + r.data = line[1 : len(line)-2] + case respBulk: + err = r.decodeBulk(line, br) + case respArray: + err = r.decodeArray(line, br) + default: + err = ErrBadRequest } + return } -func (r *resp) slice() []*resp { - return r.array[:r.arrayn] -} - -// Len represent the respArray type's length -func (r *resp) Len() int { - return r.arrayn +func (r *resp) decodeBulk(line []byte, br *bufio.Reader) error { + ls := len(line) + sBs := line[1 : ls-2] + size, err := conv.Btoi(sBs) + if err != nil { + return err + } + if size == -1 { + r.data = nil + return nil + } + br.Advance(-(ls - 1)) + all := ls - 1 + int(size) + 2 + data, err := br.ReadExact(all) + if err == bufio.ErrBufferFull { + br.Advance(-1) + return err + } else if err != nil { + return err + } + r.data = data[:len(data)-2] + return nil } -// String was only for debug -func (r *resp) String() string { - if r.rtype == respArray { - var sb strings.Builder - sb.Write([]byte("[")) - for _, sub := range r.array[:r.arrayn-1] { - sb.WriteString(sub.String()) - sb.WriteString(", ") - } - sb.WriteString(r.array[r.arrayn-1].String()) - sb.Write([]byte("]")) - sb.WriteString("\n") - return sb.String() +func (r *resp) decodeArray(line []byte, br *bufio.Reader) error { + ls := len(line) + sBs := line[1 : ls-2] + size, err := conv.Btoi(sBs) + if err != nil { + return err } - return strconv.Quote(string(r.data)) -} - -func (r *resp) bytes() []byte { - var data = r.data - var pos int - if r.rtype == respBulk { - pos = bytes.Index(data, crlfBytes) + 2 + if size == -1 { + r.data = nil + return nil + } + r.data = sBs + for i := 0; i < int(size); i++ { + nre := r.next() + err = nre.decode(br) } - return data[pos:] + return nil } -func (r *resp) encode(w *bufio.Writer) error { - switch r.rtype { - case respInt: - return r.encodeInt(w) - case respError: - return r.encodeError(w) - case respString: - return r.encodeString(w) +func (r *resp) encode(w *bufio.Writer) (err error) { + switch r.rTp { + case respInt, respString, respError: + _ = r.encodePlain(w) case respBulk: - return r.encodeBulk(w) + _ = r.encodeBulk(w) case respArray: - return r.encodeArray(w) + _ = r.encodeArray(w) } return nil } -func (r *resp) encodeError(w *bufio.Writer) (err error) { - return r.encodePlain(respErrorBytes, w) -} - -func (r *resp) encodeInt(w *bufio.Writer) (err error) { - return r.encodePlain(respIntBytes, w) -} - -func (r *resp) encodeString(w *bufio.Writer) (err error) { - return r.encodePlain(respStringBytes, w) -} - -func (r *resp) encodePlain(rtypeBytes []byte, w *bufio.Writer) (err error) { - err = w.Write(rtypeBytes) - if err != nil { - return +func (r *resp) encodePlain(w *bufio.Writer) (err error) { + switch r.rTp { + case respInt: + _ = w.Write(respIntBytes) + case respError: + _ = w.Write(respErrorBytes) + case respString: + _ = w.Write(respStringBytes) } - err = w.Write(r.data) - if err != nil { - return + if len(r.data) > 0 { + _ = w.Write(r.data) } err = w.Write(crlfBytes) - return + return err } func (r *resp) encodeBulk(w *bufio.Writer) (err error) { - // NOTICE: we need not to convert robj.Len() as int - // due number has been writen into data - err = w.Write(respBulkBytes) - if err != nil { - return - } - if r.isNull() { - err = w.Write(respNullBytes) - return - } - err = w.Write(r.data) - if err != nil { - return + _ = w.Write(respBulkBytes) + if len(r.data) > 0 { + _ = w.Write(r.data) + } else { + _ = w.Write(respNullBytes) } err = w.Write(crlfBytes) - return + return err } func (r *resp) encodeArray(w *bufio.Writer) (err error) { - err = w.Write(respArrayBytes) - if err != nil { - return - } - if r.isNull() { - err = w.Write(respNullBytes) - return - } - // output size - err = w.Write(r.data) - if err != nil { - return - } - err = w.Write(crlfBytes) - for _, item := range r.slice() { - item.encode(w) - if err != nil { - return - } - } - return -} - -func (r *resp) decode(msg *proto.Message) (err error) { - if isComplex(r.nth(0).data) { - err = newSubCmd(msg, r) - if err != nil { - return - } - } else { - withReq(msg, r) - } - return -} - -func withReq(m *proto.Message, robj *resp) { - req := m.NextReq() - if req == nil { - m.WithRequest(newRequest(robj)) + _ = w.Write(respArrayBytes) + if len(r.data) > 0 { + _ = w.Write(r.data) } else { - reqCmd := req.(*Request) - reqCmd.setRESP(robj) + _ = w.Write(respNullBytes) } -} - -// MergeType is used to decript the merge operation. -type MergeType = uint8 - -// merge types -const ( - MergeTypeCount MergeType = iota - MergeTypeOk - MergeTypeJoin - MergeTypeBasic -) - -func getMergeType(cmd []byte) MergeType { - // TODO: impl with tire tree to search quickly - if bytes.Equal(cmd, cmdMGetBytes) || bytes.Equal(cmd, cmdGetBytes) { - return MergeTypeJoin + _ = w.Write(crlfBytes) + for i := 0; i < r.arrayn; i++ { + _ = r.array[i].encode(w) } - - if bytes.Equal(cmd, cmdMSetBytes) { - return MergeTypeOk - } - - if bytes.Equal(cmd, cmdExistsBytes) || bytes.Equal(cmd, cmdDelBytes) { - return MergeTypeCount - } - - return MergeTypeBasic + return nil } diff --git a/proto/redis/resp_conn.go b/proto/redis/resp_conn.go deleted file mode 100644 index e00adca8..00000000 --- a/proto/redis/resp_conn.go +++ /dev/null @@ -1,207 +0,0 @@ -package redis - -import ( - "overlord/lib/bufio" - "overlord/lib/conv" - libnet "overlord/lib/net" - "overlord/proto" -) - -// respConn will encode and decode res object to socket -type respConn struct { - br *bufio.Reader - bw *bufio.Writer - - completed bool -} - -// newRespConn will create new redis server protocol object Conn -func newRESPConn(conn *libnet.Conn) *respConn { - r := &respConn{ - br: bufio.NewReader(conn, bufio.Get(1024)), - bw: bufio.NewWriter(conn), - completed: true, - } - return r -} - -// decodeMsg will parse all the req resp objects into message and keep the reuse reference until -// next call of this function. -func (rc *respConn) decodeMsg(msgs []*proto.Message) ([]*proto.Message, error) { - var err error - if rc.completed { - err = rc.br.Read() - if err != nil { - return nil, err - } - rc.completed = false - } - - for i, msg := range msgs { - var robj *resp - req := msg.Request() - if req == nil { - robj = respPool.Get().(*resp) - } else { - robj = req.(*Request).respObj - // reset metadata before reuse it. - robj.reset() - } - err = rc.decodeRESP(robj) - if err == bufio.ErrBufferFull { - rc.completed = true - return msgs[:i], nil - } else if err != nil { - return nil, err - } - msg.Type = proto.CacheTypeRedis - msg.MarkStart() - err = robj.decode(msg) - if err != nil { - msg.Reset() - return nil, err - } - } - return msgs, nil -} - -// decodeCount will trying to parse the buffer until meet the count. -func (rc *respConn) decodeCount(robjs []*resp) (err error) { - var ( - begin = rc.br.Mark() - now = rc.br.Mark() - n = len(robjs) - i = 0 - ) - - for { - // advance the r position to begin to avoid Read fill buffer - rc.br.AdvanceTo(begin) - err = rc.br.Read() - if err != nil { - return - } - rc.br.AdvanceTo(now) - - for { - if i == n { - return - } - - err = rc.decodeRESP(robjs[i]) - if err == bufio.ErrBufferFull { - break - } - if err != nil { - return - } - now = rc.br.Mark() - i++ - } - } -} - -// decodeOne will trying to parse the buffer until get one complete resp.. -func (rc *respConn) decodeOne(robj *resp) (err error) { - var ( - begin = rc.br.Mark() - ) - for { - // advance the r position to begin to avoid Read fill buffer - rc.br.AdvanceTo(begin) - err = rc.br.Read() - if err != nil { - return - } - err = rc.decodeRESP(robj) - if err == bufio.ErrBufferFull { - continue - } - return - } -} - -func (rc *respConn) decodeRESP(robj *resp) (err error) { - var ( - line []byte - ) - line, err = rc.br.ReadLine() - if err != nil { - return err - } - - rtype := line[0] - switch rtype { - case respString, respInt, respError: - // decocde use one line to parse - rc.decodePlain(line, robj) - case respBulk: - // decode bulkString - err = rc.decodeBulk(line, robj) - case respArray: - err = rc.decodeArray(line, robj) - } - return -} - -func (rc *respConn) decodePlain(line []byte, robj *resp) { - robj.setPlain(line[0], line[1:len(line)-2]) -} - -func (rc *respConn) decodeBulk(line []byte, robj *resp) error { - lineSize := len(line) - sizeBytes := line[1 : lineSize-2] - size, err := decodeInt(sizeBytes) - if err != nil { - return err - } - if size == -1 { - robj.setNull(respBulk) - return nil - } - rc.br.Advance(-(lineSize - 1)) - fullDataSize := lineSize - 1 + size + 2 - data, err := rc.br.ReadExact(fullDataSize) - if err == bufio.ErrBufferFull { - rc.br.Advance(-1) - return err - } else if err != nil { - return err - } - robj.setBulk(data[:len(data)-2]) - return nil -} - -func (rc *respConn) decodeArray(line []byte, robj *resp) error { - lineSize := len(line) - size, err := decodeInt(line[1 : lineSize-2]) - if err != nil { - return err - } - if size == -1 { - robj.setNull(respArray) - return nil - } - robj.data = line[1 : lineSize-2] - robj.rtype = respArray - mark := rc.br.Mark() - for i := 0; i < size; i++ { - err = rc.decodeRESP(robj.next()) - if err != nil { - rc.br.AdvanceTo(mark) - rc.br.Advance(-lineSize) - return err - } - } - return nil -} - -func decodeInt(data []byte) (int, error) { - i, err := conv.Btoi(data) - return int(i), err -} - -// Flush was used to writev to flush. -func (rc *respConn) Flush() error { - return rc.bw.Flush() -} diff --git a/proto/redis/resp_conn_test.go b/proto/redis/resp_conn_test.go deleted file mode 100644 index 18e8980f..00000000 --- a/proto/redis/resp_conn_test.go +++ /dev/null @@ -1,164 +0,0 @@ -package redis - -import ( - "io" - "testing" - - "overlord/lib/bufio" - "overlord/proto" - - "github.com/stretchr/testify/assert" -) - -func _createRespConn(data []byte) *respConn { - conn := _createConn(data) - return newRESPConn(conn) -} - -func _runDecodeResps(t *testing.T, line string, expect string, rtype respType, eErr error) { - rc := _createRespConn([]byte(line)) - err := rc.br.Read() - if assert.NoError(t, err) { - robj := &resp{} - err = rc.decodeRESP(robj) - if eErr == nil { - if assert.NoError(t, err) { - if expect == "" { - assert.Nil(t, robj.data) - } else { - assert.Equal(t, expect, string(robj.data)) - } - assert.Equal(t, rtype, robj.rtype) - } - } else { - if assert.Error(t, err) { - assert.Equal(t, eErr, err) - } - } - - } -} - -func TestDecodeRESP(t *testing.T) { - tslist := []struct { - Name string - Input string - Expect string - Rtype respType - EErr error - }{ - {"RespStringOk", "+Bilibili 干杯 - ( ゜- ゜)つロ\r\n", "Bilibili 干杯 - ( ゜- ゜)つロ", respString, nil}, - {"RespStringWithLF", "+Bilibili\n 干杯 - ( ゜- ゜)つロ\r\n", "Bilibili\n 干杯 - ( ゜- ゜)つロ", respString, nil}, - {"RespErrorOk", "-Bilibili 干杯 - ( ゜- ゜)つロ\r\n", "Bilibili 干杯 - ( ゜- ゜)つロ", respError, nil}, - {"RespIntOk", ":10\r\n", "10", respInt, nil}, - // {"RespIntWrongNumber", ":a@#\r\n", "", respInt, nil}, // now it's can't be checked - {"RespBulkOk", "$35\r\nBilibili 干杯 - ( ゜- ゜)つロ\r\n", "35\r\nBilibili 干杯 - ( ゜- ゜)つロ", respBulk, nil}, - {"RespBulkNullOk", "$-1\r\n", "", respBulk, nil}, - {"RespBulkWrongSizeError", "$37\r\nBilibili 干杯 - ( ゜- ゜)つロ\r\n", "", respBulk, bufio.ErrBufferFull}, - - {"RespArrayOk", "*3\r\n$2\r\nab\r\n+baka lv9\r\n-ServerError:deepn dark fantasy\r\n", "3", respArray, nil}, - {"RespArrayNotFull", "*3\r\n$30000\r\nab\r\n+baka lv9\r\n-ServerError:deepn dark fantasy\r\n", "", respArray, bufio.ErrBufferFull}, - {"RespArrayNullOk", "*-1\r\n", "", respArray, nil}, - } - for _, tt := range tslist { - t.Run(tt.Name, func(t *testing.T) { - _runDecodeResps(t, tt.Input, tt.Expect, tt.Rtype, tt.EErr) - }) - } -} - -func TestDecodeMsgReachMaxOk(t *testing.T) { - line := []byte("*2\r\n$3\r\nget\r\n$1\r\na\r\n*2\r\n$3\r\nget\r\n$1\r\na\r\n") - rc := _createRespConn([]byte(line)) - msgs := []*proto.Message{proto.NewMessage(), proto.NewMessage()} - rs, err := rc.decodeMsg(msgs) - assert.NoError(t, err) - assert.Len(t, rs, 2) - - assert.Equal(t, respArray, rs[0].Request().(*Request).respObj.rtype) - assert.Equal(t, 2, rs[1].Request().(*Request).respObj.Len()) -} - -func TestDecodeMsgReachFullOk(t *testing.T) { - line := []byte("*2\r\n$3\r\nget\r\n$1\r\na\r\n*2\r\n$3\r\nget\r\n$1") - rc := _createRespConn([]byte(line)) - msgs := []*proto.Message{proto.NewMessage(), proto.NewMessage()} - rs, err := rc.decodeMsg(msgs) - assert.NoError(t, err) - assert.Len(t, rs, 1) - assert.Equal(t, respArray, rs[0].Request().(*Request).respObj.rtype) -} - -func TestDecodeMsgReuseRESP(t *testing.T) { - line := []byte("*2\r\n$3\r\nget\r\n$1\r\na\r\n*2\r\n$3\r\nget\r\n$1\r\na\r\n") - rc := _createRespConn([]byte(line)) - msg0 := proto.NewMessage() - msg0.WithRequest(NewRequest("get", "a")) - msg0.Reset() - msg1 := proto.NewMessage() - msg1.WithRequest(NewRequest("get", "a")) - msg1.Reset() - msgs := []*proto.Message{msg0, msg1} - rs, err := rc.decodeMsg(msgs) - assert.NoError(t, err) - assert.Len(t, rs, 2) - - assert.Equal(t, respArray, rs[0].Request().(*Request).respObj.rtype) - assert.Equal(t, 2, rs[1].Request().(*Request).respObj.Len()) -} -func TestDecodeCountOk(t *testing.T) { - line := []byte("$1\r\na\r\n+my name is\r\n") - rc := _createRespConn([]byte(line)) - rs := []*resp{&resp{}, &resp{}} - err := rc.decodeCount(rs) - assert.NoError(t, err) - assert.Len(t, rs, 2) - assert.Equal(t, respBulk, rs[0].rtype) - assert.Equal(t, respString, rs[1].rtype) -} - -func TestDecodeCountNotFull(t *testing.T) { - line := []byte("$1\r\na\r\n+my name is\r\n") - rc := _createRespConn([]byte(line)) - rs := []*resp{&resp{}, &resp{}, &resp{}} - err := rc.decodeCount(rs) - assert.Error(t, err) - assert.Equal(t, io.EOF, err) -} - -func TestEncodeResp(t *testing.T) { - ts := []struct { - Name string - Robj *resp - Expect string - }{ - {Name: "IntOk", Robj: newRESPInt(1024), Expect: ":1024\r\n"}, - {Name: "StringOk", Robj: newRESPString([]byte("baka")), Expect: "+baka\r\n"}, - {Name: "ErrorOk", Robj: newRESPPlain(respError, []byte("kaba")), Expect: "-kaba\r\n"}, - - {Name: "BulkOk", Robj: newRESPBulk([]byte("4\r\nkaba")), Expect: "$4\r\nkaba\r\n"}, - {Name: "BulkNullOk", Robj: newRESPNull(respBulk), Expect: "$-1\r\n"}, - - {Name: "ArrayNullOk", Robj: newRESPNull(respArray), Expect: "*-1\r\n"}, - {Name: "ArrayOk", - Robj: newRESPArray([]*resp{newRESPBulk([]byte("2\r\nka")), newRESPString([]byte("baka"))}), - Expect: "*2\r\n$2\r\nka\r\n+baka\r\n"}, - } - - for _, tt := range ts { - t.Run(tt.Name, func(t *testing.T) { - sock, buf := _createDownStreamConn() - conn := newRESPConn(sock) - // err := conn.encode(newRespInt(1024)) - err := tt.Robj.encode(conn.bw) - assert.NoError(t, err) - err = conn.Flush() - assert.NoError(t, err) - data := make([]byte, 1024) - n, err := buf.Read(data) - assert.NoError(t, err) - assert.Equal(t, tt.Expect, string(data[:n])) - }) - - } -} diff --git a/proto/redis/resp_test.go b/proto/redis/resp_test.go new file mode 100644 index 00000000..924b9e6d --- /dev/null +++ b/proto/redis/resp_test.go @@ -0,0 +1,246 @@ +package redis + +import ( + "testing" + + "overlord/lib/bufio" + + "github.com/stretchr/testify/assert" +) + +func TestRespDecode(t *testing.T) { + ts := []struct { + Name string + Bytes []byte + ExpectTp respType + ExpectLen int + ExpectData []byte + ExpectArr [][]byte + }{ + { + Name: "ok", + Bytes: []byte("+OK\r\n"), + ExpectTp: respString, + ExpectLen: 0, + ExpectData: []byte("OK"), + }, + { + Name: "error", + Bytes: []byte("-Error message\r\n"), + ExpectTp: respError, + ExpectLen: 0, + ExpectData: []byte("Error message"), + }, + { + Name: "int", + Bytes: []byte(":1000\r\n"), + ExpectTp: respInt, + ExpectLen: 0, + ExpectData: []byte("1000"), + }, + { + Name: "bulk", + Bytes: []byte("$6\r\nfoobar\r\n"), + ExpectTp: respBulk, + ExpectLen: 0, + ExpectData: []byte("6\r\nfoobar"), + }, + { + Name: "array1", + Bytes: []byte("*2\r\n$3\r\nfoo\r\n$4\r\nbara\r\n"), + ExpectTp: respArray, + ExpectLen: 2, + ExpectData: []byte("2"), + ExpectArr: [][]byte{ + []byte("3\r\nfoo"), + []byte("4\r\nbara"), + }, + }, + { + Name: "array2", + Bytes: []byte("*3\r\n:1\r\n:2\r\n:3\r\n"), + ExpectTp: respArray, + ExpectLen: 3, + ExpectData: []byte("3"), + ExpectArr: [][]byte{ + []byte("1"), + []byte("2"), + []byte("3"), + }, + }, + { + Name: "array3", + Bytes: []byte("*2\r\n*3\r\n:1\r\n:2\r\n:3\r\n*2\r\n+Foo\r\n-Bar\r\n"), + ExpectTp: respArray, + ExpectLen: 2, + ExpectData: []byte("2"), + ExpectArr: [][]byte{ + []byte("3"), + []byte("2"), + }, + }, + } + for _, tt := range ts { + t.Run(tt.Name, func(t *testing.T) { + conn := _createConn(tt.Bytes) + r := &resp{} + r.reset() + br := bufio.NewReader(conn, bufio.Get(1024)) + br.Read() + if err := r.decode(br); err != nil { + t.Fatalf("decode error:%v", err) + } + assert.Equal(t, tt.ExpectTp, r.rTp) + assert.Equal(t, tt.ExpectLen, r.arrayn) + assert.Equal(t, tt.ExpectData, r.data) + if len(tt.ExpectArr) > 0 { + for i, ea := range tt.ExpectArr { + assert.Equal(t, ea, r.array[i].data) + } + } + }) + } +} + +func TestRespEncode(t *testing.T) { + ts := []struct { + Name string + Resp *resp + Expect []byte + }{ + { + Name: "ok", + Resp: &resp{ + rTp: respString, + data: []byte("OK"), + }, + Expect: []byte("+OK\r\n"), + }, + { + Name: "error", + Resp: &resp{ + rTp: respError, + data: []byte("Error message"), + }, + Expect: []byte("-Error message\r\n"), + }, + { + Name: "int", + Resp: &resp{ + rTp: respInt, + data: []byte("1000"), + }, + Expect: []byte(":1000\r\n"), + }, + { + Name: "bulk", + Resp: &resp{ + rTp: respBulk, + data: []byte("6\r\nfoobar"), + }, + Expect: []byte("$6\r\nfoobar\r\n"), + }, + { + Name: "array1", + Resp: &resp{ + rTp: respArray, + data: []byte("2"), + array: []*resp{ + &resp{ + rTp: respBulk, + data: []byte("3\r\nfoo"), + }, + &resp{ + rTp: respBulk, + data: []byte("4\r\nbara"), + }, + }, + arrayn: 2, + }, + Expect: []byte("*2\r\n$3\r\nfoo\r\n$4\r\nbara\r\n"), + }, + { + Name: "array2", + Resp: &resp{ + rTp: respArray, + data: []byte("3"), + array: []*resp{ + &resp{ + rTp: respInt, + data: []byte("1"), + }, + &resp{ + rTp: respInt, + data: []byte("2"), + }, + &resp{ + rTp: respInt, + data: []byte("3"), + }, + }, + arrayn: 3, + }, + Expect: []byte("*3\r\n:1\r\n:2\r\n:3\r\n"), + }, + { + Name: "array3", + Resp: &resp{ + rTp: respArray, + data: []byte("2"), + array: []*resp{ + &resp{ + rTp: respArray, + data: []byte("3"), + array: []*resp{ + &resp{ + rTp: respInt, + data: []byte("1"), + }, + &resp{ + rTp: respInt, + data: []byte("2"), + }, + &resp{ + rTp: respInt, + data: []byte("3"), + }, + }, + arrayn: 3, + }, + &resp{ + rTp: respArray, + data: []byte("2"), + array: []*resp{ + &resp{ + rTp: respString, + data: []byte("Foo"), + }, + &resp{ + rTp: respError, + data: []byte("Bar"), + }, + }, + arrayn: 2, + }, + }, + arrayn: 2, + }, + Expect: []byte("*2\r\n*3\r\n:1\r\n:2\r\n:3\r\n*2\r\n+Foo\r\n-Bar\r\n"), + }, + } + for _, tt := range ts { + t.Run(tt.Name, func(t *testing.T) { + conn := _createConn(nil) + bw := bufio.NewWriter(conn) + + err := tt.Resp.encode(bw) + bw.Flush() + assert.Nil(t, err) + + buf := make([]byte, 1024) + n, err := conn.Conn.(*mockConn).wbuf.Read(buf) + assert.Nil(t, err) + assert.Equal(t, tt.Expect, buf[:n]) + }) + } +} diff --git a/proto/redis/sub.go b/proto/redis/sub.go deleted file mode 100644 index 15e4da62..00000000 --- a/proto/redis/sub.go +++ /dev/null @@ -1,127 +0,0 @@ -package redis - -import ( - "bytes" - "errors" - - "overlord/proto" -) - -const ( - parityBit int = 1 -) - -// errors -var ( - ErrBadRequestSize = errors.New("wrong Mset arguments number") -) - -func isEven(v int) bool { - return v&parityBit == 0 -} - -func isComplex(cmd []byte) bool { - return bytes.Equal(cmd, cmdMSetBytes) || - bytes.Equal(cmd, cmdMGetBytes) || - bytes.Equal(cmd, cmdDelBytes) || - bytes.Equal(cmd, cmdExistsBytes) -} - -func newSubCmd(msg *proto.Message, robj *resp) error { - if bytes.Equal(robj.nth(0).data, cmdMSetBytes) { - return cmdMset(msg, robj) - } - return cmdByKeys(msg, robj) -} - -// func subCmdMset(robj *resp) ([]*Request, error) { -// if !isEven(robj.Len() - 1) { -// return nil, ErrBadRequestSize -// } - -// mid := robj.Len() / 2 -// cmds := make([]*Request, mid) -// for i := 0; i < mid; i++ { -// cmdObj := respPool.Get().(*resp) -// cmdObj.rtype = respArray -// cmdObj.data = nil -// cmdObj.replace(0, robjMSet) -// cmdObj.replace(1, robj.nth(i*2+1)) -// cmdObj.replace(2, robj.nth(i*2+2)) -// cmdObj.data = cmdMSetLenBytes -// cmds[i] = newRequestWithMergeType(cmdObj, MergeTypeOk) -// } -// return cmds, nil -// } - -func cmdMset(msg *proto.Message, robj *resp) error { - if !isEven(robj.Len() - 1) { - return ErrBadRequestSize - } - mid := robj.Len() / 2 - for i := 0; i < mid; i++ { - req := msg.NextReq() - if req == nil { - cmdObj := respPool.Get().(*resp) - cmdObj.rtype = respArray - cmdObj.data = nil - cmdObj.replace(0, newRESPBulk(cmdMSetBytes)) - cmdObj.replace(1, robj.nth(i*2+1)) - cmdObj.replace(2, robj.nth(i*2+2)) - cmdObj.data = cmdMSetLenBytes - cmd := newRequestWithMergeType(cmdObj, MergeTypeOk) - msg.WithRequest(cmd) - } else { - reqCmd := req.(*Request) - reqCmd.mergeType = MergeTypeOk - cmdObj := reqCmd.respObj - cmdObj.rtype = respArray - cmdObj.data = nil - cmdObj.replace(0, newRESPBulk(cmdMSetBytes)) - cmdObj.replace(1, robj.nth(i*2+1)) - cmdObj.replace(2, robj.nth(i*2+2)) - cmdObj.data = cmdMSetLenBytes - } - } - return nil -} - -// func subCmdByKeys(robj *resp) ([]*Request, error) { -// cmds := make([]*Request, robj.Len()-1) -// for i, sub := range robj.slice()[1:] { -// var cmdObj *resp -// if bytes.Equal(robj.nth(0).data, cmdMGetBytes) { -// cmdObj = newRESPArray([]*resp{robjGet, sub}) -// } else { -// cmdObj = newRESPArray([]*resp{robj.nth(0), sub}) -// } -// cmds[i] = newRequest(cmdObj) -// } -// return cmds, nil -// } - -func cmdByKeys(msg *proto.Message, robj *resp) error { - var cmdObj *resp - for _, sub := range robj.slice()[1:] { - req := msg.NextReq() - if req == nil { - if bytes.Equal(robj.nth(0).data, cmdMGetBytes) { - cmdObj = newRESPArray([]*resp{newRESPBulk(cmdGetBytes), sub}) - } else { - cmdObj = newRESPArray([]*resp{robj.nth(0), sub}) - } - cmd := newRequest(cmdObj) - msg.WithRequest(cmd) - } else { - reqCmd := req.(*Request) - cmdObj := reqCmd.respObj - if bytes.Equal(robj.nth(0).data, cmdMGetBytes) { - cmdObj.setArray([]*resp{newRESPBulk(cmdGetBytes), sub}) - } else { - cmdObj.setArray([]*resp{robj.nth(0), sub}) - } - reqCmd.mergeType = getMergeType(cmdObj.nth(0).data) - } - } - return nil -} diff --git a/proxy/cluster.go b/proxy/cluster.go index cb0ea0a9..703ea71d 100644 --- a/proxy/cluster.go +++ b/proxy/cluster.go @@ -188,16 +188,12 @@ func (c *Cluster) processBatchIO(addr string, ch <-chan *proto.MsgBatch, nc prot } return } - - err := c.processWriteBatch(nc, mb) - if err != nil { + if err := nc.WriteBatch(mb); err != nil { err = errors.Wrap(err, "Cluster batch write") mb.BatchDoneWithError(c.cc.Name, addr, err) continue } - - err = c.processReadBatch(nc, mb) - if err != nil { + if err := nc.ReadBatch(mb); err != nil { err = errors.Wrap(err, "Cluster batch read") mb.BatchDoneWithError(c.cc.Name, addr, err) continue @@ -206,15 +202,6 @@ func (c *Cluster) processBatchIO(addr string, ch <-chan *proto.MsgBatch, nc prot } } -func (c *Cluster) processWriteBatch(w proto.NodeConn, mb *proto.MsgBatch) error { - return w.WriteBatch(mb) -} - -func (c *Cluster) processReadBatch(r proto.NodeConn, mb *proto.MsgBatch) error { - err := r.ReadBatch(mb) - return err -} - func (c *Cluster) startPinger(cc *ClusterConfig, addrs []string, ws []int) { for idx, addr := range addrs { w := ws[idx] From b2a36af353bb29f71fda4592acf2702bf90b1baa Mon Sep 17 00:00:00 2001 From: felixhao Date: Thu, 26 Jul 2018 11:43:16 +0800 Subject: [PATCH 49/87] fix not support and ping --- lib/bufio/io.go | 68 -------------------- lib/net/conn.go | 9 +++ proto/redis/node_conn.go | 25 +++----- proto/redis/pinger.go | 5 +- proto/redis/proxy_conn.go | 9 +++ proto/redis/request.go | 126 ++++++++++++++++++++++++++++++++++++-- 6 files changed, 152 insertions(+), 90 deletions(-) diff --git a/lib/bufio/io.go b/lib/bufio/io.go index 6ef16387..966aa8db 100644 --- a/lib/bufio/io.go +++ b/lib/bufio/io.go @@ -145,74 +145,6 @@ func (r *Reader) ResetBuffer(b *Buffer) { r.b.w = b.w } -// ReadUntil reads until the first occurrence of delim in the input, -// returning a slice pointing at the bytes in the buffer. -// The bytes stop being valid at the next read. -// If ReadUntil encounters an error before finding a delimiter, -// it returns all the data in the buffer and the error itself (often io.EOF). -// ReadUntil returns err != nil if and only if line does not end in delim. -// func (r *Reader) ReadUntil(delim byte) ([]byte, error) { -// if r.err != nil { -// return nil, r.err -// } -// for { -// var index = bytes.IndexByte(r.b.buf[r.b.r:r.b.w], delim) -// if index >= 0 { -// limit := r.b.r + index + 1 -// slice := r.b.buf[r.b.r:limit] -// r.b.r = limit -// return slice, nil -// } -// if r.b.w >= r.b.len() { -// r.b.grow() -// } -// err := r.fill() -// if err == io.EOF && r.b.buffered() > 0 { -// data := r.b.buf[r.b.r:r.b.w] -// r.b.r = r.b.w -// return data, nil -// } else if err != nil { -// r.err = err -// return nil, err -// } -// } -// } - -// ReadFull reads exactly n bytes from r into buf. -// It returns the number of bytes copied and an error if fewer bytes were read. -// The error is EOF only if no bytes were read. -// If an EOF happens after reading some but not all the bytes, -// ReadFull returns ErrUnexpectedEOF. -// On return, n == len(buf) if and only if err == nil. -// func (r *Reader) ReadFull(n int) ([]byte, error) { -// if n <= 0 { -// return nil, nil -// } -// if r.err != nil { -// return nil, r.err -// } -// for { -// if r.b.buffered() >= n { -// bs := r.b.buf[r.b.r : r.b.r+n] -// r.b.r += n -// return bs, nil -// } -// maxCanRead := r.b.len() - r.b.w + r.b.buffered() -// if maxCanRead < n { -// r.b.grow() -// } -// err := r.fill() -// if err == io.EOF && r.b.buffered() > 0 { -// data := r.b.buf[r.b.r:r.b.w] -// r.b.r = r.b.w -// return data, nil -// } else if err != nil { -// r.err = err -// return nil, err -// } -// } -// } - // Writer implements buffering for an io.Writer object. // If an error occurs writing to a Writer, no more data will be // accepted and all subsequent writes, and Flush, will return the error. diff --git a/lib/net/conn.go b/lib/net/conn.go index 00b94ea1..b0a485be 100644 --- a/lib/net/conn.go +++ b/lib/net/conn.go @@ -1,10 +1,16 @@ package net import ( + "errors" "net" "time" ) +var ( + // ErrConnClosed error connection closed. + ErrConnClosed = errors.New("connection is closed") +) + // Conn is a net.Conn self implement // Add auto timeout setting. type Conn struct { @@ -103,5 +109,8 @@ func (c *Conn) Close() error { // Writev impl the net.buffersWriter to support writev func (c *Conn) Writev(buf *net.Buffers) (int64, error) { + if c.Conn == nil { + return 0, ErrConnClosed + } return buf.WriteTo(c.Conn) } diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index 46afd7eb..875fda97 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -44,8 +44,15 @@ func newNodeConn(cluster, addr string, conn *libnet.Conn) proto.NodeConn { func (nc *nodeConn) WriteBatch(mb *proto.MsgBatch) (err error) { for _, m := range mb.Msgs() { - err := nc.write(m) - if err != nil { + req, ok := m.Request().(*Request) + if !ok { + m.DoneWithError(ErrBadAssert) + return ErrBadAssert + } + if req.isSupport() || req.isCtl() { + return nil + } + if err = req.resp.encode(nc.bw); err != nil { m.DoneWithError(err) return err } @@ -54,18 +61,6 @@ func (nc *nodeConn) WriteBatch(mb *proto.MsgBatch) (err error) { return nc.bw.Flush() } -func (nc *nodeConn) write(m *proto.Message) (err error) { - req, ok := m.Request().(*Request) - if !ok { - m.DoneWithError(ErrBadAssert) - return ErrBadAssert - } - if req.notSupport() { - return nil - } - return req.resp.encode(nc.bw) -} - func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) (err error) { nc.br.ResetBuffer(mb.Buffer()) defer nc.br.ResetBuffer(nil) @@ -81,7 +76,7 @@ func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) (err error) { if !ok { return ErrBadAssert } - if req.notSupport() { + if req.isSupport() || req.isCtl() { i++ return } diff --git a/proto/redis/pinger.go b/proto/redis/pinger.go index 005e5709..5f9c8087 100644 --- a/proto/redis/pinger.go +++ b/proto/redis/pinger.go @@ -16,8 +16,9 @@ var ( ) var ( - pingBytes = []byte("*1\r\n$4\r\nPING\r\n") - pongBytes = []byte("+PONG\r\n") + pingBytes = []byte("*1\r\n$4\r\nPING\r\n") + pongBytes = []byte("+PONG\r\n") + notSupportBytes = []byte("-Error: command not support\r\n") ) type pinger struct { diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 80f2f051..a9627a48 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -157,6 +157,15 @@ func (pc *proxyConn) Encode(m *proto.Message) (err error) { return ErrBadAssert } if !m.IsBatch() { + if !req.isSupport() { + req.reply.rTp = respError + req.reply.data = notSupportBytes + } else if req.isCtl() { + if bytes.Equal(req.Cmd(), pingBytes) { + req.reply.rTp = respString + req.reply.data = pongBytes + } + } err = req.reply.encode(pc.bw) } else { switch req.mType { diff --git a/proto/redis/request.go b/proto/redis/request.go index d39ce29f..1e3e4708 100644 --- a/proto/redis/request.go +++ b/proto/redis/request.go @@ -23,7 +23,113 @@ var ( respNullBytes = []byte("-1\r\n") okBytes = []byte("OK") - notSupportCmdBytes = []byte("" + + reqReadCmdsBytes = []byte("" + + "4\r\nDUMP" + + "6\r\nEXISTS" + + "4\r\nPTTL" + + "3\r\nTTL" + + "4\r\nTYPE" + + "8\r\nBITCOUNT" + + "6\r\nBITPOS" + + "3\r\nGET" + + "6\r\nGETBIT" + + "8\r\nGETRANGE" + + "4\r\nMGET" + + "6\r\nSTRLEN" + + "7\r\nHEXISTS" + + "4\r\nHGET" + + "7\r\nHGETALL" + + "5\r\nHKEYS" + + "4\r\nHLEN" + + "5\r\nHMGET" + + "7\r\nHSTRLEN" + + "5\r\nHVALS" + + "5\r\nHSCAN" + + "5\r\nSCARD" + + "5\r\nSDIFF" + + "6\r\nSINTER" + + "9\r\nSISMEMBER" + + "8\r\nSMEMBERS" + + "11\r\nSRANDMEMBER" + + "6\r\nSUNION" + + "5\r\nSSCAN" + + "5\r\nZCARD" + + "6\r\nZCOUNT" + + "9\r\nZLEXCOUNT" + + "6\r\nZRANGE" + + "11\r\nZRANGEBYLEX" + + "13\r\nZRANGEBYSCORE" + + "5\r\nZRANK" + + "9\r\nZREVRANGE" + + "14\r\nZREVRANGEBYLEX" + + "16\r\nZREVRANGEBYSCORE" + + "8\r\nZREVRANK" + + "6\r\nZSCORE" + + "5\r\nZSCAN" + + "6\r\nLINDEX" + + "4\r\nLLEN" + + "6\r\nLRANGE" + + "7\r\nPFCOUNT") + + reqWriteCmdsBytes = []byte("" + + "3\r\nDEL" + + "6\r\nEXPIRE" + + "8\r\nEXPIREAT" + + "7\r\nPERSIST" + + "7\r\nPEXPIRE" + + "9\r\nPEXPIREAT" + + "7\r\nRESTORE" + + "4\r\nSORT" + + "6\r\nAPPEND" + + "4\r\nDECR" + + "6\r\nDECRBY" + + "6\r\nGETSET" + + "4\r\nINCR" + + "6\r\nINCRBY" + + "11\r\nINCRBYFLOAT" + + "4\r\nMSET" + + "6\r\nPSETEX" + + "3\r\nSET" + + "6\r\nSETBIT" + + "5\r\nSETEX" + + "5\r\nSETNX" + + "8\r\nSETRANGE" + + "4\r\nHDEL" + + "7\r\nHINCRBY" + + "12\r\nHINCRBYFLOAT" + + "5\r\nHMSET" + + "4\r\nHSET" + + "6\r\nHSETNX" + + "7\r\nLINSERT" + + "4\r\nLPOP" + + "5\r\nLPUSH" + + "6\r\nLPUSHX" + + "4\r\nLREM" + + "4\r\nLSET" + + "5\r\nLTRIM" + + "4\r\nRPOP" + + "9\r\nRPOPLPUSH" + + "5\r\nRPUSH" + + "6\r\nRPUSHX" + + "4\r\nSADD" + + "10\r\nSDIFFSTORE" + + "11\r\nSINTERSTORE" + + "5\r\nSMOVE" + + "4\r\nSPOP" + + "4\r\nSREM" + + "11\r\nSUNIONSTORE" + + "4\r\nZADD" + + "7\r\nZINCRBY" + + "11\r\nZINTERSTORE" + + "4\r\nZREM" + + "14\r\nZREMRANGEBYLEX" + + "15\r\nZREMRANGEBYRANK" + + "16\r\nZREMRANGEBYSCORE" + + "11\r\nZUNIONSTORE" + + "5\r\nPFADD" + + "7\r\nPFMERGE") + + reqNotSupportCmdsBytes = []byte("" + "6\r\nMSETNX" + "5\r\nBLPOP" + "5\r\nBRPOP" + @@ -50,6 +156,8 @@ var ( "4\r\nTIME" + "6\r\nCONFIG" + "8\r\nCOMMANDS") + + reqCtlCmdsBytes = []byte("4\r\nPING") ) // errors @@ -137,10 +245,18 @@ func (r *Request) Put() { reqPool.Put(r) } -// notSupport command not support -func (r *Request) notSupport() bool { +// isSupport check command support. +func (r *Request) isSupport() bool { + if r.resp.arrayn < 1 { + return bytes.Index(reqReadCmdsBytes, r.resp.data) > -1 || bytes.Index(reqWriteCmdsBytes, r.resp.data) > -1 + } + return bytes.Index(reqReadCmdsBytes, r.resp.array[0].data) > -1 || bytes.Index(reqWriteCmdsBytes, r.resp.array[0].data) > -1 +} + +// isCtl is control command. +func (r *Request) isCtl() bool { if r.resp.arrayn < 1 { - return false + return bytes.Index(reqCtlCmdsBytes, r.resp.data) > -1 } - return bytes.Index(notSupportCmdBytes, r.resp.array[0].data) > -1 + return bytes.Index(reqCtlCmdsBytes, r.resp.array[0].data) > -1 } From 675860033d4acaf028a936eff65256f3d6c00e97 Mon Sep 17 00:00:00 2001 From: wayslog Date: Thu, 26 Jul 2018 13:10:22 +0800 Subject: [PATCH 50/87] add error check for encode write --- lib/bufio/io.go | 5 ++- proto/redis/proxy_conn.go | 40 +++++++++++++----- proto/redis/resp.go | 89 ++++++++++++++++++++++++++++++--------- 3 files changed, 103 insertions(+), 31 deletions(-) diff --git a/lib/bufio/io.go b/lib/bufio/io.go index 966aa8db..8b279e99 100644 --- a/lib/bufio/io.go +++ b/lib/bufio/io.go @@ -195,7 +195,10 @@ func (w *Writer) Write(p []byte) (err error) { } if w.cursor+1 == maxBuffered { - w.Flush() + err = w.Flush() + if err != nil { + return err + } } w.bufs[w.cursor] = p w.cursor = (w.cursor + 1) % maxBuffered diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index a9627a48..73b7ab95 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -176,7 +176,7 @@ func (pc *proxyConn) Encode(m *proto.Message) (err error) { case mergeTypeCount: err = pc.mergeCount(m) default: - // TODO: panic??? + panic("unreachable merge path") } } if err != nil { @@ -189,10 +189,21 @@ func (pc *proxyConn) Encode(m *proto.Message) (err error) { return } +func (pc *proxyConn) writeOneLine(rtype, data []byte) (err error) { + err = pc.bw.Write(rtype) + if err != nil { + return err + } + err = pc.bw.Write(data) + if err != nil { + return err + } + err = pc.bw.Write(crlfBytes) + return +} + func (pc *proxyConn) mergeOK(m *proto.Message) (err error) { - _ = pc.bw.Write(respStringBytes) - _ = pc.bw.Write(okBytes) - _ = pc.bw.Write(crlfBytes) + err = pc.writeOneLine(respStringBytes, okBytes) return } @@ -209,21 +220,28 @@ func (pc *proxyConn) mergeCount(m *proto.Message) (err error) { } sum += int(ival) } - _ = pc.bw.Write(respIntBytes) - _ = pc.bw.Write([]byte(strconv.Itoa(sum))) - _ = pc.bw.Write(crlfBytes) + err = pc.writeOneLine(respIntBytes, []byte(strconv.Itoa(sum))) return } func (pc *proxyConn) mergeJoin(m *proto.Message) (err error) { reqs := m.Requests() - _ = pc.bw.Write(respArrayBytes) + err = pc.bw.Write(respArrayBytes) + if err != nil { + return + } if len(reqs) == 0 { - _ = pc.bw.Write(respNullBytes) + err = pc.bw.Write(respNullBytes) + return + } + err = pc.bw.Write([]byte(strconv.Itoa(len(reqs)))) + if err != nil { + return + } + err = pc.bw.Write(crlfBytes) + if err != nil { return } - _ = pc.bw.Write([]byte(strconv.Itoa(len(reqs)))) - _ = pc.bw.Write(crlfBytes) for _, mreq := range reqs { req, ok := mreq.(*Request) if !ok { diff --git a/proto/redis/resp.go b/proto/redis/resp.go index abf24932..90bb1258 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -121,6 +121,9 @@ func (r *resp) decodeArray(line []byte, br *bufio.Reader) error { for i := 0; i < int(size); i++ { nre := r.next() err = nre.decode(br) + if err != nil { + return err + } } return nil } @@ -128,52 +131,100 @@ func (r *resp) decodeArray(line []byte, br *bufio.Reader) error { func (r *resp) encode(w *bufio.Writer) (err error) { switch r.rTp { case respInt, respString, respError: - _ = r.encodePlain(w) + err = r.encodePlain(w) + if err != nil { + return + } case respBulk: - _ = r.encodeBulk(w) + err = r.encodeBulk(w) + if err != nil { + return + } case respArray: - _ = r.encodeArray(w) + err = r.encodeArray(w) + if err != nil { + return + } } - return nil + return } func (r *resp) encodePlain(w *bufio.Writer) (err error) { switch r.rTp { case respInt: - _ = w.Write(respIntBytes) + err = w.Write(respIntBytes) + if err != nil { + return + } + case respError: - _ = w.Write(respErrorBytes) + err = w.Write(respErrorBytes) + if err != nil { + return + } case respString: - _ = w.Write(respStringBytes) + err = w.Write(respStringBytes) + if err != nil { + return + } } if len(r.data) > 0 { - _ = w.Write(r.data) + err = w.Write(r.data) + if err != nil { + return + } } err = w.Write(crlfBytes) - return err + return } func (r *resp) encodeBulk(w *bufio.Writer) (err error) { - _ = w.Write(respBulkBytes) + err = w.Write(respBulkBytes) + if err != nil { + return + } + if len(r.data) > 0 { - _ = w.Write(r.data) + err = w.Write(r.data) + if err != nil { + return + } } else { - _ = w.Write(respNullBytes) + err = w.Write(respNullBytes) + if err != nil { + return + } } err = w.Write(crlfBytes) - return err + return } func (r *resp) encodeArray(w *bufio.Writer) (err error) { - _ = w.Write(respArrayBytes) + err = w.Write(respArrayBytes) + if err != nil { + return + } + if len(r.data) > 0 { - _ = w.Write(r.data) + err = w.Write(r.data) + if err != nil { + return + } } else { - _ = w.Write(respNullBytes) + err = w.Write(respNullBytes) + if err != nil { + return + } + } + err = w.Write(crlfBytes) + if err != nil { + return } - _ = w.Write(crlfBytes) for i := 0; i < r.arrayn; i++ { - _ = r.array[i].encode(w) + err = r.array[i].encode(w) + if err != nil { + return + } } - return nil + return } From 239a88d47a31471bd1f1465e7660a0ab35d6c703 Mon Sep 17 00:00:00 2001 From: felixhao Date: Thu, 26 Jul 2018 13:40:50 +0800 Subject: [PATCH 51/87] rm fmt --- cmd/proxy/proxy-cluster-example.toml | 2 +- proto/redis/node_conn.go | 12 ++++-------- proto/redis/proxy_conn.go | 5 ++--- proto/redis/resp.go | 6 +++++- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/cmd/proxy/proxy-cluster-example.toml b/cmd/proxy/proxy-cluster-example.toml index 265940fb..936b1cd3 100644 --- a/cmd/proxy/proxy-cluster-example.toml +++ b/cmd/proxy/proxy-cluster-example.toml @@ -60,7 +60,7 @@ node_connections = 2 # The number of consecutive failures on a server that would lead to it being temporarily ejected when auto_eject is set to true. Defaults to 3. ping_fail_limit = 3 # A boolean value that controls if server should be ejected temporarily when it fails consecutively ping_fail_limit times. -ping_auto_eject = true +ping_auto_eject = false # A list of server address, port and weight (name:port:weight or ip:port:weight) for this server pool. Also you can use alias name like: ip:port:weight alias. servers = [ "127.0.0.1:6379:1", diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index 875fda97..1f1dd99b 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -49,7 +49,7 @@ func (nc *nodeConn) WriteBatch(mb *proto.MsgBatch) (err error) { m.DoneWithError(ErrBadAssert) return ErrBadAssert } - if req.isSupport() || req.isCtl() { + if !req.isSupport() || req.isCtl() { return nil } if err = req.resp.encode(nc.bw); err != nil { @@ -64,10 +64,6 @@ func (nc *nodeConn) WriteBatch(mb *proto.MsgBatch) (err error) { func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) (err error) { nc.br.ResetBuffer(mb.Buffer()) defer nc.br.ResetBuffer(nil) - // read - if err = nc.br.Read(); err != nil { - return - } begin := nc.br.Mark() now := nc.br.Mark() for i := 0; i < mb.Count(); { @@ -76,9 +72,9 @@ func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) (err error) { if !ok { return ErrBadAssert } - if req.isSupport() || req.isCtl() { + if !req.isSupport() || req.isCtl() { i++ - return + continue } if err = req.reply.decode(nc.br); err == bufio.ErrBufferFull { nc.br.AdvanceTo(begin) @@ -91,8 +87,8 @@ func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) (err error) { return } m.MarkRead() - i++ now = nc.br.Mark() + i++ } return } diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index a9627a48..fe4fa46f 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -34,8 +34,7 @@ func NewProxyConn(conn *libnet.Conn) proto.ProxyConn { func (pc *proxyConn) Decode(msgs []*proto.Message) ([]*proto.Message, error) { var err error if pc.completed { - err = pc.br.Read() - if err != nil { + if err = pc.br.Read(); err != nil { return nil, err } pc.completed = false @@ -55,7 +54,7 @@ func (pc *proxyConn) Decode(msgs []*proto.Message) ([]*proto.Message, error) { } func (pc *proxyConn) decode(m *proto.Message) (err error) { - if err = pc.resp.decode(pc.br); err != nil { + if err = pc.resp.decode(pc.br); err != nil && err != bufio.ErrBufferFull { return } if pc.resp.arrayn < 1 { diff --git a/proto/redis/resp.go b/proto/redis/resp.go index abf24932..60cc4bd2 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -118,9 +118,13 @@ func (r *resp) decodeArray(line []byte, br *bufio.Reader) error { return nil } r.data = sBs + mark := br.Mark() for i := 0; i < int(size); i++ { nre := r.next() - err = nre.decode(br) + if err = nre.decode(br); err != nil { + br.AdvanceTo(mark) + br.Advance(-ls) + } } return nil } From 05364c2dc7e1a26da4261e46dff72e0be8ad0afe Mon Sep 17 00:00:00 2001 From: felixhao Date: Thu, 26 Jul 2018 14:07:45 +0800 Subject: [PATCH 52/87] fix conv update --- lib/conv/conv.go | 4 ++-- proto/redis/proxy_conn.go | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/conv/conv.go b/lib/conv/conv.go index 386598d3..2fd72417 100644 --- a/lib/conv/conv.go +++ b/lib/conv/conv.go @@ -39,7 +39,7 @@ func Btoi(b []byte) (int64, error) { func UpdateToLower(src []byte) { const step = byte('a') - byte('A') for i := range src { - if src[i] >= 'A' || src[i] <= 'Z' { + if src[i] >= 'A' && src[i] <= 'Z' { src[i] += step } } @@ -49,7 +49,7 @@ func UpdateToLower(src []byte) { func UpdateToUpper(src []byte) { const step = byte('a') - byte('A') for i := range src { - if src[i] >= 'a' || src[i] <= 'z' { + if src[i] >= 'a' && src[i] <= 'z' { src[i] -= step } } diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 82c2260f..5788e73f 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -58,8 +58,10 @@ func (pc *proxyConn) decode(m *proto.Message) (err error) { return } if pc.resp.arrayn < 1 { + conv.UpdateToUpper(pc.resp.data) return } + conv.UpdateToUpper(pc.resp.array[0].data) cmd := pc.resp.array[0].data // NOTE: when array, first is command if bytes.Equal(cmd, cmdMSetBytes) { mid := pc.resp.arrayn / 2 From 46aa4c412ea10df90f9f4ac5323bb367c7264a64 Mon Sep 17 00:00:00 2001 From: wayslog Date: Thu, 26 Jul 2018 14:14:17 +0800 Subject: [PATCH 53/87] add validate python scripts --- validate.py | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100755 validate.py diff --git a/validate.py b/validate.py new file mode 100755 index 00000000..aa7febce --- /dev/null +++ b/validate.py @@ -0,0 +1,83 @@ +#!/bin/env python +import redis +import sys + +host = "127.0.0.1" +port = 26379 +timeout = 3 + +def gen_items(prefix, n): + return ["%s%010d" % (prefix, num) for num in range(n)] + +def check_set_get_cmd(step, keys, vals): + rc = redis.StrictRedis(host=host, port=port, socket_timeout=timeout) + iter_count = len(keys) / step + for idx in range(iter_count): + begin = idx * step + end = idx * step + step + for i in range(begin, end): + rc.set(keys[i], vals[i]) + + for i in range(begin, end): + val = rc.get(keys[i]).encode("utf8") + assert val == vals[i], "Error Validate set get" + + +def check_mset_mget_cmd(key_count,step, keys, vals): + rc = redis.StrictRedis(host=host, port=port, socket_timeout=timeout) + iter_count = len(keys) / (key_count * step) + + for idx in range(iter_count): + base = idx * (step * key_count) + for i in range(step): + begin = base + i * key_count + end = base + (i+1) * key_count + + offsets = [offset for offset in range(begin,end)] + subkeys = [keys[o] for o in offsets] + subvals = [vals[o] for o in offsets] + + mapping = dict(zip(subkeys, subvals)) + rc.mset(mapping) + rvals = [x.encode("utf8") for x in rc.mget(subkeys)] + assert rvals == subvals, "Error validate mset mget" + +def check_hmset_hmget_cmd(key_count, step, hashs, keys, vals): + rc = redis.StrictRedis(host=host, port=port, socket_timeout=timeout) + iter_count = len(keys) / (key_count * step) + + for idx in range(iter_count): + base = idx * (step * key_count) + + for i in range(step): + begin = base + i * key_count + end = base + (i+1) * key_count + hash_idx = idx * step + i + + offsets = [offset for offset in range(begin,end)] + subkeys = [keys[o] for o in offsets] + subvals = [vals[o] for o in offsets] + + mapping = dict(zip(subkeys, subvals)) + rc.hmset(hashs[hash_idx], mapping) + rvals = rc.hmget(hashs[hash_idx],subkeys) + assert rvals == subvals, "Error validate mset mget" + + +def main(): + global host, port + if len(sys.argv) > 1: + host = sys.argv[1].strip() + port = int(sys.argv[2].strip()) + + keys = gen_items("keys-", 1000) + vals = gen_items("vals-",1000) + check_set_get_cmd(10, keys, vals) + check_mset_mget_cmd(10, 10, keys, vals) + + hashs = gen_items("hash-",100) + check_hmset_hmget_cmd(10, 10, hashs, keys, vals) + +if __name__ == "__main__": + main() +v From acb3174a5f8f996307a6f53f0163ca53b0491084 Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Thu, 26 Jul 2018 14:24:28 +0800 Subject: [PATCH 54/87] fix uint test --- proto/redis/node_conn.go | 6 ++-- proto/redis/node_conn_test.go | 64 +++++++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index 875fda97..0a57312b 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -65,9 +65,9 @@ func (nc *nodeConn) ReadBatch(mb *proto.MsgBatch) (err error) { nc.br.ResetBuffer(mb.Buffer()) defer nc.br.ResetBuffer(nil) // read - if err = nc.br.Read(); err != nil { - return - } + // if err = nc.br.Read(); err != nil { + // return + // } begin := nc.br.Mark() now := nc.br.Mark() for i := 0; i < mb.Count(); { diff --git a/proto/redis/node_conn_test.go b/proto/redis/node_conn_test.go index 9745ebaf..38a43f49 100644 --- a/proto/redis/node_conn_test.go +++ b/proto/redis/node_conn_test.go @@ -1,9 +1,12 @@ package redis import ( + "bytes" + "fmt" + "io" + "strconv" "testing" - "overlord/lib/bufio" "overlord/proto" "github.com/stretchr/testify/assert" @@ -96,13 +99,16 @@ func TestReadBatchWithNilError(t *testing.T) { mb := proto.NewMsgBatch() msg := proto.GetMsg() req := getReq() - req.mType = mergeTypeNo + req.mType = mergeTypeJoin req.reply = &resp{} + req.resp = newresp(respArray, []byte("2")) + req.resp.array = append(req.resp.array, newresp(respBulk, []byte("get"))) + req.resp.arrayn++ msg.WithRequest(req) mb.AddMsg(msg) err := nc.ReadBatch(mb) assert.Error(t, err) - assert.Equal(t, bufio.ErrBufferFull, err) + assert.Equal(t, io.EOF, err) } func TestPingOk(t *testing.T) { @@ -110,3 +116,55 @@ func TestPingOk(t *testing.T) { err := nc.Ping() assert.NoError(t, err) } + +func newRequest(cmd string, args ...string) *Request { + respObj := &resp{} + respObj.array = append(respObj.array, newresp(respBulk, []byte(fmt.Sprintf("%d\r\n%s", len(cmd), cmd)))) + respObj.arrayn++ + maxLen := len(args) + 1 + for i := 1; i < maxLen; i++ { + data := args[i-1] + line := fmt.Sprintf("%d\r\n%s", len(data), data) + respObj.array = append(respObj.array, newresp(respBulk, []byte(line))) + respObj.arrayn++ + } + respObj.data = []byte(strconv.Itoa(len(args) + 1)) + return &Request{ + resp: respObj, + mType: getMergeType(respObj.array[0].data), + reply: &resp{}, + } +} +func getMergeType(cmd []byte) mergeType { + // fmt.Println("mtype :", strconv.Quote(string(cmd))) + // TODO: impl with tire tree to search quickly + if bytes.Equal(cmd, cmdMGetBytes) || bytes.Equal(cmd, cmdGetBytes) { + return mergeTypeJoin + } + + if bytes.Equal(cmd, cmdMSetBytes) { + return mergeTypeOK + } + + if bytes.Equal(cmd, cmdExistsBytes) || bytes.Equal(cmd, cmdDelBytes) { + return mergeTypeCount + } + + return mergeTypeNo +} + +func newresp(rtype respType, data []byte) (robj *resp) { + robj = &resp{} + robj.rTp = rtype + robj.data = data + return +} + +func newrespArray(resps []*resp) (robj *resp) { + robj = &resp{} + robj.rTp = respArray + robj.data = []byte((strconv.Itoa(len(resps)))) + robj.array = resps + robj.arrayn = len(resps) + return +} From 88f1cf5241ac205902c206a30f376ca77ffe3596 Mon Sep 17 00:00:00 2001 From: felixhao Date: Thu, 26 Jul 2018 14:26:01 +0800 Subject: [PATCH 55/87] fix proxy conn encode pong and not support --- proto/redis/pinger.go | 5 ++--- proto/redis/proxy_conn.go | 9 +++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/proto/redis/pinger.go b/proto/redis/pinger.go index 5f9c8087..005e5709 100644 --- a/proto/redis/pinger.go +++ b/proto/redis/pinger.go @@ -16,9 +16,8 @@ var ( ) var ( - pingBytes = []byte("*1\r\n$4\r\nPING\r\n") - pongBytes = []byte("+PONG\r\n") - notSupportBytes = []byte("-Error: command not support\r\n") + pingBytes = []byte("*1\r\n$4\r\nPING\r\n") + pongBytes = []byte("+PONG\r\n") ) type pinger struct { diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 5788e73f..c015b1f5 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -12,6 +12,11 @@ import ( "github.com/pkg/errors" ) +var ( + pongDataBytes = []byte("+PONG") + notSupportDataBytes = []byte("-Error: command not support") +) + type proxyConn struct { br *bufio.Reader bw *bufio.Writer @@ -160,11 +165,11 @@ func (pc *proxyConn) Encode(m *proto.Message) (err error) { if !m.IsBatch() { if !req.isSupport() { req.reply.rTp = respError - req.reply.data = notSupportBytes + req.reply.data = notSupportDataBytes } else if req.isCtl() { if bytes.Equal(req.Cmd(), pingBytes) { req.reply.rTp = respString - req.reply.data = pongBytes + req.reply.data = pongDataBytes } } err = req.reply.encode(pc.bw) From 45fc815e2ad87e1876cad89b471f6e1ea4cf9dca Mon Sep 17 00:00:00 2001 From: felixhao Date: Thu, 26 Jul 2018 14:40:04 +0800 Subject: [PATCH 56/87] check upper and is support --- proto/redis/proxy_conn.go | 1 - proto/redis/request.go | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index c015b1f5..eaa0961c 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -63,7 +63,6 @@ func (pc *proxyConn) decode(m *proto.Message) (err error) { return } if pc.resp.arrayn < 1 { - conv.UpdateToUpper(pc.resp.data) return } conv.UpdateToUpper(pc.resp.array[0].data) diff --git a/proto/redis/request.go b/proto/redis/request.go index 1e3e4708..d04ea42c 100644 --- a/proto/redis/request.go +++ b/proto/redis/request.go @@ -248,7 +248,7 @@ func (r *Request) Put() { // isSupport check command support. func (r *Request) isSupport() bool { if r.resp.arrayn < 1 { - return bytes.Index(reqReadCmdsBytes, r.resp.data) > -1 || bytes.Index(reqWriteCmdsBytes, r.resp.data) > -1 + return false } return bytes.Index(reqReadCmdsBytes, r.resp.array[0].data) > -1 || bytes.Index(reqWriteCmdsBytes, r.resp.array[0].data) > -1 } @@ -256,7 +256,7 @@ func (r *Request) isSupport() bool { // isCtl is control command. func (r *Request) isCtl() bool { if r.resp.arrayn < 1 { - return bytes.Index(reqCtlCmdsBytes, r.resp.data) > -1 + return false } return bytes.Index(reqCtlCmdsBytes, r.resp.array[0].data) > -1 } From 6d21f1d214e9c388f3bf4809ed98af79b609b249 Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Thu, 26 Jul 2018 14:55:17 +0800 Subject: [PATCH 57/87] check mset len --- proto/redis/proxy_conn.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 5788e73f..587b4f40 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -57,13 +57,13 @@ func (pc *proxyConn) decode(m *proto.Message) (err error) { if err = pc.resp.decode(pc.br); err != nil && err != bufio.ErrBufferFull { return } - if pc.resp.arrayn < 1 { - conv.UpdateToUpper(pc.resp.data) - return - } conv.UpdateToUpper(pc.resp.array[0].data) cmd := pc.resp.array[0].data // NOTE: when array, first is command if bytes.Equal(cmd, cmdMSetBytes) { + if pc.resp.arrayn%2 == 0 { + err = ErrBadRequest + return + } mid := pc.resp.arrayn / 2 for i := 0; i < mid; i++ { r := nextReq(m) From bf245ae025ed7a3ff58ff2770bed9e0ac3b1dd9f Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Thu, 26 Jul 2018 15:11:47 +0800 Subject: [PATCH 58/87] fix nullresp --- proto/redis/resp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/proto/redis/resp.go b/proto/redis/resp.go index b03b1be3..13c391de 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -171,6 +171,7 @@ func (r *resp) encodeBulk(w *bufio.Writer) (err error) { err = w.Write(r.data) } else { err = w.Write(respNullBytes) + return } if err != nil { return From a140ac9d9f63659abe0c9c5fbac23483b1ed9144 Mon Sep 17 00:00:00 2001 From: felixhao Date: Thu, 26 Jul 2018 15:21:56 +0800 Subject: [PATCH 59/87] fix response -1 \r\n --- lib/bufio/io.go | 29 +++++++++++---------------- proto/redis/proxy_conn.go | 30 ++++++++++------------------ proto/redis/request.go | 3 --- proto/redis/resp.go | 41 +++++++++++++-------------------------- 4 files changed, 34 insertions(+), 69 deletions(-) diff --git a/lib/bufio/io.go b/lib/bufio/io.go index fe5b0729..d58ce2c9 100644 --- a/lib/bufio/io.go +++ b/lib/bufio/io.go @@ -9,10 +9,6 @@ import ( libnet "overlord/lib/net" ) -const ( - maxBuffered = 64 -) - // ErrProxy var ( ErrBufferFull = bufio.ErrBufferFull @@ -152,17 +148,18 @@ func (r *Reader) ResetBuffer(b *Buffer) { // Flush method to guarantee all data has been forwarded to // the underlying io.Writer. type Writer struct { - wr *libnet.Conn - bufsp net.Buffers - bufs [][]byte - cursor int + wr *libnet.Conn + bufsp net.Buffers + bufs [][]byte + cnt int err error } // NewWriter returns a new Writer whose buffer has the default size. func NewWriter(wr *libnet.Conn) *Writer { - return &Writer{wr: wr, bufs: make([][]byte, maxBuffered)} + const defaultBuffered = 64 + return &Writer{wr: wr, bufs: make([][]byte, 0, defaultBuffered)} } // Flush writes any buffered data to the underlying io.Writer. @@ -173,12 +170,13 @@ func (w *Writer) Flush() error { if len(w.bufs) == 0 { return nil } - w.bufsp = net.Buffers(w.bufs[:w.cursor]) + w.bufsp = net.Buffers(w.bufs[:w.cnt]) _, err := w.wr.Writev(&w.bufsp) if err != nil { w.err = err } - w.cursor = 0 + w.bufs = w.bufs[:0] + w.cnt = 0 return w.err } @@ -193,12 +191,7 @@ func (w *Writer) Write(p []byte) (err error) { if p == nil { return nil } - if w.cursor+1 == maxBuffered { - if err = w.Flush(); err != nil { - return err - } - } - w.bufs[w.cursor] = p - w.cursor = (w.cursor + 1) % maxBuffered + w.bufs = append(w.bufs, p) + w.cnt++ return nil } diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index eaa0961c..2be2f2f5 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -13,6 +13,8 @@ import ( ) var ( + nullBytes = []byte("-1\r\n") + okBytes = []byte("OK\r\n") pongDataBytes = []byte("+PONG") notSupportDataBytes = []byte("-Error: command not support") ) @@ -194,19 +196,9 @@ func (pc *proxyConn) Encode(m *proto.Message) (err error) { return } -func (pc *proxyConn) writeOneLine(rtype, data []byte) (err error) { - if err = pc.bw.Write(rtype); err != nil { - return - } - if err = pc.bw.Write(data); err != nil { - return - } - err = pc.bw.Write(crlfBytes) - return -} - func (pc *proxyConn) mergeOK(m *proto.Message) (err error) { - err = pc.writeOneLine(respStringBytes, okBytes) + _ = pc.bw.Write(respStringBytes) + err = pc.bw.Write(okBytes) return } @@ -223,22 +215,20 @@ func (pc *proxyConn) mergeCount(m *proto.Message) (err error) { } sum += int(ival) } - err = pc.writeOneLine(respIntBytes, []byte(strconv.Itoa(sum))) + _ = pc.bw.Write(respIntBytes) + _ = pc.bw.Write([]byte(strconv.Itoa(sum))) + err = pc.bw.Write(crlfBytes) return } func (pc *proxyConn) mergeJoin(m *proto.Message) (err error) { reqs := m.Requests() - if err = pc.bw.Write(respArrayBytes); err != nil { - return - } + _ = pc.bw.Write(respArrayBytes) if len(reqs) == 0 { - err = pc.bw.Write(respNullBytes) - return - } - if err = pc.bw.Write([]byte(strconv.Itoa(len(reqs)))); err != nil { + err = pc.bw.Write(nullBytes) return } + _ = pc.bw.Write([]byte(strconv.Itoa(len(reqs)))) if err = pc.bw.Write(crlfBytes); err != nil { return } diff --git a/proto/redis/request.go b/proto/redis/request.go index d04ea42c..f4696741 100644 --- a/proto/redis/request.go +++ b/proto/redis/request.go @@ -20,9 +20,6 @@ var ( cmdDelBytes = []byte("3\r\nDEL") cmdExistsBytes = []byte("6\r\nEXISTS") - respNullBytes = []byte("-1\r\n") - okBytes = []byte("OK") - reqReadCmdsBytes = []byte("" + "4\r\nDUMP" + "6\r\nEXISTS" + diff --git a/proto/redis/resp.go b/proto/redis/resp.go index b03b1be3..ee557e63 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -24,6 +24,8 @@ var ( respIntBytes = []byte(":") respBulkBytes = []byte("$") respArrayBytes = []byte("*") + + nullDataBytes = []byte("-1") ) // resp is a redis server protocol item. @@ -145,55 +147,38 @@ func (r *resp) encode(w *bufio.Writer) (err error) { func (r *resp) encodePlain(w *bufio.Writer) (err error) { switch r.rTp { case respInt: - err = w.Write(respIntBytes) + _ = w.Write(respIntBytes) case respError: - err = w.Write(respErrorBytes) + _ = w.Write(respErrorBytes) case respString: - err = w.Write(respStringBytes) - } - if err != nil { - return + _ = w.Write(respStringBytes) } if len(r.data) > 0 { - if err = w.Write(r.data); err != nil { - return - } + _ = w.Write(r.data) } err = w.Write(crlfBytes) return } func (r *resp) encodeBulk(w *bufio.Writer) (err error) { - if err = w.Write(respBulkBytes); err != nil { - return - } + _ = w.Write(respBulkBytes) if len(r.data) > 0 { - err = w.Write(r.data) + _ = w.Write(r.data) } else { - err = w.Write(respNullBytes) - } - if err != nil { - return + _ = w.Write(nullDataBytes) } err = w.Write(crlfBytes) return } func (r *resp) encodeArray(w *bufio.Writer) (err error) { - if err = w.Write(respArrayBytes); err != nil { - return - } + _ = w.Write(respArrayBytes) if len(r.data) > 0 { - err = w.Write(r.data) + _ = w.Write(r.data) } else { - err = w.Write(respNullBytes) - } - if err != nil { - return - } - if err = w.Write(crlfBytes); err != nil { - return + _ = w.Write(nullDataBytes) } + _ = w.Write(crlfBytes) for i := 0; i < r.arrayn; i++ { if err = r.array[i].encode(w); err != nil { return From 31ff70d6f7a67806e9b1a8b33d5a1beaf8607885 Mon Sep 17 00:00:00 2001 From: felixhao Date: Thu, 26 Jul 2018 15:38:28 +0800 Subject: [PATCH 60/87] fix not array request --- proto/redis/proxy_conn.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index a0b52d40..28da559d 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -16,7 +16,7 @@ var ( nullBytes = []byte("-1\r\n") okBytes = []byte("OK\r\n") pongDataBytes = []byte("+PONG") - notSupportDataBytes = []byte("-Error: command not support") + notSupportDataBytes = []byte("Error: command not support") ) type proxyConn struct { @@ -65,6 +65,10 @@ func (pc *proxyConn) decode(m *proto.Message) (err error) { return } if pc.resp.arrayn < 1 { + r := nextReq(m) + r.resp.reset() + r.resp.rTp = pc.resp.rTp + r.resp.data = pc.resp.data return } conv.UpdateToUpper(pc.resp.array[0].data) From 705befdeb4a883d919ff25db470ad42d591d3a45 Mon Sep 17 00:00:00 2001 From: wayslog Date: Thu, 26 Jul 2018 14:42:39 +0800 Subject: [PATCH 61/87] add ErrBufferFull advance read pointer fixed of validate commands --- proto/redis/proxy_conn.go | 6 +++++- proto/redis/resp.go | 23 +++++++++++++++++++++++ validate.py | 7 ++++--- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index 28da559d..bfd2136a 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -61,7 +61,11 @@ func (pc *proxyConn) Decode(msgs []*proto.Message) ([]*proto.Message, error) { } func (pc *proxyConn) decode(m *proto.Message) (err error) { - if err = pc.resp.decode(pc.br); err != nil && err != bufio.ErrBufferFull { + mark := pc.br.Mark() + if err = pc.resp.decode(pc.br); err != nil { + if err == bufio.ErrBufferFull { + pc.br.AdvanceTo(mark) + } return } if pc.resp.arrayn < 1 { diff --git a/proto/redis/resp.go b/proto/redis/resp.go index ee557e63..4953eb0c 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -186,3 +186,26 @@ func (r *resp) encodeArray(w *bufio.Writer) (err error) { } return } + +// func (r *resp) String() string { +// var sb strings.Builder +// sb.Write([]byte{r.rTp}) +// switch r.rTp { +// case respString, respInt, respError: +// sb.Write(r.data) +// sb.Write(crlfBytes) +// case respBulk: +// sb.Write(r.data) +// sb.Write(crlfBytes) +// case respArray: +// sb.Write([]byte(strconv.Itoa(r.arrayn))) +// sb.Write(crlfBytes) + +// for i := 0; i < r.arrayn; i++ { +// sb.WriteString(r.array[i].String()) +// } +// default: +// panic(fmt.Sprintf("not support robj:%s", sb.String())) +// } +// return sb.String() +// } diff --git a/validate.py b/validate.py index aa7febce..3f217a91 100755 --- a/validate.py +++ b/validate.py @@ -1,4 +1,4 @@ -#!/bin/env python +#!/usr/bin/env python import redis import sys @@ -66,9 +66,11 @@ def check_hmset_hmget_cmd(key_count, step, hashs, keys, vals): def main(): global host, port - if len(sys.argv) > 1: + if len(sys.argv) == 3: host = sys.argv[1].strip() port = int(sys.argv[2].strip()) + elif len(sys.argv) == 2: + port = int(sys.argv[2].strip()) keys = gen_items("keys-", 1000) vals = gen_items("vals-",1000) @@ -80,4 +82,3 @@ def main(): if __name__ == "__main__": main() -v From 7c834a67ce58ec35edad62f8a8cbcd2287882a3a Mon Sep 17 00:00:00 2001 From: wayslog Date: Fri, 27 Jul 2018 13:36:34 +0800 Subject: [PATCH 62/87] reduce response data init buffer size to reduce alloc of connection --- proto/batch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto/batch.go b/proto/batch.go index 91cb7c12..ce7df3b9 100644 --- a/proto/batch.go +++ b/proto/batch.go @@ -12,7 +12,7 @@ import ( ) const ( - defaultRespBufSize = 4096 + defaultRespBufSize = 1024 defaultMsgBatchSize = 2 ) From 3fd9f6b885e6cc301e74105cc410aa6ebcc2ba51 Mon Sep 17 00:00:00 2001 From: wayslog Date: Fri, 27 Jul 2018 17:00:21 +0800 Subject: [PATCH 63/87] refactor validate.py using fakeredis to make diff --- validate.py | 257 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 197 insertions(+), 60 deletions(-) diff --git a/validate.py b/validate.py index 3f217a91..b46d3a40 100755 --- a/validate.py +++ b/validate.py @@ -1,67 +1,208 @@ #!/usr/bin/env python -import redis +from gevent.monkey import patch_all +patch_all() +from gevent.pool import Pool + +from fakeredis import FakeStrictRedis +from redis import StrictRedis + +import random import sys host = "127.0.0.1" port = 26379 timeout = 3 + def gen_items(prefix, n): return ["%s%010d" % (prefix, num) for num in range(n)] -def check_set_get_cmd(step, keys, vals): - rc = redis.StrictRedis(host=host, port=port, socket_timeout=timeout) - iter_count = len(keys) / step - for idx in range(iter_count): - begin = idx * step - end = idx * step + step - for i in range(begin, end): - rc.set(keys[i], vals[i]) - - for i in range(begin, end): - val = rc.get(keys[i]).encode("utf8") - assert val == vals[i], "Error Validate set get" - - -def check_mset_mget_cmd(key_count,step, keys, vals): - rc = redis.StrictRedis(host=host, port=port, socket_timeout=timeout) - iter_count = len(keys) / (key_count * step) - - for idx in range(iter_count): - base = idx * (step * key_count) - for i in range(step): - begin = base + i * key_count - end = base + (i+1) * key_count - offsets = [offset for offset in range(begin,end)] - subkeys = [keys[o] for o in offsets] - subvals = [vals[o] for o in offsets] - - mapping = dict(zip(subkeys, subvals)) - rc.mset(mapping) - rvals = [x.encode("utf8") for x in rc.mget(subkeys)] - assert rvals == subvals, "Error validate mset mget" - -def check_hmset_hmget_cmd(key_count, step, hashs, keys, vals): - rc = redis.StrictRedis(host=host, port=port, socket_timeout=timeout) - iter_count = len(keys) / (key_count * step) - - for idx in range(iter_count): - base = idx * (step * key_count) - - for i in range(step): - begin = base + i * key_count - end = base + (i+1) * key_count - hash_idx = idx * step + i - - offsets = [offset for offset in range(begin,end)] - subkeys = [keys[o] for o in offsets] - subvals = [vals[o] for o in offsets] - - mapping = dict(zip(subkeys, subvals)) - rc.hmset(hashs[hash_idx], mapping) - rvals = rc.hmget(hashs[hash_idx],subkeys) - assert rvals == subvals, "Error validate mset mget" +class Cmd(object): + def __init__(self, fn, *args, **kwargs): + self.fn = fn + self.args = args + self.kwargs = kwargs + + def execute(self): + rslt = self.fn(*self.args, **self.kwargs) + + def __str__(self): + return "Cmd" % (self.fn.__name__, self.args, + self.kwargs) + + def __repr__(self): + return self.__str__() + + +def append_cmd(l, is_write, rcfn, fakefn, *args, **kwargs): + l.append((is_write, Cmd(rcfn, *args, **kwargs), Cmd( + fakefn, *args, **kwargs))) + + +def gen_cmds(fake, rc, keys, vals): + """ Checked Commands list: + STRING Commands: GET(1) SET(2) MGET(n) MSET(2n) + HASH Commands: HGET(2) HGETALL(1) HMGET(2n) HSET(3) HMSET(1+2n) + SET Commands: SCARD(1) SMEMBERS(1) SISMEMBER(2) SADD(2) + ZSET Commands: ZCOUNT(1) ZCARD(1) ZRANGE(3) ZADD(3) + LIST Commands: LLEN(1) LPOP(1) LPUSH(2) RPOP(1) + HYPERLOGLOG Commands: PFCOUNT(1) PFADD + """ + # string + string_cmd_pairs = [] + append_cmd(string_cmd_pairs, False, rc.get, fake.get, keys[0]) + append_cmd(string_cmd_pairs, True, rc.set, fake.set, keys[1], vals[1]) + append_cmd(string_cmd_pairs, False, rc.mget, fake.mget, keys[2], keys[3]) + append_cmd(string_cmd_pairs, True, rc.mset, fake.mset, { + keys[4]: vals[4], + keys[5]: vals[5] + }) + yield string_cmd_pairs + + # hash + hash_cmd_pairs = [] + append_cmd(hash_cmd_pairs, True, rc.hset, fake.hset, keys[100], keys[10], + vals[10]) + append_cmd(hash_cmd_pairs, True, rc.hset, fake.hset, keys[100], keys[11], + vals[11]) + append_cmd(hash_cmd_pairs, True, rc.hset, fake.hset, keys[100], keys[12], + vals[12]) + + append_cmd(hash_cmd_pairs, True, rc.hset, fake.hset, keys[101], keys[10], + vals[10]) + append_cmd(hash_cmd_pairs, True, rc.hset, fake.hset, keys[101], keys[11], + vals[11]) + append_cmd(hash_cmd_pairs, True, rc.hset, fake.hset, keys[101], keys[12], + vals[12]) + + append_cmd(hash_cmd_pairs, False, rc.hget, fake.hget, keys[100], keys[10]) + append_cmd(hash_cmd_pairs, False, rc.hget, fake.hget, keys[100], keys[11]) + append_cmd(hash_cmd_pairs, False, rc.hget, fake.hget, keys[100], keys[12]) + append_cmd(hash_cmd_pairs, False, rc.hget, fake.hget, keys[101], keys[10]) + append_cmd(hash_cmd_pairs, False, rc.hget, fake.hget, keys[101], keys[11]) + append_cmd(hash_cmd_pairs, False, rc.hget, fake.hget, keys[101], keys[12]) + + append_cmd(hash_cmd_pairs, False, rc.hmget, fake.hmget, keys[101], + [keys[10], keys[11]]) + append_cmd(hash_cmd_pairs, False, rc.hmget, fake.hmget, keys[101], + [keys[11], keys[12]]) + append_cmd(hash_cmd_pairs, False, rc.hmget, fake.hmget, keys[101], + [keys[10], keys[12]]) + append_cmd(hash_cmd_pairs, False, rc.hmget, fake.hmget, keys[101], + [keys[13]]) + + append_cmd(hash_cmd_pairs, True, rc.hmset, fake.hmset, keys[100], { + keys[14]: vals[14], + keys[15]: vals[15] + }) + append_cmd(hash_cmd_pairs, True, rc.hmset, fake.hmset, keys[100], { + keys[14]: vals[15], + keys[15]: vals[14] + }) + append_cmd(hash_cmd_pairs, True, rc.hmset, fake.hmset, keys[101], { + keys[14]: vals[14], + keys[15]: vals[15] + }) + append_cmd(hash_cmd_pairs, True, rc.hmset, fake.hmset, keys[101], { + keys[14]: vals[15], + keys[15]: vals[14] + }) + + append_cmd(hash_cmd_pairs, False, rc.hgetall, fake.hgetall, keys[100]) + append_cmd(hash_cmd_pairs, False, rc.hgetall, fake.hgetall, keys[101]) + append_cmd(hash_cmd_pairs, False, rc.hgetall, fake.hgetall, keys[102]) + + yield hash_cmd_pairs + + # set + set_cmd_pairs = [] + append_cmd(set_cmd_pairs, True, rc.sadd, fake.sadd, keys[200], vals[20]) + append_cmd(set_cmd_pairs, True, rc.sadd, fake.sadd, keys[200], vals[21]) + append_cmd(set_cmd_pairs, True, rc.sadd, fake.sadd, keys[200], vals[22]) + + append_cmd(set_cmd_pairs, True, rc.sadd, fake.sadd, keys[201], vals[20]) + append_cmd(set_cmd_pairs, True, rc.sadd, fake.sadd, keys[201], vals[21]) + + append_cmd(set_cmd_pairs, False, rc.smembers, fake.smembers, keys[201]) + append_cmd(set_cmd_pairs, False, rc.smembers, fake.smembers, keys[200]) + append_cmd(set_cmd_pairs, False, rc.smembers, fake.smembers, keys[202]) + + append_cmd(set_cmd_pairs, False, rc.sismember, fake.sismember, keys[200], + vals[20]) + append_cmd(set_cmd_pairs, False, rc.sismember, fake.sismember, keys[200], + vals[21]) + append_cmd(set_cmd_pairs, False, rc.sismember, fake.sismember, keys[200], + vals[23]) + + append_cmd(set_cmd_pairs, False, rc.scard, fake.scard, keys[200]) + append_cmd(set_cmd_pairs, False, rc.scard, fake.scard, keys[201]) + yield set_cmd_pairs + + # zset + zset_cmd_pairs = [] + append_cmd(zset_cmd_pairs, True, rc.zadd, fake.zadd, keys[300], 1.1, + vals[30], 2.2, vals[40]) + append_cmd(zset_cmd_pairs, False, rc.zcount, fake.zcount, keys[300], 0.1, + 1.5) + append_cmd(zset_cmd_pairs, False, rc.zrange, fake.zrange, keys[300], 0, 20) + append_cmd(zset_cmd_pairs, False, rc.zcard, fake.zcard, keys[300]) + yield zset_cmd_pairs + + list_cmd_pairs = [] + append_cmd(list_cmd_pairs, True, rc.lpush, fake.lpush, keys[400], vals[40]) + append_cmd(list_cmd_pairs, False, rc.llen, fake.llen, keys[400]) + append_cmd(list_cmd_pairs, False, rc.rpop, fake.rpop, keys[400]) + append_cmd(list_cmd_pairs, False, rc.lpop, fake.lpop, keys[400]) + yield list_cmd_pairs + + hyperloglog_cmd_pairs = [] + append_cmd(hyperloglog_cmd_pairs, True, rc.pfadd, fake.pfadd, keys[500], vals[50]) + append_cmd(hyperloglog_cmd_pairs, True, rc.pfadd, fake.pfadd, keys[500], vals[51]) + append_cmd(hyperloglog_cmd_pairs, True, rc.pfadd, fake.pfadd, keys[500], vals[52]) + + append_cmd(hyperloglog_cmd_pairs, True, rc.pfcount, fake.pfcount, keys[500]) + append_cmd(hyperloglog_cmd_pairs, True, rc.pfcount, fake.pfcount, keys[500]) + yield hyperloglog_cmd_pairs + +def run_check(is_write, cmd1, cmd2): + if is_write: + cmd1.execute() + cmd2.execute() + else: + rslt1 = None + rslt2 = None + try: + rslt1 = cmd1.execute() + rslt2 = cmd2.execute() + assert rslt1 == rslt2 + except AssertionError as e: + print("assert execute %s == %s" % (cmd1, cmd2)) + print("\tassert result %s == %s" % (rslt1, rslt2)) + raise + + +def check(cmds_list_all, iround=100): + for cmds_list in cmds_list_all: + for _ in range(iround): + if cmds_list: + is_write, cmd1, cmd2 = random.choice(cmds_list) + run_check(is_write, cmd1, cmd2) + +def delete_all(rc, keys): + pipe = rc.pipeline(transaction=False) + for key in keys: + rc.delete(key) + pipe.execute() + +def run(i): + keys = gen_items("keys-%04d" % (i, ), 501) + vals = gen_items("vals-%04d" % (i, ), 60) + rc = StrictRedis(host=host, port=port) + fake = FakeStrictRedis() + cmds_list = list(gen_cmds(fake, rc, keys, vals)) + check(cmds_list) + delete_all(rc, keys) def main(): @@ -70,15 +211,11 @@ def main(): host = sys.argv[1].strip() port = int(sys.argv[2].strip()) elif len(sys.argv) == 2: - port = int(sys.argv[2].strip()) + port = int(sys.argv[1].strip()) - keys = gen_items("keys-", 1000) - vals = gen_items("vals-",1000) - check_set_get_cmd(10, keys, vals) - check_mset_mget_cmd(10, 10, keys, vals) + pool = Pool(20) + list(pool.map(run, range(100))) - hashs = gen_items("hash-",100) - check_hmset_hmget_cmd(10, 10, hashs, keys, vals) if __name__ == "__main__": main() From 0f6d4ce9eeed271d482da31cb6b3ba02776e2315 Mon Sep 17 00:00:00 2001 From: wayslog Date: Fri, 27 Jul 2018 17:11:27 +0800 Subject: [PATCH 64/87] add import fixed dependencies command of redis validate.py --- validate.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/validate.py b/validate.py index b46d3a40..a0a48668 100755 --- a/validate.py +++ b/validate.py @@ -1,10 +1,20 @@ #!/usr/bin/env python -from gevent.monkey import patch_all -patch_all() -from gevent.pool import Pool -from fakeredis import FakeStrictRedis -from redis import StrictRedis +try: + from gevent.monkey import patch_all + patch_all() + from gevent.pool import Pool + + from fakeredis import FakeStrictRedis + from redis import StrictRedis +except ImportError: + print("""ERROR: you are running within a bad dependencies environment. +You may run the follows commands to fixed it: + + pip install fakeredis==0.11.0 redis==2.10.6 gevent==1.3.5 + +""") + raise import random import sys From 5ec9506717b5e01f23a314d38bd5219e383443d1 Mon Sep 17 00:00:00 2001 From: felixhao Date: Fri, 27 Jul 2018 19:56:30 +0800 Subject: [PATCH 65/87] change not support cmd --- proto/redis/request.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/proto/redis/request.go b/proto/redis/request.go index f4696741..cdb25670 100644 --- a/proto/redis/request.go +++ b/proto/redis/request.go @@ -85,6 +85,7 @@ var ( "6\r\nINCRBY" + "11\r\nINCRBYFLOAT" + "4\r\nMSET" + + "6\r\nMSETNX" + "6\r\nPSETEX" + "3\r\nSET" + "6\r\nSETBIT" + @@ -109,12 +110,9 @@ var ( "5\r\nRPUSH" + "6\r\nRPUSHX" + "4\r\nSADD" + - "10\r\nSDIFFSTORE" + - "11\r\nSINTERSTORE" + "5\r\nSMOVE" + "4\r\nSPOP" + "4\r\nSREM" + - "11\r\nSUNIONSTORE" + "4\r\nZADD" + "7\r\nZINCRBY" + "11\r\nZINTERSTORE" + @@ -122,12 +120,14 @@ var ( "14\r\nZREMRANGEBYLEX" + "15\r\nZREMRANGEBYRANK" + "16\r\nZREMRANGEBYSCORE" + - "11\r\nZUNIONSTORE" + "5\r\nPFADD" + "7\r\nPFMERGE") reqNotSupportCmdsBytes = []byte("" + - "6\r\nMSETNX" + + "10\r\nSDIFFSTORE" + + "11\r\nSINTERSTORE" + + "11\r\nSUNIONSTORE" + + "11\r\nZUNIONSTORE" + "5\r\nBLPOP" + "5\r\nBRPOP" + "10\r\nBRPOPLPUSH" + From a4354043456b47239f18be7949e3c684edae74f5 Mon Sep 17 00:00:00 2001 From: felixhao Date: Sat, 28 Jul 2018 10:18:25 +0800 Subject: [PATCH 66/87] fix not support cmd --- proto/redis/request.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto/redis/request.go b/proto/redis/request.go index cdb25670..adf4c058 100644 --- a/proto/redis/request.go +++ b/proto/redis/request.go @@ -85,7 +85,6 @@ var ( "6\r\nINCRBY" + "11\r\nINCRBYFLOAT" + "4\r\nMSET" + - "6\r\nMSETNX" + "6\r\nPSETEX" + "3\r\nSET" + "6\r\nSETBIT" + @@ -124,6 +123,7 @@ var ( "7\r\nPFMERGE") reqNotSupportCmdsBytes = []byte("" + + "6\r\nMSETNX" + "10\r\nSDIFFSTORE" + "11\r\nSINTERSTORE" + "11\r\nSUNIONSTORE" + From 6be3f088bbb523e4ec410cfcc2f1ccd0acd032dd Mon Sep 17 00:00:00 2001 From: felixhao Date: Sat, 28 Jul 2018 13:38:00 +0800 Subject: [PATCH 67/87] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5997d35..f797422d 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Congratulations! You've just run the overlord proxy. ## Features - [x] support memcache protocol -- [ ] support redis protocol +- [x] support redis protocol - [x] connection pool for reduce number to backend caching servers - [x] keepalive & failover - [x] hash tag: specify the part of the key used for hashing From 273ec32d0fe27d62ef3eed211cbe0032caa2c6ab Mon Sep 17 00:00:00 2001 From: wayslog Date: Mon, 30 Jul 2018 11:00:20 +0800 Subject: [PATCH 68/87] add merge flush back --- proto/memcache/binary/proxy_conn.go | 4 ++++ proto/memcache/proxy_conn.go | 10 +++++++--- proto/redis/proxy_conn.go | 11 +++++++---- proto/types.go | 1 + proxy/handler.go | 5 +++++ 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/proto/memcache/binary/proxy_conn.go b/proto/memcache/binary/proxy_conn.go index 89fa4ad0..d14b4a40 100644 --- a/proto/memcache/binary/proxy_conn.go +++ b/proto/memcache/binary/proxy_conn.go @@ -164,6 +164,10 @@ func (p *proxyConn) Encode(m *proto.Message) (err error) { _ = p.bw.Write(mcr.data) } } + return +} + +func (p *proxyConn) Flush() (err error) { if err = p.bw.Flush(); err != nil { err = errors.Wrap(err, "MC Encoder encode response flush bytes") } diff --git a/proto/memcache/proxy_conn.go b/proto/memcache/proxy_conn.go index 8c370b68..98cc056f 100644 --- a/proto/memcache/proxy_conn.go +++ b/proto/memcache/proxy_conn.go @@ -339,6 +339,13 @@ func revSpacIdx(bs []byte) int { return -1 } +func (p *proxyConn) Flush() (err error) { + if err = p.bw.Flush(); err != nil { + err = errors.Wrap(err, "MC Encoder encode response flush bytes") + } + return +} + // Encode encode response and write into writer. func (p *proxyConn) Encode(m *proto.Message) (err error) { if err = m.Err(); err != nil { @@ -374,8 +381,5 @@ func (p *proxyConn) Encode(m *proto.Message) (err error) { _ = p.bw.Write(endBytes) } } - if err = p.bw.Flush(); err != nil { - err = errors.Wrap(err, "MC Encoder encode response flush bytes") - } return } diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index bfd2136a..fad4f9e2 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -200,10 +200,6 @@ func (pc *proxyConn) Encode(m *proto.Message) (err error) { } if err != nil { err = errors.Wrap(err, "Redis Encoder before flush response") - return - } - if err = pc.bw.Flush(); err != nil { - err = errors.Wrap(err, "Redis Encoder flush response") } return } @@ -255,3 +251,10 @@ func (pc *proxyConn) mergeJoin(m *proto.Message) (err error) { } return } + +func (pc *proxyConn) Flush() (err error) { + if err = pc.bw.Flush(); err != nil { + err = errors.Wrap(err, "Redis Encoder flush response") + } + return +} diff --git a/proto/types.go b/proto/types.go index 6261cb55..a46b7a8c 100644 --- a/proto/types.go +++ b/proto/types.go @@ -32,6 +32,7 @@ type Request interface { type ProxyConn interface { Decode([]*Message) ([]*Message, error) Encode(msg *Message) error + Flush() error } // NodeConn handle Msg to backend cache server and read response. diff --git a/proxy/handler.go b/proxy/handler.go index 1a8a1019..b3bb1b02 100644 --- a/proxy/handler.go +++ b/proxy/handler.go @@ -117,6 +117,11 @@ func (h *Handler) handle() { prom.ProxyTime(h.cluster.cc.Name, msg.Request().CmdString(), int64(msg.TotalDur()/time.Microsecond)) } + err = h.pc.Flush() + if err != nil { + return + } + // 4. release resource for _, msg := range msgs { msg.Reset() From 3e7bc85aaeda51f000198d34838587e6e7116f21 Mon Sep 17 00:00:00 2001 From: wayslog Date: Mon, 30 Jul 2018 11:48:23 +0800 Subject: [PATCH 69/87] update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f797422d..ed600ae0 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,16 @@ go build #### Test ```shell +# test memcache echo -e "set a_11 0 0 5\r\nhello\r\n" | nc 127.0.0.1 21211 # STORED echo -e "get a_11\r\n" | nc 127.0.0.1 21211 # VALUE a_11 0 5 # hello # END + +# test redis +python ./validate.py # require fakeredis==0.11.0 redis==2.10.6 gevent==1.3.5 ``` Congratulations! You've just run the overlord proxy. From dfef350c82cb391352f16059530daf1f2fc68abb Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Mon, 30 Jul 2018 13:18:11 +0800 Subject: [PATCH 70/87] use string builder to reuse byte convey --- proxy/handler.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/proxy/handler.go b/proxy/handler.go index b3bb1b02..10541dd9 100644 --- a/proxy/handler.go +++ b/proxy/handler.go @@ -4,6 +4,7 @@ import ( "context" "io" "net" + "strings" "sync/atomic" "time" @@ -45,6 +46,7 @@ type Handler struct { closed int32 // wg sync.WaitGroup err error + str strings.Builder } // NewHandler new a conn handler. @@ -77,6 +79,12 @@ func (h *Handler) Handle() { go h.handle() } +func (h *Handler) toStr(p []byte) string { + h.str.Reset() + h.str.Write(p) + return h.str.String() +} + func (h *Handler) handle() { var ( messages = proto.GetMsgSlice(defaultConcurrent) @@ -114,7 +122,7 @@ func (h *Handler) handle() { } msg.MarkEnd() msg.ReleaseSubs() - prom.ProxyTime(h.cluster.cc.Name, msg.Request().CmdString(), int64(msg.TotalDur()/time.Microsecond)) + prom.ProxyTime(h.cluster.cc.Name, h.toStr(msg.Request().Cmd()), int64(msg.TotalDur()/time.Microsecond)) } err = h.pc.Flush() From b56884811c72fbae69f5c2a3e4b62592ada22c1e Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Mon, 30 Jul 2018 14:11:42 +0800 Subject: [PATCH 71/87] add prom switch --- lib/prom/prom.go | 7 +++++++ proto/batch.go | 12 ++++++++---- proto/memcache/binary/node_conn.go | 4 +++- proto/memcache/node_conn.go | 5 +++-- proxy/handler.go | 8 ++++++-- 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/prom/prom.go b/lib/prom/prom.go index d0fffb9f..32d320e8 100644 --- a/lib/prom/prom.go +++ b/lib/prom/prom.go @@ -30,10 +30,17 @@ var ( clusterNodeErrLabels = []string{"cluster", "node", "cmd", "error"} clusterCmdLabels = []string{"cluster", "cmd"} clusterNodeCmdLabels = []string{"cluster", "node", "cmd"} + on bool ) +// On return if open prom metrics. +func On() bool { + return on +} + // Init init prometheus. func Init() { + on = true conns = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: statConns, diff --git a/proto/batch.go b/proto/batch.go index ce7df3b9..d370e262 100644 --- a/proto/batch.go +++ b/proto/batch.go @@ -115,10 +115,12 @@ func (m *MsgBatch) Msgs() []*Message { // BatchDone will set done and report prom HandleTime. func (m *MsgBatch) BatchDone(cluster, addr string) { - for _, msg := range m.Msgs() { - prom.HandleTime(cluster, addr, msg.Request().CmdString(), int64(msg.RemoteDur()/time.Microsecond)) + if prom.On() { + for _, msg := range m.Msgs() { + prom.HandleTime(cluster, addr, msg.Request().CmdString(), int64(msg.RemoteDur()/time.Microsecond)) + } + m.Done() } - m.Done() } // BatchDoneWithError will set done with error and report prom ErrIncr. @@ -128,7 +130,9 @@ func (m *MsgBatch) BatchDoneWithError(cluster, addr string, err error) { if log.V(1) { log.Errorf("cluster(%s) Msg(%s) cluster process handle error:%+v", cluster, msg.Request().Key(), err) } - prom.ErrIncr(cluster, addr, msg.Request().CmdString(), errors.Cause(err).Error()) + if prom.On() { + prom.ErrIncr(cluster, addr, msg.Request().CmdString(), errors.Cause(err).Error()) + } } m.Done() } diff --git a/proto/memcache/binary/node_conn.go b/proto/memcache/binary/node_conn.go index 10ad0092..ef029435 100644 --- a/proto/memcache/binary/node_conn.go +++ b/proto/memcache/binary/node_conn.go @@ -184,7 +184,9 @@ func (n *nodeConn) fillMCRequest(mcr *MCRequest, data []byte) (size int, err err mcr.data = data[requestHeaderLen : requestHeaderLen+bl] if mcr.rTp == RequestTypeGet || mcr.rTp == RequestTypeGetQ || mcr.rTp == RequestTypeGetK || mcr.rTp == RequestTypeGetKQ { - prom.Hit(n.cluster, n.addr) + if prom.On() { + prom.Hit(n.cluster, n.addr) + } } return } diff --git a/proto/memcache/node_conn.go b/proto/memcache/node_conn.go index 65cbbb09..99179ca6 100644 --- a/proto/memcache/node_conn.go +++ b/proto/memcache/node_conn.go @@ -175,8 +175,9 @@ func (n *nodeConn) fillMCRequest(mcr *MCRequest, data []byte) (size int, err err prom.Miss(n.cluster, n.addr) return } - prom.Hit(n.cluster, n.addr) - + if prom.On() { + prom.Hit(n.cluster, n.addr) + } length, err := findLength(bs, mcr.rTp == RequestTypeGets || mcr.rTp == RequestTypeGats) if err != nil { err = errors.Wrap(err, "MC Handler while parse length") diff --git a/proxy/handler.go b/proxy/handler.go index 10541dd9..c90dc12f 100644 --- a/proxy/handler.go +++ b/proxy/handler.go @@ -122,7 +122,9 @@ func (h *Handler) handle() { } msg.MarkEnd() msg.ReleaseSubs() - prom.ProxyTime(h.cluster.cc.Name, h.toStr(msg.Request().Cmd()), int64(msg.TotalDur()/time.Microsecond)) + if prom.On() { + prom.ProxyTime(h.cluster.cc.Name, h.toStr(msg.Request().Cmd()), int64(msg.TotalDur()/time.Microsecond)) + } } err = h.pc.Flush() @@ -165,7 +167,9 @@ func (h *Handler) closeWithError(err error) { h.cancel() h.msgCh.Close() _ = h.conn.Close() - prom.ConnDecr(h.cluster.cc.Name) + if prom.On() { + prom.ConnDecr(h.cluster.cc.Name) + } if log.V(3) { if err != io.EOF { log.Warnf("cluster(%s) addr(%s) remoteAddr(%s) handler close error:%+v", h.cluster.cc.Name, h.cluster.cc.ListenAddr, h.conn.RemoteAddr(), err) From f9f87df7ad3d90273b11f55a96a1cfddc4ceabcc Mon Sep 17 00:00:00 2001 From: wayslog Date: Mon, 30 Jul 2018 15:54:08 +0800 Subject: [PATCH 72/87] add scripts and validate_keys_dist.py for redis --- README.md | 2 +- scripts/validate_keys_dist.py | 78 +++++++++++++++++++ .../validate_redis_features.py | 0 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100755 scripts/validate_keys_dist.py rename validate.py => scripts/validate_redis_features.py (100%) diff --git a/README.md b/README.md index ed600ae0..7b23b325 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ echo -e "get a_11\r\n" | nc 127.0.0.1 21211 # END # test redis -python ./validate.py # require fakeredis==0.11.0 redis==2.10.6 gevent==1.3.5 +python ./scripts/validate_redis_features.py # require fakeredis==0.11.0 redis==2.10.6 gevent==1.3.5 ``` Congratulations! You've just run the overlord proxy. diff --git a/scripts/validate_keys_dist.py b/scripts/validate_keys_dist.py new file mode 100755 index 00000000..f4ab1a7f --- /dev/null +++ b/scripts/validate_keys_dist.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python + +import redis +import argparse +import os + +from contextlib import contextmanager + +def gen_str(n): + return ''.join(map(lambda xx: (hex(ord(xx))[2:]), os.urandom(n))) + + +def gen_items(prefix, n): + return [ + "_overlord-%s-%s-%010d" % (prefix, gen_str(8), num) for num in range(n) + ] + + +def parse_ip_port(addr): + asp = addr.split(":") + return (asp[0], int(asp[1])) + + +def dial(expect): + ip, port = parse_ip_port(expect) + rc = redis.StrictRedis(host=ip, port=port) + return rc + +@contextmanager +def del_keys(rc, keys): + try: + yield + finally: + epipe = rc.pipeline(transaction=False) + for key in keys: + epipe.delete(key) + epipe.execute() + + +def check_vals(expect_rc, check_rc, keys, vals): + epipe = expect_rc.pipeline(transaction=False) + for key, val in zip(keys, vals): + epipe.set(key, val, ex=10) + epipe.execute() + + cpipe = check_rc.pipeline(transaction=False) + for key in keys: + epipe.get(key) + for i,val in enumerate(epipe.execute()): + assert vals[i] == val + + +def run_check(check, expect, n=1024): + keys = gen_items("keys", n) + vals = gen_items("vals", n) + + expect_rc = dial(expect) + check_rc = dial(check) + + with del_keys(expect_rc, keys): + check_vals(expect_rc, check_rc, keys, vals) + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("check", help="address need to be checked.") + parser.add_argument( + "expect", + help= + "expect validate address. command will be send to this address first.") + parser.add_argument("-k", "--keys", default=1024, help="range range of keys") + opt = parser.parse_args() + check = opt.check + expect = opt.check + run_check(check, expect, n=opt.keys) + + +if __name__ == "__main__": + main() diff --git a/validate.py b/scripts/validate_redis_features.py similarity index 100% rename from validate.py rename to scripts/validate_redis_features.py From 41aebaa87987969985dd7d2b5868b0a363d75b61 Mon Sep 17 00:00:00 2001 From: wayslog Date: Mon, 30 Jul 2018 16:38:31 +0800 Subject: [PATCH 73/87] fixed of usage of validate_keys_dist.py --- scripts/validate_keys_dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/validate_keys_dist.py b/scripts/validate_keys_dist.py index f4ab1a7f..7dc99004 100755 --- a/scripts/validate_keys_dist.py +++ b/scripts/validate_keys_dist.py @@ -67,7 +67,7 @@ def main(): "expect", help= "expect validate address. command will be send to this address first.") - parser.add_argument("-k", "--keys", default=1024, help="range range of keys") + parser.add_argument("-k", "--keys", type=int, default=1024, help="default 1024. It's recommands be the 10 times than the count of backends.") opt = parser.parse_args() check = opt.check expect = opt.check From 3480ed5d7d9f3fa86879055ec843caafa3ad8049 Mon Sep 17 00:00:00 2001 From: wayslog Date: Mon, 30 Jul 2018 20:17:09 +0800 Subject: [PATCH 74/87] fixed of test and MsgBatch bugs and add ReadBytes for ping_test --- codecov.sh | 1 + lib/bufio/io.go | 34 ++++++++++++- proto/batch.go | 2 +- proto/memcache/binary/proxy_conn_test.go | 1 + proto/memcache/pinger.go | 3 +- proto/memcache/pinger_test.go | 2 +- proto/memcache/proxy_conn_test.go | 2 + proto/redis/proxy_conn_test.go | 64 +++++++++++++----------- proxy/proxy_test.go | 16 +++--- 9 files changed, 83 insertions(+), 42 deletions(-) diff --git a/codecov.sh b/codecov.sh index 74519774..834b0d1f 100755 --- a/codecov.sh +++ b/codecov.sh @@ -4,6 +4,7 @@ set -e echo "" > coverage.txt for d in $(go list ./... | grep -v vendor | grep -v cmd); do + echo "testing for $d ..." go test -coverprofile=profile.out -covermode=atomic $d if [ -f profile.out ]; then cat profile.out >> coverage.txt diff --git a/lib/bufio/io.go b/lib/bufio/io.go index d58ce2c9..39164dd3 100644 --- a/lib/bufio/io.go +++ b/lib/bufio/io.go @@ -9,6 +9,8 @@ import ( libnet "overlord/lib/net" ) +const maxWritevSize = 1024 + // ErrProxy var ( ErrBufferFull = bufio.ErrBufferFull @@ -106,6 +108,33 @@ func (r *Reader) ReadSlice(delim byte) (data []byte, err error) { return } +// ReadBytes is the same as (*bufio.Buffer).ReadBytes in std. +func (r *Reader) ReadBytes(delim byte) (data []byte, err error) { + if r.err != nil { + err = r.err + return + } + + for { + idx := bytes.IndexByte(r.b.buf[r.b.r:r.b.w], delim) + if idx != -1 { + data = r.b.buf[r.b.r : r.b.r+idx+1] + r.b.r += idx + 1 + return + } + if r.b.buffered() == r.b.len() { + r.b.grow() + } + if r.b.w == r.b.len() { + r.b.shrink() + } + + if err = r.fill(); err != nil { + return + } + } +} + // ReadExact will read n size bytes or return ErrBufferFull. // It never contains any I/O operation func (r *Reader) ReadExact(n int) (data []byte, err error) { @@ -193,5 +222,8 @@ func (w *Writer) Write(p []byte) (err error) { } w.bufs = append(w.bufs, p) w.cnt++ - return nil + if len(w.bufs) == maxWritevSize { + err = w.Flush() + } + return } diff --git a/proto/batch.go b/proto/batch.go index d370e262..05d304aa 100644 --- a/proto/batch.go +++ b/proto/batch.go @@ -115,11 +115,11 @@ func (m *MsgBatch) Msgs() []*Message { // BatchDone will set done and report prom HandleTime. func (m *MsgBatch) BatchDone(cluster, addr string) { + m.Done() if prom.On() { for _, msg := range m.Msgs() { prom.HandleTime(cluster, addr, msg.Request().CmdString(), int64(msg.RemoteDur()/time.Microsecond)) } - m.Done() } } diff --git a/proto/memcache/binary/proxy_conn_test.go b/proto/memcache/binary/proxy_conn_test.go index 753cce45..02068ab4 100644 --- a/proto/memcache/binary/proxy_conn_test.go +++ b/proto/memcache/binary/proxy_conn_test.go @@ -347,6 +347,7 @@ func TestProxyConnEncodeOk(t *testing.T) { msg := _createRespMsg(t, []byte(tt.Req), tt.Resp) err := p.Encode(msg) assert.NoError(t, err) + assert.NoError(t, p.Flush()) c := conn.Conn.(*mockConn) buf := make([]byte, 1024) size, err := c.wbuf.Read(buf) diff --git a/proto/memcache/pinger.go b/proto/memcache/pinger.go index a4d9c39b..3c0026f0 100644 --- a/proto/memcache/pinger.go +++ b/proto/memcache/pinger.go @@ -47,9 +47,8 @@ func (m *mcPinger) Ping() (err error) { err = errors.Wrap(err, "MC ping flush") return } - _ = m.br.Read() var b []byte - if b, err = m.br.ReadLine(); err != nil { + if b, err = m.br.ReadBytes(delim); err != nil { err = errors.Wrap(err, "MC ping read response") return } diff --git a/proto/memcache/pinger_test.go b/proto/memcache/pinger_test.go index 966bf8f2..7b1543b7 100644 --- a/proto/memcache/pinger_test.go +++ b/proto/memcache/pinger_test.go @@ -36,7 +36,7 @@ func TestPingerPing100Ok(t *testing.T) { for i := 0; i < 100; i++ { err := pinger.Ping() - assert.NoError(t, err) + assert.NoError(t, err, "error iter: %d", i) } err := pinger.Ping() diff --git a/proto/memcache/proxy_conn_test.go b/proto/memcache/proxy_conn_test.go index 81ac6daa..dd0513f8 100644 --- a/proto/memcache/proxy_conn_test.go +++ b/proto/memcache/proxy_conn_test.go @@ -183,6 +183,8 @@ func TestProxyConnEncodeOk(t *testing.T) { msg := _createRespMsg(t, []byte(tt.Req), tt.Resp) err := p.Encode(msg) assert.NoError(t, err) + err = p.Flush() + assert.NoError(t, err) c := conn.Conn.(*mockConn) buf := make([]byte, 1024) size, err := c.wbuf.Read(buf) diff --git a/proto/redis/proxy_conn_test.go b/proto/redis/proxy_conn_test.go index 61b4d97d..bea634a6 100644 --- a/proto/redis/proxy_conn_test.go +++ b/proto/redis/proxy_conn_test.go @@ -131,7 +131,7 @@ func TestEncodeCmdOk(t *testing.T) { Expect string }{ { - Name: "mergeStr", + Name: "mergeNotSupport", MType: mergeTypeNo, Reply: []*resp{ &resp{ @@ -139,30 +139,30 @@ func TestEncodeCmdOk(t *testing.T) { data: []byte("123456789"), }, }, - Expect: "+123456789\r\n", - }, - { - Name: "mergeInt", - MType: mergeTypeNo, - Reply: []*resp{ - &resp{ - rTp: respInt, - data: []byte("12"), - }, - }, - Expect: ":12\r\n", - }, - { - Name: "mergeError", - MType: mergeTypeNo, - Reply: []*resp{ - &resp{ - rTp: respError, - data: []byte("i am error"), - }, - }, - Expect: "-i am error\r\n", + Expect: "-Error: command not support\r\n", }, + // { + // Name: "mergeCtl", + // MType: mergeTypeNo, + // Reply: []*resp{ + // &resp{ + // rTp: respInt, + // data: []byte("12"), + // }, + // }, + // Expect: ":12\r\n", + // }, + // { + // Name: "mergeError", + // MType: mergeTypeNo, + // Reply: []*resp{ + // &resp{ + // rTp: respError, + // data: []byte("i am error"), + // }, + // }, + // Expect: "-i am error\r\n", + // }, { Name: "mergeOK", MType: mergeTypeOK, @@ -228,12 +228,18 @@ func TestEncodeCmdOk(t *testing.T) { conn, buf := _createDownStreamConn() pc := NewProxyConn(conn) err := pc.Encode(msg) - if assert.NoError(t, err) { - data := make([]byte, 2048) - size, err := buf.Read(data) - assert.NoError(t, err) - assert.Equal(t, tt.Expect, string(data[:size])) + if !assert.NoError(t, err) { + return } + err = pc.Flush() + if !assert.NoError(t, err) { + return + } + data := make([]byte, 2048) + size, err := buf.Read(data) + assert.NoError(t, err) + assert.Equal(t, tt.Expect, string(data[:size])) + }) } } diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index c1a0bb1e..0935d364 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -26,8 +26,8 @@ var ( ListenProto: "tcp", ListenAddr: "127.0.0.1:21211", RedisAuth: "", - DialTimeout: 1000, - ReadTimeout: 1000, + DialTimeout: 100, + ReadTimeout: 100, NodeConnections: 10, WriteTimeout: 1000, PingFailLimit: 3, @@ -47,8 +47,8 @@ var ( ListenProto: "tcp", ListenAddr: "127.0.0.1:21212", RedisAuth: "", - DialTimeout: 1000, - ReadTimeout: 1000, + DialTimeout: 100, + ReadTimeout: 100, NodeConnections: 10, WriteTimeout: 1000, PingFailLimit: 3, @@ -207,10 +207,10 @@ func testCmdBin(t testing.TB, cmds ...[]byte) { } func TestProxyFull(t *testing.T) { - for i := 0; i < 10; i++ { - testCmd(t, cmds[0], cmds[1], cmds[2], cmds[10], cmds[11]) - testCmdBin(t, cmdBins[0], cmdBins[1]) - } + // for i := 0; i < 10; i++ { + testCmd(t, cmds[0], cmds[1], cmds[2], cmds[10], cmds[11]) + // testCmdBin(t, cmdBins[0], cmdBins[1]) + // } } func TestProxyWithAssert(t *testing.T) { From 46c2003060c7351cc067e5e092929d35f66f42a3 Mon Sep 17 00:00:00 2001 From: felixhao Date: Mon, 30 Jul 2018 22:26:13 +0800 Subject: [PATCH 75/87] adjust prom, unit test ... --- lib/bufio/buffer.go | 12 +++--- lib/bufio/io.go | 38 +++-------------- lib/bufio/io_test.go | 4 +- lib/prom/prom.go | 3 +- proto/memcache/binary/node_conn.go | 4 +- proto/memcache/binary/pinger.go | 4 +- proto/memcache/binary/pinger_test.go | 64 ++++------------------------ proto/memcache/mc_test.go | 23 +++++++--- proto/memcache/node_conn.go | 4 +- proto/memcache/node_conn_test.go | 2 +- proto/memcache/pinger.go | 15 ++++--- proto/memcache/pinger_test.go | 15 ++++--- proto/memcache/proxy_conn.go | 14 +++--- proto/redis/pinger.go | 2 +- proto/redis/pinger_test.go | 17 +++++--- proto/redis/redis_test.go | 20 ++++++--- proxy/handler.go | 4 +- 17 files changed, 100 insertions(+), 145 deletions(-) diff --git a/lib/bufio/buffer.go b/lib/bufio/buffer.go index 46c06c90..658b004e 100644 --- a/lib/bufio/buffer.go +++ b/lib/bufio/buffer.go @@ -36,9 +36,7 @@ func init() { func initBufPool(idx int) { pools[idx] = &sync.Pool{ New: func() interface{} { - return &Buffer{ - buf: make([]byte, sizes[idx]), - } + return NewBuffer(sizes[idx]) }, } } @@ -49,6 +47,11 @@ type Buffer struct { r, w int } +// NewBuffer new buffer. +func NewBuffer(size int) *Buffer { + return &Buffer{buf: make([]byte, size)} +} + // Bytes return the bytes readed func (b *Buffer) Bytes() []byte { return b.buf[b.r:b.w] @@ -97,8 +100,7 @@ func Get(size int) *Buffer { } i := sort.SearchInts(sizes, size) if i >= len(pools) { - b := &Buffer{buf: make([]byte, size)} - return b + return NewBuffer(size) } b := pools[i].Get().(*Buffer) b.Reset() diff --git a/lib/bufio/io.go b/lib/bufio/io.go index 39164dd3..281e8724 100644 --- a/lib/bufio/io.go +++ b/lib/bufio/io.go @@ -9,10 +9,8 @@ import ( libnet "overlord/lib/net" ) -const maxWritevSize = 1024 - -// ErrProxy var ( + // ErrBufferFull err buffer full ErrBufferFull = bufio.ErrBufferFull ) @@ -108,33 +106,6 @@ func (r *Reader) ReadSlice(delim byte) (data []byte, err error) { return } -// ReadBytes is the same as (*bufio.Buffer).ReadBytes in std. -func (r *Reader) ReadBytes(delim byte) (data []byte, err error) { - if r.err != nil { - err = r.err - return - } - - for { - idx := bytes.IndexByte(r.b.buf[r.b.r:r.b.w], delim) - if idx != -1 { - data = r.b.buf[r.b.r : r.b.r+idx+1] - r.b.r += idx + 1 - return - } - if r.b.buffered() == r.b.len() { - r.b.grow() - } - if r.b.w == r.b.len() { - r.b.shrink() - } - - if err = r.fill(); err != nil { - return - } - } -} - // ReadExact will read n size bytes or return ErrBufferFull. // It never contains any I/O operation func (r *Reader) ReadExact(n int) (data []byte, err error) { @@ -170,6 +141,10 @@ func (r *Reader) ResetBuffer(b *Buffer) { r.b.w = b.w } +const ( + maxWritevSize = 1024 +) + // Writer implements buffering for an io.Writer object. // If an error occurs writing to a Writer, no more data will be // accepted and all subsequent writes, and Flush, will return the error. @@ -187,8 +162,7 @@ type Writer struct { // NewWriter returns a new Writer whose buffer has the default size. func NewWriter(wr *libnet.Conn) *Writer { - const defaultBuffered = 64 - return &Writer{wr: wr, bufs: make([][]byte, 0, defaultBuffered)} + return &Writer{wr: wr, bufs: make([][]byte, 0, maxWritevSize)} } // Flush writes any buffered data to the underlying io.Writer. diff --git a/lib/bufio/io_test.go b/lib/bufio/io_test.go index 140ab30b..d7cd861c 100644 --- a/lib/bufio/io_test.go +++ b/lib/bufio/io_test.go @@ -67,7 +67,7 @@ func TestReaderReadSlice(t *testing.T) { assert.Len(t, data, 3) _, err = b.ReadSlice('\n') - assert.EqualError(t, err, "bufio: buffer full") + assert.Equal(t, ErrBufferFull, err) } func TestReaderReadExact(t *testing.T) { @@ -80,7 +80,7 @@ func TestReaderReadExact(t *testing.T) { assert.Len(t, data, 5) _, err = b.ReadExact(5 * 3 * 100) - assert.EqualError(t, err, "bufio: buffer full") + assert.Equal(t, ErrBufferFull, err) } func TestReaderResetBuffer(t *testing.T) { diff --git a/lib/prom/prom.go b/lib/prom/prom.go index 32d320e8..86af4979 100644 --- a/lib/prom/prom.go +++ b/lib/prom/prom.go @@ -30,7 +30,8 @@ var ( clusterNodeErrLabels = []string{"cluster", "node", "cmd", "error"} clusterCmdLabels = []string{"cluster", "cmd"} clusterNodeCmdLabels = []string{"cluster", "node", "cmd"} - on bool + + on bool ) // On return if open prom metrics. diff --git a/proto/memcache/binary/node_conn.go b/proto/memcache/binary/node_conn.go index ef029435..bc892d35 100644 --- a/proto/memcache/binary/node_conn.go +++ b/proto/memcache/binary/node_conn.go @@ -172,7 +172,9 @@ func (n *nodeConn) fillMCRequest(mcr *MCRequest, data []byte) (size int, err err bl := binary.BigEndian.Uint32(mcr.bodyLen) if bl == 0 { if mcr.rTp == RequestTypeGet || mcr.rTp == RequestTypeGetQ || mcr.rTp == RequestTypeGetK || mcr.rTp == RequestTypeGetKQ { - prom.Miss(n.cluster, n.addr) + if prom.On() { + prom.Miss(n.cluster, n.addr) + } } size = requestHeaderLen return diff --git a/proto/memcache/binary/pinger.go b/proto/memcache/binary/pinger.go index ba60ad9e..5262753d 100644 --- a/proto/memcache/binary/pinger.go +++ b/proto/memcache/binary/pinger.go @@ -11,7 +11,7 @@ import ( ) const ( - pingBufferSize = 32 + pingBufferSize = 24 ) var ( @@ -50,7 +50,7 @@ func newMCPinger(nc *libnet.Conn) *mcPinger { return &mcPinger{ conn: nc, bw: bufio.NewWriter(nc), - br: bufio.NewReader(nc, bufio.Get(pingBufferSize)), + br: bufio.NewReader(nc, bufio.NewBuffer(pingBufferSize)), } } diff --git a/proto/memcache/binary/pinger_test.go b/proto/memcache/binary/pinger_test.go index 9b2b8027..bf271d08 100644 --- a/proto/memcache/binary/pinger_test.go +++ b/proto/memcache/binary/pinger_test.go @@ -1,68 +1,14 @@ package binary import ( - "bytes" - "net" "testing" - "time" "overlord/lib/bufio" - libnet "overlord/lib/net" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) -type mockAddr string - -func (m mockAddr) Network() string { - return "tcp" -} -func (m mockAddr) String() string { - return string(m) -} - -type mockConn struct { - rbuf *bytes.Buffer - wbuf *bytes.Buffer - addr mockAddr -} - -func (m *mockConn) Read(b []byte) (n int, err error) { - return m.rbuf.Read(b) -} -func (m *mockConn) Write(b []byte) (n int, err error) { - return m.wbuf.Write(b) -} - -// writeBuffers impl the net.buffersWriter to support writev -func (m *mockConn) writeBuffers(buf *net.Buffers) (int64, error) { - return buf.WriteTo(m.wbuf) -} - -func (m *mockConn) Close() error { return nil } -func (m *mockConn) LocalAddr() net.Addr { return m.addr } -func (m *mockConn) RemoteAddr() net.Addr { return m.addr } - -func (m *mockConn) SetDeadline(t time.Time) error { return nil } -func (m *mockConn) SetReadDeadline(t time.Time) error { return nil } -func (m *mockConn) SetWriteDeadline(t time.Time) error { return nil } - -// _createConn is useful tools for handler test -func _createConn(data []byte) *libnet.Conn { - return _createRepeatConn(data, 1) -} - -func _createRepeatConn(data []byte, r int) *libnet.Conn { - mconn := &mockConn{ - addr: "127.0.0.1:12345", - rbuf: bytes.NewBuffer(bytes.Repeat(data, r)), - wbuf: new(bytes.Buffer), - } - conn := libnet.NewConn(mconn, time.Second, time.Second) - return conn -} - func TestPingerPingOk(t *testing.T) { conn := _createConn(pongBs) pinger := newMCPinger(conn) @@ -91,7 +37,7 @@ func TestPingerPing100Ok(t *testing.T) { for i := 0; i < 100; i++ { err := pinger.Ping() - assert.NoError(t, err) + assert.NoError(t, err, "error iter: %d", i) } err := pinger.Ping() @@ -111,9 +57,15 @@ func TestPingerClosed(t *testing.T) { } func TestPingerNotReturnPong(t *testing.T) { - conn := _createRepeatConn([]byte("emmmmm...."), 100) + conn := _createConn([]byte("iam test bytes 24 length")) pinger := newMCPinger(conn) err := pinger.Ping() assert.Error(t, err) _causeEqual(t, ErrPingerPong, err) + + conn = _createConn([]byte("less than 24 length")) + pinger = newMCPinger(conn) + err = pinger.Ping() + assert.Error(t, err) + _causeEqual(t, bufio.ErrBufferFull, err) } diff --git a/proto/memcache/mc_test.go b/proto/memcache/mc_test.go index b52d2c54..37f2d8b2 100644 --- a/proto/memcache/mc_test.go +++ b/proto/memcache/mc_test.go @@ -3,8 +3,9 @@ package memcache import ( "bytes" "net" - libnet "overlord/lib/net" "time" + + libnet "overlord/lib/net" ) type mockAddr string @@ -17,12 +18,18 @@ func (m mockAddr) String() string { } type mockConn struct { - rbuf *bytes.Buffer - wbuf *bytes.Buffer - addr mockAddr + addr mockAddr + rbuf *bytes.Buffer + wbuf *bytes.Buffer + data []byte + repeat int } func (m *mockConn) Read(b []byte) (n int, err error) { + if m.repeat > 0 { + m.rbuf.Write(m.data) + m.repeat-- + } return m.rbuf.Read(b) } func (m *mockConn) Write(b []byte) (n int, err error) { @@ -49,9 +56,11 @@ func _createConn(data []byte) *libnet.Conn { func _createRepeatConn(data []byte, r int) *libnet.Conn { mconn := &mockConn{ - addr: "127.0.0.1:12345", - rbuf: bytes.NewBuffer(bytes.Repeat(data, r)), - wbuf: new(bytes.Buffer), + addr: "127.0.0.1:12345", + rbuf: bytes.NewBuffer(nil), + wbuf: new(bytes.Buffer), + data: data, + repeat: r, } conn := libnet.NewConn(mconn, time.Second, time.Second) return conn diff --git a/proto/memcache/node_conn.go b/proto/memcache/node_conn.go index 99179ca6..0a0937d8 100644 --- a/proto/memcache/node_conn.go +++ b/proto/memcache/node_conn.go @@ -172,7 +172,9 @@ func (n *nodeConn) fillMCRequest(mcr *MCRequest, data []byte) (size int, err err } if bytes.Equal(bs, endBytes) { - prom.Miss(n.cluster, n.addr) + if prom.On() { + prom.Miss(n.cluster, n.addr) + } return } if prom.On() { diff --git a/proto/memcache/node_conn_test.go b/proto/memcache/node_conn_test.go index 6739016d..972acc98 100644 --- a/proto/memcache/node_conn_test.go +++ b/proto/memcache/node_conn_test.go @@ -221,7 +221,7 @@ func TestNodeConnAssertError(t *testing.T) { } func TestNocdConnPingOk(t *testing.T) { - nc := _createNodeConn(pong) + nc := _createNodeConn(pongBytes) err := nc.Ping() assert.NoError(t, err) assert.NoError(t, nc.Close()) diff --git a/proto/memcache/pinger.go b/proto/memcache/pinger.go index 3c0026f0..14ffb60d 100644 --- a/proto/memcache/pinger.go +++ b/proto/memcache/pinger.go @@ -11,12 +11,12 @@ import ( ) const ( - pingBufferSize = 32 + pingBufferSize = 8 ) var ( - ping = []byte("set _ping 0 0 4\r\npong\r\n") - pong = []byte("STORED\r\n") + pingBytes = []byte("set _ping 0 0 4\r\npong\r\n") + pongBytes = []byte("STORED\r\n") ) type mcPinger struct { @@ -30,7 +30,7 @@ func newMCPinger(nc *libnet.Conn) *mcPinger { return &mcPinger{ conn: nc, bw: bufio.NewWriter(nc), - br: bufio.NewReader(nc, bufio.Get(pingBufferSize)), + br: bufio.NewReader(nc, bufio.NewBuffer(pingBufferSize)), } } @@ -39,7 +39,7 @@ func (m *mcPinger) Ping() (err error) { err = ErrPingerPong return } - if err = m.bw.Write(ping); err != nil { + if err = m.bw.Write(pingBytes); err != nil { err = errors.Wrap(err, "MC ping write") return } @@ -47,12 +47,13 @@ func (m *mcPinger) Ping() (err error) { err = errors.Wrap(err, "MC ping flush") return } + _ = m.br.Read() var b []byte - if b, err = m.br.ReadBytes(delim); err != nil { + if b, err = m.br.ReadLine(); err != nil { err = errors.Wrap(err, "MC ping read response") return } - if !bytes.Equal(b, pong) { + if !bytes.Equal(b, pongBytes) { err = ErrPingerPong } return diff --git a/proto/memcache/pinger_test.go b/proto/memcache/pinger_test.go index 7b1543b7..7f54da5d 100644 --- a/proto/memcache/pinger_test.go +++ b/proto/memcache/pinger_test.go @@ -1,15 +1,16 @@ package memcache import ( - "io" "testing" + "overlord/lib/bufio" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) func TestPingerPingOk(t *testing.T) { - conn := _createConn(pong) + conn := _createConn(pongBytes) pinger := newMCPinger(conn) err := pinger.Ping() @@ -17,7 +18,7 @@ func TestPingerPingOk(t *testing.T) { } func TestPingerPingEOF(t *testing.T) { - conn := _createConn(pong) + conn := _createConn(pongBytes) pinger := newMCPinger(conn) err := pinger.Ping() @@ -27,11 +28,11 @@ func TestPingerPingEOF(t *testing.T) { assert.Error(t, err) err = errors.Cause(err) - assert.Equal(t, io.EOF, err) + assert.Equal(t, bufio.ErrBufferFull, err) } func TestPingerPing100Ok(t *testing.T) { - conn := _createRepeatConn(pong, 100) + conn := _createRepeatConn(pongBytes, 100) pinger := newMCPinger(conn) for i := 0; i < 100; i++ { @@ -41,11 +42,11 @@ func TestPingerPing100Ok(t *testing.T) { err := pinger.Ping() assert.Error(t, err) - _causeEqual(t, io.EOF, err) + _causeEqual(t, bufio.ErrBufferFull, err) } func TestPingerClosed(t *testing.T) { - conn := _createRepeatConn(pong, 100) + conn := _createRepeatConn(pongBytes, 100) pinger := newMCPinger(conn) err := pinger.Close() assert.NoError(t, err) diff --git a/proto/memcache/proxy_conn.go b/proto/memcache/proxy_conn.go index 98cc056f..92c3151e 100644 --- a/proto/memcache/proxy_conn.go +++ b/proto/memcache/proxy_conn.go @@ -339,13 +339,6 @@ func revSpacIdx(bs []byte) int { return -1 } -func (p *proxyConn) Flush() (err error) { - if err = p.bw.Flush(); err != nil { - err = errors.Wrap(err, "MC Encoder encode response flush bytes") - } - return -} - // Encode encode response and write into writer. func (p *proxyConn) Encode(m *proto.Message) (err error) { if err = m.Err(); err != nil { @@ -383,3 +376,10 @@ func (p *proxyConn) Encode(m *proto.Message) (err error) { } return } + +func (p *proxyConn) Flush() (err error) { + if err = p.bw.Flush(); err != nil { + err = errors.Wrap(err, "MC Encoder encode response flush bytes") + } + return +} diff --git a/proto/redis/pinger.go b/proto/redis/pinger.go index 005e5709..21e934e5 100644 --- a/proto/redis/pinger.go +++ b/proto/redis/pinger.go @@ -32,7 +32,7 @@ type pinger struct { func newPinger(conn *libnet.Conn) *pinger { return &pinger{ conn: conn, - br: bufio.NewReader(conn, bufio.Get(64)), + br: bufio.NewReader(conn, bufio.NewBuffer(7)), bw: bufio.NewWriter(conn), state: opened, } diff --git a/proto/redis/pinger_test.go b/proto/redis/pinger_test.go index 34ba0d7c..42929148 100644 --- a/proto/redis/pinger_test.go +++ b/proto/redis/pinger_test.go @@ -3,11 +3,13 @@ package redis import ( "testing" + "overlord/lib/bufio" + "github.com/stretchr/testify/assert" ) func TestPingerPingOk(t *testing.T) { - conn := _createRepeatConn(pongBytes, 10) + conn := _createConn(pongBytes) p := newPinger(conn) err := p.ping() assert.NoError(t, err) @@ -18,15 +20,18 @@ func TestPingerClosed(t *testing.T) { p := newPinger(conn) assert.NoError(t, p.Close()) err := p.ping() - assert.Error(t, err) - assert.EqualError(t, err, "ping interface has been closed") + assert.Equal(t, ErrPingClosed, err) assert.NoError(t, p.Close()) } func TestPingerWrongResp(t *testing.T) { - conn := _createRepeatConn([]byte("-Error:badping\r\n"), 10) + conn := _createConn([]byte("-Error: iam more than 7 bytes\r\n")) p := newPinger(conn) err := p.ping() - assert.Error(t, err) - assert.EqualError(t, err, "pong response payload is bad") + assert.Equal(t, bufio.ErrBufferFull, err) + + conn = _createConn([]byte("-Err\r\n")) + p = newPinger(conn) + err = p.ping() + assert.Equal(t, ErrBadPong, err) } diff --git a/proto/redis/redis_test.go b/proto/redis/redis_test.go index c5d678b0..ee4dc455 100644 --- a/proto/redis/redis_test.go +++ b/proto/redis/redis_test.go @@ -18,12 +18,18 @@ func (m mockAddr) String() string { } type mockConn struct { - rbuf *bytes.Buffer - wbuf *bytes.Buffer - addr mockAddr + addr mockAddr + rbuf *bytes.Buffer + wbuf *bytes.Buffer + data []byte + repeat int } func (m *mockConn) Read(b []byte) (n int, err error) { + if m.repeat > 0 { + m.rbuf.Write(m.data) + m.repeat-- + } return m.rbuf.Read(b) } func (m *mockConn) Write(b []byte) (n int, err error) { @@ -50,9 +56,11 @@ func _createConn(data []byte) *libnet.Conn { func _createRepeatConn(data []byte, r int) *libnet.Conn { mconn := &mockConn{ - addr: "127.0.0.1:12345", - rbuf: bytes.NewBuffer(bytes.Repeat(data, r)), - wbuf: new(bytes.Buffer), + addr: "127.0.0.1:12345", + rbuf: bytes.NewBuffer(nil), + wbuf: new(bytes.Buffer), + data: data, + repeat: r, } conn := libnet.NewConn(mconn, time.Second, time.Second) return conn diff --git a/proxy/handler.go b/proxy/handler.go index c90dc12f..e073e0b3 100644 --- a/proxy/handler.go +++ b/proxy/handler.go @@ -126,9 +126,7 @@ func (h *Handler) handle() { prom.ProxyTime(h.cluster.cc.Name, h.toStr(msg.Request().Cmd()), int64(msg.TotalDur()/time.Microsecond)) } } - - err = h.pc.Flush() - if err != nil { + if err = h.pc.Flush(); err != nil { return } From cf66396fa356de254be5519772fbcfdb7f8c676f Mon Sep 17 00:00:00 2001 From: felixhao Date: Mon, 30 Jul 2018 22:26:30 +0800 Subject: [PATCH 76/87] add mc bin mock conn --- proto/memcache/binary/mcbin_test.go | 67 +++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 proto/memcache/binary/mcbin_test.go diff --git a/proto/memcache/binary/mcbin_test.go b/proto/memcache/binary/mcbin_test.go new file mode 100644 index 00000000..7d123bbb --- /dev/null +++ b/proto/memcache/binary/mcbin_test.go @@ -0,0 +1,67 @@ +package binary + +import ( + "bytes" + "net" + "time" + + libnet "overlord/lib/net" +) + +type mockAddr string + +func (m mockAddr) Network() string { + return "tcp" +} +func (m mockAddr) String() string { + return string(m) +} + +type mockConn struct { + addr mockAddr + rbuf *bytes.Buffer + wbuf *bytes.Buffer + data []byte + repeat int +} + +func (m *mockConn) Read(b []byte) (n int, err error) { + if m.repeat > 0 { + m.rbuf.Write(m.data) + m.repeat-- + } + return m.rbuf.Read(b) +} +func (m *mockConn) Write(b []byte) (n int, err error) { + return m.wbuf.Write(b) +} + +// writeBuffers impl the net.buffersWriter to support writev +func (m *mockConn) writeBuffers(buf *net.Buffers) (int64, error) { + return buf.WriteTo(m.wbuf) +} + +func (m *mockConn) Close() error { return nil } +func (m *mockConn) LocalAddr() net.Addr { return m.addr } +func (m *mockConn) RemoteAddr() net.Addr { return m.addr } + +func (m *mockConn) SetDeadline(t time.Time) error { return nil } +func (m *mockConn) SetReadDeadline(t time.Time) error { return nil } +func (m *mockConn) SetWriteDeadline(t time.Time) error { return nil } + +// _createConn is useful tools for handler test +func _createConn(data []byte) *libnet.Conn { + return _createRepeatConn(data, 1) +} + +func _createRepeatConn(data []byte, r int) *libnet.Conn { + mconn := &mockConn{ + addr: "127.0.0.1:12345", + rbuf: bytes.NewBuffer(nil), + wbuf: new(bytes.Buffer), + data: data, + repeat: r, + } + conn := libnet.NewConn(mconn, time.Second, time.Second) + return conn +} From cbb56dc42b8099a2988b33241263d8b91ca1501a Mon Sep 17 00:00:00 2001 From: felixhao Date: Tue, 31 Jul 2018 11:10:42 +0800 Subject: [PATCH 77/87] add resp copy func --- proto/redis/proxy_conn.go | 34 +++++++--------------------------- proto/redis/resp.go | 12 ++++++++++++ 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/proto/redis/proxy_conn.go b/proto/redis/proxy_conn.go index fad4f9e2..e0d7168a 100644 --- a/proto/redis/proxy_conn.go +++ b/proto/redis/proxy_conn.go @@ -70,9 +70,7 @@ func (pc *proxyConn) decode(m *proto.Message) (err error) { } if pc.resp.arrayn < 1 { r := nextReq(m) - r.resp.reset() - r.resp.rTp = pc.resp.rTp - r.resp.data = pc.resp.data + r.resp.copy(pc.resp) return } conv.UpdateToUpper(pc.resp.array[0].data) @@ -96,14 +94,10 @@ func (pc *proxyConn) decode(m *proto.Message) (err error) { nre1.data = cmdMSetBytes // array resp: key nre2 := r.resp.next() // NOTE: $klen\r\nkey\r\n - nre2.reset() - nre2.rTp = pc.resp.array[i*2+1].rTp - nre2.data = pc.resp.array[i*2+1].data + nre2.copy(pc.resp.array[i*2+1]) // array resp: value nre3 := r.resp.next() // NOTE: $vlen\r\nvalue\r\n - nre3.reset() - nre3.rTp = pc.resp.array[i*2+2].rTp - nre3.data = pc.resp.array[i*2+2].data + nre3.copy(pc.resp.array[i*2+2]) } } else if bytes.Equal(cmd, cmdMGetBytes) { for i := 1; i < pc.resp.arrayn; i++ { @@ -119,9 +113,7 @@ func (pc *proxyConn) decode(m *proto.Message) (err error) { nre1.data = cmdGetBytes // array resp: key nre2 := r.resp.next() // NOTE: $klen\r\nkey\r\n - nre2.reset() - nre2.rTp = pc.resp.array[i].rTp - nre2.data = pc.resp.array[i].data + nre2.copy(pc.resp.array[i]) } } else if bytes.Equal(cmd, cmdDelBytes) || bytes.Equal(cmd, cmdExistsBytes) { for i := 1; i < pc.resp.arrayn; i++ { @@ -132,26 +124,14 @@ func (pc *proxyConn) decode(m *proto.Message) (err error) { r.resp.data = arrayLenTwo // array resp: get nre1 := r.resp.next() // NOTE: $3\r\nDEL\r\n | $6\r\nEXISTS\r\n - nre1.reset() - nre1.rTp = pc.resp.array[0].rTp - nre1.data = pc.resp.array[0].data + nre1.copy(pc.resp.array[0]) // array resp: key nre2 := r.resp.next() // NOTE: $klen\r\nkey\r\n - nre2.reset() - nre2.rTp = pc.resp.array[i].rTp - nre2.data = pc.resp.array[i].data + nre2.copy(pc.resp.array[i]) } } else { r := nextReq(m) - r.resp.reset() - r.resp.rTp = pc.resp.rTp - r.resp.data = pc.resp.data - for i := 0; i < pc.resp.arrayn; i++ { - nre := r.resp.next() - nre.reset() - nre.rTp = pc.resp.array[i].rTp - nre.data = pc.resp.array[i].data - } + r.resp.copy(pc.resp) } return } diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 4953eb0c..54ff0fd1 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -48,6 +48,18 @@ func (r *resp) reset() { } } +func (r *resp) copy(re *resp) { + r.reset() + r.rTp = re.rTp + r.data = re.data + if re.arrayn > 0 { + for i := 0; i < re.arrayn; i++ { + nre := r.next() + nre.copy(re.array[i]) + } + } +} + func (r *resp) next() *resp { if r.arrayn < len(r.array) { nr := r.array[r.arrayn] From 74b475cdd727e561c8239d0137c1791274830545 Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 31 Jul 2018 11:18:39 +0800 Subject: [PATCH 78/87] remove unnassessary check of arrayyn --- proto/redis/resp.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/proto/redis/resp.go b/proto/redis/resp.go index 54ff0fd1..c8ed56a7 100644 --- a/proto/redis/resp.go +++ b/proto/redis/resp.go @@ -52,11 +52,9 @@ func (r *resp) copy(re *resp) { r.reset() r.rTp = re.rTp r.data = re.data - if re.arrayn > 0 { - for i := 0; i < re.arrayn; i++ { - nre := r.next() - nre.copy(re.array[i]) - } + for i := 0; i < re.arrayn; i++ { + nre := r.next() + nre.copy(re.array[i]) } } From 651a838c0b300d6e7907d73ec85d1e8a729d0b3c Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Tue, 31 Jul 2018 14:28:01 +0800 Subject: [PATCH 79/87] del unuse vendor --- .../github.com/tatsushid/go-critbit/LICENSE | 21 - .../github.com/tatsushid/go-critbit/README.md | 105 ----- .../tatsushid/go-critbit/critbit.go | 370 ------------------ vendor/vendor.json | 6 - 4 files changed, 502 deletions(-) delete mode 100644 vendor/github.com/tatsushid/go-critbit/LICENSE delete mode 100644 vendor/github.com/tatsushid/go-critbit/README.md delete mode 100644 vendor/github.com/tatsushid/go-critbit/critbit.go diff --git a/vendor/github.com/tatsushid/go-critbit/LICENSE b/vendor/github.com/tatsushid/go-critbit/LICENSE deleted file mode 100644 index fba1c1da..00000000 --- a/vendor/github.com/tatsushid/go-critbit/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Tatsushi Demachi - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/tatsushid/go-critbit/README.md b/vendor/github.com/tatsushid/go-critbit/README.md deleted file mode 100644 index 37911109..00000000 --- a/vendor/github.com/tatsushid/go-critbit/README.md +++ /dev/null @@ -1,105 +0,0 @@ -Crit-bit tree library written in Go -=================================== - -[![GoDoc](https://godoc.org/github.com/tatsushid/go-critbit?status.svg)][godoc] -[![Build Status](https://travis-ci.org/tatsushid/go-critbit.svg?branch=master)](https://travis-ci.org/tatsushid/go-critbit) -[![Go Report Card](https://goreportcard.com/badge/github.com/tatsushid/go-critbit)](https://goreportcard.com/report/github.com/tatsushid/go-critbit) - -This is a [Crit-bit tree](http://cr.yp.to/critbit.html) implementation written in Go by referring [the C implementation and document](https://github.com/agl/critbit). - -## Installation - -Install and update by `go get -u github.com/tatsushid/go-critbit`. - -## Usage - -```go -package main - -import ( - "github.com/tatsushid/go-critbit" -) - -func main() { - // create a tree - tr := critbit.New() - - tr.Insert([]byte("foo"), 1) - tr.Insert([]byte("bar"), 2) - tr.Insert([]byte("foobar"), 3) - - // search exact key - v, ok := tr.Get([]byte("bar")) - if !ok { - panic("should be ok") - } - if v.(int) != 2 { - panic("should be 2") - } - - // find the longest prefix of a given key - k, _, _ := tr.LongestPrefix([]byte("foozip")) - if string(k) != "foo" { - panic("should be foo") - } - - var sum int - // walk the tree with a callback function - tr.Walk(func(k []byte, v interface{}) bool { - sum += v.(int) - return false - }) - if sum != 6 { - panic("should be 6 = 1 + 2 + 3") - } -} -``` - -Please see [GoDoc][godoc] for more APIs and details. - -This also has Graphviz exporter sample. It is implemented as a part of tests. -To use it, please compile a test command by - -```shellsession -go test -c -``` - -and run the generated command like - -```shellsession -./critbit.test -random 10 -printdot > critbit.dot -``` - -You can see Graphviz image by opening the created file. - -The command has following options - -``` --add key - add key to critbit tree. this can be used multiple times --del key - delete key from critbit tree. this can be used multiple times --printdot - print graphviz dot of critbit tree and exit --random times - insert keys chosen at random up to specified times -``` - -## Notes -- [go-radix](https://github.com/armon/go-radix) is widly used tree library and its API is well organized I think. I wrote this library to have a similar API to that and used some test data patterns to make sure it returns same results as that. -- To compare performance, I took the performance test data from [An Adaptive Radix Tree Implementation in Go](https://github.com/plar/go-adaptive-radix-tree). If you are interested in it, you can see it by running - - ```shellsession - go test -bench . -benchmem - ``` - - in both cloned repositories. In my environment, this library is a bit slower but a little more memory efficient than that. -- This can handle a byte sequence with null bytes in it as same as [critbitgo](https://github.com/k-sone/critbitgo), the other Crit-bit library written in Go. - -Thanks for all these libraries! - -## License -This program is under MIT license. Please see the [LICENSE][license] file for details. - -[godoc]: http://godoc.org/github.com/tatsushid/go-critbit -[license]: https://github.com/tatsushid/go-critbit/blob/master/LICENSE diff --git a/vendor/github.com/tatsushid/go-critbit/critbit.go b/vendor/github.com/tatsushid/go-critbit/critbit.go deleted file mode 100644 index 570c5560..00000000 --- a/vendor/github.com/tatsushid/go-critbit/critbit.go +++ /dev/null @@ -1,370 +0,0 @@ -// Package critbit implements Crit-Bit tree for byte sequences. -// -// Crit-Bit tree [1] is fast, memory efficient and a variant of PATRICIA trie. -// This implementation can be used for byte sequences if it includes a null -// byte or not. This is based on [2] and extends it to support a null byte in a -// byte sequence. -// -// [1]: http://cr.yp.to/critbit.html (definition) -// [2]: https://github.com/agl/critbit (C implementation and document) -package critbit - -import "bytes" - -type nodeType int - -const ( - internal nodeType = iota - external -) - -type node interface { - kind() nodeType -} - -type iNode struct { - children [2]node - pos int - other uint8 -} - -func (n *iNode) kind() nodeType { return internal } - -type eNode struct { - key []byte - value interface{} -} - -func (n *eNode) kind() nodeType { return external } - -// Tree represents a critbit tree. -type Tree struct { - root node - size int -} - -// New returns an empty tree. -func New() *Tree { - return &Tree{} -} - -// Len returns a number of elements in the tree. -func (t *Tree) Len() int { - return t.size -} - -func (t *Tree) direction(k []byte, pos int, other uint8) int { - var c uint8 - if pos < len(k) { - c = k[pos] - } else if other == 0xff { - return 0 - } - return (1 + int(other|c)) >> 8 -} - -func (t *Tree) lookup(k []byte) (*eNode, *iNode) { - if t.root == nil { - return nil, nil - } - - var top *iNode - p := t.root - for { - switch n := p.(type) { - case *eNode: - return n, top - case *iNode: - if top == nil || n.pos < len(k) { - top = n - } - p = n.children[t.direction(k, n.pos, n.other)] - } - } -} - -// Get searches a given key from the tree. If the key exists in the tree, it -// returns its value and true. If not, it returns nil and false. -func (t *Tree) Get(k []byte) (interface{}, bool) { - n, _ := t.lookup(k) - if n != nil && bytes.Equal(k, n.key) { - return n.value, true - } - return nil, false -} - -func (t *Tree) findFirstDiffByte(k []byte, n *eNode) (pos int, other uint8, match bool) { - var byt, b byte - for pos = 0; pos < len(k); pos++ { - b = k[pos] - byt = 0 - if pos < len(n.key) { - byt = n.key[pos] - } - if byt != b { - return pos, byt ^ b, false - } - } - if pos < len(n.key) { - return pos, n.key[pos], false - } else if pos == len(n.key) { - return 0, 0, true - } - return pos - 1, 0, false -} - -func (t *Tree) findInsertPos(k []byte, pos int, other uint8) (*node, node) { - p := &t.root - for { - switch n := (*p).(type) { - case *eNode: - return p, n - case *iNode: - if n.pos > pos { - return p, n - } - if n.pos == pos && n.other > other { - return p, n - } - p = &n.children[t.direction(k, n.pos, n.other)] - } - } -} - -// Insert adds or updates a given key to the tree and returns its previous -// value and if anything was set or not. If there is the key in the tree, it -// adds the key and the value to the tree and returns nil and true when it -// succeeded while if not, it updates the key's value and returns its previous -// value and true when it succeeded. -func (t *Tree) Insert(k []byte, v interface{}) (interface{}, bool) { - key := append([]byte{}, k...) - - n, _ := t.lookup(k) - if n == nil { // only happens when t.root is nil - t.root = &eNode{key: key, value: v} - t.size++ - return nil, true - } - - pos, other, match := t.findFirstDiffByte(k, n) - if match { - orig := n.value - n.value = v - return orig, true - } - - other |= other >> 1 - other |= other >> 2 - other |= other >> 4 - other = ^(other &^ (other >> 1)) - di := t.direction(n.key, pos, other) - - newn := &iNode{pos: pos, other: other} - newn.children[1-di] = &eNode{key: key, value: v} - - p, child := t.findInsertPos(k, pos, other) - newn.children[di] = child - *p = newn - - t.size++ - return nil, true -} - -func (t *Tree) findDeletePos(k []byte) (*node, *eNode, int) { - if t.root == nil { - return nil, nil, 0 - } - - var di int - var q *node - p := &t.root - for { - switch n := (*p).(type) { - case *eNode: - return q, n, di - case *iNode: - di = t.direction(k, n.pos, n.other) - q = p - p = &n.children[di] - } - } -} - -// Delete removes a given key and its value from the tree. If it succeeded, it -// returns the key's previous value and true while if not, it returns nil and -// false. On an empty tree, it always fails. -func (t *Tree) Delete(k []byte) (interface{}, bool) { - q, n, di := t.findDeletePos(k) - if n == nil || !bytes.Equal(k, n.key) { - return nil, false - } - t.size-- - if q == nil { - t.root = nil - return n.value, true - } - tmp := (*q).(*iNode) - *q = tmp.children[1-di] - return n.value, true -} - -// Clear removes all elements in the tree. If it removes something, it returns -// true while the tree is empty and there is nothing to remove, it returns -// false. -func (t *Tree) Clear() bool { - if t.root != nil { - t.root = nil - t.size = 0 - return true - } - return false -} - -// Minimum searches a key from the tree in lexicographic order and returns the -// first one and its value. If it found such a key, it also returns true as the -// bool value while if not, it returns false as it. -func (t *Tree) Minimum() ([]byte, interface{}, bool) { - if t.root == nil { - return nil, nil, false - } - - p := t.root - for { - switch n := p.(type) { - case *eNode: - return n.key, n.value, true - case *iNode: - p = n.children[0] - } - } -} - -// Maximum searches a key from the tree in lexicographic order and returns the -// last one and its value. If it found such a key, it also returns true as the -// bool value while if not, it returns false as it. -func (t *Tree) Maximum() ([]byte, interface{}, bool) { - if t.root == nil { - return nil, nil, false - } - - p := t.root - for { - switch n := p.(type) { - case *eNode: - return n.key, n.value, true - case *iNode: - p = n.children[1] - } - } -} - -func (t *Tree) longestPrefix(p node, prefix []byte) ([]byte, interface{}, bool) { - if p == nil { - return nil, nil, false - } - var di int - var c uint8 - switch n := p.(type) { - case *eNode: - if bytes.HasPrefix(prefix, n.key) { - return n.key, n.value, true - } - case *iNode: - c = 0 - if n.pos < len(prefix) { - c = prefix[n.pos] - } - di = (1 + int(n.other|c)) >> 8 - - if k, v, ok := t.longestPrefix(n.children[di], prefix); ok { - return k, v, ok - } else if di == 1 { - return t.longestPrefix(n.children[0], prefix) - } - } - return nil, nil, false -} - -// LongestPrefix searches the longest key which is included in a given key and -// returns the found key and its value. For example, if there are "f", "fo", -// "foobar" in the tree and "foo" is given, it returns "fo". If it found such a -// key, it returns true as the bool value while if not, it returns false as it. -func (t *Tree) LongestPrefix(prefix []byte) ([]byte, interface{}, bool) { - return t.longestPrefix(t.root, prefix) -} - -// WalkFn is used at walking a tree. It receives a key and its value of each -// elements which a walk function gives. If it returns true, a walk function -// should be terminated at there. -type WalkFn func(k []byte, v interface{}) bool - -func (t *Tree) walk(p node, fn WalkFn) bool { - if p == nil { - return false - } - switch n := p.(type) { - case *eNode: - return fn(n.key, n.value) - case *iNode: - for i := 0; i < 2; i++ { - if t.walk(n.children[i], fn) { - return true - } - } - } - return false -} - -// Walk walks whole the tree and call a given function with each element's key -// and value. If the function returns true, the walk is terminated at there. -func (t *Tree) Walk(fn WalkFn) { - t.walk(t.root, fn) -} - -// WalkPrefix walks the tree under a given prefix and call a given function -// with each element's key and value. For example, the tree has "f", "fo", -// "foob", "foobar" and "foo" is given, it visits "foob" and "foobar" elements. -// If the function returns true, the walk is terminated at there. -func (t *Tree) WalkPrefix(prefix []byte, fn WalkFn) { - n, top := t.lookup(prefix) - if n == nil || !bytes.HasPrefix(n.key, prefix) { - return - } - wrapper := func(k []byte, v interface{}) bool { - if bytes.HasPrefix(k, prefix) { - return fn(k, v) - } - return false - } - t.walk(top, wrapper) -} - -func (t *Tree) walkPath(p node, path []byte, fn WalkFn) bool { - if p == nil { - return false - } - var di int - switch n := p.(type) { - case *eNode: - if bytes.HasPrefix(path, n.key) { - return fn(n.key, n.value) - } - case *iNode: - di = t.direction(path, n.pos, n.other) - if di == 1 { - if t.walkPath(n.children[0], path, fn) { - return true - } - } - return t.walkPath(n.children[di], path, fn) - } - return false -} - -// WalkPath walks the tree from the root up to a given key and call a given -// function with each element's key and value. For example, the tree has "f", -// "fo", "foob", "foobar" and "foo" is given, it visits "f" and "fo" elements. -// If the function returns true, the walk is terminated at there. -func (t *Tree) WalkPath(path []byte, fn WalkFn) { - t.walkPath(t.root, path, fn) -} diff --git a/vendor/vendor.json b/vendor/vendor.json index 73020abb..93d06e02 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -109,12 +109,6 @@ "path": "github.com/stretchr/testify/assert", "revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686", "revisionTime": "2018-05-06T18:05:49Z" - }, - { - "checksumSHA1": "9yMoHF1RPGh802dGQtG/8EvS6FA=", - "path": "github.com/tatsushid/go-critbit", - "revision": "487ef94b52c147166c6c62a3c2db431cad0f329b", - "revisionTime": "2018-03-27T15:21:58Z" } ], "rootPath": "overlord" From b3f0a3b9544d1b6e56c00edfe25482fd8c837c6d Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Tue, 31 Jul 2018 15:36:11 +0800 Subject: [PATCH 80/87] add uint test --- proto/memcache/proxy_conn_test.go | 4 +++- proto/memcache/request_test.go | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/proto/memcache/proxy_conn_test.go b/proto/memcache/proxy_conn_test.go index dd0513f8..a4b8b497 100644 --- a/proto/memcache/proxy_conn_test.go +++ b/proto/memcache/proxy_conn_test.go @@ -106,7 +106,9 @@ func TestProxyConnDecodeOk(t *testing.T) { conn := _createConn([]byte(tt.Data)) p := NewProxyConn(conn) mlist := proto.GetMsgSlice(2) - + // test req reuse. + mlist[0].WithRequest(NewReq()) + mlist[0].Reset() msgs, err := p.Decode(mlist) if tt.Err != nil { diff --git a/proto/memcache/request_test.go b/proto/memcache/request_test.go index 564ccd2c..f0b741a3 100644 --- a/proto/memcache/request_test.go +++ b/proto/memcache/request_test.go @@ -29,6 +29,7 @@ func TestRequestTypeString(t *testing.T) { reg := regexp.MustCompile(`[a-z]+`) for _, rtype := range _allReqTypes { assert.True(t, reg.Match(rtype.Bytes())) + assert.True(t, reg.MatchString(rtype.String())) } } From 2e25424afac6d04f3a1824ad7928b11b9a82b1ac Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Tue, 31 Jul 2018 15:56:18 +0800 Subject: [PATCH 81/87] add uint test --- lib/bufio/io_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/bufio/io_test.go b/lib/bufio/io_test.go index d7cd861c..51a0a3a4 100644 --- a/lib/bufio/io_test.go +++ b/lib/bufio/io_test.go @@ -70,6 +70,14 @@ func TestReaderReadSlice(t *testing.T) { assert.Equal(t, ErrBufferFull, err) } +func TestReaderReadLine(t *testing.T) { + b := NewReader(bytes.NewBuffer([]byte("abcd\r\nabc")), Get(defaultBufferSize)) + b.Read() + data, err := b.ReadLine() + assert.NoError(t, err) + assert.Len(t, data, 6) +} + func TestReaderReadExact(t *testing.T) { bts := _genData() From 13d49c0b74a7cb404164956d6d0c1cf17e24daec Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 31 Jul 2018 15:30:54 +0800 Subject: [PATCH 82/87] add DropMsgBatch of reusing MB to avoid GC fixed of bugs of msg batch --- proto/batch.go | 10 ++++++++++ proxy/handler.go | 3 +++ 2 files changed, 13 insertions(+) diff --git a/proto/batch.go b/proto/batch.go index 05d304aa..369b4225 100644 --- a/proto/batch.go +++ b/proto/batch.go @@ -136,3 +136,13 @@ func (m *MsgBatch) BatchDoneWithError(cluster, addr string, err error) { } m.Done() } + +// DropMsgBatch put MsgBatch into recycle using pool. +func DropMsgBatch(m *MsgBatch) { + m.buf.Reset() + m.msgs = m.msgs[:0] + m.count = 0 + m.wg = nil + msgBatchPool.Put(m) + m = nil +} diff --git a/proxy/handler.go b/proxy/handler.go index e073e0b3..c2cbacd3 100644 --- a/proxy/handler.go +++ b/proxy/handler.go @@ -97,6 +97,9 @@ func (h *Handler) handle() { for _, msg := range msgs { msg.Clear() } + for _, mb := range mbatch { + proto.DropMsgBatch(mb) + } h.closeWithError(err) }() From af53de1142c881403bd772dac4043c9a4639fdde Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Tue, 31 Jul 2018 18:15:59 +0800 Subject: [PATCH 83/87] fix uint test --- proto/redis/node_conn.go | 2 +- proto/redis/node_conn_test.go | 31 +++++++++++-------------------- proto/redis/proxy_conn_test.go | 13 ++++++++++--- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/proto/redis/node_conn.go b/proto/redis/node_conn.go index 1f1dd99b..9301c7da 100644 --- a/proto/redis/node_conn.go +++ b/proto/redis/node_conn.go @@ -50,7 +50,7 @@ func (nc *nodeConn) WriteBatch(mb *proto.MsgBatch) (err error) { return ErrBadAssert } if !req.isSupport() || req.isCtl() { - return nil + continue } if err = req.resp.encode(nc.bw); err != nil { m.DoneWithError(err) diff --git a/proto/redis/node_conn_test.go b/proto/redis/node_conn_test.go index da45c45a..bb197150 100644 --- a/proto/redis/node_conn_test.go +++ b/proto/redis/node_conn_test.go @@ -34,27 +34,16 @@ func TestNodeConnWriteBatchOk(t *testing.T) { nc := newNodeConn("baka", "127.0.0.1:12345", _createConn(nil)) mb := proto.NewMsgBatch() msg := proto.GetMsg() - req := getReq() - req.mType = mergeTypeNo - req.resp = &resp{ - rTp: respArray, - data: []byte("2"), - array: []*resp{ - &resp{ - rTp: respBulk, - data: []byte("3\r\nGET"), - }, - &resp{ - rTp: respBulk, - data: []byte("5\r\nabcde"), - }, - }, - arrayn: 2, - } + req := newRequest("GET", "AA") + msg.WithRequest(req) + mb.AddMsg(msg) + msg = proto.NewMessage() + req = newRequest("unsupport") msg.WithRequest(req) mb.AddMsg(msg) err := nc.WriteBatch(mb) assert.NoError(t, err) + nc.Close() } func TestNodeConnWriteBadAssert(t *testing.T) { @@ -74,9 +63,11 @@ func TestReadBatchOk(t *testing.T) { nc := newNodeConn("baka", "127.0.0.1:12345", _createConn([]byte(data))) mb := proto.NewMsgBatch() msg := proto.GetMsg() - req := getReq() - req.mType = mergeTypeNo - req.reply = &resp{} + req := newRequest("unsportcmd", "a") + msg.WithRequest(req) + mb.AddMsg(msg) + msg = proto.GetMsg() + req = newRequest("GET", "a") msg.WithRequest(req) mb.AddMsg(msg) err := nc.ReadBatch(mb) diff --git a/proto/redis/proxy_conn_test.go b/proto/redis/proxy_conn_test.go index bea634a6..52df85d4 100644 --- a/proto/redis/proxy_conn_test.go +++ b/proto/redis/proxy_conn_test.go @@ -13,7 +13,7 @@ func TestDecodeBasicOk(t *testing.T) { conn := _createConn([]byte(data)) pc := NewProxyConn(conn) - msgs := proto.GetMsgSlice(2) + msgs := proto.GetMsgSlice(1) nmsgs, err := pc.Decode(msgs) assert.NoError(t, err) assert.Len(t, nmsgs, 1) @@ -30,7 +30,7 @@ func TestDecodeBasicOk(t *testing.T) { } func TestDecodeComplexOk(t *testing.T) { - data := "*3\r\n$4\r\nMGET\r\n$4\r\nbaka\r\n$4\r\nkaba\r\n*5\r\n$4\r\nMSET\r\n$1\r\na\r\n$1\r\nb\r\n$3\r\neee\r\n$5\r\n12345\r\n*3\r\n$4\r\nMGET\r\n$4\r\nenen\r\n$4\r\nnime\r\n*2\r\n$3\r\nGET\r\n$5\r\nabcde\r\n" + data := "*3\r\n$4\r\nMGET\r\n$4\r\nbaka\r\n$4\r\nkaba\r\n*5\r\n$4\r\nMSET\r\n$1\r\na\r\n$1\r\nb\r\n$3\r\neee\r\n$5\r\n12345\r\n*3\r\n$4\r\nMGET\r\n$4\r\nenen\r\n$4\r\nnime\r\n*2\r\n$3\r\nGET\r\n$5\r\nabcde\r\n*3\r\n$3\r\nDEL\r\n$1\r\na\r\n$1\r\nb\r\n" conn := _createConn([]byte(data)) pc := NewProxyConn(conn) // test reuse command @@ -45,7 +45,7 @@ func TestDecodeComplexOk(t *testing.T) { // decode nmsgs, err := pc.Decode(msgs) assert.NoError(t, err) - assert.Len(t, nmsgs, 4) + assert.Len(t, nmsgs, 5) // MGET baka assert.Len(t, nmsgs[0].Batch(), 2) req := msgs[0].Requests()[0].(*Request) @@ -121,6 +121,13 @@ func TestDecodeComplexOk(t *testing.T) { assert.Equal(t, []byte("2"), req.resp.data) assert.Equal(t, []byte("3\r\nGET"), req.resp.array[0].data) assert.Equal(t, []byte("5\r\nabcde"), req.resp.array[1].data) + + req = msgs[4].Requests()[0].(*Request) + assert.Equal(t, mergeTypeCount, req.mType) + assert.Equal(t, 2, req.resp.arrayn) + assert.Equal(t, "DEL", req.CmdString()) + assert.Equal(t, "a", string(req.Key())) + assert.Equal(t, []byte("2"), req.resp.data) } func TestEncodeCmdOk(t *testing.T) { From d2230de8cfe71bbff82664d8f4cec8cf124e26df Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 31 Jul 2018 18:29:21 +0800 Subject: [PATCH 84/87] add .codecov.yml to close patch check. --- .codecov.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 00000000..ab5eeaad --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,4 @@ +coverage: + status: + project: on + patch: off From db92ad304ad71f2dd58a9a210654aee526a00dc5 Mon Sep 17 00:00:00 2001 From: wayslog Date: Tue, 31 Jul 2018 19:21:51 +0800 Subject: [PATCH 85/87] remove PutMsgBatch function --- proto/batch.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/proto/batch.go b/proto/batch.go index 369b4225..00afc01d 100644 --- a/proto/batch.go +++ b/proto/batch.go @@ -41,13 +41,6 @@ func NewMsgBatch() *MsgBatch { return msgBatchPool.Get().(*MsgBatch) } -// PutMsgBatch will release the batch object -func PutMsgBatch(b *MsgBatch) { - b.Reset() - b.wg = nil - msgBatchPool.Put(b) -} - // MsgBatch is a single execute unit type MsgBatch struct { buf *bufio.Buffer From ea7ae77f70741ce4191229de5e784ac259ce1b96 Mon Sep 17 00:00:00 2001 From: Tanghui Lin Date: Tue, 31 Jul 2018 19:43:45 +0800 Subject: [PATCH 86/87] prom default on --- cmd/proxy/main.go | 2 ++ lib/prom/prom.go | 10 ++-------- proto/batch.go | 4 ++-- proto/memcache/binary/node_conn.go | 4 ++-- proto/memcache/node_conn.go | 4 ++-- proxy/handler.go | 4 ++-- 6 files changed, 12 insertions(+), 16 deletions(-) diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index e6acbd5b..83a172b0 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -82,6 +82,8 @@ func main() { go http.ListenAndServe(c.Pprof, nil) if c.Proxy.UseMetrics { prom.Init() + } else { + prom.On = false } } // new proxy diff --git a/lib/prom/prom.go b/lib/prom/prom.go index 86af4979..3a47fe98 100644 --- a/lib/prom/prom.go +++ b/lib/prom/prom.go @@ -30,18 +30,12 @@ var ( clusterNodeErrLabels = []string{"cluster", "node", "cmd", "error"} clusterCmdLabels = []string{"cluster", "cmd"} clusterNodeCmdLabels = []string{"cluster", "node", "cmd"} - - on bool + // On Prom switch + On = true ) -// On return if open prom metrics. -func On() bool { - return on -} - // Init init prometheus. func Init() { - on = true conns = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: statConns, diff --git a/proto/batch.go b/proto/batch.go index 369b4225..aea1a90f 100644 --- a/proto/batch.go +++ b/proto/batch.go @@ -116,7 +116,7 @@ func (m *MsgBatch) Msgs() []*Message { // BatchDone will set done and report prom HandleTime. func (m *MsgBatch) BatchDone(cluster, addr string) { m.Done() - if prom.On() { + if prom.On { for _, msg := range m.Msgs() { prom.HandleTime(cluster, addr, msg.Request().CmdString(), int64(msg.RemoteDur()/time.Microsecond)) } @@ -130,7 +130,7 @@ func (m *MsgBatch) BatchDoneWithError(cluster, addr string, err error) { if log.V(1) { log.Errorf("cluster(%s) Msg(%s) cluster process handle error:%+v", cluster, msg.Request().Key(), err) } - if prom.On() { + if prom.On { prom.ErrIncr(cluster, addr, msg.Request().CmdString(), errors.Cause(err).Error()) } } diff --git a/proto/memcache/binary/node_conn.go b/proto/memcache/binary/node_conn.go index bc892d35..8dab7354 100644 --- a/proto/memcache/binary/node_conn.go +++ b/proto/memcache/binary/node_conn.go @@ -172,7 +172,7 @@ func (n *nodeConn) fillMCRequest(mcr *MCRequest, data []byte) (size int, err err bl := binary.BigEndian.Uint32(mcr.bodyLen) if bl == 0 { if mcr.rTp == RequestTypeGet || mcr.rTp == RequestTypeGetQ || mcr.rTp == RequestTypeGetK || mcr.rTp == RequestTypeGetKQ { - if prom.On() { + if prom.On { prom.Miss(n.cluster, n.addr) } } @@ -186,7 +186,7 @@ func (n *nodeConn) fillMCRequest(mcr *MCRequest, data []byte) (size int, err err mcr.data = data[requestHeaderLen : requestHeaderLen+bl] if mcr.rTp == RequestTypeGet || mcr.rTp == RequestTypeGetQ || mcr.rTp == RequestTypeGetK || mcr.rTp == RequestTypeGetKQ { - if prom.On() { + if prom.On { prom.Hit(n.cluster, n.addr) } } diff --git a/proto/memcache/node_conn.go b/proto/memcache/node_conn.go index 0a0937d8..65ded7e5 100644 --- a/proto/memcache/node_conn.go +++ b/proto/memcache/node_conn.go @@ -172,12 +172,12 @@ func (n *nodeConn) fillMCRequest(mcr *MCRequest, data []byte) (size int, err err } if bytes.Equal(bs, endBytes) { - if prom.On() { + if prom.On { prom.Miss(n.cluster, n.addr) } return } - if prom.On() { + if prom.On { prom.Hit(n.cluster, n.addr) } length, err := findLength(bs, mcr.rTp == RequestTypeGets || mcr.rTp == RequestTypeGats) diff --git a/proxy/handler.go b/proxy/handler.go index c2cbacd3..76230d88 100644 --- a/proxy/handler.go +++ b/proxy/handler.go @@ -125,7 +125,7 @@ func (h *Handler) handle() { } msg.MarkEnd() msg.ReleaseSubs() - if prom.On() { + if prom.On { prom.ProxyTime(h.cluster.cc.Name, h.toStr(msg.Request().Cmd()), int64(msg.TotalDur()/time.Microsecond)) } } @@ -168,7 +168,7 @@ func (h *Handler) closeWithError(err error) { h.cancel() h.msgCh.Close() _ = h.conn.Close() - if prom.On() { + if prom.On { prom.ConnDecr(h.cluster.cc.Name) } if log.V(3) { From 43344aee1d4fa331782bc4827fd7e42cfe152f23 Mon Sep 17 00:00:00 2001 From: felixhao Date: Tue, 31 Jul 2018 19:56:27 +0800 Subject: [PATCH 87/87] conn unit test --- lib/net/conn.go | 10 +++++--- lib/net/{net_test.go => conn_test.go} | 35 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) rename lib/net/{net_test.go => conn_test.go} (84%) diff --git a/lib/net/conn.go b/lib/net/conn.go index b0a485be..5717de02 100644 --- a/lib/net/conn.go +++ b/lib/net/conn.go @@ -58,8 +58,8 @@ func (c *Conn) ReConnect() (err error) { } func (c *Conn) Read(b []byte) (n int, err error) { - if c.closed { - return + if c.closed || c.Conn == nil { + return 0, ErrConnClosed } if c.err != nil && c.addr != "" { if re := c.ReConnect(); re != nil { @@ -74,12 +74,16 @@ func (c *Conn) Read(b []byte) (n int, err error) { return } } + n, err = c.Conn.Read(b) c.err = err return } func (c *Conn) Write(b []byte) (n int, err error) { + if c.closed || c.Conn == nil { + return 0, ErrConnClosed + } if c.err != nil && c.addr != "" { if re := c.ReConnect(); re != nil { err = c.err @@ -109,7 +113,7 @@ func (c *Conn) Close() error { // Writev impl the net.buffersWriter to support writev func (c *Conn) Writev(buf *net.Buffers) (int64, error) { - if c.Conn == nil { + if c.closed || c.Conn == nil { return 0, ErrConnClosed } return buf.WriteTo(c.Conn) diff --git a/lib/net/net_test.go b/lib/net/conn_test.go similarity index 84% rename from lib/net/net_test.go rename to lib/net/conn_test.go index c5a1a9b0..f5cab8ab 100644 --- a/lib/net/net_test.go +++ b/lib/net/conn_test.go @@ -194,3 +194,38 @@ func TestConnWriteBuffersOk(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 7, int(n)) } + +func TestConnNoConn(t *testing.T) { + addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") + assert.NoError(t, err) + l, err := net.ListenTCP("tcp", addr) + assert.NoError(t, err) + laddr := l.Addr() + go func() { + defer l.Close() + buf := make([]byte, 1024) + for { + sock, err := l.Accept() + assert.NoError(t, err) + n, err := sock.Read(buf) + assert.NoError(t, err) + assert.NotZero(t, n) + } + }() + conn := DialWithTimeout(laddr.String(), time.Second, time.Second, time.Second) + conn.Conn = nil + + bs := make([]byte, 1) + n, err := conn.Read(bs) + assert.Equal(t, 0, n) + assert.Equal(t, ErrConnClosed, err) + + n, err = conn.Write(bs) + assert.Equal(t, 0, n) + assert.Equal(t, ErrConnClosed, err) + + buffers := net.Buffers([][]byte{[]byte("baka"), []byte("qiu")}) + n64, err := conn.Writev(&buffers) + assert.Equal(t, int64(0), n64) + assert.Equal(t, ErrConnClosed, err) +}