惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
美团技术团队
H
Hacker News: Front Page
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
Simon Willison's Weblog
Simon Willison's Weblog
T
The Exploit Database - CXSecurity.com
Cisco Talos Blog
Cisco Talos Blog
A
Arctic Wolf
C
CXSECURITY Database RSS Feed - CXSecurity.com
Application and Cybersecurity Blog
Application and Cybersecurity Blog
A
About on SuperTechFans
F
Fortinet All Blogs
量子位
GbyAI
GbyAI
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The Hacker News
The Hacker News
AWS News Blog
AWS News Blog
Forbes - Security
Forbes - Security
Help Net Security
Help Net Security
I
InfoQ
有赞技术团队
有赞技术团队
W
WeLiveSecurity
Google DeepMind News
Google DeepMind News
Engineering at Meta
Engineering at Meta
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
S
Secure Thoughts
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Webroot Blog
Webroot Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园_首页
C
Check Point Blog
T
Troy Hunt's Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
Latest news
Latest news
P
Proofpoint News Feed
Jina AI
Jina AI
Last Week in AI
Last Week in AI
Martin Fowler
Martin Fowler
雷峰网
雷峰网
博客园 - Franky
L
LangChain Blog
罗磊的独立博客
Blog — PlanetScale
Blog — PlanetScale
Google DeepMind News
Google DeepMind News
D
Docker
G
GRAHAM CLULEY
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC

卡瓦邦噶!

服务器高性能网络调优 | 卡瓦邦噶! 为何写作 | 卡瓦邦噶! 读《金阁寺》 | 卡瓦邦噶! 雨季又来 | 卡瓦邦噶! MTU Probe 引起的初始延迟 | 卡瓦邦噶! 3.5 秒的固定延迟问题 | 卡瓦邦噶! 学习网络的一点经验 | 卡瓦邦噶! ARP 问题诊断 | 卡瓦邦噶! 网络断断续续…… | 卡瓦邦噶! Piccolo P2P 镜像分发 | 卡瓦邦噶! 一起看电影 | 卡瓦邦噶! 《征服C指针》 | 卡瓦邦噶! 我的姥姥 | 卡瓦邦噶! Python的哲学 Python 3.5的新特性 学校不教的计算机课 垃圾回收(GC)的三种基本方式 在编程中体验纯粹的快乐 从《美丽新世界》谈自由 在快钱实习 迷人的嗓音和迷人的故事——《Sleepyhead》 Python 的十个自然语言处理工具 记一个愚蠢的bug 一年炉石传说的游戏体验 《以撒的结合:重生》网页版图鉴 分清 C++的指针、引用和数组 笑话三则 自由比皇帝更伟大——《悲惨世界》笔记 Git 10 周年访谈:Linus 讲述背后故事 用 0x3f3f3f3f 设定最大int值的优点 Joel给计算机系学生的建议 一个词法分析器的简单实现 怎样才算健康的生活方式 MacVim 配置攻略 学习培训课程的视频效果好吗? 语言的控制 可悲的大多数 CSS样式思维导图 2014年终总结 奥巴马成为首位写程序的美国总统 青岛老城区的下水道好在哪里? 做优秀 UI 的七个建议(第二部分) 选择爱情的骑士 Git简明教程 Java集合总览 读 《1984》 java问答:终极父类(六)——等待/唤醒和接口 Java 问答:终极父类(五)——toString() Hyperlapse快速视频背后的技术细节 平庸之恶 Java程序员须知的七个日志管理工具 苏州 逛书摊随想 关于考试作弊 你的工作不仅仅是编程 推荐在线学习Java的英文资源 关注女性命运——《千禧年三部曲》 用好你的幻灯片——《演说之禅》 使用ReentrantLock和Lambda表达式让同步更纯净 新手学编程,从哪里开始? 程序员都是工程师吗? 不要学习代码,要学会思考 Java 问答:终极父类(四)——hashCode() Java 问答:终极父类(三)——finalize()和 getClass() 程序员职业之路的选择 我是一名摄影家 写给何小树的城市指南 为什么一些语言会比别的快? Java的常见误区与细节 跟朋友在一起玩游戏 死神永生——读《三体》 五种类型的程序员 创业圣经——读《黑客与画家》 纪念加西亚·马尔克斯 Junit中处理异常的另一种方式:catch-exception Java8采用Martin Fowler的方法创建内部DSL Linux HotSopt虚拟机GC线程的CPU占用率 J2EE概念介绍 如何成为一名黑客 Java 问答:终极父类(二)——equals()方法 为什么我喜欢Java Java 问答:终极父类(一)——clone()方法 七个改变世界的Java项目 java中默认类型转换的小问题 传统与创新 欢迎来到互联网 莫言和马尔克斯——读《生死疲劳》 位运算的妙用 读《人为什么活着》 写博客教会我的事情 中国特色操作系统 2013年总结 《永不妥协》影评 恨不相逢未嫁时——《廊桥遗梦》影评 如何优雅地使用PPT 给明年依然年轻的我们 天才与柱子 黑客守则和黑客精神 Looking for Freedom——《被解救的姜戈》 简洁之道
Redis RESP3 的一些想法
laixintao · 2020-03-15 · via 卡瓦邦噶!

