很多人都知道,Redis 对代码上简单有一种极致的追求,比如坚持命令处理单线程。我在很多用户层面的配置和命令中,也看到了这种简单而优雅。比如 SAVE
的参数,save 900 1
表示 900 秒内有一次写,就执行备份,这样可以配置多条配置,就可以达到即可以根据时间配置备份频率,又可以根据写入次数配置频率。
又比如 ACL 的设计,虽然不能说设计的特别简单,但是功能非常丰富,即可以根据 key 来设置权限,又可以根据命令来设置权限,并且既可以设置白名单,又可以设置黑名单。单单来说白名单与黑名单,我见过很多系统,只要涉及这两个概念,就会让用户一头雾水了。如果我有一个A是在黑名单中,然后A里面的X在白名单中,那么X到底会不会生效呢?然后你就必须去看它的实现才知道是怎么样一个逻辑。(比如 poetry 对文件的 include 和 exclude 配置,就是一个反例)但是 Redis 的 ACL 设计,用了类似 DSL 的方式,一点歧义都没有。
但是 KeyDB 中说,Antirez 对代码库的简单要求的太过分了,以至于将很多复杂留给了用户。在 Redis 命令设计方面,我有类似的感受。甚至对于有一些命令的设计上,我认为并没有设计的取舍,纯粹是不知道为啥就设计成了这个样子。
这篇文档就来说说我对 Redis 中那些理解不能的设计。首先声明,这都是我个人无法理解,也可能它有它的道理,只是我不知道,如果你知道这个设计的道理,麻烦在评论中告诉我。
第一个想到的就是,为什么命令会允许有空格?
在 RESP 协议中,是用首位标志指示后面的 String 的,假如我用 RESP 将 set foo bar
打包发出去,那么我实际上发出去的就是(以下输出用这个脚本抓到的):
1 2 3 4 5 6 7 |
=> *3 => $3 => set => $3 => foo => $3 => bar |
这里 =>
表示发出去的内容,<=
表示收到的内容,这是抓包脚本添加的。*3
表示后面会发送 3 个 token,$3
表示接下来的字符串长度是 3.
假如命令中可以允许空的话,那么对于 client list
这个命令,实际发出去的就是:
1 2 3 4 5 6 |
=> *2 => $6 => client => $4 => list <= $324 |
可以看到,redis-cli 是将 client
和 list
分开作为两个 token 发送出去的,根本无法知道第一个“变量”一个命令,还是第一个变量+第二个变量是一个命令啊!对于一些要解析发送内容的场景来说很是头疼。
这个问题仔细想想,如果你用 Redis Module 来扩展 Redis 的功能的话,也会遇到的。比如你要实现一个 HELLOWORD RAND
命令怎么办?你没办法告诉 Redis说 “嘿! 兄弟我实现的这个命令,你收到的时候要拿前两个参数作为命令名字,后面的参数才是命令参数,不要搞混了哦~”,因为 Redis Module 总不能连 Redis 去解析命令那部分代码也干涉吧(我不确定做不做得到,没有去看 Redis 这一部分的源代码,读者知道的话请赐教),我写个 Redis Module 就是想做个简单的功能,你不要对我要求太多!
所以怎么办呢?Redis 文档里面说,哥们你得这么写 HELLOWORLD.RAND
. 你看这不就没空格了吗?我解析的时候总是拿第一个参数来解析就可以了。
我靠,凭什么只能你官方的命令防火,不能我 Module 的命令点个灯啊!
第二个问题,人格分裂。
在 Redis 中,有一些命令是需要接受 pair 的。比如 HSET
,你可以同时设置多个键值对(since 4.0):HSET foo bar hello world
. 所以 HSET 的语法是 HSET key field value [field value ...]
。So far so good, no problem at all.
But,XREADGROUP
这个命令的语法是什么鬼???
1 |
XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...] |
为什么会有 key key ... id id ...
这种设计??
假如我想写一个自动补全的客户端,在 key
这个 token 的时候,我没有任何办法知道,应该根据 key
来补全,还是要根据 id
来补全啊! T T
第三个问题,一些命令的向后兼容。
这里说的是 AUTH
,在 Redis5 之前,AUTH
的用法是 AUTH PASSWORD
。但是自动 Redis6 增加了 ACL 开始,AUTH 支持两种用法:AUTH USERNAME PASSWORD
或者 AUTH PASSWORD
.
我的痛苦非常简单,也非常痛。
我有一个超屌的功能,就是能在命令行中将一部分输入替换成 * (屌吧,至少我没见过任何一个命令行能够这么做,sudo 让你输密码的时候,你根本不知道输入了几位,如果不小心输错,只能按下 N 次 Delete)
但是现在,第一个参数可能是 USERNAME
,可能是 PASSWORD
,我到底是隐藏呢?还是不隐藏呢?(好吧其实这个痛点对于大部分用户来说不痛,向后兼容了嘛,还挺好的!只有我哭晕在厕所)
PS: IRedis 即将支持 Redis 6~ 欢迎尝试
“如果不小心输错,只能按下 N 次 Delete” 可以用
ctrl-u
来拯救一下这个不错
学习了。