Java代码中Redis的scan方法中cursor(即scanResult.getStringCursor())返回乱码

Agnes ·
更新时间:2024-11-15
· 702 次阅读

Java代码中Redis的scan方法中cursor(即scanResult.getStringCursor())返回乱码错误信息遍历问题scan用法Java代码及问题错误原因新的问题 错误信息

新学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]
示例如下
scan操作示意
可见该命令返回有两部分:
第一部分为“49152”—下次执行scan操作时候的游标起点;
第二部分对应的就是我们想要扫描的key,即符合test11*的key。
同时可见:COUNT 10并非返回10个以test11开始的key,而是限定服务器单次遍历的字典槽位数量(约等于)。

Java代码及问题 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的数据类型,保存和返回方法都有区别,至于为什么这么做?我也还没研究呢。。。


作者:Receptive2WE



乱码 JAVA cursor Redis

需要 登录 后方可回复, 如果你还没有账号请 注册新账号