Redis命令之hscan

1、业务背景

      在互联网的项目中为了提高性能和吞吐量,通常需要做一些优化和数据异构,比如查询DB,我们可以优化索引,通过命中索引来提高查询速度,也可以把数据异构到Redis,虽然Redis的性能非常好也支持5种数据结构,如果想性能更好的话,可以考虑异构到JVM缓存,也就是DB的数据异构到Redis,Redis的数据定期异构到JVM缓存

2、带来问题

    在Redis中通过用一个hashmap来存储业务数据,当这些业务数据比较小,我们可以通过hGetAll来获取redis的整个map然后设置到JVM缓存中,伴随这业务的增长Redis中的key会非常多,这是我们调用hGetAll获取数据时就会把Redis给hang住,造成整个redis阻塞,进而影响整个redis集群的使用,导致其它业务调用redis时性能抖动

3、解决方案

      1调整获取命令hGetAll改成hscan,分阶段获取
      2调整hash-max-ziplist-entries参数阈值由于调整改配置需要重新刷数据(不建议)

       我们采取方案1,通过HSCAN来迭代获取对应的值,通过指定游标和获取的数量,比如一次获取10个,命令如下HSCAN key cursor [MATCH pattern] [COUNT count]  它是一个增量式命令,每次查询都是返回一部分数据,还可以通过它处理模糊查询,所以不会像hGetAll类命令hang住Redis导致服务短暂停滞,最开始cursor我们都设置为0就好,count来控制每次遍历的数量,由于这个命令执行的时间复杂度是O(N),所以count越大,每次执行时间理论上会越长,可以根据实际场景进行调整,在遍历时候判断返回的游标cursor是否为0,如果为0代表整个遍历结束。

类似的命令如下:原理都一样,都是避免把redis的进场hang住,

  • SCAN 命令用于迭代当前数据库中的数据库键。
  • SSCAN 命令用于迭代集合键中的元素。
  • HSCAN 命令用于迭代哈希键中的键值对。
  • ZSCAN 命令用于迭代有序集合中的元素(包括元素成员和元素分值)。

4、参考文档

 http://doc.redisfans.com/key/scan.html#scan  

http://redisdoc.com/database/scan.html#scan

5、测试用例 

测试过程发现如果map集合的数据比较少,比如100个,虽然设置count只查10个,也会都返回过来 注意点:所有jimdb scan类操作,都要确保在一个副本

 public Map<String,String> hScan(String key) {
        Map<String,String> allResult =new HashMap<>(1024);
        CallerInfo callerInfo = Profiler.registerInfo("CacheServicneire_hScan", O2nConstants.APP_NAME,false, true);
        try {
            ScanOptions scanOptions =  ScanOptions.scanOptions().match("*").count(50).concurrent(1).build();
            ScanResult<Map.Entry<String, String>> scanResult =  cluster.hScan(key,0,scanOptions);
            if(scanResult !=null && CollectionUtils.isNotEmpty(scanResult.getResult())){
                allResult.putAll(scanResult.getResult().stream().collect(Collectors.toMap(Entry::getKey,Entry::getValue)));
                while (scanResult.getCursor()>0){
                    scanResult =  cluster.hScan(key,scanResult.getCursor(), scanOptions);
                    if(scanResult !=null && CollectionUtils.isNotEmpty(scanResult.getResult())){
                        allResult.putAll(scanResult.getResult().stream().collect(Collectors.toMap(Entry::getKey,Entry::getValue)));
                    }
                }
            }
        }catch (Exception e) {
            Profiler.functionError(callerInfo);
            log.error("执行hScan操作失败, key:{}", key, e);
            throw e;
        } finally {
            Profiler.registerInfoEnd(callerInfo);
        }
        return allResult;
    }

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值