Redis 通信协议

本文和大家分享的主要是redis 通信协议相关内容,一起来看看吧,希望对大家 学习redis有所帮助。

几乎所有的主流编程语言都有Redis 的客户端,不考虑 Redis 非常流行的原因,如果站在技术的角度看原因还有两个:

1.  客户端与服务端之间的通信协议是在  TCP 协议  之上构建的。

客户端和服务器通过 TCP  连接来进行数据交互, 服务器默认的端口号为  6379  。

客户端和服务器发送的命令或数据一律以  \r\n  (CRLF )结尾。

1. 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;//  处理状态回复 caseREDIS_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;//  处理多回复 caseREDIS_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 = 0; i < r->elements; i++) {/* 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 ,_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) 。

一个多条批量回复以 *\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"]

多命令和 pipline

客户端可以通过 pipline , 在一次写入操作中发送多个命令:

· 在发送新命令之前, 无须阅读前一个命令的回复。

· 多个命令的回复会在最后一并返回。

内联命令

当你需要和 Redis 服务器进行沟通, 但又找不到 redis-cli , 而手上只有 telnet 的时候, 你可以通过 Redis 特别为这种情形而设的内联命令格式来发送命令。

以下是一个客户端和服务器使用内联命令来进行交互的例子:

客户端: PING

服务器: +PONG

以下另一个返回整数值的内联命令的例子:

客户端: EXISTS somekey

服务器: :0

因为没有了统一请求协议中的 "*" 项来声明参数的数量, 所以在 telnet 会话输入命令的时候, 必须使用空格来分割各个参数,服务器在接收到数据之后, 会按空格对用户的输入进行分析(parse), 并获取其中的命令参数。

高性能 Redis 协议分析器

尽管 Redis 的协议非常利于人类阅读, 定义也很简单, 但这个协议的实现性能仍然可以和二进制协议一样快。

因为 Redis 协议将数据的长度放在数据正文之前, 所以程序无须像 JSON 那样, 为了寻找某个特殊字符而扫描整个 payload , 也无须对发送至服务器的 payload 进行转义(quote)。

程序可以在对协议文本中的各个字符进行处理的同时, 查找 CR 字符, 并计算出批量回复或多条批量回复的长度, 就像这样:

#include

int main(void) {

unsigned char *p = "$123\r\n";int len = 0;

p++;while(*p != ’\r’) {

len = (len*10)+(*p - ’0’);

p++;

}

/* Now p points at ’\r’, and the len is in bulk_len. */printf("%d\n", len);return 0;

}

得到了批量回复或多条批量回复的长度之后, 程序只需调用一次 read 函数, 就可以将回复的正文数据全部读入到内存中, 而无须对这些数据做任何的处理。

在回复最末尾的 CR 和 LF 不作处理,丢弃它们。

Redis 协议的实现性能可以和二进制协议的实现性能相媲美, 并且由于 Redis 协议的简单性, 大部分高级语言都可以轻易地实现这个协议, 这使得客户端软件的 bug 数量大大减少。

Linux 下 使用 nc 命令操作 Redis

[coderknock ~]# nc 127.0.0.1 6379

set hello world

+OK                          #状态回复

sethx

-ERR unknown command ’sethx’ #错误回复:由于sethx这条命令不存在,那么返回结果就是"-"号加上错误消息

incr counter:1                           #整数回复:当命令的执行结果是整数时,返回结果就是整数回复,例如 incr、exists、del、dbsize返回结果都是整数

get hello

$5                           #字符串回复:当命令的执行结果是字符串时,返回结果就是字符串回复。

world                        #实际返回的是 $5\r\nworld\r\n

mset java jedis python redis-py

+OK

mget java python             #多条字符串回复:当命令的执行结果是多条字符串时,返回结果就是多条字符串回复

*2

$5

jedis

$8

redis-py

get not_exist_key            #无论是字符串回复还是多条字符串回复,如果有 nil 值,那么会返回$-1。

$-1

mget hello not_exist_key java

*3

$5

world

$-1

$5

jedis

Python Socket 操作 Redis

使用 socket 操作 Redis:

import socket

# AF_INET指定使用 IPv4 协议

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.SOL_TCP)

s.connect((’127.0.0.1’, 6379))

print(’get connected from’, ’127.0.0.1’)# 验证密码

s.send(b’*2\r\n$4\r\nAUTH\r\n$8\r\nadmin123\r\n’)

ra = s.recv(512)

print(ra)# 发送一条信息

s.send(b’*3\r\n$3\r\nSET\r\n$8\r\ntestRESP\r\n$10\r\nRESPpython\r\n’)

ra = s.recv(512)

print(ra)

s.close()

执行结果:

get connected from 127.0.0.1

b’+OK\r\n’

b’+OK\r\n’

我们从命令行中查询:

redis> GET testRESP"RESPpython"

可以看到正确的向 Redis 中插入了键值。

来源:网络

时间: 2024-10-09 22:38:36

Redis 通信协议的相关文章