在 Python 中使用 Redis,基本上都会选择 redis-py 这个库。本质上,它是封装了 Redis 命令,将它们变成 Python 的函数。比如 SET 这个命令,它的使用方式,在 redis 文档中是这么定义的:

Python

1

SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]

然后 redis-py 对应的 Python 函数是这样的:

Python

1

def set(self, name, value, ex=None, px=None, nx=False, xx=False, keepttl=False):

redis-py 中有几百个函数,封装了对应的 redis 命令。

这样的好处是你可以使用原生的函数来调用 Redis,返回结果也是 Python 的原生类型。在写代码的时候编辑器可以自动为你补全 Python 函数,调用的什么 Redis 命令也比较清楚,对 Python 代码静态分析就知道对 Redis 做了什么操作。

缺点也显而易见:

  • 如果 Redis 对已有命令支持了新的参数,需要 redis-py 支持才能使用。比如 让 HSET 支持多对 key value给 SET 添加 KEEPTTL 参数,这两个PR。合并之后要等 redis-py 发布新的版本,到应用能真的用的上 redis 的 Feature,将会是很漫长的一个过程;
  • 第二个问题是从 Redis 的命令,到 Python 的函数,相当于加了一层中间的转换。首先我要去看 Redis 的文档,知道了 SET 的用法,然后我还要去看 redis-py 的文档,去学习一下在 Python 中是如何抽象的,哪些是值是字面值,哪些值翻译成了 Python 的 bool;

在群里看到过网友这么吐槽,对这个问题总结的比较精辟:

所以,这一层抽象真的有必要吗?Redis 的命令如此简单,我们能否直接给 Redis 发送想要执行的命令呢?

我一直在想, 理想的 Redis Python 客户端应该是这样使用的:

Python

1

redis.call("SET", "foo", "bar", "XX", "KEEPTTL")

而接口的定义,显然可以只需要一个,能够通过这个接口接收任何命令:

Python

1

2

def call(command_name, *args):

    # package request using resp...

以目前的 RESP,实现起来还是有点难度的。我在 IRedis 中做了类似的事情,先分享一下 IRedis 里面是怎么做的,阻止我们实现一个这样的客户端的问题是什么。

IRedis 是一个 redis-cli 的替代品,一个命令行 REPL 工具。在 IRedis 中,如果使用 redis-py 这些函数的话,那么一个命令从用户输入,到真正到达 Redis server,要经过这样的转换:cli input -> 分解出来命令,和这些命令的参数 -> 调用相应的 redis-py 里面的函数 -> redis-py 打包成 RESP 协议的请求 -> 发送给 Redis Server。显然太复杂了。并且我认为上面的 问题2 对 IRedis 也是一个不小的限制:使用 IRedis 应该体验到 Redis 最新的 Feature,而不是等到 redis-py 发布之后。

redis-py 中 的 connection.py 是封装了 RESP 协议的内容的,这部分代码解耦的比较好,所以目前 IRedis 只依赖了打包和解析 Redis RESP 协议的部分代码。在 IRedis 中向 Redis 发送命令的代码是这样的(简化了重试之类的细节代码):

Python

1

2

3

4

5

6

7

8

def repl():

    while 1:

        user_input = input()

        command, args = split_command_args(user_input)

        # connection from redis-py

        connection.send_command(command, *args)

        response = connection.read_response()

        print(response)

这样如果 Redis 支持了新的命令,IRedis 用户不需要升级 redis-py,甚至不需要升级 IRedis 本身,就可以直接使用新的命令。

