最近在学习redis,顺便看了下其相关的客户端(c语言版本)的源码,在此做个简单的学习总结。
-
总的结构
-
如何交互
不妨假设把abc = 123存入到redis,通过交换客户端,流程可能如下图所示
那如何取abc的值呢,如下图
就是这么简单!!!
问题:
客户端和Redis底层是怎么交互这些信息的?
先介绍5种redis协议定义:
-
simple string(一种简单的字符串) 以"+"开始,以" \r\n"结束
eg:前面的Redis应答的OK, 则是"+0k\r\n
-
eerrors (错误信息) 以"-"开始,以"\r\n"结束
eg:"-WRONGTYPE Operation against a key holding the wrong kind of value"
-
integers(整数)以"-"开始,以"\r\n"结束
eg:":1000\r\n"
-
bulk strings(块字符串),这个通过一个例子来看它的格式,还是以OK为例子
"$2\r\n0K\r\n" 调整下格式方便查看 $2\r\n(第一部分,字符串长度)
0k\r\n (第二部分,具体字符串)
可以看出其分两部分,第一部分是字符串的长度以"$"开始,以"\r\n"结束
第二部分是局部字符串内容,也是以"\r\n"结束
两个特殊块字符串:
空字符串:"$0\r\n\r\n"
NULL字符串:"$-1\r\n"
-
arrays(数组),这个也通过例子来说明它的格式,比如["foo", "bar"]
"*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n" 调整格式方便查看
*2\r\n (数组的个数)
$3\r\nfoo\r\n(第一个元素)
$3\r\nbar\r\n (第二个元素)
可以看出数组也是包括两部分,第一部分是数组的个数以"*"开始,以"\r\n"结束
第二部分是各个元素的具体内容,例子中是两个bulk strings
空数组:"*0\r\n"
NULL数组:"*-1\r\n"
现在回头再一次来看set abc 123, 底层的传输应该是这样的(客户端发给Redis的命令都是arrays格式的)
-
客户端过程图
如上图整个过程比较清晰,也不复杂,struct redisContext{}(图中结构中有些字段并没有标出,后面出现都类似)中fd对应网络层的信息交互,接收发数据;
obuf存放是解析后的command,准备发送;从redis服务端接收到的数据放到struct redisReader中,并解析,并返回给用户。
Struct redisReader这个结构体相对比较复杂,对它进行单独说明
如上图所示:buf中存放从redis服务端接收的数据,待解析;struct redisReadTask rstack[9]是一个栈,它主要是用在对服务端应答数据解析用的,具体怎么使用下面进行说明;
reply是一个指向redisReply的指针,redisReply用来返回给用户的。
上图先列出2个结构体大致包含的内容,具体定义在下面解释如何解析redis应答数据例子中进行说明
下面通过有比较代表性的2个例子来进行说明:
一、bulkstring
如上图压入一个struct redisReadTask{},pos处发现是"$",知道是bulkstring类型,redisReadTask中的type 被赋值为REDIS_REPLY_STRING,
然后往后移可以知道其长度是len=3,内容是str="abc", 然后根据 len 和 str生存一个 struct redisReply(type,len,str被赋值),
struct redisReadTask{}中的obj指针指向这个struct redisReply,同时struct redisReader{}中reply也指向这个结构体
对应integers 、simple string、errors这些类型,可能就是被赋值的字段不同,如integers时,其整数值存在struct redisReply{}中的integer字段,len 和 str 就未被使用。相对复杂就是arrays类型
二、arrays
这是一个数组里有两个元素,一个"ab"字符串,一个数组;被包含的这个数组有一个元素,一个"cd"字符串,调整结构方便阅读
*2\r\n
$2\r\n
ab\r\n
*1\r\n
$2\r\n
Cd\rn
如上面一开始压入A1(struct redisReadTask{}) ,从pos的"*"解析 出是array,A1的type被赋值成REDIS_REPLY_ARRAY,
pos往后移动解析出这个数组有2个元素,A1的elements被 赋值为2,然后生成一个struct redisReply{}B1, 其type
和elements被赋值为同样的值, A1.obj = B1, 同时struct redisReader{}中reply也指向B1
如上图压入A2(struct redisReadTask{}) ,A2.parent = A1,A2.idx=0,这时pos在"$"处,解析出其是bulkstrings,
A2.type=REDIS_REPLY_STRING,, pos往后移动,解析出Len = 2 ,str = "ab", 根据len和 str生成一个 struct
redisReply{}B2,因为A2.parent=A1,所以B1. Element[0]= B2,可以看出前面的A2.idx就是在B1中的位置
因为前面是一个bulkstrings,所以其已经解析完成,所以 这里不再压人Struct redisReadTask{},复用A2,
A2.parent = A1,A2.idx = 1,A2.type=REDIS_REPLY_ARRAY, A2.elments = 1。原理同上,生成一个struct redisReply{}B3,
B1. Element[1]= B3,
压人Struct redisReadTask{} A3,A3.parent = A2,A3.idx = 0原理 同上,生成一个struct redisReply{} B4,B3.element[0]=B4
这时,因为A3.idx = A3.Ppaent.element-1,(A3.Ppaent= A2),所以是A2其最后一个元素解析完成,弹出A3;这时A2解析完成,
同时A2.idx=2,A2.idx = A2.Ppaent.element-1,,(A2.Ppaent.element= A1),所以是A1最后一个元素解析完成,弹出A2;
A1的所以元素解析完成继续弹出,这时是一个空栈,这时struct redisReader{}中reply指向的B1就是最后的结果
总结:
本文只是介绍了redis客户端中的最基本的一些流程,其中涉及命令的批量处理和订阅相关的内容在后续中介绍。
有任何错误或是有相关问题一起讨论,可以在下面留言。