虚拟主机域名注册-常见问题 → 服务器知识 → 服务器问题

Redis通信协议-RESP协议

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

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

  Redis制定了RESP(REdis Serialization Protocol,Redis序列化协议)实现客户端与服务端的正常交互,这种协议简单高效,既能够被机器解析,又容易被人类识别。

  例如客户端发送一条set hello world命令给服务端,按照RESP 的标准,客户端需要将其封装为如下格式(每行用\r\n分隔):

  *3

  $3

  SET

  $5

  hello

  $5

  world

  这样 Redis服务端能够按照RESP将其解析为set hello world命令,执行后回复的格式如下:

  +OK

  可以看到除了命令(sethelloworld)和返回结果(0K)本身还包含了一些特殊字符以及数字,下面将对这些格式进行说明。

  3.1 发送命令格式

  RESP的规定一条命令的格式如下,CRLF代表 "\r\n" :

  *<参数数量> CRLF

  $<参数1的字节数量> CRLF

  <参数 1> CRLF

  ...

  $<参数的字节数量>CRLF

  <参数 N> CRLF

  依然以 set hell world 这条命令进行说明。参数数量为3个,因此第一行为:

  *3

  参数字节数分别是 3 5 5,因此后面几行为:

  $3

  SET

  $5

  hello

  $5

  world

  有一点要注意的是,上面只是格式化显示的结果,实际传输格式为如下代码,整个过程如图所示:

  *3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n

  3.2.返回结果格式

  Redis 的返回结果类型分为以下五种,如图所示:

  状态回复:在 RESP 中第一个字节为"+"。

  错误回复:在RESP 中第一个字节为"-"。

  整数回复:在 RESP 中第一个字节为":"。

  字符串回复:在 RESP 中第一个字节为"$"。

  多条字符串回复:在RESP 中第一个字节为"*"。

  我们知道 redis-c1i只能看到最终的执行结果,那是因为redis-cli本身就是按照RESP进行结果解析的,所以看不到中间结果,redis-cli.c源码对命令结果的解析结构如下:

  static sds cliFormatReplyTTY(redisReply *r,char *pre@x){

  sds out=sdsempty();

  switch(r->type)(

  Case REDIS REPLY_ERROR:

  // 处理错误回复

  Case REDIS REPLY STATUS:

  // 处理状态回复

  Case REDIS REPLY_INTEGER:

  // 处理整数回复

  Case REDIS REPLY_STRING:

  //处理字符串回复

  Case REDIS_REPLY_NIL:

  //处理空

  Case REDIS_REPLY_ARRAY:

  //处理多条字符串回复

  return out;

  }

  例如执行 set hello world,返回结果是 0K,并不能看到加号:

  127.0.0.1:6379> set hello world

  OK

  为了看到Reds服务端返回的"真正"结果,可以使用nc命令、telnet 命令、甚写一个socket 程序进行模拟。下面以nc命令进行演示,首先使用nc 127.0.0.6379连接到 Redis:

  nc 127.0.0.1 6379

  状态回复:set hello world的返回结果为 + OK:

  sh复制代码

  set hello world

  +OK

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

  sethx

  -ERR unknown command 'sethx'

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

  incr counter

  :1

  字符串回复:当命令的执行结果是字符串时,返回结果就是字符串回复。例如get、hget 返回结果都是字符串,例如get hello 的结果为 "$5\r\nworld\r\n":

  get hello

  $5

  world

  多条字符串回复:当命令的执行结果是多条字符串时,返回结果就是多条字符串回复例如 mget、hgetall、lrange 等命令会返回多个结果,例如下面操作:

  首先使用 mset 设置多个键值对:

  mset java jedis python redis-py

  +OK

  然后执行 mget 命令返回多个结果,第一个 *2 代表返回结果的个数,后面的格式是和字符串回复一致的:

  mget java python

  *2

  $5

  jedis

  $8

  redis-py

  有一点需要注意,无论是字符串回复还是多条字符串回复,如果有nil值,那么会返回 $-1。

  例如,对一个不存在的键执行 get 操作,返回结果为:

  get not_exist_key

  $-1

  如果批量操作中包含一条为 nil值的结果,那么返回结果如下:

  mget hello not_exist_key java

  *3

  $5

  world

  $-1

  $5

  jedis

  有了 RESP 提供的发送命令和返回结果的协议格式,各种编程语言就可以利用其来实现相应的 Redis 客户端。

  3.3 Redis通信协议-基于Socket自定义Redis的客户端

  Redis支持TCP通信,因此我们可以使用Socket来模拟客户端,与Redis服务端建立连接:

  public class Main {

  static Socket s;

  static PrintWriter writer;

  static BufferedReader reader;

  public static void main(String[] args) {

  try {

  // 1.建立连接

  String host = "192.168.150.101";

  int port = 6379;

  s = new Socket(host, port);

  // 2.获取输出流、输入流

  writer = new PrintWriter(new OutputStreamWriter(s.getOutputStream(), StandardCharsets.UTF_8));

  reader = new BufferedReader(new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8));

  // 3.发出请求

  // 3.1.获取授权 auth 123456

  sendRequest("auth", "1232456");

  Object obj = handleResponse();

  System.out.println("obj = " + obj);

  // 3.2.set name hello

  sendRequest("set", "name", "hello");

  // 4.解析响应

  obj = handleResponse();

  System.out.println("obj = " + obj);

  // 3.2.set name hello

  sendRequest("get", "name");

  // 4.解析响应

  obj = handleResponse();

  System.out.println("obj = " + obj);

  // 3.2.set name hello

  sendRequest("mget", "name", "num", "msg");

  // 4.解析响应

  obj = handleResponse();

  System.out.println("obj = " + obj);

  } catch (IOException e) {

  e.printStackTrace();

  } finally {

  // 5.释放连接

  try {

  if (reader != null) reader.close();

  if (writer != null) writer.close();

  if (s != null) s.close();

  } catch (IOException e) {

  e.printStackTrace();

  }

  }

  }

  private static Object handleResponse() throws IOException {

  // 读取首字节

  int prefix = reader.read();

  // 判断数据类型标示

  switch (prefix) {

  case '+': // 单行字符串,直接读一行

  return reader.readLine();

  case '-': // 异常,也读一行

  throw new RuntimeException(reader.readLine());

  case ':': // 数字

  return Long.parseLong(reader.readLine());

  case '$': // 多行字符串

  // 先读长度

  int len = Integer.parseInt(reader.readLine());

  if (len == -1) {

  return null;

  }

  if (len == 0) {

  return "";

  }

  // 再读数据,读len个字节。我们假设没有特殊字符,所以读一行(简化)

  return reader.readLine();

  case '*':

  return readBulkString();

  default:

  throw new RuntimeException("错误的数据格式!");

  }

  }

  private static Object readBulkString() throws IOException {

  // 获取数组大小

  int len = Integer.parseInt(reader.readLine());

  if (len <= 0) {

  return null;

  }

  // 定义集合,接收多个元素

  List




免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:bkook@qq.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
【 双击滚屏 】 【 推荐朋友 】 【 收藏 】 【 打印 】 【 关闭 】 【 字体:大 中 小 】 
上一篇:Redis线程模型
下一篇:内存回收
  >> 相关文章
没有相关文章。