程序员

如何对线上Redis进行数据清理

2018-11-18  本文已影响36人  简xiaoyao

前言

在现今的互联网系统中,Redis以其优秀的性能和简洁的设计,被广泛使用,尤其是在分布式系统中;Redis最主要的应用场景在于为分布式系统提供公用内存服务,而在使用的过程中,尤其是线上的高可用环境,需要时刻注意其内存占用情况,一旦其内存吃紧,就会导致Redis性能下降,并引发服务不可用;此时除了硬件扩展,一个经常使用的手段就是线上数据清理

如何清理线上环境Redis数据

一般有正规运营团队的公司里,运营团队都会针对Redis出一套规范,这里面一般就包括:如无特殊情况,所有数据必须设置超时时间,以防数据无限堆积;即便如此,还是无法保证所有开发人员百分百遵守,同时即便遵守,理论上也还是有内存吃紧的情况,此时除了增加硬件资源外,常用的做法就是根据业务对Redis无用数据进行清理

另一方面,由于Redis是基于单线程事件模型(为什么是单线程,可以参照Redis FAQ),为了保持线上高可用,一般决不允许单一命令阻塞过长,如:KEYSSMEMBERS等命令就需要限制不能随便使用;此时如何对Redis的数据进行清理呢?针对这种情况,Redis给出了「迭代扫描」系列命令,先来看看Redis官方对该系列命令的描述:

The SCAN command and the closely related commands SSCAN, HSCAN and ZSCAN are used in order to incrementally iterate over a collection of elements.

  • SCAN iterates the set of keys in the currently selected Redis database.
  • SSCAN iterates elements of Sets types.
  • HSCAN iterates fields of Hash types and their associated values.
  • ZSCAN iterates elements of Sorted Set types and their associated scores.

Since these commands allow for incremental iteration, returning only a small number of elements per call, they can be used in production without the downside of commands like KEYS or SMEMBERS that may block the server for a long time (even several seconds) when called against big collections of keys or elements

大思就是SCAN系列命令主要包含四个:SCANSSCANHSCANZSCAN,分别针对不同的数据类型,它们的主要特点在于支持对数据进行迭代式扫描,每次只返回部分数据(返回多少可定制),这就有效屏蔽了全量扫描命令如KEYSSMEMBERS等单一命令执行时间过长造成的服务器阻塞

下面就如何利用HSCAN进行数据扫描并清理进行举例说明,实现语言为Java,其他语言逻辑大同小异,具体如下:

  /**
   * 迭代式删除Hash数据
   * @param hashKey, hash key
   * @param iterSize, 每次迭代元素数量,过大会可能会导致阻塞,过小会导致清理效率底下
   * @return
     */
  public int delHash(String hashKey, int iterSize) {
    int cursor = 0;

    ScanParams scanParams = new ScanParams().match("*").count(iterSize > 0 ? iterSize : 1000);
    ScanResult<Entry<String, String>> scanResult;
    List<Entry<String, String>> processList = new ArrayList<>();
    int delCount = 0;
    do {
      try {
        log.info("begin scan, cursor is {}", cursor);
        scanResult = jedis.hscan(hashKey, String.valueOf(cursor), scanParams);
        processList.addAll(scanResult.getResult());
        List<String> dList = processList.stream().map(Entry::getKey).collect(Collectors.toList());
        //批量删除
        jedis.hdel(hashKey, dList.toArray(new String[dList.size()]));
        delCount += processList.size();
        processList.clear();
        //获取游标位置,若大于0,则代表还有数据,需要继续迭代
        cursor = Integer.parseInt(scanResult.getStringCursor());
        log.info("scan next, cursor is {}", cursor);
      }catch (Exception e) {
        log.error(e.getLocalizedMessage(), e);
      }
    } while (cursor > 0);

    log.info("delete hash {} over, {} key be deleted", hashKey, delCount);

    return delCount;
  }

使用方式不复杂,如果使用过Java迭代器的同学应该对这种形式很熟悉,主要流程就是通过判断标识(Java Iterator 中的 hasNext()next())对整体数据进行切分并操作,如此循环,直至覆盖全部数据;这里有一个点需要注意的就是,每次迭代元素数量需要设置一个合适的值,过大会可能会导致阻塞,过小会导致处理效率底下,我在实际使用的时候发现每次处理1000个效率较高,当然,实际情况可能会根据硬件和带宽不同而有差异

总结

本文只从纯技术角度简单的阐述了Redis线上数据清理需要注意的点及如何操作,实际的过程中还需要有很多其他考虑,如:

上一篇 下一篇

猜你喜欢

热点阅读