分批获取Redis中 采用Hash结构存储的数据
写在前面
本文是想记录一下自己在工作之中,使用Redis的HASH来存储数据时,并全分批量获取时遇到的问题。这个问题其实也不应该算是一个问题,说到底也就是一个命令的事情。其实是因为自己才疏学浅,还没有及时观看官方文档,导致没有想到方案。所以在此记录一下自己的愚蠢以及获取到新知识的喜悦。
问题的由来
当时整个在设计的时候,就想着把大量的数据存入Redis,然后将数据库中查询到的数据,与Redis中的数据进行对比,相当于是取两个数据的交集。然后又不想让KEY泛滥成灾,加上当时的我只知道HASH
和 String
两种数据结构😂 所以就使用了HASH结构。可是突然有一天发现需要 全量获取所有数据。(嗯…幸好使用了HASH
结构,不然这全量的数据,还获取不了了。果然多接触点东西不会错。)其实现在想想看,既然想要分页获取全量数据,并且还要判断需要的东西在不在里面,完全可以考虑用Set
的。但是现在后悔不了了,已经有了历史数据,历史数据变换类型的割接会非常的麻烦,况且想要割接也得把原来存储的所有数据都得获取下来才能转移,所以归根结底还是需要遍历出原来HASH
中的全量数据。
问题描述
那问题就清楚了需要将Redis中的全量数据获取出来,但由于自己使用的是HASH结构存储了大量的数据,一个 KEY 下面大约有上百万的数据。但是此时由于业务需求,需要获取HASH中的全量数据。如果数量少的话,也就可以直接获取了,但是百万的数据真的不敢一下获取出来。所以就想能不能分页获取某个KEY下的所有HASH呢?
「今でもゎたはれたしの光」 “时至今日你仍然是我的光芒”—— 米津玄师 《lemon》
解决过程
可能是像我这种奇葩设计的人,真的太少了。我在搜索的时候,搜到的东西真的廖廖无几。有的说可以使用pipline
有的说可以使用脚本 但是看了看好像都不太符合我的需要。终于~不知道找了多久,也忘记了换了什么关键字,终于我搜到了一个命令HSCAN
。
看到这个命令宛如碰到了救星一般,开始疯狂搜索这个命令的博客。却忘记了应该去官网看一下这个命令(只怪自己太蠢,没有这个习惯。) 不过也确实搜到一些不错的,还是比较有帮助。但是还是建议去看官网教程。虽然这里也没写什么,都是使用帮助。
安装Redis-Cli客户端
那看到这里当然要先试用一下这个命令,所以需要使用Redis的客户端redis-cli
。MAC 安装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条记录,不过返回的是一对一对的HASH
和Value
。当偏移量返回0的时候,代表迭代结束了。
但是到这里还有个特殊情况。当我在hash中存入其他的四组数据,并且想要一个一个取出来的时候,却发现并不能如愿。并不是一个一个返回,而是一次性全部返回了。
这样的结果让我一度怀疑🤨,这个方法到底能不能真的按照我的预想去工作。看到这个现象有两个想法冒出来:
- 这个方法会不会觉得我的数据太少了?就一次性返回了?
- 对这个方法一无所知,其实并不能满足我的要求。
那无论是哪一种,都需要查一下资料。这里推荐一下这篇文章,写的非常详细。或者可以直接看看官网文档,让我了解到其中的奥秘。实际上就是第一个猜想那样,也算是Redis
的一种优化机制。
- 如果
Value
的长度超过某个限制,count
属性生效 - 如果数据量超过某个限制,
count
属性生效
我喜欢那些闪光的东西,比如冬日的雪花,天上的星星,还有你的眼睛。
现在终于确认了,这个的确可以用来迭代HASH
中的所有KEY
,那现在就是需要使用java来实现了。好在jedis
的命令与redis
的hscan
方法名字是一致的。但是可惜公司封装的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;
}
最后
最后再推荐一篇文章,主要可以看看jedis
对redis
的方法封装。可以直接使用。
最后还是感谢各位前辈的分享,希望我们共同进步。
这短短的一生我们终将会失去,你不妨大胆一点,爱一个人,攀一座山,追一个梦 ——《大鱼海棠》
参考链接: