关于网络:网络协议之redis-protocol-详解

32次阅读

共计 4442 个字符,预计需要花费 12 分钟才能阅读完成。

简介

redis 是一个十分优良的软件,它能够用作内存数据库或者缓存。因为他的优良性能,redis 被利用在很多场合中。

redis 是一个客户端和服务器端的模式,客户端和服务器端是通过 TCP 协定进行连贯的,客户端将申请数据发送到服务器端,服务器端将申请返回给客户端。这样一个申请流程就实现了。

当然在最开始的时候,因为用的人很少,零碎还不够稳固,通过 TCP 协定传输的数据不标准的。然而当用的人越来越多,尤其是心愿开发实用于不同语言和平台的 redis 客户端的时候,就要思考到兼容性的问题了。

这时候客户端和服务器端就须要一个对立的交互协定,对于 redis 来说这个通用的交互协定就叫做 Redis serialization protocol(RESP)。

RESP 是在 Redis 1.2 版本中引入的,并在 Redis 2.0 中成为了与 Redis 服务器通信的规范形式。

这就是说,从 Redis 2.0 之后,就能够基于 redis protocol 协定开发出本人的 redis 客户端了。

redis 的高级用法

一般来说,redis 的客户端和服务器端组成的是一个申请 - 响应的模式,也就是说客户端向服务器端发送申请,而后失去服务器端的响应后果。

申请和响应是 redis 中最简略的用法。相熟 redis 的敌人可能会想到了两个 redis 的高级用法,这两个用法并不是传统意义上的申请 - 响应模式。

到底是哪两种用法呢?

第一种就是 redis 反对 pipline,也就是管道操作,管道的益处就是 redis 客户端能够一次性向服务器端发送多条命令,而后期待服务器端的返回。

第二种 redis 还反对 Pub/Sub,也就是播送模型,在这一种状况下,就不是申请和响应的模式了,在 Pub/Sub 下,切换成了服务器端推送的模式。

Redis 中的 pipline

为什么要用 pipline 呢?

因为 redis 是一个典型的申请响应模式,咱们来举个常见的 incr 命令的例子:

Client: INCR X
Server: 1
Client: INCR X
Server: 2
Client: INCR X
Server: 3
Client: INCR X
Server: 4

事实上客户端只想得到最终的后果,然而每次客户端都须要期待服务器端返回后果之后,能力发送下一次的命令。这样就会导致一个叫做 RTT(Round Trip Time)的工夫节约。

尽管每次 RTT 的工夫不长,然而累计起来也是一个十分主观的数字。

那么可不可以将所有的客户端命令放在一起发送给服务器呢? 这个优化就叫做 Pipeline。

piepline 的意思就是客户端能够在没有收到服务器端返回的时候持续向服务器端发送命令。

下面的命令能够用 pipline 进行如下改写:

(printf "INCR X\r\nINCR X\r\nINCR X\r\nINCR X\r\n"; sleep 1) | nc localhost 6379
:1
:2
:3
:4

因为 redis 服务器反对 TCP 协定进行连贯,所以咱们能够间接用 nc 连到 redis 服务器中执行命令。

在应用 pipline 的时候有一点要留神,因为 redis 服务器会将申请的后果缓存在服务器端,等到 pipline 中的所有命令都执行结束之后再对立返回,所以如果服务器端返回的数据比拟多的状况下,须要思考内存占用的问题。

那么 pipline 仅仅是为了缩小 RTT 吗?

相熟操作系统的敌人可能有据说过用户空间和操作系统空间的概念,从用户输出读取数据而后再写入到零碎空间中,这里波及到了一个用户空间的切换,在 IO 操作中,这种空间切换或者拷贝是比拟耗时的,如果频繁的进行申请和响应,就会造成这种频繁的空间切换,从而升高了零碎的效率。

应用 pipline 能够一次性发送多条指令,从而无效防止空间的切换行为。

Redis 中的 Pub/Sub

和 Pub/Sub 相干的命令是 SUBSCRIBE, UNSUBSCRIBE 和 PUBLISH。

为什么要用 Pub/Sub 呢?其次要的目标就是解耦,在 Pub/Sub 中音讯发送方不须要晓得具体的接管方的地址,同样的对于音讯接管方来说,也不须要晓得具体的音讯发送方的地址。他们只须要晓得关联的主题即可。

subscribe 和 publish 的命令比较简单,咱们举一个例子,首先是客户端 subscribe topic:

redis-cli -h 127.0.0.1
127.0.0.1:6379> subscribe topic
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "topic"
3) (integer) 1

而后在另外一个终端,调用 publish 命令:

redis-cli -h 127.0.0.1
127.0.0.1:6379> publish topic "what is your name?"
(integer) 1

能够看到客户端会收到上面的音讯:

1) "message"
2) "topic"
3) "what is your name?"

RESP protocol

RESP 协定有 5 种类型,别离是 imple Strings, Errors, Integers, Bulk Strings 和 Arrays。

不同的类型以音讯中的第一个 byte 进行辨别,如下所示:

类型 第一个 byte
Simple Strings +
Errors
Integers :
Bulk Strings $
Arrays *

protocol 中不同的局部以 “\r\n” (CRLF)来进行区别。

Simple Strings

Simple Strings 的意思是简略的字符串。