So far, so good. 但是有一个我没提到的缺点是,RESP2(redis-py 是屏蔽了 RESP 协议的,如果不熟悉当前 RESP 的话,可以读一下这篇文章,解释的比较好:理解 Redis 的 RESP 协议)的类型太少,比如 LRANGE SMEMBERS HGETALL 三个命令,返回的都是 Array(术语中叫做 multi bulk reply),但其实这三个命令返回的结果分别是:list, set, dict。redis-py 里面是对这些命令的结果做了转换。所以,现在的 RESP 中是无法区分出返回的类型的,这就要求 Redis 的客户端必须记住每一个命令的返回类型。因此,redis-py 在当前的 RESP 协议中,对每一个命令封装是最合理的做法。

IRedis 中我也有同样的问题,所以我用一个 csv 文件整理了所有的命令返回的类型,以针对不同的类型做不同的渲染(redis-cli中并没有区分出类型,所以用户看到的都是 Array):

在 IRedis 中将不同类型分开解析了

redis-cli 中全部当成 list 显示

另外,当前的 RESP 协议也缺乏很多其他类型,比如浮点数是没有的,要用 string 来返回,boolean 也没有,要用 int 来返回。比如在写 Lua 代码的时候,因为 Redis 协议的本身并没有 Boolean 类型,如果我们要在 Lua 脚本中判断 True/False 的话,必须通过字符串相等来替代。比如这个PR

除此以外,当前的 RESP 还有其他一些限制。但是我觉得最严重的就是混用了一些类型,导致开发一个语言的客户端必须一个一个地按照 Redis 的文档来实现它的命令。

好在,这个问题在下一代的 Redis 协议 RESP3 里面彻底解决了。RESP3 有丰富的类型

  • Null: a single null value replacing RESP v2 *-1 and $-1 null values.
  • Double: a floating point number
  • Boolean: true or false
  • Blob error: binary safe error code and message.
  • Verbatim string: a binary safe string that should be displayed to humans without any escaping or filtering. For instance the output of LATENCY DOCTOR in Redis.
  • Map: an ordered collection of key-value pairs. Keys and values can be any other RESP3 type.
  • Set: an unordered collection of N other types.
  • Attribute: Like the Map type, but the client should keep reading the reply ignoring the attribute type, and return it to the client as additional information.
  • Push: Out of band data. The format is like the Array type, but the client should just check the first string element, stating the type of the out of band data, a call a callback if there is one registered for this specific type of push information. Push types are not related to replies, since they are information that the server may push at any time in the connection, so the client should keep reading if it is reading the reply of a command.
  • Hello: Like the Map type, but is sent only when the connection between the client and the server is established, in order to welcome the client with different information like the name of the server, its version, and so forth.
  • Big number: a large number non representable by the Number type

这样,我们可以实现这样一个客户端,完全不关心执行的命令,不需要对特定的命令进行支持(一些影响客户端行为比如 CLIENT REPLY OFF 依然需要特殊支持),就可以与 Redis Server 交互,无论 Redis 对已有命令做出了修改,或者支持了新的 Feature 新的命令,客户端都不需要做升级,应用就能直接使用。

我尝试写一个基于 RESP3 的这样的客户端,如果有兴趣可以在这里 follow:https://github.com/laixintao/resp3-py

RESP3 并没有采用基于现有数据打包结构(BSON 或者 msgpack)的方式实现,简单来说原因是 1)这些只是数据结构,依然要实现通讯层的东西,没有脱离要“设计一个通讯协议”的问题。2)引入了复杂性,用户本质上要和两个库相处 3)这些数据结构不是基于流处理的,比如 json,你要读完(至少读一定程度)才能开始处理,所以需要某种 buffer,可能是潜在的性能瓶颈。而 RESP3 是在 TCP 上的一种简单的协议,在协议上发送大的字符串的时候不需要全部读完,客户端就可以开始处理。

往远处想,RESP3 的意义不仅仅是适用于 Redis 的协议,甚至不局限于数据库,基于 RESP3 实现 RPC 也是完全可以的。

在 Unix 的哲学中,文本是优于二进制的,RESP3 中说道:

RESP 在过去的几年中工作的非常好。这说明,如果用心设计,那么一种简单的、用户可读的协议不会成为通信的性能瓶颈,而且这种对用户阅读友好的协议对客户端生态的建设大有好处。

最后,RESP3 是不兼容 2 的,而 Redis6 只支持 RESP3,这意味从 Redis5 迁移到 Redis6 可能要麻烦一些。这个设计选择可以看 Antirez 的博客:Why RESP3 will be the only protocol supported by Redis 6

RESP3 的 spec: RESP3