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

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

错误信息

新学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<String> getScan(Jedis jedis, String key) {
		List<String> keysList = new ArrayList<>();
		ScanParams paramas = new ScanParams();
		paramas.match(key);
		paramas.count(200);
		String cursor = "0";
		
		while (true) {
			ScanResult<String> scanResult = jedis.scan(cursor, paramas);
			List<String> 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<String> 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<T> {
  private byte[] cursor;
  private List<T> 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的数据类型,保存和返回方法都有区别,至于为什么这么做?我也还没研究呢。。。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值