通常用在服务器端的返回中,这种音讯的格局就是 ”+” 加上文本音讯,最初以 ”\r\n” 结尾。

比方服务器端返回 OK, 那么对应的音讯就是:

"+OK\r\n"

下面的音讯是一个非二进制平安的音讯,如果想要发送二进制平安的音讯,则能够应用 Bulk Strings。

什么是非二进制平安的音讯呢?对于 Simple Strings 来说,因为音讯是以 ”\r\n” 结尾,所以音讯两头不能蕴含 ”\r\n” 这两个特殊字符,否则就会产生谬误的含意。

Bulk Strings

Bulk Strings 是二进制平安的。这是因为 Bulk Strings 蕴含了一个字符长度字段,因为是依据长度来判断字符长度的,所以并不存在依据字符中某个特定字符来判断是否字符完结的毛病。

具体而言 Bulk Strings 的构造是 ”$”+ 字符串长度 +”\r\n”+ 字符串 +”\r\n”。

以 OK 为例,如果以 Bulk Strings 来示意,则如下所示:

"$2\r\nok\r\n"

Bulk Strings 还能够蕴含空字符串:

"$0\r\n\r\n"

当然还能够示意不存在的 Null 值:

"$-1\r\n"

RESP Integers

这是 redis 中的整数示意,具体的格局是 ”:”+ 整数 +”\r\n”。

比方 18 这个整数就能够用上面的格局来示意:

":18\r\n"

RESP Arrays

redis 的多个命令能够以 array 来示意,服务器端返回的多个值也能够用 arrays 来示意。

RESP Arrays 的格局是 ”*”+ 数组中的元素个数 + 其余相似的数据。

所以 RESP Arrays 是一个复合构造的数据。比方一个数组中蕴含了两个 Bulk Strings:”redis”,”server” 则能够用上面的格局来示意:

"*2\r\n$5\r\nredis\r\n$6\r\nserver\r\n"

RESP Arrays 中的原始不仅能够应用不同类型,还能蕴含 RESP Arrays,也就是 array 的嵌套:

"*3\r\n$5\r\nredis\r\n$6\r\nserver\r\n*1\r\n$4\r\ngood\r\n"

为了不便察看,咱们将下面的音讯格局一下:

"*3\r\n
$5\r\nredis\r\n
$6\r\nserver\r\n
*1\r\n
$4\r\ngood\r\n"

下面的音讯是一个蕴含三个元素的数组,后面两个元素是 Bulk Strings,最初一个是蕴含一个元素的数组。

RESP Errors

最初,RESP 还能够示意谬误音讯。RESP Errors 的音讯格局是 ”-“+ 字符串,如下所示:

"-Err something wrong\r\n"

个别状况下,”-“ 前面的第一个单词示意的是谬误类型,然而这只是一个约定俗成的规定,并不是 RESP 协定中的强制要求。

另外,通过比照,大家可能会发现 RESP Errors 和 Simple Strings 是音讯格局是差不多的。

这种对不同音讯类型的解决是在客户端进行辨别的。

Inline commands

如果齐全按 RESP 协定的要求,当咱们连贯到服务器端的时候须要蕴含 RESP 中定义音讯的所有格局,然而这些音讯中蕴含了额定的音讯类型和回车换行符,所以间接应用协定来执行的话会比拟困惑。

于是 redis 还提供一些内联的命令,也就是协定命令的精简版本,这个精简版本去除了音讯类型和回车换行符。

咱们以 ”get world” 这个命令为例。来看下不同形式的连贯状况。

首先是应用 redis-cli 进行连贯:

redis-cli -h 127.0.0.1
127.0.0.1:6379> get world
"hello"

因为 redis-cli 是 redis 的客户端,所以能够间接应用 inline command 来执行命令。

如果应用 telnet, 咱们也能够应用同样的命令来取得后果:

telnet 127.0.0.1 6379
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
get world
$5
hello

能够看到返回的后果是 ”$5\r\nhello\r\n”。

如果要应用协定音讯来申请 redis 服务器应该怎么做呢?

咱们要申请的命令是 ”get world”, 将其转换成为 RESP 的音讯则是:

"*2\r\n$3\r\nget\r\n$5\r\nworld\r\n"

咱们尝试一下将上述命令应用 nc 传递到 redis server 上:

(printf "*2\r\n$3\r\nget\r\n$5\r\nworld\r\n"; sleep 1) |  nc localhost 6379
-ERR Protocol error: expected '$', got ' '

很遗憾咱们失去了 ERR,那么是不是不能间接应用 RESP 音讯格局进行传输呢?当然不是,下面的问题在于 $ 符号是一个特殊字符,咱们须要本义一下:

(printf "*2\r\n\$3\r\nget\r\n\$5\r\nworld\r\n"; sleep 1) |  nc localhost 6379
$5
hello

能够看到输入的后果和间接应用 redis-cli 统一。

总结

以上就是 RESP 协定的根本内容和手动应用的例子,有了 RESP,咱们就能够依据协定中定义的格局来创立 redis 客户端。

可能大家又会问了,为什么只是 redis 客户端呢?有了协定是不是 redis 服务器端也能够创立呢?答案当然是必定的,只须要依照协定进行音讯传输即可。次要的问题在于 redis 服务器端的实现比较复杂,不是那么容易实现的。

正文完
 0