下面是一个包含Null元素的RESP数组的例子(为了看得更清楚,分多行进行编码,实际上不能这样做):
*3rn$3rnfoorn$-1rn$3rnbarrn复制代码RESP数组中的第2个元素是Null元素,客户端API最终返回的内容应该是:
# Ruby["foo",nil,"bar"]# Java["foo",null,"bar"]复制代码RESP其他相关内容主要包括:
- 将命令发送到Redis服务端的示例 。
- 批量命令与管道 。
- 内联命令(Inline Commands) 。
将命令发送到Redis服务端
如果已经相对熟悉RESP中的序列化格式,那么编写Redis客户端类库就会变得很容易 。我们可以进一步指定客户端和服务器之间的交互方式:
- Redis客户端向Redis服务端发送仅仅包含定长字符串类型元素的RESP数组 。
- Redis服务端可以采用任意一种RESP数据类型向Redis客户端进行回复,具体的数据类型一般取决于命令类型 。
C: *2rnC: $4rnC: LLENrnC: $6rnC: mylistrnS: :48293rn复制代码为了简单起见,我们使用换行符来分隔协议的不同部分(这里指上面的代码分行展示),但是实际交互的时候Redis客户端在发送*2rn$4rnLLENrn$6rnmylistrn的时候是整体发送的 。
批量命令与管道
Redis客户端可以使用相同的连接发送批量命令 。Redis支持管道特性,因此Redis客户端可以通过一次写操作发送多个命令,而无需在发送下一个命令之前读取Redis服务端对上一个命令的回复 。批量发送命令之后,所有的回复可以在最后得到(合并为一个回复) 。更多相关信息可以查看Using pipelining to speedup Redis queries 。
内联命令
有些场景下,我们可能只有telnet命令可以使用,在这种条件下,我们需要发送命令到Redis服务端 。尽管Redis协议易于实现,但在交互式会话中并不理想,并且redis-cli有些情况下不一定可用 。处于这类原因,Redis设计了一种专为人类设计的命令格式,称为内联命令(Inline Command格式 。
以下是服务器/客户端使用内联命令进行聊天的示例(S代表服务端,C代表客户端):
C: PINGS: +PONG复制代码以下是使用内联命令返回整数的另一个示例:
C: EXISTS somekeyS: :0复制代码基本上只需在telnet会话中编写以空格分隔的参数 。由于除了统一的请求协议之外没有命令会以*开头,Redis能够检测到这种情况并解析输入的命令 。
基于RESP编写高性能解析器因为JDK原生提供的字节缓冲区java.nio.ByteBuffer存在不能自动扩容、需要切换读写模式等等问题,这里直接引入Netty并且使用Netty提供的ByteBuf进行RESP数据类型解析 。编写本文的时候(2019-10-09)Netty的最新版本为4.1.42.Final 。引入依赖:
<dependency> <groupId>io.netty</groupId> <artifactId>netty-buffer</artifactId> <version>4.1.42.Final</version></dependency>复制代码定义解码器接口:
public interface RespDecoder<V>{V decode(ByteBuf buffer);}复制代码定义常量:
public class RespConstants { public static final Charset ASCII = StandardCharsets.US_ASCII; public static final Charset UTF_8 = StandardCharsets.UTF_8; 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 = ':'; public static final String EMPTY_STRING = ""; public static final Long ZERO = 0L; public static final Long NEGATIVE_ONE = -1L; public static final byte CR = (byte) 'r'; public static final byte LF = (byte) 'n'; public static final byte[] CRLF = "rn".getBytes(ASCII); public enum ReplyType { SIMPLE_STRING, ERROR, INTEGER, BULK_STRING, RESP_ARRAY }}复制代码下面的章节中解析模块的实现已经忽略第一个字节的解析,因为第一个字节是决定具体的数据类型 。
解析简单字符串
简单字符串类型就是单行字符串,它的解析结果对应的就是Java中的String类型 。解码器实现如下:
// 解析单行字符串public class LineStringDecoder implements RespDecoder<String> { @Override public String decode(ByteBuf buffer) { return CodecUtils.X.readLine(buffer); }}public enum CodecUtils { X; public int findLineEndIndex(ByteBuf buffer) { int index = buffer.forEachByte(ByteProcessor.FIND_LF); return (index > 0 && buffer.getByte(index - 1) == 'r') ? index : -1; } public String readLine(ByteBuf buffer) { int lineEndIndex = findLineEndIndex(buffer); if (lineEndIndex > -1) { int lineStartIndex = buffer.readerIndex(); // 计算字节长度 int size = lineEndIndex - lineStartIndex - 1; byte[] bytes = new byte[size]; buffer.readBytes(bytes); // 重置读游标为rn之后的第一个字节 buffer.readerIndex(lineEndIndex + 1); buffer.markReaderIndex(); return new String(bytes, RespConstants.UTF_8); } return null; }}public class RespSimpleStringDecoder extends LineStringDecoder { }复制代码
推荐阅读
- 番荔枝释迦有什么区别
- 看完这些“秋天的第一杯奶茶”,手里的蜜雪冰城忽然就不香了!
- 茶就是幸福
- 蓝翔技校校长跨省打架 蓝翔技校跨省打架
- 汽车加油不是加满就是加200?学习起来,别再加错了
- 大盘、消费、医药、半导体科技板块全线大跌,有闲钱就加点仓吧!
- 阳气不到的地方就会生病
- 5种日用品该扔就扔,当心毁了健康!
- 一什么相声? 相声就是要逗乐能把观众逗乐的就是好的相声
- 选择安溪读茶校 升学就业两不愁