Go 实现简易的 redis 客户端

3个月前 0 0 75

今天突然想看看客户端是如何与 redis server 交互的,所以就想着简单实现一下 redis 的客户端。当我们在使用 redis 的时候,redis 官方也提供了 redis-cli 客户端予以使用,通过一下命令操作,那么依据此,是不是客户端可以这么做呢?是不是遵从着某种 特定的协议呢?

首先通过 Tcp 连接到 redis-server, 保证可通。利用 GO 提供的 net 包,可以很轻松的实现。但是在这之前先定义个 interface,面向对象嘛#滑稽

type redis interface {
    set(key string, value string) (bool, error)
    get(key string) string
    del(key string) int
}
type Client struct {
    Conn net.Conn
}
// 连接, 特简单
func connect(host string) net.Conn {
    conn, err := net.Dial("tcp", host)
    if err != nil {
        log.Fatalln(err)
    }
    return conn
}

当使用 redis-cli 的时候,提供的 cli 命令操作。当然 redis 的提供的很多的 API 操作,单下面的例子就以 set get 为例。主要是操作字符串。对于 set 是这样的

> set blog njphper
> ok

类似这样的一个操作,如果将这里看成一个 im 服务的话, 说明在这里我们向 redis 服务器发送了一个“ set blog njphper” 字符串,redis-server 在收到这个字符串的后,进行了一系列操作,然后返回之后的状态。那么这里肯定会约束双方以怎么样的协议去发送以及返回。好了,这里就需要借助文档了,看一下 redis 协议文档 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 "$" // bulk字符第一个返回$
  • For Arrays the first byte of the reply is "" // 对于array第一个字符是

以上是服务端返回的信息,对于客户端而言,必须当 "\r\n" (CRLF) 结束,当然服务端也是,但是他们之间有一点区别。下面再说。因为 redis 的协议足够简单,所以操作起来还是很方便的。 这里实现以下 set , get 以及 del 操作

func(client Client) set(key string, value string) (bool, error) {
    var (
        res bool
        err error
    )

    client.Conn.Write([]byte(fmt.Sprintf("set %s %s \r\n",  key, value)))
    reader := bufio.NewReader(client.Conn)
    line, _ , err := reader.ReadLine()
    if err != nil {
        log.Fatalln(err)
    }
    switch string(line[0]) {
        case "+":
            res, err = true, nil
        case "-":
            res, err = false, errors.New(string(line[1:]))
    }
    // 清空 buff
    reader.Reset(client.Conn)
    return res, err
}

// 获取字符串
func(client Client) get(key string) string {
    _, err := client.Conn.Write([]byte(fmt.Sprintf("get %s \r\n",  key)))
    if err != nil {
        log.Fatalln(err)
    }
    reader := bufio.NewReader(client.Conn)
    // 第一行 redis 返回的状态,这里可以进行一些判断之类的
    reader.ReadLine()
    // 第二行才是 value 值
    line, _ , err := reader.ReadLine()
    // 清空 buff
    reader.Reset(client.Conn)
    return string(line)
}

// 删除字符串
func(client Client) del(key string) int  {
    _, err := client.Conn.Write([]byte(fmt.Sprintf("del %s \r\n",  key)))
    if err != nil {
        log.Fatalln(err)
    }
    reader := bufio.NewReader(client.Conn)
    line, _ , err := reader.ReadLine()
    code, _ := strconv.Atoi(string(line[1:]))
    return code
}

来测试一下看看,有没有成功?

  var client redis
    conn := connects("127.0.0.1:6379")
    client = Client{Conn: conn}
    fmt.Println(client.set("hi", "见见空空"))
    // 有返回值
    fmt.Println(client.get("hi"))
    // 设置
    fmt.Println(client.set("name", "hello"))
    // 获取
    fmt.Println(client.get("name"))
    // 删除 ,返回了 in(1)
    fmt.Println(client.del("name"))
    // nil
    fmt.Println(client.get("name"))

这里只是简单了解一下 redis,如果需要更加健壮的 redis 客户端,还是找一些开源包比较靠谱,毕竟轮子不需要再造一遍,可以了解,但没必要在自己花费精力造一遍 。这里还要提一下,go 的 interface 真好用,个人比较虽然倾向这种隐示的实现

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)

评论 (0)

    暂无评论~

njphper@copyright From 2014 to 2019-02-17