当前位置: 首页 > 科技观察

Redis6通信协议升级至RESP3,一口气看完13种新数据类型

时间:2023-03-16 19:04:57 科技观察

Redis6通信协议升级为RESP3,一口气读取了13种新的数据类型。RedisSerializationProtocol,基于这个实现简单、解析性能优异的通信协议,Redis服务器端和客户端可以通过底层命令进行数据通信。随着Redis版本的不断更新和功能的迭代,RESPV2协议逐渐不能满足新的需求。为了适应Redis6.0出现的一些新功能,在其基础上开发了新的下一代RESP3协议。.我们先回顾一下继承自RESPV2的五种数据返回类型。了解了这些类型的局限性之后,我们再来看看RESP3中新的数据返回类型在哪些方面做了改进。1、继承RESPv2的类型首先,协议中数据的请求格式与RESPv2完全一致。请求格式如下:*<参数个数>CRLF$<参数1的字节长度>CRLF<参数1的数据>CRLF$<参数2的字节长度>CRLF<参数2的数据>CRLF...$<参数N的字节长度>CRLF<参数N的数据>CRLF每行末尾的CRLF转换成程序语言是\r\n,即回车换行。以命令集名称hydra为例,转换过程及转换结果如下:了解发送协议后,针对不同类型的回复进行如下测试。如何模拟这个过程?在上一篇文章中,我们通过java代码中的Socket连接redis服务,发送数据,接收返回结果,模拟了这个协议。不过我们今天采用更简单的方法,直接在命令行下使用telnet连接即可。以我本机启动的redis为例,直接输入telnet127.0.0.16379连接redis服务。之后,将包含换行符的命令一次性复制到命令行中,然后回车即可收到Redis服务的回复:来看看继承自RESPV2的五种返回格式。为了统一命名规范,引入RESP3官方文档中的新名称来替代RESPV2中的旧名称。例如,不再使用批回复和多批回复的旧名称。Simplestring表示简单字符串回复,只有一行回复,回复内容以+开头,不允许换行,以\r\n结尾。有很多命令在成功执行后只会回复OK。使用这种格式,可以有效地减少传输和分析的开销。仍以上面的set命令为例,发送请求:*3$3set$4name$5hydra收到回复:+OK\r\nSimpleerror错误回复,可以看成是简单字符串回复的变体形式,它们之间的格式也很相似,唯一的区别是第一个字符以-开头,错误回复的内容通常是错误类型和描述错误的字符串。错误回复出现在一些异常场景中,比如发送了一条错误指令,操作数个数错误,就会进行错误回复。发送错误命令:*1$8Dr.Hydra收到回复,提示错误信息:https://mp.weixin.qq.com/s/rLk2EW0TKAIkQvx1WefafA#:~:text=%2DERR%C2%A0unknown%C2%A0command%C2%A0%60Dr.Hydra%60%2C%C2%A0with%C2%A0args%C2%A0beginning%C2%A0with%3A%5Cr%5CnNumber整数回复,也被广泛使用,以:开头,结尾与\r\n,用于返回一个整数。比如执行incr返回自增值,执行llen返回数组长度,或者用exists命令返回的0或者1作为判断key是否存在的依据,所有的这些使用整数回复。发送命令查看数组长度:*2$4llen$7myarray收到回复::4\r\nBlobstring多行字符串的回复也称为批量回复,在RESPV2。它以$开头,后面是要发送的字节长度,然后是\r\n,然后发送实际数据,最后以\r\n结束。如果要回复的数据不存在,则回复长度为-1。发送get命令请求:*2$3get$4name收到回复:$5\r\nhydra\r\nArray在RESPV2中可以理解为多批次回复。当服务器要返回多个值时,例如返回一些Array,当使用元素集合时。它以*开头,后面是返回元素的个数,后面是上面的多个BlobString。*4$6lrange$7myarray$10$2-1收到回复,其中包含集合中的4个元素:*4$11$12$12$232RESPV2协议继承的5种返回数据类型的简单回顾到此结束,我们开始吧RESP3协议新特性的探索之旅。2、RESP3中的新类型目前在Redis6.0.X版本,仍然是默认使用的RESPV2协议,在兼容RESPV2的基础上,还支持启用RESP3。预计在未来的某个版本中,Redis可能会全面切换到RESP3,但这样做会对目前的Redis客户端连接工具产生相当大的影响,底层通信需要根据协议内容进行修改。使用telnet连接redis服务后,首先输入如下命令切换到RESP3版本的协议。至于具体的返回数据以及hello命令的数据表示的含义,这里先略过,以后再看。hello3下面我们来详细了解一下RESP3,除了保留上面5种老的回复类型外,还新增了13种通信返回数据类型,部分数据类型会通过实例进行演示。为了看起来更简洁,以下演示示例使用原始命令发送命令,不再转换为协议格式,并在数据返回结果中省略每行末尾的\r\n!(1)Null新协议使用下划线字符后跟CR和LF字符来表示空值,即用_\r\n代替原来的单个空值返回$-1。例如使用get命令查找不存在的key时:gethydraRESPV2返回:$-1RESP3返回:_(2)双浮点数返回时以逗号开头,格式为,\r\n,使用zsetscorekeymember获取Score命令进行测试:zscorefruitappleRESPV2使用BulkString格式返回:$185.6600000000000001RESP3返回格式:,5.6600000000000001(3)布尔数据返回值,其中true表示为#t\r\n,而false表示为#f\r\n。但是Hydra暂时没有找到返回boolean类型结果的例子,即使lua脚本直接返回boolean类型也无法实现。eval"returntrue"0eval"returnfalse"0上面lua脚本返回true时结果为:1\r\n,返回false时结果为_\r\n,这是因为lua中boolean类型为true将在redis中转为整型返回1,false类型会转为NilBulk。至于哪些指令可以返回布尔数据,有知道的朋友可以给我留言补充。(4)Blob错误类似于字符串类型,其格式为!\r\n\r\n,但与简单错误类型相同,使用!\r\n\r\n。在开头表示返回错误信息描述。例如会返回错误SYNTAXinvalidsyntax,格式如下:!21SYNTAXinvalidsyntax(5)VerbatimstringVerbatimstring也表示一种字符串格式,和BlobString很相似,只是用=来代替开头的$,而后面的三个字节提供了字符串格式的信息,比如txt表示纯文本,mkd表示markdown格式,第四个字节固定为:.这种格式适合在没有任何转义或过滤的情况下显示给用户。使用延迟事件统计和分析命令进行测试,发送:latencydoctorRESP2返回数据或BlobString格式:$196Dave,在这个Redis实例的生命周期内没有观察到延迟尖峰,没有丝毫。老实说,我认为你应该冷静地坐下来,吃一颗减压药,好好想想。RESPV3返回的数据采用了一种新格式:=200txt:Dave,在这个Redis实例的生命周期内没有观察到延迟峰值,丝毫没有。老实说,我认为你应该冷静地坐下来,吃一颗减压药,好好想想。(6)BignumberBignumber类型用于返回非常大的整数,可以表示有符号64位数字范围内的整数,包括正数或负数,但注意不要包含小数。数据格式为(\r\n,以左括号开头。示例如下:(3492890328409238509324850943850943825024385注意,当大数不可用时,客户端会返回一串数据。(7)聚合数据类型为和我们前面介绍的一样,聚合数据类型可以理解为聚合数据类型,不同于给定数据类型的单个值,这也是RESP3的一个核心思想,需要能够从协议和类型的角度来描述具有不同语义的聚合数据类型。聚合数据类型的格式如下,通常由聚合类型、元素个数、具体单个元素组成:...nuelementsothertypes...比如一个包含三个数的数组[1,2,3]可以表示为:*3:1:2:3当然聚合数据类型中的元素可以是其他聚合数据类型,例如,数组中还可以嵌套其他数组(以下内容为了便于理解,有缩进):*2*3:1$5hello:2#f上面的聚合数据类型表示的数据为[[1,“你好”,2],假]。(8)MapMap数据类型类似于数组,但以%开头,后面是Map中键值对的个数,而不是单个数据项的个数。它的数据内容是一个有序的键值对数组,然后键值对的key和value分行显示,所以后面的数据行一定是偶数行。我们先看看官方文档给出的例子。以下面的Json字符串为例:{"first":1,"second":2}转换为Map类型格式如下:%2+first:1+second:2但是Hydra通过实验发现有趣的事情。当我们发送hgetall命令请求hash类型的数据时:hgetalluserRESPV2返回的数据仍然使用旧的Array格式,符合我们的预期:*4$4name$5Hydra$3age$218但是下面RESP3返回的数据超出了我们的期望。我们可以看到前面的%2虽然表示使用的是Map格式,但是并没有按照官方文档给出的规范,除了开头的%2,其余的和Array()完全一样。%2$4name$5Hydra$3age$218关于实际传输数据与文档中给出的例子有出入,Hydra有自己的一点猜测,放在最后的总结中。(9)SetSet与Array类型很相似,只是它的第一个字节用~代替了*,是一种无序的数据集合。我们先看一下官方文档给出的例子。下面是一个包含5个元素的集合类型数据,具体数据类型可以不同:~5+orange+apple#t:100:999接下来使用SMEMBERS命令获取集合中的所有元素进行测试:SMEMBERS当mysetRESPV2返回时仍然使用Array格式:*3$1a$1c$1bRESP3的数据返回类似于Map,以~开头,但并不完全遵循协议中的格式:~3$1a$1c$1b(10)Attribute属性类型和Map类型很相似,但是第一个字节使用|而不是%,Attribute描述的数据内容更像是Map中的字典映射。客户端不应将本字典的内容作为数据回复的一部分,而应作为辅助数据来增强回复内容。文档中提到,以后某个版本的Redis可能会出现这样的功能。每次执行该命令时,都会打印访问的key的请求频率。这个值可能用一个浮点数来表示,所以当执行MGETab你可能会收到一个回复??:|1+key-popularity%2$1a,0.1923$1b,0.0012*2:2039123:9543892在上面的数据回复中,实际回复的数据应该是[2039123,9543892],但是前面附上了他们要求的属性。读取完Attribute类型的数据后,还要继续读取实际的数据。(11)PushPush数据类型是服务器向客户端发送的一种异步数据。其格式与Array类型类似,但以>开头,下一个数组的第一个数据为字符串类型,表示服务器向客户端发送的是什么类型的推送数据。数组中的其他数据也包含自己的类型,需要根据协议中的类型规范进行解析。简单看一下文档中给出的示例。执行getkey命令后??,可能会得到两个有效回复:>4+pubsub+message+somechannel+thisisthemessage$9Get-Replyrequiresintheabovereply注意收到的两个回复中第一个是push的类型数据,第二个是回复的实际数据内容。笔记!这里在文档中有一个提示:虽然下面的演示使用的是Simple字符串格式,但实际的数据传输使用的是Blob字符串格式。于是瞎猜一猜,上面的Map和Set也是同样的情况?这里简单介绍一下Pushreply类型,它是redis6客户端缓存中一个非常重要的使用场景,可以让数据存储在本地应用,访问时不再需要访问redis服务器,而是其他客户端修改数据时,需要通知当前客户端使本地应用的客户端缓存失效。这时候就会用到Push类型的消息。我们首先在客户端A执行如下命令:clienttrackingongetkey1在客户端B执行:setkey1newValue此时,客户端A会收到一条Push类型的消息,通知客户端缓存失效。下面收到的消息包含两个部分。第一部分表示接收到的消息类型为invalidate,第二部分为需要失效的缓存key1:>2$10invalidate*1$4key1(12)前面介绍过的Stream类型中,返回的数据字符串一般有一个指定的长度,例如如下:$1234....1234bytesofdatahere...但有时需要当一段未知长度的字符串数据时从客户端传输到服务器(或反向传输),显然不能使用这种格式,因此需要一种新的格式来传输不确定长度的数据。文档中提到,服务器端曾经有一种私有的扩展数据格式,规范如下:$EOF:<40bytesmarker>...anynumberbytesofdataherenotcontainingthemarker...<40bytesmarker>以$EOF:开头,后面是40字节的标记标识符,后面跟着\r\n才是真正的数据,结尾也是40字节的标识符.标识符以伪随机方式生成,基本不与正常数据发生冲突。但是,这种格式有一定的局限性。主要问题在于生成标识符和解析标识符。由于某些原因,上述格式在实际使用中非常脆弱。因此,规范中最终提出了分块编码格式。举个简单的例子,当需要发送事先不知道长度的字符串Helloworld时:$?;4Hell;5owor;2ld;0这种格式以$?开头表示是未知长度的块编码格式,后面传输的数据量没有限制。最后,使用零长度;0作为传输的结尾。文档中提到,目前还没有命令可以回复这种格式的数据,但是这个协议会在后面的功能模块中实现。(13)HELLORESP3引入之初,我们在telnet中使用hello3命令将协议切换到V3版本。这个特殊的命令完成了两件事:它允许服务器向后兼容RESPV2,并且它也使得将来更容易切换到RESP3。hello命令可以返回有关服务器和客户端使用的协议的信息。hello命令的格式如下。可以看到除了协议版本号,还可以指定用户名和密码:HELLO[AUTH]hello命令的返回结果就是上面介绍的Map类型.当客户端和服务器建立连接时发送。%7$6server$5redis$7version$66.0.16$5proto:3$2id:18$4mode$10standalone$4role$6master$7modules*0转换为我们可读的Map格式后,可以看到它返回的Redis服务器的一些信息:{"server":"redis","version":"6.0.16","proto":3,"id":18,"mode":"standalone","role":"master","modules"":[]}3.综上所述,在RESPV2中,通信协议比较简单,大部分通信内容仍然是编码后以数组的形式发送。命令的类型用于判断返回数据的具体类型,这无疑增加了客户端解析数据的难度和复杂度。在RESP3中,通过引入多种新的数据类型,通过首字节的字符对类型进行区分和编码,使得客户端可以直接判断返回数据的类型,在相当程度上降低了分析的复杂度.度,提高效率。对于本文新增的返回数据类型,给出了一些通信数据的例子,但仍有部分类型没有找到合适的命令进行测试。另外,对于Map和Set,实际传输的数据和官方文档还是有些出入的。个人认为情况和Push是一样的。可能官方文档更多的是为了演示,使用Simplestring而不是Blobstring。最后说一下协议的命名。新一代协议的名字叫RESP3,不继承二代的命名约定叫RESPV3,并不是RESPversion3的乱七八糟,暂时不用担心。是RESPV2,以后是RESP3的非对称命名。