用Python实现redis方轮子-第0篇-了解通信协议

1,690 阅读3分钟
原文链接: zhuanlan.zhihu.com

初识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:

github.com/sljeff/pyth…

redis server的实现直接使用Python自带的TCPServer:

# redis_server.py
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端还会打印出请求的每行字符。

运行效果

直接敲入python redis_server.py运行代码;安装redis-cli作为客户端。

当敲入redis-cli时,可以看到客户端发送了一个长度为1的数组过来,里面只有一个字符串为COMMAND

我们在redis-cli敲入命令get a,客户端将geta作为长度为2的数组发送到server。当然,我们的server现在还只能响应OK给客户端。


这个系列可能还会继续更新下去……

项目地址:github.com/sljeff/pyth…

欢迎star