1.背景
在使用jedis客户端操作redis数据的过程中,发现了一个通用的方法sendCommand,封装了redis的所有命令,代码路径:redis.clients.jedis.BinaryJedis#sendCommand(redis.clients.jedis.commands.ProtocolCommand, byte[]...),具体代码如下
public Object sendCommand(ProtocolCommand cmd, byte[]... args) {
this.checkIsInMultiOrPipeline();
this.client.sendCommand(cmd, args);
return this.client.getOne();
}
2.封装使用sendCommand方法
封装代码
public void sendCommandWrapper(List<List<byte[]>> commandList) {
Jedis jedis = new Jedis("127.0.0.1",3306);
for (List<byte[]> command : commandList) {
byte[][] splitResult = command.stream().toArray(byte[][]::new);
for (byte[] cmd : command) {
jedis.sendCommand(() -> splitResult[0],
Arrays.copyOfRange(splitResult, 1, splitResult.length));
}
}
}
方法入参
参数类似如下
[[["set"],["a"],["v"]],[["set"],["a"],["b"]]],[["set"],["a"],["1"]]]]
然后把每个字符换成byte
[[[115, 101, 116], [97], [118]], [[115, 101, 116], [97], [98]], [[115, 101, 116], [97], [49]]]
3.存在的问题
sendCommand方法可以传递string的命令转成的byte[]参数,但是其中有两个特例
需要注意的特例
zset的score是有符号的浮点型
Pexpireat key 时间戳:时间戳是long型
问题归纳
以“Pexpireat key 时间戳”命令为例,long型时间戳通过redis协议到redis底层存储是byte[]的方式,使用sendCommand的时候传递的命令入参也是byte[],但是这两种byte[]不是同一种byte[]。
这两种 byte[] 不同的主要原因是它们所表示的含义不同。
在使用 Redis 命令时,我们需要将命令的参数转换为 byte[] 格式,以便可以发送给 Redis 服务器。这里的 byte[] 实际上是字符串的字节数组表示。Redis 协议是基于文本的,即它要求在与服务器通信时发送文本字符串,因此发送给 Redis 服务器的 byte[] 实际上是表示字符串的字节数组。
String.valueOf(redisTTLLong).getBytes(StandardCharsets.UTF_8)
而在 Redis 底层存储中,时间戳所表示的含义是一个数字,而不是一个字符串。在底层存储中,Redis 将时间戳转换为了二进制形式,即一个 byte[] 数组。这个 byte[] 数组表示的是一个数字,它在内存中以二进制补码的形式存储。
//比如从底层取出来的byte,想转回时间戳需要的转换逻辑: byte转long
private static long convertRedisTTLToTimestamp(byte[] ttlBytes) {
// Convert the byte array to an 8-byte binary string
byte[] binaryBytes = new byte[8];
for (int i = 0; i < 8; i++) {
binaryBytes[i] = i < ttlBytes.length ? ttlBytes[i] : 0;
if (binaryBytes[i] < 0) {
binaryBytes[i] += 256;
}
}
// Rearrange the binary string according to the big endian byte order
long timestamp = 0L;
for (int i = 0; i < 8; i++) {
timestamp = (timestamp << 8) + (binaryBytes[i] & 0xff);
}
// returns the converted timestamp in milliseconds
return timestamp;
}
因此,这两种 byte[] 不同的原因在于它们所表示的含义不同。一个表示字符串,一个表示数字的二进制补码。虽然它们都是 byte[] 类型,但它们的内部存储和解析方式是不同的。
4.总结
使用jedis的sendCommand命令时,要记住要传入的参数,原本属于数值类型时,需要转byte[]数组是直接转成字符串的字节数组。也就是把long时间戳通过String.valueOf(redisTTLLong).getBytes(StandardCharsets.UTF_8)命令转换出来的byte[]。
请勿与long转byte等相关的补码、大小端等概念混淆。