Go 實現簡易的 Redis 客戶端

JaguarJack發表於2019-04-05

Go 實現簡易的 redis 客戶端

今天突然想看看客戶端是如何與 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 真好用,個人比較雖然傾向這種隱示的實現

相關文章