redis - resp

RESP (Redis Serialization Protocol)

其实就是 我们把可读性搞的命令通过 resp 转换为 redis server 能够识别的文本, redis server 给我们的响应体也是符合 resp 的.
我们再反序列化成可读的内容

https://redis.io/topics/protocol

简介

In RESP, the type of some data depends on the first byte:

  • For Simple Strings the first byte of the reply is “+”
  • For Errors the first byte of the reply is “-“
  • For Integers the first byte of the reply is “:”
  • For Bulk Strings the first byte of the reply is “\$”
  • For Arrays the first byte of the reply is “*“

Additionally RESP is able to represent a Null value using a special variation of Bulk Strings or Array as specified later.

In RESP different parts of the protocol are always terminated with “\r\n” (CRLF).

发送

发送的命令除了根据 resp 转换发送, 还可以通过简化
比如

command = fmt.Sprintf("SET %s %s\r\n", key, strconv.Quote(string(reflect.ValueOf(value).String())))

这里其实并没有完全把 set key value 进行 resp 的转换, 只是加了转义避免特殊字符是命令发生错误, 注意结尾需要 \r\n

lock

因为我们需要知道 server 响应体是对应哪一次命令的,所以我们每次执行命令前都需要 lock,
得到本次的响应后才会 unlock

接受

接受的时候就没有简化的方式, 只能老老实实的解析

func integersHandler(c *Client, head string, r *bufio.Reader, locked bool) (string, error) {
if !locked {
defer c.Unlock()
}
trimedLine := Trim(head, ":")
size, err := strconv.Atoi(trimedLine)

if err != nil {
return "", err
}

buf := make([]byte, size)
_, err = r.Read(buf)
if err != nil {
return "", err
}

return string(buf), nil
}

对整数的拂去, 截取 : 后面的为字节数

再去读取相应长度的内容,就是那个数字

解析 slowlog

slowlog 的结构是嵌套的,他的结构比较特殊,所以解析的时候比较”死”

package client

import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"time"

"github.com/olekukonko/tablewriter"
)

// Slowlog -
func (c *Client) Slowlog(num int) error {
c.Lock()
defer c.Unlock()
pub := fmt.Sprintf("slowlog get %d\r\n", num)
if err := c.ExcuteCmd(pub); err != nil {
return err
}
r := bufio.NewReader(c.Conn)
line, err := r.ReadString('\n')
if err != nil {
return err
}
trimedLine := Trim(line, "*")
size, err := strconv.Atoi(trimedLine)
if err != nil {
return err
}

data := make([][]string, 0, 0)
for index := 0; index < size; index++ {
row, err := parserLog(c, r)
if err != nil {
return err
}
data = append(data, row)
}

table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ID", "timestamp", "duration(微秒)", "command"})
table.SetCaption(true, "慢日志查询结果")
table.SetHeaderColor(tablewriter.Colors{tablewriter.Bold, tablewriter.BgGreenColor},
tablewriter.Colors{tablewriter.FgHiRedColor, tablewriter.Bold, tablewriter.BgBlackColor},
tablewriter.Colors{tablewriter.BgRedColor, tablewriter.FgWhiteColor},
tablewriter.Colors{tablewriter.BgCyanColor, tablewriter.FgWhiteColor})
table.SetColumnColor(tablewriter.Colors{tablewriter.Normal, tablewriter.FgGreenColor},
tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiRedColor},
tablewriter.Colors{tablewriter.Bold, tablewriter.FgWhiteColor},
tablewriter.Colors{tablewriter.Bold, tablewriter.FgBlackColor})
table.SetRowLine(true) // Enable row line
table.SetAutoWrapText(true)
table.SetReflowDuringAutoWrap(true)
// Change table lines
table.SetCenterSeparator("*")
table.SetColumnSeparator("╪")
table.SetRowSeparator("-")

table.SetAlignment(tablewriter.ALIGN_LEFT)
for _, v := range data {
table.Append(v)
}

table.Render()
return nil
}
func parserLog(c *Client, r *bufio.Reader) ([]string, error) {
line, err := r.ReadString('\n')
if err != nil {
return nil, err
}
items := Trim(line, "*")
size, err := strconv.Atoi(items)
if err != nil {
return nil, err
}
row := make([]string, 0)
for index := 0; index < 3; index++ {
line, err := r.ReadString('\n')
if err != nil {
return nil, err
}
if index == 1 {
i, err := strconv.ParseInt(Trim(line, ":"), 10, 64)
if err != nil {
panic(err)
}
tm := time.Unix(i, 0)
row = append(row, tm.String())
} else {
row = append(row, Trim(line, ":"))
}

}
line, err = r.ReadString('\n')
if err != nil {
return nil, err
}
params := Trim(line, "*")
size, err = strconv.Atoi(params)

paramsStr := ""
for index := 0; index < size*2; index++ {
line, err := r.ReadString('\n')
//有空行,需要跳跳
if index%2 == 0 {
if err != nil {
return nil, err
}
paramsStr = paramsStr + " " + parseSingleLine(c, line, r)
}
}
row = append(row, Trim(paramsStr, ":"))
return row, nil
}

func parseSingleLine(c *Client, head string, r *bufio.Reader) string {
if strings.HasPrefix(head, "$") {
str, err := bulkStringsHandler(c, head, r, true)
if err != nil {

return ""
}
return str
}
return head
}