当前位置: 首页 > 科技观察

你知道Redis使用什么协议吗?

时间:2023-03-17 14:46:54 科技观察

有个小伙伴面试回来说面试官问了他一些Redis的问题,他好像没有回答。我说,你不是很会用Redis吗?你有什么烦恼?他说,事情是这样的。一开始问了一些基础的问题,比如Redis的几种基本数据类型和使用场景,还有一些关于主从复制和集群的问题。这些都很好。然后问了Redis的两种持久化方式。他表示,相对于RDB和AOF,RDB数据文件小,恢复速度快,但对性能有影响,不适合实时存储。AOF是现在最常用的持久化方式。它的优势之一是实时性,对Redis半体性能影响最小。然后面试又问了,知道AOF持久化后文件的格式吗?答:好像是文本文件?好的,文本文件,你知道它有什么规则吗?或者,它与Redis有协议吗?答:啊,这个,这个,我不清楚。下面我们来看看AOF和RESP协议的关系,先从两种持久化方式说起。什么是RESP协议?实现一个简单的协议分析命令行工具。让我们从坚持开始。虽然一提到Redis,大家首先想到的就是缓存,但是Redis并不仅仅是缓存那么简单。它被定位为内存数据库。它可以存储各种类型的数据结构,也可以作为一个简单的消息队列。既然是数据库,持久化功能必不可少。Redis的两种持久化方式Redis提供了两种持久化方式,一种是RDB方式,一种是AOF方式。AOF是目前比较流行的持久化方案。RDB模式RDB持久化是通过快照将内存中数据集的快照在指定的时间间隔内写入磁盘。它以压缩二进制文件的形式出现。可以将快照复制到其他服务器以创建相同数据的服务器副本,或在重新启动服务器后恢复数据。RDB是Redis默认的持久化方式,也是早期版本必备的解决方案。RDB由以下参数控制。#设置dump的文件名dbfilenamedump.rdb#持久化文件的存放目录dir./#900秒内,如果至少有一个key改变,会自动触发bgsave命令创建快照save9001#300秒内,如果至少有10个key发生变化,会自动触发bgsave命令创建快照save30010#60秒内,如果至少有10000个key发生变化,会自动触发bgsave命令创建快照save6010000持久化过程的上面在配置文件中提到了几种触发持久化的机制,比如900秒、300秒、60秒,当然也可以通过手动执行命令save或者bgsave来触发。bgsave是非阻塞版本,通过fork子进程生成快照,而save会阻塞主进程,不推荐使用。1、首先触发bgsave命令;2.父进程fork子进程。这一步是比较重量级的操作,也是RDB方式性能不如AOF的重要原因;3.父进程fork出子进程后,可以正常对应客户端发送的其他命令;4、子进程开始持久化,完成对已有数据的快照存储;5、子进程完成操作后,通知父进程;RDB的优点:RDB是一个紧凑的压缩二进制文件,代表Redis数据在某个时间点的快照。非常适合备份、全量拷贝等场景。例如,每6小时进行一次bgsave备份,将RDB文件复制到远程机器或文件系统(如hdfs),用于容灾。Redis加载RDB恢复数据的速度比AOF快很多。RDB的缺点:RDB数据无法实现实时持久化/秒级持久化。因为bgsave每次运行都需要执行fork操作创建子进程,属于重量级操作,频繁执行成本太高。RDB文件以特定的二进制格式保存。在Redis版本的演进过程中,存在多种格式的RDB版本。存在旧版本Redis服务无法兼容新版本RDB格式的问题。AOF模式AOF由以下??参数控制。#appendonly参数启用AOF持久化appendonlyyes#AOF持久化文件名,默认为appendonly.aofappendfilename"appendonly.aof"#AOF文件的保存位置与RDB文件的位置相同,均由dir参数dir设置。/#同步策略#appendfsyncalwaysappendfsynceverysec#appendfsyncno#aofrewrite是否同步no-appendfsync-on-rewriteno#rewrite触发配置auto-aof-rewrite-percentage100auto-aof-rewrite-min-size64mb#loadaoferror如何处理aof-load-truncatedyes#文件重写策略aof-rewrite-incremental-fsyncyes针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决。AOF也是目前最多的进程持久化方式。AOF(appendonlyfile),将每条写入命令记录在独立的日志中,重启时重新执行AOF文件中的命令恢复数据。1、所有写命令都会追加到aof_buf(buffer)中;2、AOF缓冲区会根据相应的策略同步到硬盘;3、随着AOF文件越来越大,需要周期性地重新加载AOF文件Write,以达到压缩的目的;4、当Redis服务器重启时,可以加载AOF文件进行数据恢复;AOF文件中存储了什么?我在本地测试redis环境随便刷了几条命令,然后打开appendonly.aof文件查看,发现里面的内容大概是下面这个样子。RESP协议Redis客户端与服务端通信使用RESP协议,RESP协议是专门为Redis设计的通信协议,但也可以用于其他客户端-服务器通信场景。RESP协议具有以下特点:实现简单;快速解析;可读;客户端向服务端发送命令,服务端收到命令后进行解析,然后执行相应的逻辑,然后返回给客户端。当然,这个Onesend和onereply都是使用了RESP协议的格式特性。一般来说,我们会使用redis-cli或者一些客户端工具来连接Redis服务器。./redis-cli那么整个交互过程的命令发送和返回结果如下,绿色部分为发送命令,红色部分为返回结果。这是我们都非常熟悉的部分。然而,这并没有给出RESP协议的真实情况。尝试使用远程登录。RESP是基于TCP协议实现的,所以除了使用各种客户端工具和Redis提供的redis-cli工具外,还可以使用telnet查看。可以使用telnet查看RESP返回的原始数据格式。我本地的Redis默认使用6379端口,没有设置requirepass。让我们尝试使用telnet连接。telnet127.0.0.16379然后执行和之前一样的命令,发送和返回结果如下,绿色部分是发送的命令,红色部分是返回结果。怎么样,有些命令的返回还可以,但是像getstr:hello,除了world值本身,返回结果里多了一行$5,是不是有点乱。协议规则请求命令客户端向服务器端发送命令的规则如下:*<参数个数>CRLF$<参数1中的字节数>CRLF<参数1中的数据>CRLF...$<个数参数N中的字节数>CRLF<参数N的数据>CRLFRESP使用\r\n作为分隔符,表示该命令具体参数的个数。从命令来看,所有分隔的空格代表一个参数,比如setstr:helloworld第一个命令是3个参数,会标明字符个数和每个参数的具体内容。以这个命令为例,对应RESP协议规则,会是这样的:*3\r\n$3\r\nset\r\n$9str:hello\r\n$5world\r\nservice的客户端回复Redis命令并返回几种不同类型的回复。通过查看服务器发回的数据的第一个字节,可以判断是什么类型的回复:1、状态回复(statusreply)的第一个字节是“+”,比如ping命令的回复,+PONG\r\n2.错误回复的第一个字节是“-”。比如输入redis中不存在的命令,或者某些命令设置了错误的参数,比如输入auth,auth命令后需要一个password参数,如果不输入,会返回错误响应类型。-ERR'auth'命令的参数数量错误\r\n3。整数回复(integerreply)的第一个字节为“:”,如INCR和DECR自增自减命令,返回结果如下:2\r\n4。批量回复(bulkreply)的第一个字节是“$”。例如对字符串类型$5\r\nworld\r\n进行get操作,$后面的数字5表示返回结果为5个字符,后面是返回结果的实际内容。5.multibulkreply的第一个字节是“*”,比如LRANGEkeystartstop或者hgetall等返回多个结果的命令,比如lrange命令返回的结果:*2\r\n$6\r\nnews-2\r\n$6\r\nnews-1\r\n多批回复,格式同上文客户端发送的命令。实现一个简单的Redis交互工具了解了Redis的协议规则后,我们就可以自己写一个简单的客户端了。当然,通过官网我们可以看到已经有多种语言,而且每种语言都有不止一种客户端工具。比如Java语言的客户端就那么多,其中用得最多的应该是Jedis。既然已经有了这么好用的轮子,当然就不用再重复轮子了,主要是为了加深印象。RESP协议基于TCP协议,可以通过套接字进行连接。publicSocketcreateSocket()throwsIOException{Socketsocket=null;try{socket=newSocket();socket.setReuseAddress(true);socket.setKeepAlive(true);socket.setTcpNoDelay(true);socket.setSoLinger(true,0);socket.connect(newInetSocketAddress(host,port),DEFAULT_TIMEOUT);socket.setSoTimeout(DEFAULT_TIMEOUT);outputStream=socket.getOutputStream();inputStream=socket.getInputStream();returnsocket;}catch(Exceptionex){if(socket!=null){socket.close();}throwex;}}然后就是分析返回结果的字符串了。我制作的工具非常简单。以下是一些简单命令的返回输出。代码已经放在github上了,有兴趣的可以clone看看。https://github.com/huzhicheng/medis本文转载自微信公众号“古风筝”,可通过以下二维码关注。转载本文请联系风筝古筝公众号。