初识 RESP
当我们想实现一个 redis server,首先要了解 redis 的通信协议。
redis 作者认为数据库系统的瓶颈一般不在于网络流量上,所以使用了一个简单的纯文本的通信协议,叫做 RESP(Redis Serialization Protocol)。
RESP 是 Redis 序列化协议的简写。它是一种直观的文本协议,优势在于实现异常简单,解析性能极好。
RESP 定义了五种类型的数据结构,每个最小单元之间用\r\n隔开。
简单字符串 以 + 字符开头,后跟字符串本体。
错误消息 以 - 字符开头,后跟错误消息本体。
整数值 以 : 字符开头,后跟整数的字符串形式。
复杂字符串(最多 512M 的) 以 $ 字符开头,后跟字符串长度;后面再跟字符串本体。
数组 以 * 字符开头,后跟数组的长度;后面再跟数组元素。
如简单字符串:
"+OK\r\n"
错误消息:
"-Error message\r\n"
整数:
":1000\r\n"
":0\r\n"
复杂字符串:
"$6\r\nfoobar\r\n"
"$0\r\n\r\n"
"$-1\r\n" // 长度为-1 的是 null
数组:
"*0\r\n"
"*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"
"*3\r\n:1\r\n:2\r\n:3\r\n"
"*-1\r\n" // 长度为-1 的是 null。比如 blpop 超时时应该返回它,客户端就应该展示为 null。
数组可以是混合类型,如数组的数组,把他们每行分隔开展示如下:
*2\r\n
*3\r\n
:1\r\n
:2\r\n
:3\r\n
*2\r\n
+Foo\r\n
-Bar\r\n
状态机
RESP 非常简单,协议的定义十分有限。所以要实现一个 redis server,我们可以按行读取客户端的请求数据,同时按行解析它们。
我们可以按照客户端的行数据到达的不同状态画出一个状态机。
从 S 状态(开始状态)开始,如果接受到的字符串时+或-或:,则是三种最简单的一行数据结构:简单字符串、错误消息、整数。那么进入 A 状态,接下来接收到任意值,就进入 END 状态,代表一条请求接收完毕。
如果接收到的是$符号,那么进入 B 状态。接下来接收到-1则为 null,接收结束;接收到的是 0 或正整数,则进入 C 状态,此时还会接收到任意一行数据,就进入 END 状态代表结束。
如果接收到*号,代表数组,这时情况稍微复杂一点。先进入 D 状态,此时接受到的是0和-1的话都不会有一个新行,请求结束;此时接收到一个自然数值 n 的话,则代表数组长度为 n,则进入 En 状态,重新开始一个 S 状态的状态机,直到该状态机到 END 状态,n 可以自减 1 ;当 n 为 0 时,直接成为 END 状态。
Python 实现
RESP 的状态机实现部分略长,限于文章篇幅已经放在 GitHub:
redis server 的实现直接使用 Python 自带的 TCPServer:
from socketserver import TCPServer, StreamRequestHandler
from resp import handle_line
class RedisHandler(StreamRequestHandler):
def handle(self):
state = None
while True:
data = self.rfile.readline().strip()
print(data)
state = handle_line(data, state)
if state.is_stoped:
break
self.wfile.write(b'+OK\r\n')
print('end')
if __name__ == '__main__':
host, port = 'localhost', 6379
with TCPServer((host, port), RedisHandler) as server:
server.serve_forever()
现在不管什么请求到达,server 都会响应+OK\r\n给客户端。我们的 server 端还会打印出请求的每行字符。
运行效果
直接敲入
当敲入redis-cli时,可以看到客户端发送了一个长度为 1 的数组过来,里面只有一个字符串为COMMAND:
我们在redis-cli敲入命令get a,客户端将get和a作为长度为 2 的数组发送到 server。当然,我们的 server 现在还只能响应OK给客户端。
这个系列可能还会继续更新下去……
欢迎 star