江湖上说,江湖上,万物无敌,唯快不破。这句话是为我量身定做的。我是做Redis服务的,最引以为豪的是我的速度。我的QPS可以达到10万级。手下有无数小弟。他们会不时来找我存储或带走一些数据。我称他们为clients,并给他们起了英文名Redis-client。有时一个小弟会来的很频繁,有时一帮小弟会同时来,但不管有多少小弟,我都能管理得井井有条。有一天,我的兄弟们问我。当年,为了不拖慢我可观的速度,在和他们设计通信协议时,我绞尽脑汁制定了以下三个原则:计算机实现简单,人类解析快说,为什么设计的可读性这么好?让我们看一下发出指令的过程。首先,指令操作需要在客户端进行封装,通过网络传输,最后在服务端解析执行。如果把这个过程设计成一个非常复杂的协议,封装、解析、传输的过程会非常耗时,这无疑会拖慢我的速度。什么,你问我为什么要遵守最后一条规则?送给程序员的礼物,我太客气了。我创建的协议叫做RESP(REdisSerializationProtocol)协议,它工作在TCP协议的上层,作为我和客户端通信的标准形式。说起来,我已经迫不及待地想让你看看我设计的杰作了,但我毕竟是大哥,所以不得不摆架子,不能主动给你看。所以我建议大家直接使用客户端向服务端发送一个命令,然后把这个命令对应的消息拿出来直观的看一下。话虽如此,我已经被封装得很严实了。一般情况下,你是看不到我内部通讯的具体信息的。所以,可以伪装成一个Redis服务器来拦截小弟发来的消息。我的消息。实现起来也非常简单。我和弟弟的通信是基于Socket的,所以先在本地启动一个ServerSocket来监听Redis服务的6379端口:accept();byte[]bytes=newbyte[1024];InputStreaminput=socket.getInputStream();while(input.read(bytes)!=0){System.out.println(newString(bytes));}}然后启动redis-cli客户端,发送命令:setkey1value1这时候伪装的服务端会收到消息,在控制台打印:*3$3set$4key1$6value1看到这里,隐约看到我的那几个关键字刚刚输入,但是还有一些其他的字符,怎么解释呢,我该在协议报文中揭晓格式了。我告诉弟弟们,跟哥哥说话要守规矩。所以,在请求时应遵循以下规则:*<参数个数>CRLF$<参数1字节长度>CRLF<参数1数据>CRLF$<参数2字节长度>CRLF<参数2数据>CRLF。..$<参数N的字节长度>CRLF<参数N的数据>CRLF先解释一下每行末尾的CRLF,转换成编程语言就是\r\n,即回车换行。看到这里,你也能理解为什么控制台打印出来的命令是竖排的了。命令的解析过程中,set、key1、value1会被认为是3个参数,所以参数个数为3,对应第一行的*3。第一个参数集,长度为3对应$3;第二个参数key1,长度为4对应$4;第三个参数value1,长度为6,对应$6。每个参数长度的下一行对应实际参数数据。看到这里,是不是就很容易理解命令转换为协议报文的过程了呢?小弟向我发出请求后,作为大哥,我会回复小弟的请求并附上说明,而且我要按照回复的内容进行分类,否则小弟是看不懂我的说明的.简单字符串简单字符串回复只有一行回复。回复内容以+开头,不允许换行,以\r\n结尾。有很多命令在成功执行后只会回复OK。使用这种格式,可以有效地减少传输和分析的开销。错误回复在RESP协议中,错误回复可以看作是简单字符串回复的变体,它们之间的格式也很相似,唯一的区别是第一个字符以-开头,而错误的内容reply通常是错误类型和正确的描述错误的字符串。错误回复出现在一些异常场景中,比如发送了一条错误指令,操作数个数错误,就会进行错误回复。客户端收到错误回复后,与简单的字符串回复区别为异常。IntegerreplyIntegerreply也被广泛使用,它以:开头,以\r\n结尾,用于返回一个整数。比如执行incr返回自增值,执行llen返回数组长度,或者用exists命令返回的0或者1作为判断key是否存在的依据,所有的这些使用整数回复。批量回复批量回复是多行字符串的回复。它以$开头,后面是要发送的字节长度,然后是\r\n,然后发送实际数据,最后以\r\n结束。如果要回复的数据不存在,则回复长度为-1。多批回复当服务器要返回多个值时,比如返回一个元素集合,就会使用多批回复。以*开头,后面是返回元素的个数,后面是上面提到的多个批量回复。至此,基本上我和弟弟的通信协议就介绍完了。刚才你假装是服务端,那我们再试试,写个客户端直接和我交互。privatestaticvoidclient()throwsIOException{StringCRLF="\r\n";Socketsocket=newSocket("localhost",6379);try(OutputStreamout=socket.getOutputStream()){StringBuffersb=newStringBuffer();sb.append("*3").append(CRLF).append("$3").append(CRLF).append("set").append(CRLF).append("$4").append(CRLF).append("key1").append(CRLF).append("$6").append(CRLF).append("value1").append(CRLF);out.write(sb.toString().getBytes());out.flush();try(InputStreaminputStream=socket.getInputStream()){byte[]buff=newbyte[1024];intlen=inputStream.read(buff);if(len>0){Stringret=newString(buff,0,len);System.out.println("Recv:"+ret);}}}}运行上面的代码,控制台输出:Recv:+OK上面模拟了客户端发出set命令,并收到回复的过程。以此类推,你也可以自己封装其他的命令,实现自己的Redis客户端,小弟和我交流一下。但是记住,叫我大哥。
