分批获取Redis中 采用Hash结构存储的数据

写在前面

本文是想记录一下自己在工作之中,使用Redis的HASH来存储数据时,并全分批量获取时遇到的问题。这个问题其实也不应该算是一个问题,说到底也就是一个命令的事情。其实是因为自己才疏学浅,还没有及时观看官方文档,导致没有想到方案。所以在此记录一下自己的愚蠢以及获取到新知识的喜悦。

问题的由来

当时整个在设计的时候,就想着把大量的数据存入Redis,然后将数据库中查询到的数据,与Redis中的数据进行对比,相当于是取两个数据的交集。然后又不想让KEY泛滥成灾,加上当时的我只知道HASHString 两种数据结构😂 所以就使用了HASH结构。可是突然有一天发现需要 全量获取所有数据。(嗯…幸好使用了HASH结构,不然这全量的数据,还获取不了了。果然多接触点东西不会错。)其实现在想想看,既然想要分页获取全量数据,并且还要判断需要的东西在不在里面,完全可以考虑用Set的。但是现在后悔不了了,已经有了历史数据,历史数据变换类型的割接会非常的麻烦,况且想要割接也得把原来存储的所有数据都得获取下来才能转移,所以归根结底还是需要遍历出原来HASH 中的全量数据。

问题描述

那问题就清楚了需要将Redis中的全量数据获取出来,但由于自己使用的是HASH结构存储了大量的数据,一个 KEY 下面大约有上百万的数据。但是此时由于业务需求,需要获取HASH中的全量数据。如果数量少的话,也就可以直接获取了,但是百万的数据真的不敢一下获取出来。所以就想能不能分页获取某个KEY下的所有HASH呢?


「今でもゎたはれたしの光」 “时至今日你仍然是我的光芒”—— 米津玄师 《lemon》


解决过程

可能是像我这种奇葩设计的人,真的太少了。我在搜索的时候,搜到的东西真的廖廖无几。有的说可以使用pipline有的说可以使用脚本 但是看了看好像都不太符合我的需要。终于~不知道找了多久,也忘记了换了什么关键字,终于我搜到了一个命令HSCAN

看到这个命令宛如碰到了救星一般,开始疯狂搜索这个命令的博客。却忘记了应该去官网看一下这个命令(只怪自己太蠢,没有这个习惯。) 不过也确实搜到一些不错的,还是比较有帮助。但是还是建议去看官网教程。虽然这里也没写什么,都是使用帮助。

安装Redis-Cli客户端

那看到这里当然要先试用一下这个命令,所以需要使用Redis的客户端redis-cliMAC 安装redis-cil 可以看看这篇文章。但是需要使用brew命令。如果提示brew 不是内部命令,推荐使用这个脚本安装。当时选择清华超时了,选择中国科学技术大学镜像挺快的。不过安装过程需还是要持续挺久的… 安装完毕一定要执行一下这个命令source /Users/xxxx/.bash_profile 才能继续安装redis-cli

连接Redis集群

可以使用命令redis-cli -h 10.19.25.12 -p 41500 -a 1234!QAZ -c --raw 来连接到Redis集群

  • -h 主机
  • -p 端口
  • -a 密码
  • -c 集群模式
  • –raw 解决乱码问题

尝试一下命令,确实如愿了。心里别提多开心了。
在这里插入图片描述
可以看到返回1个数字,还有10行记录。起初不知道最上面的数字是什么意思,也不懂为什么返回了10条。后来了解到最上面的数组是返回的***偏移量***,而10条记录实际上是5条记录,不过返回的是一对一对的HASHValue。当偏移量返回0的时候,代表迭代结束了。


但是到这里还有个特殊情况。当我在hash中存入其他的四组数据,并且想要一个一个取出来的时候,却发现并不能如愿。并不是一个一个返回,而是一次性全部返回了。
在这里插入图片描述
这样的结果让我一度怀疑🤨,这个方法到底能不能真的按照我的预想去工作。看到这个现象有两个想法冒出来:

  • 这个方法会不会觉得我的数据太少了?就一次性返回了?
  • 对这个方法一无所知,其实并不能满足我的要求。

那无论是哪一种,都需要查一下资料。这里推荐一下这篇文章,写的非常详细。或者可以直接看看官网文档,让我了解到其中的奥秘。实际上就是第一个猜想那样,也算是Redis的一种优化机制。

  • 如果Value的长度超过某个限制,count属性生效
  • 如果数据量超过某个限制,count 属性生效

我喜欢那些闪光的东西,比如冬日的雪花,天上的星星,还有你的眼睛。


现在终于确认了,这个的确可以用来迭代HASH 中的所有KEY,那现在就是需要使用java来实现了。好在jedis的命令与redishscan方法名字是一致的。但是可惜公司封装的jedis客户端没有暴露出来这个hscan的方法。所以无奈之下,只好自己连接Redis集群。部分代码如下:

private GenericObjectPoolConfig configSelf;
private String[] hostsSelf;
private String pwdSelf;
private JedisCluster jcSelf;
private boolean needAuth = false;
private int connectionTimeout = 20000;
private int soTimeout = 20000;
private int maxAttempts = 3;

/**
  * 获取Jedis集群客户端
  * 使用完毕,一定要记得关闭客户端
  *
  * @return Jedis客户端实例
  */
 public JedisCluster getClusterClient() {
     logger.info("-----------------------create Self Jedis Cluster Pool------------------------begin---");
     GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
     genericObjectPoolConfig.setMaxTotal(500);
     genericObjectPoolConfig.setMaxIdle(10);
     genericObjectPoolConfig.setMinIdle(5);
     genericObjectPoolConfig.setTestOnBorrow(true);
     Set<HostAndPort> jedisClusterNodes = new HashSet<>();
     try {
         if (null != jcSelf)
             jcSelf.close();
         for (String address : hostsSelf) {
             String[] ipAndPort = address.split(":");
             jedisClusterNodes.add(new HostAndPort(ipAndPort[0], Integer.parseInt(ipAndPort[1])));
             logger.debug(address);
         }
         if (configSelf.getMaxWaitMillis() < 20000)
             configSelf.setMaxWaitMillis(20000);
         if (needAuth)
             jcSelf = new JedisCluster(jedisClusterNodes, connectionTimeout, soTimeout, maxAttempts, pwdSelf, genericObjectPoolConfig);
         else
             jcSelf = new JedisCluster(jedisClusterNodes, connectionTimeout, maxAttempts, genericObjectPoolConfig);
     } catch (Exception e) {
         logger.error("----create Self Jedis Cluster Error:{}----", e.getMessage(), e);
     }
     logger.info("-----------------------create-Self-Jedis-Cluster-Pool------------------------end---");
     return jcSelf;
 }

最后

最后再推荐一篇文章,主要可以看看jedisredis的方法封装。可以直接使用。
最后还是感谢各位前辈的分享,希望我们共同进步。

这短短的一生我们终将会失去,你不妨大胆一点,爱一个人,攀一座山,追一个梦 ——《大鱼海棠》

参考链接:

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