[转帖]Redis:我是如何与客户端进行通信的

redis,如何,客户端,进行,通信 · 浏览次数 : 0

小编点评

**指令回复:** ``` Recv:OK ``` **说明:** * 指令成功执行后会返回`OK`字符串。 * 错误回复以`-`开头,并以`OK`或错误类型及描述的字符串结尾。 * 整数回复以`:`开头,以`\r`结束,用于返回一个整数。 * 批量回复以`$`开头,后面是元素的个数,之后再跟随多个上面讲到过的批量回复。

正文

江湖上说,天下武功,无坚不摧,唯快不破,这句话简直是为我量身定制。

 

我是一个Redis服务,最引以为傲的就是我的速度,我的 QPS 能达到10万级别。

在我的手下有数不清的小弟,他们会时不时到我这来存放或者取走一些数据,我管他们叫做客户端,还给他们起了英文名叫 Redis-client。

有时候一个小弟会来的非常频繁,有时候一堆小弟会同时过来,但是,即使再多的小弟我也能管理的井井有条。

有一天,小弟们问我。

想当年,为了不让小弟们拖垮我傲人的速度,在设计和他们的通信协议时,我绞尽脑汁,制定了下面的三条原则:

  • 实现简单

  • 针对计算机来说,解析速度快

  • 针对人类来说,可读性强

为什么这么设计呢?先来看看一条指令发出的过程,首先在客户端需要对指令操作进行封装,使用网络进行传输,最后在服务端进行相应的解析、执行。

这一过程如果设计成一种非常复杂的协议,那么封装、解析、传输的过程都将非常耗时,无疑会降低我的速度。什么,你问我为什么要遵循最后一条规则?算是对于程序员们的馈赠吧,我真是太善良了。

我把创造出来的这种协议称为 RESP (REdis Serialization Protocol)协议,它工作在 TCP 协议的上层,作为我和客户端之间进行通讯的标准形式。

说到这,我已经有点迫不及待想让你们看看我设计出来的杰作了,但我好歹也是个大哥,得摆点架子,不能我主动拿来给你们看。

所以我建议你直接使用客户端发出一条向服务器的命令,然后取出这条命令对应的报文来直观的看一下。话虽如此,不过我已经被封装的很严实了,正常情况下你是看不到我内部进行通讯的具体报文的,所以,你可以伪装成一个Redis的服务端,来截获小弟们发给我的消息。

实现起来也很简单,我和小弟之间是基于 Socket 进行通讯,所以在本地先启动一个ServerSocket,用来监听Redis服务的6379端口:

  1. public static void server() throws IOException {
  2.     ServerSocket serverSocket = new ServerSocket(6379);
  3.     Socket socket = serverSocket.accept();
  4.     byte[] bytes = new byte[1024];
  5.     InputStream input = socket.getInputStream();
  6.     while(input.read(bytes)!=0){
  7.         System.out.println(new String(bytes));
  8.     }
  9. }

然后启动redis-cli客户端,发送一条命令:

set key1 value1

这时,伪装的服务端就会收到报文了,在控制台打印了:

  1. *3
  2. $3
  3. set
  4. $4
  5. key1
  6. $6
  7. value1

看到这里,隐隐约约看到了刚才输入的几个关键字,但是还有一些其他的字符,要怎么解释呢,是时候让我对协议报文中的格式进行一下揭秘了。

我对小弟们说了,对大哥说话的时候得按规矩来,这样吧,你们在请求的时候要遵循下面的规则:

  1. *<参数数量> CRLF
  2. $<参数1的字节长度> CRLF
  3. <参数1的数据> CRLF
  4. $<参数2的字节长度> CRLF
  5. <参数2的数据> CRLF
  6. ...
  7. $<参数N的字节长度> CRLF
  8. <参数N的数据> CRLF

首先解释一下每行末尾的CRLF,转换成程序语言就是\r\n,也就是回车加换行。看到这里,你也就能够明白为什么控制台打印出的指令是竖向排列了吧。

在命令的解析过程中,setkey1value1会被认为是3个参数,因此参数数量为3,对应第一行的*3

第一个参数set,长度为3对应$3;第二个参数key1,长度为4对应$4;第三个参数value1,长度为6对应$6。在每个参数长度的下一行对应真正的参数数据。

看到这,一条指令被转换为协议报文的过程是不是就很好理解了?

当小弟对我发送完请求后,作为大哥,我就要对小弟的请求进行指令回复了,而且我得根据回复内容进行一下分类,要不然小弟该搞不清我的指示了。

简单字符串

简单字符串回复只有一行回复,回复的内容以+作为开头,不允许换行,并以\r\n结束。有很多指令在执行成功后只会回复一个OK,使用的就是这种格式,能够有效的将传输、解析的开销降到最低。

错误回复

在RESP协议中,错误回复可以当做简单字符串回复的变种形式,它们之间的格式也非常类似,区别只有第一个字符是以-作为开头,错误回复的内容通常是错误类型及对错误描述的字符串。

错误回复出现在一些异常的场景,例如当发送了错误的指令、操作数的数量不对时,都会进行错误回复。在客户端收到错误回复后,会将它与简单字符串回复进行区分,视为异常。

整数回复

整数回复的应用也非常广泛,它以:作为开头,以\r\n结束,用于返回一个整数。例如当执行incr后返回自增后的值,执行llen返回数组的长度,或者使用exists命令返回的0或1作为判断一个key是否存在的依据,这些都使用了整数回复。

批量回复

批量回复,就是多行字符串的回复。它以$作为开头,后面是发送的字节长度,然后是\r\n,然后发送实际的数据,最终以\r\n结束。如果要回复的数据不存在,那么回复长度为-1。

多条批量回复

