我们要在 Java 中操作 Redis,怎么做呢?首先我们先来了解一下 Redis Serialization Protocol (Redis 序列化协议),这个是 Redis 提供的一种,客户端和 Redis 服务端通信传输的编码协议,服务端收到消息后,会基于这个约定编码进行解码。
测试使用的redis是安装在虚拟机中的。
-
打开 Wireshark 工具,对 VMnet8 这个网络进行抓包
-
增加过滤条件
ip.dst_host==192.168.221.128 and tcp.port in {6379} -
使用 RDM 工具连接到 Redis Server 进行 key-value 操作,比如执行 set name cc
-
通过 Wireshark 工具监控数据包内容,可以看到实际发出的数据包是:
*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$3\r\ncc- 其中
*3代表参数个数,set name cc, 表示三个参数。 $3表示属性长度,表示包含 3 个字符。
- 其中
客户端和服务器发送的命令或数据一律以 \r\n (CRLF 回车 + 换行)结尾。
所以基于这个协议,我们可以自己实现一个 Java 客户端。
自定义客户端
下面我们通过抓包相关的命令,了解 Redis 客户端的工作机制。
常量池
定义相关常量,包括协议中的相关常用符号及get、set方法。
public class CommandConstant {
public static final String START = "*";
public static final String LENGTH = "$";
public static final String LINE = "\r\n";
public enum CommandEnum {
SET,
GET
}
}
CustomClientSocket
CustomClientSocket 用来建立网络通信连接,并且发送数据指定到 RedisServer;
public class CustomerRedisClientSocket {
private Socket socket;
private InputStream inputStream;
private OutputStream outputStream;
public CustomerRedisClientSocket(String ip, int port) {
try {
socket = new Socket(ip, port);
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
public void send(String cmd) {
try {
outputStream.write(cmd.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
public String read() {
byte[] bytes = new byte[1024];
int count = 0;
try {
count = inputStream.read(bytes);
} catch (IOException e) {
e.printStackTrace();
}
return new String(bytes, 0, count);
}
}
客户端
public class CustomerRedisClient {
private CustomerRedisClientSocket customerRedisClientSocket;
//建立连接
public CustomerRedisClient(String host, int port) {
customerRedisClientSocket = new CustomerRedisClientSocket(host, port);
}
//设置
public String set(String key, String value) {
customerRedisClientSocket.send(convertToCommand(CommandConstant.CommandEnum.SET, key.getBytes(), value.getBytes()));
return customerRedisClientSocket.read(); //在等待返回结果的时候,是阻塞的
}
//获取
public String get(String key) {
customerRedisClientSocket.send(convertToCommand(CommandConstant.CommandEnum.GET, key.getBytes()));
return customerRedisClientSocket.read();
}
//根据协议转换参数
public static String convertToCommand(CommandConstant.CommandEnum commandEnum, byte[]... bytes) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(CommandConstant.START).append(bytes.length + 1).append(CommandConstant.LINE);
stringBuilder.append(CommandConstant.LENGTH).append(commandEnum.toString().length()).append(CommandConstant.LINE);
stringBuilder.append(commandEnum.toString()).append(CommandConstant.LINE);
for (byte[] by : bytes) {
stringBuilder.append(CommandConstant.LENGTH).append(by.length).append(CommandConstant.LINE);
stringBuilder.append(new String(by)).append(CommandConstant.LINE);
}
return stringBuilder.toString();
}
}
测试
通过自定义客户端向redis存值并读取。
public class MainClient {
public static void main(String[] args) {
CustomerRedisClient customerRedisClient = new CustomerRedisClient("127.0.0.1", 6379);
System.out.println(customerRedisClient.set("customer", "jack"));
System.out.println(customerRedisClient.get("customer"));
}
}
$4 代表长度为4,代表返回结果jack的长度。
总结
当我们了解了相关原理后,可以发现我们手写一个自定义客户端其实不是想象中的那么难,虽然我们的这个自定义客户端很简陋…hahhaha…