Redis 通信协议简单研究

1.Redis网络通信协议 Redis底层网络通信协议其实是通过TCP来完成的. 2.Redis通信协议 Redis的通信协议首先是以行来划分,每行以\r\n行结束.每一行都有一个消息头,消息头共分为5种分别如下: (+) 表示一个正确的状态信息,具体信息是当前行+后面的字符. (-) 表示一个错误信息,具体信息是当前行-后面的字符. (*) 表示消息体总共有多少行,不包括当前行,*后面是具体的行数. (\() 表示下一行数据长度,不包括换行符长度\r\n,\)后面则是对应的长度的数据. (:)

Redis 通信协议-了解 Redis 客户端实现原理

简介 几乎所有的主流编程语言都有Redis的客户端(http://redis.io/clients),不考虑Redis非常流行的原因,如果站在技术的角度看原因还有两个: 客户端与服务端之间的通信协议是在 TCP 协议之上构建的. 客户端和服务器通过 TCP 连接来进行数据交互, 服务器默认的端口号为 6379 . 客户端和服务器发送的命令或数据一律以 \r\n (CRLF)结尾. Redis制定了 RESP(REdis Serialization Protocol,Redis序列化协议)实现客户

Redis研究(十三)—安全和通信协议

一.安全 Redis的作者Salvatore Sanfilippo曾经发表过Redis宣言,其中提到Redis以简洁为美.同样在安全层面Redis也没有做太多的工作. 1.可信的环境       Redis的安全设计是在“Redis运行在可信环境”这个前提下做出的,在生产环境运行时不能允许外界直接连接到Redis服务器上,而应该通过应用程序进行中转,运行在可信的环境中是保证Redis安全的最重要方法. Redis的默认配置会接受来自任何地址发送来的请求,即在任何一个拥有公网IP的服务器上启动Re

豆瓣Redis解决方案Codis源码剖析:Proxy代理

豆瓣Redis解决方案Codis源码剖析:Proxy代理 1.预备知识 1.1 Codis Codis就不详细说了,摘抄一下GitHub上的一些项目描述: Codis is a proxy based high performance Redis cluster solution written in Go/C, an alternative to Twemproxy. It supports multiple stateless proxy with multiple redis instan

Redis研究(十二)—数据复制

在上一节中我们写了Redis的数据持久化 http://blog.csdn.net/wtyvhreal/article/details/42916503 通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据.但是由于数据是存储在一台服务器上的,如果这台服务器的硬盘出现故障,也会导致数据丢失.为了避免单点故障,我们希望将数据库复制多个副本以部署在不同的服务器上,即使有一台服务器出现故障其他服务器依然可以继续提供服务.这就要求当一台服务器上的数据库更新后,可以自动将更

redis client protocol 解析

在官网http://redis.io/topics/protocol有对redis通信协议有做说明. 基于下面的一些原因,我想解析redis client protocol: 1.足够了解通信协议,有助于做出更好的系统设计. 2.学习RESP的设计思想,不仅能扩展我的思维,也许将来能应用于我的代码中. 3.因为有些人想将redis client直接并入自己已有的系统中:包括我在内.这个将在我下一篇文章再做说明. 下面我翻译一下http://redis.io/topics/protocol一些我认

发布一个参考ssdb,用go实现的类似redis的高性能nosql:ledisdb

起因 ledisdb是一个参考ssdb,采用go实现,底层基于leveldb,类似redis的高性能nosql数据库,提供了kv,list,hash以及zset数据结构的支持. 我们现在的应用极大的依赖redis,但随着我们用户量越来越大,redis的内存越来越不够用,并且replication可能还会导致超时问题.虽然后续我们可以通过添加多台机器来解决,但是在现有机器配置下面,我们仍希望单台机器承载更多的用户.另外,因为业务的特性,我们其实并不需要将所有的数据放到内存,只需要存放当前活跃用户.

Redis集群方案应该怎么做

方案1:Redis官方集群方案 Redis Cluster Redis Cluster是一种服务器sharding分片技术. Redis3.0版本开始正式提供,解决了多Redis实例协同服务问题,时间较晚,目前能证明在大规模生产环境下成功的案例还不是很多,需要时间检验. Redis Cluster中,Sharding采用slot(槽)的概念,一共分成16384个槽.对于每个进入Redis的键值对,根据key进行散列,分配到这16384个slot中的某一个中.使用的hash算法也比较简单,CRC1

【轮子狂魔】手把手教你自造Redis Client

为什么做Redis Client? Redis Client顾名思义,redis的客户端,主要是封装了一些对于Redis的操作. 而目前用的比较广泛的 ServiceStack.Redis 不学好,居然开始收费了. 作为轮子狂魔,是可忍孰不可忍啊.于是我决定自己造轮子了. Redis通信协议 先给个Redis官方的通信协议地址:http://redisdoc.com/topic/protocol.html 关键是我截图的部分,我们可以得到以下几个信息: 1.tcp协议 2.默认端口6379 3.