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 真好用,個人比較雖然傾向這種隱示的實現