用 Python 实现 redis 方轮子-第 0 篇-了解通信协议 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
kindjeff
V2EX    分享创造

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

  •  2
     
  •   kindjeff
    sljeff 2018-09-10 00:42:18 +08:00 3066 次点击
    这是一个创建于 2593 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    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

    3 条回复    2018-09-11 06:02:58 +08:00
    hanxiV2EX
        1
    hanxiV2EX  
       2018-09-10 06:36:10 +08:00 via Android
    牛逼,可以先搞个 python-redis-client
    kindjeff
        2
    kindjeff  
    OP
       2018-09-10 09:24:11 +08:00
    @hanxiV2EX python 的 redis client 已经非常多了,想做个复杂一点的,顺便能多写几篇文章。
    yoyohaha
        3
    yoyohaha  
       2018-09-11 06:02:58 +08:00
    非常好,继续写,不要停。我准备照着你的思路用 C 写一个,因为我刚学完 c,正找东西练手。非常感谢!
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5339 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 06:59 PVG 14:59 LAX 23:59 JFK 02:59
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86