用go写一个简单的Redis客户端框架
我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情
本篇文章是
Redis
系列的第5篇文章,写完这篇,Redis
就暂时告一段落了,在看本篇文章的时候,建议看一下前4篇文章,分别为:
- Redis主从复制搭建: juejin.cn/post/713470…
- 通过日志的方式理解Redis主从复制: juejin.cn/post/713495…
- 用go实现Redis读写分离: juejin.cn/post/713658…
- Redis RESP协议实现起来到底有多简单: juejin.cn/post/713805…
本篇文章,我们将用go
写一个Redis
的客户端,其实主要还是探究RESP
协议,如果你是第一次看本专栏文章,建议先看【用go实现Redis读写分离】 和 【Redis RESP协议实现起来到底有多简单】, 本篇文章的初衷也并不是真正写一个Redis
客户端。主要还是探究通信协议。
demo展示
我们将该代码已经上传到了gitee
,地址: gitee.com/pdudo/Redis…
package main
import (
"fmt"
"gitee.com/pdudo/RedisClient"
)
func main() {
// 连接客户端
client, err := RedisClient.Client("localhost:6379","123456",0)
if err != nil {
panic(err)
}
// Set
// set name pdudo EX 60
result , err := client.Set("name","pdudo","60","")
if err != nil {
fmt.Println("set 失败",err)
return
}
fmt.Println("set 结果:",result)
fmt.Println("\n")
// Get
// get name
result , err = client.Get("name")
if err != nil {
fmt.Println("get 失败",err)
return
}
fmt.Println("get 结果: " , result)
}
如上代码,我们连接到了本地Redis
,并且设置了一个key
,名称为name
,值为pdudo
,ttl
为60
秒,而后再查询了一下name
的值,我们加了一些日志,我们来看下具体执行过程:
Redis RESP报文回顾
我们只要想和Redis
服务器通信或者拆解命令,这个一定是绕不开的问题,因为很重要,本篇文章核心都是基于此,所以我们需要先回顾下。
RESP
是专门为Redis
而发明的通信协议,在Redis 1.2
引入,并以Redis 2.0
成为Redis
的默认通信协议。 该协议基于传输层TCP
,默认端口为6379
,支持5种类型: 数组、复杂字符串、数字、简单字符串以及错误数据。
我们列一个表格来说明数据类型以及其含义,最后再描述一下涉及面。
数据类型 | 含义 | 涉及面 |
---|---|---|
* | 数组 | 发送/接收 |
$ | 复杂字符串 | 发送/接收 |
: | 数字 | 接收 |
+ | 简单正常输出 | 接收 |
- | 错误输出 | 接收 |
我们注意到,若我们只用发送数据的话,我们直接使用*
和$
2种数据就可以了。
关于这个点,我们手写一版协议就清楚了。
我们想向Redis
服务器输入
set age 21
若我们将21
使用数字类型代替的话,会出错,如:
若想正确输入的话,可以使用
如上就正确使用了set
。
而若是需要接收数据,则需要使用全部数据类型了。
如何发送数据
通过如上协议回顾,我们知晓,我们发送数据的时候,仅需要*
和 $
数据类型即可。 其中*
是数字类型,代表有多少组数据,$
是复杂字符串类型,表示字符串的长度。
若set name pdudo
使用RESP
可以定义如下:
*3
表示数组有3个数据,set
name
pdudo
但是这些数据,不能直接发送,需要由$
来指定一下长度,比如set
,长度为3,则标识为$3
,而pdudo
则标识为$5
。
根据如上定义,我们将代码编写如下:
如上代码表示接收的数据中,先计算数组command
的长度,且写入到[]byte
中,接着再循环遍历command
,最后计算每个v
的长度,以复杂字符串的类型$
写入到[]byte
中。
如何接收数据
接收数据,我一直认为是整个协议实现中最难得,但是我最近找到了新方法,我们展开讲一下,我们先抛开数组类型不谈,我们面临的数据类型有这么几类:
数据类型 | 含义 | 涉及面 |
---|---|---|
$ | 复杂字符串 | 发送/接收 |
: | 数字 | 接收 |
+ | 简单正常输出 | 接收 |
- | 错误输出 | 接收 |
其中,+
、-
、:
数据类型,其值均在同一行上,例如:
+OK
-NOAUTH Authentication required.
:123
而复杂字符串$
比较特殊,它数据长度定义是一行,而实际数据在下一行,例如:
$5
pdudo
基于如上特性,若没有数组类型的话,而一旦使用了数组,该协议报文中,就不可能出现+``-
等数据类型,仅有$
以及:
,基于此我们很容易写出代码来:
如上代码,是获取$
和:
数据类型的值,其中:
数据就在该符号后面,而$
则需要先获取复杂字符串的长度,而后再取相应的数据+最后的空行。
那我们现在还有3种数据类型没有编写,即: +
、-
、*
,对于+
、-
我们将当行取出来即可,对于*
我们需要获取其值n
,然后循环n
次才能获取到数据。
我们再看获取数组函数的编写
即,重复的调用我们编写的第一个函数,将数据汇总到一起后,我们最后再返回回去。
总结
所谓的编写一个Redis
客户端,其实还是玩转RESP
协议,正所谓万变不离其宗,怎么样,好玩么? 快来使用go
写一个专属你的Redis
客户端吧。