简介

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.1127.0.0.1:6379> subscribe topicReading messages... (press Ctrl-C to quit)1) "subscribe"2) "topic"3) (integer) 1

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

redis-cli -h 127.0.0.1127.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.1127.0.0.1:6379> get world"hello"

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

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

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

能够看到返回的后果是"$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$5hello

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

总结

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

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