Redis:我是如何与客户端进行通信的
江湖上说,天下武功,无坚不摧,唯快不破,这句话简直是为我量身定制。
我是一个Redis服务,最引以为傲的就是我的速度,我的 QPS 能达到10万级别。
在我的手下有数不清的小弟,他们会时不时到我这来存放或者取走一些数据,我管他们叫做客户端,还给他们起了英文名叫 Redis-client。
有时候一个小弟会来的非常频繁,有时候一堆小弟会同时过来,但是,即使再多的小弟我也能管理的井井有条。
有一天,小弟们问我。
想当年,为了不让小弟们拖垮我傲人的速度,在设计和他们的通信协议时,我绞尽脑汁,制定了下面的三条原则:
- 实现简单
- 针对计算机来说,解析速度快
- 针对人类来说,可读性强
为什么这么设计呢?先来看看一条指令发出的过程,首先在客户端需要对指令操作进行封装,使用网络进行传输,最后在服务端进行相应的解析、执行。
这一过程如果设计成一种非常复杂的协议,那么封装、解析、传输的过程都将非常耗时,无疑会降低我的速度。什么,你问我为什么要遵循最后一条规则?算是对于程序员们的馈赠吧,我真是太善良了。
我把创造出来的这种协议称为 RESP (REdis Serialization Protocol
)协议,它工作在 TCP 协议的上层,作为我和客户端之间进行通讯的标准形式。
说到这,我已经有点迫不及待想让你们看看我设计出来的杰作了,但我好歹也是个大哥,得摆点架子,不能我主动拿来给你们看。
所以我建议你直接使用客户端发出一条向服务器的命令,然后取出这条命令对应的报文来直观的看一下。话虽如此,不过我已经被封装的很严实了,正常情况下你是看不到我内部进行通讯的具体报文的,所以,你可以伪装成一个Redis的服务端,来截获小弟们发给我的消息。
实现起来也很简单,我和小弟之间是基于 Socket 进行通讯,所以在本地先启动一个ServerSocket
,用来监听Redis服务的6379端口:
public static void server() throws IOException {
ServerSocket serverSocket = new ServerSocket(6379);
Socket socket = serverSocket.accept();
byte[] bytes = new byte[1024];
InputStream input = socket.getInputStream();
while(input.read(bytes)!=0){
System.out.println(new String(bytes));
}
}
然后启动redis-cli
客户端,发送一条命令:
set key1 value1
这时,伪装的服务端就会收到报文了,在控制台打印了:
*3
$3
set
$4
key1
$6
value1
看到这里,隐隐约约看到了刚才输入的几个关键字,但是还有一些其他的字符,要怎么解释呢,是时候让我对协议报文中的格式进行一下揭秘了。
我对小弟们说了,对大哥说话的时候得按规矩来,这样吧,你们在请求的时候要遵循下面的规则:
*<参数数量> CRLF
$<参数1的字节长度> CRLF
<参数1的数据> CRLF
$<参数2的字节长度> CRLF
<参数2的数据> CRLF
...
$<参数N的字节长度> CRLF
<参数N的数据> CRLF
首先解释一下每行末尾的CRLF
,转换成程序语言就是\r\n
,也就是回车加换行。看到这里,你也就能够明白为什么控制台打印出的指令是竖向排列了吧。
在命令的解析过程中,set
、key1
、value1
会被认为是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。
多条批量回复
当服务端要返回多个值时,例如返回一些元素的集合时,就会使用多条批量回复。它以*
作为开头,后面是返回元素的个数,之后再跟随多个上面讲到过的批量回复。
到这里,基本上我和小弟之间的通讯协议就介绍完了。刚才你尝试了伪装成一个服务端,这会再来试一试直接写一个客户端来直接和我进行交互吧。
private static void client() throws IOException {
String CRLF="\r\n";
Socket socket=new Socket("localhost", 6379);
try (OutputStream out = socket.getOutputStream()) {
StringBuffer sb=new StringBuffer();
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 (InputStream inputStream = socket.getInputStream()) {
byte[] buff = new byte[1024];
int len = inputStream.read(buff);
if (len > 0) {
String ret = new String(buff, 0, len);
System.out.println("Recv:" + ret);
}
}
}
}
运行上面的代码,控制台输出:
Recv:+OK
上面模仿了客户端发出set
命令的过程,并收到了回复。依此类推,你也可以自己封装其他的命令,来实现一个自己的Redis客户端来和我进行通信。
不过记住,要叫我大哥。
最后
如果觉得对您有所帮助,小伙伴们可以点赞、转发一下~非常感谢
微信搜索:码农参上,来加个好友,点赞之交也好啊~
公众号后台回复“面试”、“导图”、“架构”、“实战”,获得免费资料哦~
Redis:我是如何与客户端进行通信的的更多相关文章
- Redis服务器和客户端的通信
Redis客户端使用RESP(Redis序列化协议)与Redis服务器进行通信,RESP在位于TCP之上,而网络模型上客户端和服务器是保持的双工的连接.如图1 而一个简单的请求/响应的串行通信模型如下 ...
- 基于 HTML5 WebGL 的 3D 服务器与客户端的通信
这个例子的初衷是模拟服务器与客户端的通信,我把整个需求简化变成了今天的这个例子.3D 机房方面的模拟一般都是需要鹰眼来辅助的,这样找产品以及整个空间的概括会比较明确,在这个例子中我也加了,这篇文章就算 ...
- 使用tcp+select实现客户端与客户端的通信
使用多路复用实现客户端与客户端进行通信: 原理:客户端只要一连上服务器,立马给服务器发送用户名,然后在服务端将newsocketfd存放在同一个结构体中,客户端先给服务器发送数据,然后通过服务器转发给 ...
- java多线程实现多客户端socket通信
一.服务端 package com.czhappy.hello.socket; import java.io.IOException; import java.net.InetAddress; imp ...
- 带你100% 地了解 Redis 6.0 的客户端缓存
近日 Redis 6.0.0 GA 版本发布,这是 Redis 历史上最大的一次版本更新,包括了客户端缓存 (Client side caching).ACL.Threaded I/O 和 Redis ...
- Redis系列(二)-Hredis客户端设计及开源
接上篇c#实现redis客户端(一),重新整理些了下. 阅读目录: 项目说明 Hredis设计图 单元测试场景 总结 项目说明 背景:因为有地方要用,而又没找到对sentinel良好支持的Net客户端 ...
- 一: WCF的服务端与客户端在通信时有三种模式:请求响应模式、数据报模式和双工通讯模式。
说一下基本知识, 1.如果想要将当前接口作为wcf服务器,则一定要加上[ServiceContract] 契约 2.要想将方法作为wcf服务方法发布给外部调用,则一定要加上 [Operatio ...
- Java使用多线程实现Socket多客户端的通信
要想详细了解socket,大家请自行百度,我这里只简单介绍. 在网络中,我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程.而socket编程就是为了完成两个唯一进程之间的通信(一个是客户端, ...
- Socket通信——服务器和客户端相互通信
所谓socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄.应用程序通常通过"套接字"向网络发出请求或者应答网络请求. Socket和S ...
随机推荐
- layui中的视频上传(PHP )
1.html中: <div class="layui-form-item"> <label class="layui-form-label"& ...
- mongodb 在PHP中常见问题及解决方法
1.$in needs an array 解决:查询用到in操作的时候,说in操作对应的不是我一个数组,或者数组索引不是以0开始的 方法:array_values重新生成一个索引为0开始的数组即可 $ ...
- 一、HttpRunner学习汇总
HttpRunner是一款面向Http和HTTPS协议的通用测试框架,只需编写维护一份YAML/JSON脚本即可实现自动化测试.性能测试.线上监控.持续集成等多种测试需求,是基于关键字驱动的框架,基于 ...
- Spring Boot & Cloud 轻量替代框架 Solon 1.3.35 发布
Solon 是一个微型的Java开发框架.强调,克制 + 简洁 + 开放的原则:力求,更小.更快.更自由的体验.支持:RPC.REST API.MVC.Micro service.WebSocket. ...
- 手写一个LRU工具类
LRU概述 LRU算法,即最近最少使用算法.其使用场景非常广泛,像我们日常用的手机的后台应用展示,软件的复制粘贴板等. 本文将基于算法思想手写一个具有LRU算法功能的Java工具类. 结构设计 在插入 ...
- Redis数据结构—跳跃表
目录 Redis数据结构-跳跃表 跳跃表产生的背景 跳跃表的结构 利用跳跃表查询有序链表 Redis跳跃表图示 Redis跳跃表数据结构 小结 Redis数据结构-跳跃表 大家好,我是白泽,最近学校有 ...
- 面试题:ArrayList、LinkedList、Vector三者的异同?
面试题:ArrayList.LinkedList.Vector三者的异同? 同:三个类都是实现了List接口(Collection的子接口之一),存储数据的特点相同:存储有序的.可重复的数据不同: * ...
- 是时候学习Linux了
前言: Linux是一个开源.免费的操作系统.其稳定性.安全性.处理多并发已经得到业界的认可,目前很多企业级的项目都会部署到Linux/unix系统上.如果你还不太了解Linux,希望本篇文章能够带你 ...
- training11.14
7-10 关于堆的判断 (25分) 题目:将一系列给定数字顺序插入一个初始为空的小顶堆H[].随后判断一系列相关命题是否为真.命题分下列几种: x is the root:x是根结点: x and ...
- [Qt] 信号和槽
信号与槽:是一种对象间的通信机制 观察者模式:当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal).这种发出是没有目的的,类似广播.如果有对象对这个信号感兴趣,它就 ...