作者:孤独烟 来源:微信订阅号(程序员孤独烟)
原文链接: https://mp.weixin.qq.com/s/IBynkex-FHhvJ3tmizvJhA
引言
redis大家在项目中经常会使用到。官网也提供了多语言的客户端供大家操作redis,如下图所示
但是,大家有思考过,这些语言操作redis背后的原理么?其实,某些大神会说
只要按照redis的协议,发送指定数据给redis,监听返回值即可。
确实,本质原理就是如上面那句话所说。博主也是以这种思路,去看了一下JAVA端的开源组件jedis的源码,然后取其精华,写了一个段能操作redis的demo,希望大家能有所收获。
jedis的github地址为:
https://github.com/xetorthio/jedis
有兴趣的童鞋,也可以自行去阅读。需要说明的是,这毕竟不是源码分析系列文章,不是带你去看jedis的源码。只是借鉴思路,写一个能操作redis的程序。
正文
首先,我先说一下操作思路,如下图所示:
说明一下,上面的第四步,就是我们自己要写的操作redis的小demo。
1、先写一个socket监听6379端口
这个程序很easy,度娘一下出来一大把
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.net.ServerSocket; import java.net.Socket; public class SocketServer { public static void main(String[] args) throws IOException { ServerSocket server = new ServerSocket(6379); Socket socket = server.accept(); byte[] chars = new byte[64]; socket.getInputStream().read(chars); System.out.println( new String(chars)); } } |
2、采用开源客户端,操作一次redis
我这里用的是JAVA语言的jedis,大家自己也可以用其他的任意语言组件,目的是为了采集客户端在操作redis时,发送出的数据
1 2 3 4 5 6 7 | import redis.clients.jedis.Jedis; public class RedisTest { public static void main(String[] args) { Jedis jedis = new Jedis( "127.0.0.1" , 6379); jedis.set( "eat" , "I want to eat" ); } } |
3、看看socket监听到的数据
在这里运行一下第二步的代码,查看第一步的代码输出的数据,如下所示
1 2 3 4 5 6 7 | *3 $3 SET $3 eat $13 I want to eat |
那么,这组数据是什么含义呢?
我们去官网进行查询。原来,redis的客户端和服务端采取了一种RESP协议。相应文档地址如下
https://redis.io/topics/protocol
RESP设计巧妙,它的前景在于下面三个方面:
Simple to implement.
Fast to parse.
Human readable.
那么+、-、*、:、$这些符号是什么意思呢?
官网有这么一段话
In RESP, the type of some data depends on the first byte: For Simple Strings the first byte of the reply is "+"
For Errors the first byte of the reply is "-"
For Integers the first byte of the reply is ":"
For Bulk Strings the first byte of the reply is "$"
For Arrays the first byte of the reply is "*"
Additionally RESP is able to represent a Null value using a special variation of Bulk Strings or Array as specified later.
In RESP different parts of the protocol are always terminated with "\r\n" (CRLF).
翻译过来
(1)简单字符串Simple Strings, 以 "+"加号 开头
(2)错误Errors, 以"-"减号 开头
(3)整数型Integer, 以 ":" 冒号开头
(4)大字符串类型Bulk Strings, 以 "$"美元符号开头,长度限制512M
(5)组类型Arrays,以 "*"星号开头
并且,协议的每部分都是以 "\r\n" (CRLF) 结尾的。
OK,那我们刚才的那一串的数据的意思就是(没有看到""\r\n",是因为已经转义了,所以无法看到):
1 2 3 4 5 6 7 | *3 数组包含3个元素,分别是SET、eat、I want to eat $3 是一个字符串,且字符串长度为3 SET 字符串的内容 $3 是一个字符串,且字符串长度为3 eat 字符串的内容 $13 是一个字符串,且字符串长度为13 I want to eat 字符串的内容 |
提问,如果是get命令,那么传输的RESP的内容长什么样?
比如有一个命令get eat,那么此时的内容如下所示
1 2 3 4 5 | *2 $3 GET $3 eat |
没有\r\n是因为已经转义了,所以没看到。其他的命令,可以自行测试。
4、尝试构造一样的数据操作redis
OK,经过上面的铺垫。我们如果要对redis做一个set操作,则构造set命令的RESP协议内容,并且利用socket编程,将这串内容发送给redis即可。这里用java的socket编程实现,用其他语言也是一样的。
我们有一个类 RedisClient.java
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; public class RedisClient { private Socket socket; private OutputStream outputStream; private InputStream inputStream; public RedisClient(String host, int port){ try { this .socket = new Socket(host,port); this .outputStream = this .socket.getOutputStream(); this .inputStream = this .socket.getInputStream(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public String set(final String key, String value) { StringBuilder sb = new StringBuilder(); //虽然输出的时候,会被转义,然而我们传送的时候还是要带上\r\n sb.append( "*3" ).append( "\r\n" ); sb.append( "$3" ).append( "\r\n" ); sb.append( "SET" ).append( "\r\n" ); sb.append( "$" ).append(key.length()).append( "\r\n" ); sb.append(key).append( "\r\n" ); sb.append( "$" ).append(value.length()).append( "\r\n" ); sb.append(value).append( "\r\n" ); byte[] bytes= new byte[1024]; try { outputStream.write(sb.toString().getBytes()); inputStream.read(bytes); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return new String(bytes); } public static void main(String[] args) { RedisClient redisClient = new RedisClient( "127.0.0.1" , 6379); String result = redisClient.set( "eat" , "please eat" ); System.out.println(result); } } |
上面的public String set(final String key, String value)方法中,显示了,我们假如需要对redis进行set操作,需要传输的RESP协议的内容。记住,一定要带\r\n字符作为结尾
OK,运行上述代码,你会发现你可以往redis中set数据了,并且控制台输出如下
1 | +OK |
提问,你自己会封装get命令么?
总结
本文以一种循序渐进的方式带领大家写了一个能操作redis的demo,希望大家有所收获。