新学redis,某天用scan操作做个删除某些键的小测试,碰到如下报错。
after the scan-action, the cursor equals : 㠵㔰�
Exception in thread "main" redis.clients.jedis.exceptions.JedisDataException: ERR invalid cursor
at redis.clients.jedis.Protocol.processError(Protocol.java:127)
at redis.clients.jedis.Protocol.process(Protocol.java:161)
at redis.clients.jedis.Protocol.read(Protocol.java:215)
at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:340)
at redis.clients.jedis.Connection.getRawObjectMultiBulkReply(Connection.java:285)
at redis.clients.jedis.Connection.getObjectMultiBulkReply(Connection.java:291)
at redis.clients.jedis.Jedis.scan(Jedis.java:3186)
at com.bobo.test.redis.RedisTester.getScan(RedisTester.java:33)
即scanResult.getStringCursor()返回的不是一个可以转化为int的字符串了。导致第二次用scan遍历时候,游标(cursor)参数传入错误。即 redis.clients.jedis.exceptions.JedisDataException: ERR invalid cursor。
以下为问题的前因后果
用keys test* 命令可以返回redis中所有的以test开头的key。但是会引起问题就是卡顿,因为redis是单线程执行,而keys是要拿所有的key来做比对。实际测试当有12W的keys时候,在本机执行就需要25ms以上。于是有了scan来代替keys解决卡顿问题。
scan用法scan基本命令格式如下scan cursor [MATCH pattern] [COUNT count]
示例如下
可见该命令返回有两部分:
第一部分为“49152”—下次执行scan操作时候的游标起点;
第二部分对应的就是我们想要扫描的key,即符合test11*的key。
同时可见:COUNT 10并非返回10个以test11开始的key,而是限定服务器单次遍历的字典槽位数量(约等于)。
protected static Jedis jedis = null;
protected static void initJedis() {
jedis = new Jedis("localhost", 6379);
}
protected static void closeJedis() {
jedis.close();
}
/**
* 测试scan操作来代替keys操作
* @param jedis
* @param key
* @return
*/
private static List getScan(Jedis jedis, String key) {
List keysList = new ArrayList();
ScanParams paramas = new ScanParams();
paramas.match(key);
paramas.count(200);
String cursor = "0";
while (true) {
ScanResult scanResult = jedis.scan(cursor, paramas);
List selectedElements = scanResult.getResult();
if(selectedElements!=null && !selectedElements.isEmpty())
keysList.addAll(selectedElements);
cursor = scanResult.getStringCursor();
System.out.println("after the scan-action, the cursor equals : "+ cursor);
if("0".equals(cursor)) {
break;
}
}
return keysList;
}
public static void main(String[] args) throws InterruptedException {
initJedis();
// /**********测试scan操作************
List selectedKeys = RedisTester.getScan(jedis, "test1111*");
if(!selectedKeys.isEmpty()) {
int keysCount = selectedKeys.size();
System.out.println("获取到的key有:"+keysCount+"个。");
for(String keyName : selectedKeys) {
jedis.del(keyName);
}
} else {
System.out.println("没有扫描到对应的key");
}
// ***********************************/
closeJedis();
}
然而代码并没有按照预计中的操作执行,即扫描所有test*的key,然后进行删除操作。而是报错了,报错信息如下
after the scan-action, the cursor equals : 㠵㔰�
Exception in thread "main" redis.clients.jedis.exceptions.JedisDataException: ERR invalid cursor
at redis.clients.jedis.Protocol.processError(Protocol.java:127)
at redis.clients.jedis.Protocol.process(Protocol.java:161)
at redis.clients.jedis.Protocol.read(Protocol.java:215)
at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:340)
at redis.clients.jedis.Connection.getRawObjectMultiBulkReply(Connection.java:285)
at redis.clients.jedis.Connection.getObjectMultiBulkReply(Connection.java:291)
at redis.clients.jedis.Jedis.scan(Jedis.java:3186)
at com.bobo.test.redis.RedisTester.getScan(RedisTester.java:33)
错误原因
debug执行可知在第一轮操作时候可以正确执行。但是在执行第二轮迭代的时候,因为cursor = scanResult.getStringCursor()在上一轮返回的是个乱码,所以在执行scan操作的时候报错了。
为什么不是数字而是个乱码呢?通过以下两个源码找到原因
public class ScanResult {
private byte[] cursor;
private List results;
…………
/**
* Returns the new value of the cursor
* @return the new cursor value. {@link ScanParams#SCAN_POINTER_START} when a complete iteration has finished
* FIXME: This method should be changed to getCursor() on next major release
*/
public String getStringCursor() {
return SafeEncoder.encode(cursor);
}
…………
}
SafeEncoder代码如下
public final class SafeEncoder {
……
public static String encode(final byte[] data) {
try {
return new String(data, Protocol.CHARSET);
} catch (UnsupportedEncodingException e) {
throw new JedisException(e);
}
}
}
其中Protocol.CHARSET = “UTF-8”; 而我本机Java文件的编码格式为utf-16;
修改文件编码格式为UTF-8,问题解决。
那么为啥在UTF-16编码下,scanResult.getResult()就能返回的正确的形式呢?
新鸡呲哇一呲も黑と呲
答案藏在ScanResult的构造函数中。他对cursor和results的数据类型,保存和返回方法都有区别,至于为什么这么做?我也还没研究呢。。。