当前位置: 首页 > redis, 分布式系统, 缓存系统 > 正文

Redis通信协议

关键字:
1 星2 星3 星4 星5 星 (1 次投票, 评分: 5.00, 总分: 5)
Loading ... Loading ...
baidu_share
文章目录

Redis 协议

Redis 协议在以下三个目标之间进行折中:
1.易于实现
2.可以高效地被计算机分析(parse)
3.可以很容易地被人类读懂

网络层

网络层:客户端和服务器通过 TCP 连接来进行数据交互, 服务器默认的端口号为 6379 。客户端和服务器发送的命令或数据一律以 \r\n (CRLF)结尾。

请求

请求:Redis 服务器接受命令以及命令的参数。服务器会在接到命令之后,对命令进行处理,并将命令的回复传送回客户端。

统一请求协议:所有发送至 Redis 服务器的参数都是二进制安全(binary safe)的。以下是这个协议的一般形式:

*<参数数量> CR LF
$<参数 1 的字节数量> CR LF
<参数 1 的数据> CR LF
...
$<参数 N 的字节数量> CR LF
<参数 N 的数据> CR LF

注:命令本身也作为协议的其中一个参数来发送。

举个例子, 以下是一个命令协议的打印版本:

*3
$3
SET
$5
mykey
$7
myvalue

这个命令的实际协议值如下:

"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"

这种只有一个参数的回复格式被称为批量回复(Bulk Reply)。
统一协议请求原本是用在回复协议中, 用于将列表的多个项返回给客户端的, 这种回复格式被称为多条批量回复(Multi Bulk Reply)。

回复

Redis 命令会返回多种不同类型的回复。

通过检查服务器发回数据的第一个字节, 可以确定这个回复是什么类型:

状态回复(status reply)的第一个字节是 “+”
错误回复(error reply)的第一个字节是 “-”
整数回复(integer reply)的第一个字节是 “:”
批量回复(bulk reply)的第一个字节是 “$”
多条批量回复(multi bulk reply)的第一个字节是 “*”

java实现

  public static final byte DOLLAR_BYTE = '$';
  public static final byte ASTERISK_BYTE = '*';
  public static final byte PLUS_BYTE = '+';
  public static final byte MINUS_BYTE = '-';
  public static final byte COLON_BYTE = ':';
 
//发送命令
private static void sendCommand(final RedisOutputStream os, final byte[] command,
      final byte[]... args) {
    try {
      os.write(ASTERISK_BYTE);
      os.writeIntCrLf(args.length + 1);
      os.write(DOLLAR_BYTE);
      os.writeIntCrLf(command.length);
      os.write(command);
      os.writeCrLf();
 
      for (final byte[] arg : args) {
        os.write(DOLLAR_BYTE);
        os.writeIntCrLf(arg.length);
        os.write(arg);
        os.writeCrLf();
      }
    } catch (IOException e) {
      throw new JedisConnectionException(e);
    }
  }
 
//回复处理
private static Object process(final RedisInputStream is) {
 
    final byte b = is.readByte();
    if (b == PLUS_BYTE) {
      //状态回复
      return processStatusCodeReply(is);
    } else if (b == DOLLAR_BYTE) {
      //批量回复
      return processBulkReply(is);
    } else if (b == ASTERISK_BYTE) {
      //多条批量回复
      return processMultiBulkReply(is);
    } else if (b == COLON_BYTE) {
      //整数回复
      return processInteger(is);
    } else if (b == MINUS_BYTE) {
      //错误回复
      processError(is);
      return null;
    } else {
      throw new JedisConnectionException("Unknown reply: " + (char) b);
    }
  }
 
  private static byte[] processStatusCodeReply(final RedisInputStream is) {
    return is.readLineBytes();
  }
 
  private static byte[] processBulkReply(final RedisInputStream is) {
    final int len = is.readIntCrLf();
    if (len == -1) {
      return null;
    }
 
    final byte[] read = new byte[len];
    int offset = 0;
    while (offset < len) {
      final int size = is.read(read, offset, (len - offset));
      if (size == -1) throw new JedisConnectionException(
          "It seems like server has closed the connection.");
      offset += size;
    }
 
    // read 2 more bytes for the command delimiter
    is.readByte();
    is.readByte();
 
    return read;
  }
 
  private static Long processInteger(final RedisInputStream is) {
    return is.readLongCrLf();
  }
 
  private static List<Object> processMultiBulkReply(final RedisInputStream is) {
    final int num = is.readIntCrLf();
    if (num == -1) {
      return null;
    }
    final List<Object> ret = new ArrayList<Object>(num);
    for (int i = 0; i < num; i++) {
      try {
        ret.add(process(is));
      } catch (JedisDataException e) {
        ret.add(e);
      }
    }
    return ret;
  }
private static void processError(final RedisInputStream is) {
    String message = is.readLine();
    // TODO: I'm not sure if this is the best way to do this.
    // Maybe Read only first 5 bytes instead?
    if (message.startsWith(MOVED_RESPONSE)) {
      String[] movedInfo = parseTargetHostAndSlot(message);
      throw new JedisMovedDataException(message, new HostAndPort(movedInfo[1],
          Integer.valueOf(movedInfo[2])), Integer.valueOf(movedInfo[0]));
    } else if (message.startsWith(ASK_RESPONSE)) {
      String[] askInfo = parseTargetHostAndSlot(message);
      throw new JedisAskDataException(message, new HostAndPort(askInfo[1],
          Integer.valueOf(askInfo[2])), Integer.valueOf(askInfo[0]));
    } else if (message.startsWith(CLUSTERDOWN_RESPONSE)) {
      throw new JedisClusterException(message);
    }
    throw new JedisDataException(message);
  }

本文固定链接: http://www.chepoo.com/redis-protocol.html | IT技术精华网

Redis通信协议:等您坐沙发呢!

发表评论