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

2018-09-10 00:42:18 +08:00
 kindjeff

初识 RESP

当我们想实现一个 redis server,首先要了解 redis 的通信协议。

redis 作者认为数据库系统的瓶颈一般不在于网络流量上,所以使用了一个简单的纯文本的通信协议,叫做 RESP(Redis Serialization Protocol)。

RESP 是 Redis 序列化协议的简写。它是一种直观的文本协议,优势在于实现异常简单,解析性能极好。

RESP 定义了五种类型的数据结构,每个最小单元之间用\r\n隔开。

如简单字符串:

"+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:

https://github.com/sljeff/python-redis-server/blob/master/resp.py

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

运行效果

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

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

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


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

项目地址: https://github.com/sljeff/python-redis-server

欢迎 star

2752 次点击
所在节点    分享创造
3 条回复
hanxiV2EX
2018-09-10 06:36:10 +08:00
牛逼,可以先搞个 python-redis-client
kindjeff
2018-09-10 09:24:11 +08:00
@hanxiV2EX python 的 redis client 已经非常多了,想做个复杂一点的,顺便能多写几篇文章。
yoyohaha
2018-09-11 06:02:58 +08:00
非常好,继续写,不要停。我准备照着你的思路用 C 写一个,因为我刚学完 c,正找东西练手。非常感谢!

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/487722

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX