本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云
网上百度和谷歌花了大量的时间去搜索 Jedis 的相关用法,要么不全,要么乱用。基本上没有完整的用例,于是我就写了这篇文章。
参考我前面的那篇文章《删除 Redis 大 Key 让程序出现雪崩导致程序员被开除!》,当我们使用 keys * 进行查询 key 的时候会进行堵塞,导致 redis 整体不可用,而使用 scan 命令则不会。
SCAN、SSCAN、HSCAN、ZSCAN 4 个命令,分别用于集合、哈希键及有序集等。
- SCAN:命令用于迭代当前数据库中的数据库键。
- SSCAN:命令用于迭代集合键中的元素。
- HSCAN:命令用于迭代哈希键中的键值对。
- ZSCAN:命令用于迭代有序集合中的元素(包括元素成员和元素分值)。
命令格式如下:
SCAN cursor [MATCH pattern] [COUNT count]
scan 游标 MATCH <返回和给定模式相匹配的元素> count 每次迭代所返回的元素数量 SCAN 命令是增量的循环,每次调用只会返回一小部分的元素。所以不会有 KEYS 命令的坑(key 的数量比较多,一次 KEYS 查询会 block 其他操作)。
在 Redis 中的具体用法如下:
scan 0 match xttblog.com* count 5 sscan myset 0 match 业余草*
SCAN 命令对应的 Jedis 中的操作如下:
public static void delLargeListKey(Jedis jedis){ // 游标初始值为0 String cursor = ScanParams.SCAN_POINTER_START; String key = "test:xttblog:*"; ScanParams scanParams = new ScanParams(); scanParams.match(key);// 匹配以 test:xttblog:* 为前缀的 key scanParams.count(1000); while (true){ //使用scan命令获取500条数据,使用cursor游标记录位置,下次循环使用 ScanResult<String> scanResult = jedis.scan(cursor, scanParams); cursor = scanResult.getStringCursor();// 返回0 说明遍历完成 List<String> list = scanResult.getResult(); long t1 = System.currentTimeMillis(); for(int m = 0;m < list.size();m++){ String mapentry = list.get(m); //jedis.del(key, mapentry); jedis.ltrim("test:xttblog:", 0 ,1); } long t2 = System.currentTimeMillis(); System.out.println("删除" + list.size() + "条数据,耗时: " + (t2-t1) + "毫秒,cursor:" + cursor); if ("0".equals(cursor)){ break; } } }
SSCAN 命令对应的 Jedis 中的操作如下:
public static void delLargeSetKey(Jedis jedis){ // 游标初始值为0 String cursor = ScanParams.SCAN_POINTER_START; ScanParams scanParams = new ScanParams(); scanParams.count(1000); String key = "test:xttblog"; while (true){ //使用sscan命令获取500条数据,使用cursor游标记录位置,下次循环使用 ScanResult<String> sscanResult = jedis.sscan(key, cursor, scanParams); cursor = sscanResult.getStringCursor();// 返回0 说明遍历完成 List<String> scanResult = sscanResult.getResult(); long t1 = System.currentTimeMillis(); for(int m = 0;m < scanResult.size();m++){ String mapentry = scanResult.get(m); jedis.srem(key, mapentry); } long t2 = System.currentTimeMillis(); System.out.println("删除" + scanResult.size() + "条数据,耗时: " + (t2-t1) + "毫秒,cursor:" + cursor); if ("0".equals(cursor)){ break; } } }
HSCAN 命令对应的 Jedis 中的操作如下:
public static void delLargeHashKey(Jedis jedis){ // 游标初始值为0 String cursor = ScanParams.SCAN_POINTER_START; ScanParams scanParams = new ScanParams(); scanParams.count(1000); String key = "test:xttblog"; while (true){ //使用hscan命令获取500条数据,使用cursor游标记录位置,下次循环使用 ScanResult<Map.Entry<String, String>> hscanResult = jedis.hscan(key, cursor, scanParams); cursor = hscanResult.getStringCursor();// 返回0 说明遍历完成 List<Map.Entry<String, String>> scanResult = hscanResult.getResult(); long t1 = System.currentTimeMillis(); for(int m = 0;m < scanResult.size();m++){ Map.Entry<String, String> mapentry = scanResult.get(m); jedis.hdel(key, mapentry.getKey()); } long t2 = System.currentTimeMillis(); System.out.println("删除" + scanResult.size() + "条数据,耗时: " + (t2-t1) + "毫秒,cursor:" + cursor); if ("0".equals(cursor)){ break; } } }
ZSCAN 命令对应的 Jedis 中的操作如下:
public static void delLargeZSetKey(Jedis jedis){ // 游标初始值为0 String cursor = ScanParams.SCAN_POINTER_START; String key = "test:xttblog:*"; ScanParams scanParams = new ScanParams(); scanParams.match(key);// 匹配以 test:xttblog:* 为前缀的 key scanParams.count(1000); while (true){ //使用 zscan 命令获取 500 条数据,使用cursor游标记录位置,下次循环使用 ScanResult<Tuple> scanResult = jedis.zscan(key, cursor, scanParams); cursor = scanResult.getStringCursor();// 返回0 说明遍历完成 List<Tuple> list = scanResult.getResult(); long t1 = System.currentTimeMillis(); for(int m = 0;m < list.size();m++){ Tuple tuple = list.get(m); System.out.println("Element:" + tuple.getElement() + ",Score:" + tuple.getScore()); } long t2 = System.currentTimeMillis(); System.out.println("删除" + list.size() + "条数据,耗时: " + (t2-t1) + "毫秒,cursor:" + cursor); if ("0".equals(cursor)){ break; } } }
上面 4 个案例都没有对应具体的业务。基本上写的都很明白了。
SCAN 命令、 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令都返回一个包含两个元素的 multi-bulk 回复。
回复的第一个元素是字符串表示的无符号 64 位整数(游标),SCAN 命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。当 SCAN 命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。
回复的第二个元素是另一个 multi-bulk 回复, 这个 multi-bulk 回复包含了本次被迭代的元素。
注意:SCAN 命令不能保证每次返回的值都是有序的,另外同一个 key 有可能返回多次,不做区分,需要应用程序去处理。不要在使用 scan(int) 这个方法,它存在一个 bug,参数应该是 unsigned long 而不是 int,这个方法在以后 jedis 版本大改时会被删除。
最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号:xttblog2。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!
本文原文出处:业余草: » 详解 Jedis 的 SCAN、SSCAN、HSCAN、ZSCAN 用法