当服务端要返回多个值时,例如返回一些元素的集合时,就会使用多条批量回复。它以*作为开头,后面是返回元素的个数,之后再跟随多个上面讲到过的批量回复。

到这里,基本上我和小弟之间的通讯协议就介绍完了。刚才你尝试了伪装成一个服务端,这会再来试一试直接写一个客户端来直接和我进行交互吧。

  1. private static void client() throws IOException {
  2.     String CRLF="\r\n";
  3.     Socket socket=new Socket("localhost"6379);
  4.     try (OutputStream out = socket.getOutputStream()) {
  5.         StringBuffer sb=new StringBuffer();
  6.         sb.append("*3").append(CRLF)
  7.                 .append("$3").append(CRLF).append("set").append(CRLF)
  8.                 .append("$4").append(CRLF).append("key1").append(CRLF)
  9.                 .append("$6").append(CRLF).append("value1").append(CRLF);
  10.         out.write(sb.toString().getBytes());
  11.         out.flush();
  12.         try (InputStream inputStream = socket.getInputStream()) {
  13.             byte[] buff = new byte[1024];
  14.             int len = inputStream.read(buff);
  15.             if (len > 0) {
  16.                 String ret = new String(buff, 0, len);
  17.                 System.out.println("Recv:" + ret);
  18.             }
  19.         }
  20.     }
  21. }

运行上面的代码,控制台输出:

Recv:+OK

上面模仿了客户端发出set命令的过程,并收到了回复。依此类推,你也可以自己封装其他的命令,来实现一个自己的Redis客户端来和我进行通信。

不过记住,要叫我大哥。

与[转帖]Redis:我是如何与客户端进行通信的相似的内容:

[转帖]Redis:我是如何与客户端进行通信的

江湖上说,天下武功,无坚不摧,唯快不破,这句话简直是为我量身定制。 我是一个Redis服务,最引以为傲的就是我的速度,我的 QPS 能达到10万级别。 在我的手下有数不清的小弟,他们会时不时到我这来存放或者取走一些数据,我管他们叫做客户端,还给他们起了英文名叫 Redis-client。 有时候一个

[转帖]Redis6通信协议升级至RESP3,一口气看完13种新数据类型

原创:微信公众号 码农参上,欢迎分享,转载请保留出处。 在前面的文章 Redis:我是如何与客户端进行通信的 中,我们介绍过RESP V2版本协议的规范,RESP的全程是Redis Serialization Protocol,基于这个实现简单且解析性能优秀的通信协议,Redis的服务端与客户端可以

[转帖]Redis进阶实践之十八 使用管道模式提高Redis查询的速度

https://www.cnblogs.com/PatrickLiu/p/8580301.html 一、引言 学习redis 也有一段时间了,该接触的也差不多了。后来有一天,以前的同事问我,如何向redis中批量的增加数据,肯定是大批量的,为了这主题,我又重新找起了解决方案。目前的解决方案大都是从官

[转帖]Redis进阶实践之十八 使用管道模式提高Redis查询的速度

https://www.cnblogs.com/PatrickLiu/p/8580301.html 一、引言 学习redis 也有一段时间了,该接触的也差不多了。后来有一天,以前的同事问我,如何向redis中批量的增加数据,肯定是大批量的,为了这主题,我又重新找起了解决方案。目前的解决方案大都是从官

[转帖]庐山真面目之十三微服务架构中如何在Docker上使用Redis缓存

https://www.cnblogs.com/PatrickLiu/p/14518160.html 一、介绍 1、开始说明 在微服务器架构中,有一个组件是不能少的,那就是缓存组件。其实来说,缓存组件,这个叫法不是完全正确,因为除了缓存功能,它还能完成其他很多功能。我就不隐瞒了,今天我们要探讨的就是

[转帖]Redis服务器启动之后3个警告信息的解决方案

https://www.cnblogs.com/PatrickLiu/p/8448230.html 今天是年前最后一篇文章了,不想写太多的东西,就写一些有关Redis相关问题的解决方案。当我们启动了Redis服务器之后,会看到3个警告,如果没看到,那是很好的,但是我看到了。看到了就不能不管,所以就好

[转帖]【Jmeter】Jmeter压力测试工具安装及使用教程(redis测试)

摘自:https://www.cnblogs.com/monjeo/p/9330464.html 一、Jmeter下载 进入官网:http://jmeter.apache.org/ 1.第一步进入官网如下图 2.选择进行下载,下载下来为一个压缩包,解压即可。 3.我下载的是jmeter4.0版本,对

[转帖]Day742.Redis阻塞主线程的问题 -Redis 核心技术与实战

Redis阻塞主线程的问题 Hi,我是阿昌,今天学习记录的内容是Redis阻塞主线程的问题。 Redis 之所以被广泛应用,很重要的一个原因就是它支持高性能访问。 也正因为这样,我们必须要重视所有可能影响 Redis 性能的因素(例如命令操作、系统配置、关键机制、硬件配置等),不仅要知道具体的机制,

[转帖]高性能IO模型:为什么单线程Redis能那么快?

https://zhuanlan.zhihu.com/p/596170085 你好,我是蒋德钧。 今天,我们来探讨一个很多人都很关心的问题:“为什么单线程的Redis能那么快?” 首先,我要和你厘清一个事实,我们通常说,Redis是单线程,主要是指Redis的网络IO和键值对读写是由一个线程来完成的

[转帖]为什么我Redis中key惊现“乱码”?

为什么Redis中key会惊现“乱码”? 最近在做一个秒杀项目,过程中大量应用到了redis。 而我在用ElasticJob进行数据化初始化到Redis数据库时发现这些key都出现了一段前缀“乱码”。 数据结构为Hash,可以观察到hashkey也带有前缀“乱码” 这究竟是怎么回事呢?原来问题出在这