response session等。
其中,
request携带请求地址、参数,用于寻找需要执行的Servlet和其参数。
response就是要返回给浏览器的结果对象。你可以通过getOutputStream()来得到这个流,往里面写数据内容。
session则是标志一个会话(在同一浏览器一定时间内访问统一服务器则认为是同一个会话)
Redis客户端使用被称为 RESP(Redis序列化协议) 的协议与Redis服务器进行通讯。虽然该协议是专门为Redis设计的,但它同样可以被用于其他客户端/服务器的软件项目。
RESP 是以下几点的折中方案:
RESP 可以序列化诸如整型、字符串和数组等不同的数据类型,还有一个特定的错误类型。请求以字符串数组的形式由客户端发送到Redis服务器,字符串数组表示需要执行的命令。Redis用特定于命令的数据类型回复。
RESP 是二进制安全的,不需要处理从一个进程传输到另一个进程的批量数据,因为它使用长度前缀来传输批量数据。
注意: 这里描述的协议仅用于客户端/服务器通信,Redis集群使用不同的二进制协议在节点之间交换信息。
客户端通过创建端口号为6379的TCP来连接Redis服务器。
虽然 RESP 在技术上是非TCP特定的,但该协议仅用于Redis上下文的(或者等效的面向流的连接,如Unix套接字)TCP连接。
Redis接收由不同参数组成的命令。一旦命令被接收,将会被执行并且发送一个回复给客户端。
这可能是最简单的模型,然而,有两个例外:
除了这两种例外,Redis协议是一种简单的请求-应答协议。
Redis RESP 协议在v1.2版本中介绍,但是到v2.0才变为与服务器通信的标准。
RESP 协议支持以下数据类型: Simple Strings(简单字符串),Errors(错误),Integers(整型),Bulk Strings(批量字符串)以及Arrays(数组)。
Redis通过以下方式将 RESP 用作请求-应答协议:
在 RESP 中,第一个字节决定了数据类型:
在 RESP 中,协议不同部分总是以 \r\n (CRLF)结尾。
RESP 使用特殊的组合表示空的Bulk Strings或者空的Arrays: $-1\r\n 表示空的Bulk Strings, *-1\r\n 表示空的Arrays,需要注意的是: $0\r\n 与 *0\r\n 分别表示有回复,但长度为0。
Simple Strings(简单字符串)的编码方式为:一个 + 号在最前面,后面跟着一个不能包含CR或者LF字符的字符串(即不允许换行符),并且最后以CRLF( \r\n )结尾。
Simple Strings(简单字符串)以最小的开销传输非二进制安全的字符串。例如:很多Redis命令执行成功后的回复只是 OK , RESP 简单字符串将以5个字节编码: +OK\r\n
如果想要传输二进制安全的字符串,请使用Bulk Strings替代。
当Redis以简单字符串回复时,客户端库应该返回 + 号后面第一个字符后面的所有字符串(不包括CRLF字节)。
Redis有特定的错误类型,与Simple Strings相似,不同的是第一个字符是减号 - 而不是加号 + ,二者真正不同的是,客户端将错误视为异常,而构成Error类型的字符串就是错误消息本身。
错误类型的基本格式为:
-Error message\r\n
只有当发生错误时才会回复错误,比如你想要在错误的数据类型上执行命令,或者命令根本不存在。客户端收到Error回复时应该抛出异常。
下面是错误回复的例子:
- 号到后面第一个空格或者新行的第一个单词表示返回的错误类型,这只是Redis使用的约定,而不是 RESP 错误格式的一部分。
比如, ERR 是一般错误,但是 WRONGTYPE 是一个更具体的错误,暗示客户端尝试执行应对错误类型的操作。这被称为 错误前缀 ,是一种允许客户端了解服务器返回的错误类型而无需检查确切错误消息的方法。
客户端实现可能会针对不同的错误返回不同类型的异常,或者通过直接将错误名称作为字符串提供给调用者来提供捕获错误的通用方法。
但是不应将此类功能视为至关重要,因为它很少有用,并且有限的客户端实现可能会简单地返回通用错误条件,例如false
这种类型只是一个以CRLF结尾的字符串,表示一个整数,前缀为 : ,比如: :0\r\n 和 :1000\r\n 。
有很多返回整型的Redis命令,比如: INCR 、 LLEN 以及 LASTSAVE 。返回的整型数据范围为有符号的64位整数。
整型回复同样可以用来表示true或者false,比如 EXISTS 或者 SISMEMBER 将会返回1表示true,0表示false。
其他命令比如 SADD 、 SREM 、 SETNX 如果被执行了将会返回1,否则返回0。
其他返回整型的命令: SETNX 、 DEL 、 EXISTS 、 INCR 、 INCRBY 、 DECR 、 DECRBY 、 DBSIZE 、 LASTSAVE 、 RENAMENX 、 MOVE 、 LLEN 、 SADD 、 SREM 、 SISMEMBER 、 SCARD 。
Bulk Strings被用来表示单个的最大长度512MB的二进制安全字符串。
Bulk Strings编码方式为:
所以,字符串 hello 被编码为: $5\r\nhello\r\n
一个空字符串被编码为: $0\r\n\r\n
RESP Bulk Strings也可用特殊格式表示不存在(NULL),在这种格式中,长度为-1,没有数据: $-1\r\n ,这被称作 NULL Bulk String ,当服务器回复NULL Bulk String时,客户端库的API不应该返回空的字符串,而是返回nil对象。
客户端使用RESP Arrays发送命令到服务器。同样,某些返回元素集合给客户端的命令使用RESP数组作为回复,比如: LRANGE 命令。RESP Arrays以下面的格式发送:
所以,空数组编码为: *0\r\n
包含"hello"和"world"两个元素的RESP数组被编码为: *2\r\n$5\r\nhello\r\n$5\r\nworld\r\n
如你所见, *<count>CRLF 前缀后面,组成数组的其他数据类型只是一个接一个的连接起来,比如一个由3个整型构成的Array编码结果为: *3\r\n:1\r\n:2\r\n:3\r\n
Array可以包含不同的数据类型,比如一个有4个整型和一个批量字符串组成的Array编码为:(为了直观,以换行的形式展现)
第一行 *5\r\n 为了表示后面还有5个回复,然后再读取后面的5个数组元素。
值为NULL的数组也存在(通常使用NULL Bulk String,由于历史原因,NULL存在两种格式)。比如 BLPOP 超时时将会返回一个长度为-1的NULL Array: *-1\r\n
在RESP中同样存在嵌套的数组,比如两个嵌套的数组编码结果为:
上面的编码结果包含两个元素的数组,第一个元素由(1,2,3)构成的子数组,第二个元素由一个Bulk String(+Hello)和一个Error(-World)组成的数组。
一个Array的单个元素可能为NULL。这在Redis回复中用来表示这些元素丢失而不是空字符串。当 SORT 命令使用 GET pattern 子命令并且key缺失时,将会发生这种情况。一个包含NULL元素的数组回复为:
上面的编码解析结果为:["hello", nil, "world"]
可以根据上面几部分的介绍来编写Redis客户端,同时进一步了解客户端和服务器之间的交互是如何工作的。
所以,一种典型的交互场景可能如下:
为了获取存储在 mylist 中的列表的长度,客户端发送命令 LLEN mylist 到服务器,然后服务器回复客户端一个整型回复:
protocol-spec
Redis系列第一篇之SPEC协议
简介
几乎所有的主流编程语言都有Redis的客户端,不考虑Redis非常流行的原因,如果站在技术的角度看原因还有两个:
客户端与服务端之间的通信协议是在 TCP 协议之上构建的。
客户端和服务器通过 TCP 连接来进行数据交互, 服务器默认的端口号为 6379 。
客户端和服务器发送的命令或数据一律以 \r\n (CRLF)结尾。
Redis制定了 RESP(REdis Serialization Protocol,Redis序列化协议)实现客户端与服务端的正常交互,这种协议简单高效,既能够被机器解析,又容易被人类识别。
发送命令
RESP 在 Redis 1.2 版本中引入, 并最终在 Redis 2.0 版本成为 Redis 服务器通信的标准方式。
在这个协议中, 所有发送至 Redis 服务器的参数都是二进制安全(binary safe)的。
RESP 的规定一条命令的格式如下:
*<参数数量>CR LF$<参数 1 的字节数量>CR LF
<参数 1 的数据>CR LF
...
$<参数 N 的字节数量>CR LF
<参数 N 的数据>CR LF
命令本身也作为协议的其中一个参数来发送。
例如我们经常执行的 SET 命令,在命令行中我们输入如下:
SET key value使用 RESP 协议规定的格式:
*3$3
SET
$3 # 这里 key 一共三个字节
key
$5 # 这里 value 一共五个字节
value
这个命令的实际协议值如下:
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n"
回复
Redis 命令会返回多种不同类型的回复。
通过检查服务器发回数据的第一个字节, 可以确定这个回复是什么类型:
状态回复(status reply)的第一个字节是 "+"
错误回复(error reply)的第一个字节是 "-"
整数回复(integer reply)的第一个字节是 ":"
批量回复(bulk reply)的第一个字节是 "$"
多条批量回复(multi bulk reply)的第一个字节是 "*"
我们知道redis-cli只能看到最终的执行结果,那是因为redis-cli本身就按照RESP进行结果解析的,所以看不到中间结果,redis-cli.c 源码对命令结果的解析结构如下:
static sds cliFormatReplyTTY(redisReply *r, char *prefix) {sds out = sdsempty()
switch (r->type) {
// 处理错误回复
case REDIS_REPLY_ERROR:
out = sdscatprintf(out,"(error) %s\n", r->str)
break
// 处理状态回复
case REDIS_REPLY_STATUS:
out = sdscat(out,r->str)
out = sdscat(out,"\n")
break
// 处理整数回复
case REDIS_REPLY_INTEGER:
out = sdscatprintf(out,"(integer) %lld\n",r->integer)
break
// 处理字符串回复
case REDIS_REPLY_STRING:
/* If you are producing output for the standard output we want
* a more interesting output with quoted characters and so forth */
out = sdscatrepr(out,r->str,r->len)
out = sdscat(out,"\n")
break
// 处理 nil
case REDIS_REPLY_NIL:
out = sdscat(out,"(nil)\n")
break
// 处理多回复
case REDIS_REPLY_ARRAY:
if (r->elements == 0) {
out = sdscat(out,"(empty list or set)\n")
} else {
unsigned int i, idxlen = 0
char _prefixlen[16]
char _prefixfmt[16]
sds _prefix
sds tmp
/* Calculate chars needed to represent the largest index */
i = r->elements
do {
idxlen++
i /= 10
} while(i)
/* Prefix for nested multi bulks should grow with idxlen+2 spaces */
memset(_prefixlen,' ',idxlen+2)
_prefixlen[idxlen+2] = '\0'
_prefix = sdscat(sdsnew(prefix),_prefixlen)
/* Setup prefix format for every entry */
snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%ud) ",idxlen)
for (i = 0i <r->elementsi++) {
/* Don't use the prefix for the first element, as the parent
* caller already prepended the index number. */
out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1)
/* Format the multi bulk entry */
tmp = cliFormatReplyTTY(r->element[i],_prefix)
out = sdscatlen(out,tmp,sdslen(tmp))
sdsfree(tmp)
}
sdsfree(_prefix)
}
break
default:
fprintf(stderr,"Unknown reply type: %d\n", r->type)
exit(1)
}
return out
}
在 发送命令 一节中使用的格式除了用作命令请求协议之外, 也用在命令的回复协议中: 这种只有一个参数的回复格式被称为批量回复(Bulk Reply)。
统一协议请求原本是用在回复协议中, 用于将列表的多个项返回给客户端的, 这种回复格式被称为多条批量回复(Multi Bulk Reply)。
一个多条批量回复以 *<argc>\r\n 为前缀, 后跟多条不同的批量回复, 其中 argc 为这些批量回复的数量。
状态回复
一个状态回复(或者单行回复,single line reply)是一段以 "+" 开始、 "\r\n" 结尾的单行字符串。
以下是一个状态回复的例子:
+OK客户端库应该返回 "+" 号之后的所有内容。 比如在在上面的这个例子中, 客户端就应该返回字符串 "OK" 。
状态回复通常由那些不需要返回数据的命令返回,这种回复不是二进制安全的,它也不能包含新行。
状态回复的额外开销非常少,只需要三个字节(开头的 "+" 和结尾的 CRLF)。
错误回复
错误回复和状态回复非常相似, 它们之间的唯一区别是, 错误回复的第一个字节是 "-" , 而状态回复的第一个字节是 "+" 。
错误回复只在某些地方出现问题时发送: 比如说, 当用户对不正确的数据类型执行命令, 或者执行一个不存在的命令, 等等。
一个客户端库应该在收到错误回复时产生一个异常。
以下是两个错误回复的例子:
-ERR unknown command 'foobar'-WRONGTYPE Operation against a key holding the wrong kind of value
在 "-" 之后,直到遇到第一个空格或新行为止,这中间的内容表示所返回错误的类型。
ERR 是一个通用错误,而 WRONGTYPE 则是一个更特定的错误。 一个客户端实现可以为不同类型的错误产生不同类型的异常, 或者提供一种通用的方式, 让调用者可以通过提供字符串形式的错误名来捕捉(trap)不同的错误。
不过这些特性用得并不多, 所以并不是特别重要, 一个受限的(limited)客户端可以通过简单地返回一个逻辑假(false)来表示一个通用的错误条件。
整数回复
整数回复就是一个以 ":" 开头, CRLF 结尾的字符串表示的整数。
比如说, ":0\r\n" 和 ":1000\r\n" 都是整数回复。
返回整数回复的其中两个命令是 INCR 和 LASTSAVE 。 被返回的整数没有什么特殊的含义, INCR 返回键的一个自增后的整数值, 而 LASTSAVE 则返回一个 UNIX 时间戳, 返回值的唯一限制是这些数必须能够用 64 位有符号整数表示。
整数回复也被广泛地用于表示逻辑真和逻辑假: 比如 EXISTS 和 SISMEMBER 都用返回值 1 表示真, 0 表示假。
其他一些命令, 比如 SADD 、 SREM 和 SETNX , 只在操作真正被执行了的时候, 才返回 1 , 否则返回 0 。
以下命令都返回整数回复: SETNX 、 DEL 、 EXISTS 、 INCR 、 INCRBY 、 DECR 、 DECRBY 、 DBSIZE 、 LASTSAVE 、RENAMENX 、 MOVE 、 LLEN 、 SADD 、 SREM 、 SISMEMBER 、 SCARD 。
批量回复
服务器使用批量回复来返回二进制安全的字符串,字符串的最大长度为 512 MB 。
客户端:GET mykey服务器:foobar
服务器发送的内容中:
第一字节为 "$" 符号
- 接下来跟着的是表示实际回复长度的数字值- 之后跟着一个 CRLF
- 再后面跟着的是实际回复数据
- 最末尾是另一个 CRLF
对于前面的 GET 命令,服务器实际发送的内容为:
"$6\r\nfoobar\r\n"如果被请求的值不存在, 那么批量回复会将特殊值 -1 用作回复的长度值, 就像这样:
客户端:GET non-existing-key服务器:$-1
这种回复称为空批量回复(NULL Bulk Reply)。
当请求对象不存在时,客户端应该返回空对象,而不是空字符串: 比如 Ruby 库应该返回 nil , 而 C 库应该返回NULL (或者在回复对象中设置一个特殊标志), 诸如此类。
多条批量回复
像 LRANGE 这样的命令需要返回多个值, 这一目标可以通过多条批量回复来完成。
多条批量回复是由多个回复组成的数组, 数组中的每个元素都可以是任意类型的回复, 包括多条批量回复本身。
多条批量回复的第一个字节为 "*" , 后跟一个字符串表示的整数值, 这个值记录了多条批量回复所包含的回复数量, 再后面是一个 CRLF 。
客户端: LRANGE mylist 0 3服务器: *4
服务器: $3
服务器: foo
服务器: $3
服务器: bar
服务器: $5
服务器: Hello
服务器: $5
服务器: World
在上面的示例中,服务器发送的所有字符串都由 CRLF 结尾。
正如你所见到的那样, 多条批量回复所使用的格式, 和客户端发送命令时使用的统一请求协议的格式一模一样。 它们之间的唯一区别是:
统一请求协议只发送批量回复。
而服务器应答命令时所发送的多条批量回复,则可以包含任意类型的回复。
以下例子展示了一个多条批量回复, 回复中包含四个整数值, 以及一个二进制安全字符串:
*5\r\n:1\r\n
:2\r\n
:3\r\n
:4\r\n
$6\r\n
foobar\r\n
在回复的第一行, 服务器发送 *5\r\n , 表示这个多条批量回复包含 5 条回复, 再后面跟着的则是 5 条回复的正文。
多条批量回复也可以是空白的(empty), 就像这样:
客户端: LRANGE nokey 0 1服务器: *0\r\n
无内容的多条批量回复(null multi bulk reply)也是存在的, 比如当 BLPOP 命令的阻塞时间超过最大时限时, 它就返回一个无内容的多条批量回复, 这个回复的计数值为 -1 :
客户端: BLPOP key 1服务器: *-1\r\n
客户端库应该区别对待空白多条回复和无内容多条回复: 当 Redis 返回一个无内容多条回复时, 客户端库应该返回一个 null 对象, 而不是一个空数组。
多条批量回复中的空元素
多条批量回复中的元素可以将自身的长度设置为 -1 , 从而表示该元素不存在, 并且也不是一个空白字符串(empty string)。
当 SORT 命令使用 GET pattern 选项对一个不存在的键进行操作时, 就会发生多条批量回复中带有空白元素的情况。
以下例子展示了一个包含空元素的多重批量回复:
服务器: *3服务器: $3
服务器: foo
服务器: $-1
服务器: $3
服务器: bar
其中, 回复中的第二个元素为空。
对于这个回复, 客户端库应该返回类似于这样的回复:
["foo", nil, "bar"]欢迎分享,转载请注明来源:夏雨云
评论列表(0条)