1.什么是BigKey和HotKey

1.1.Big Key

Redis big key problem,实际上不是大Key问题,而是Key对应的value过大,因而严格来说是Big Value问题,Redis value is too large (key value is too large)。

究竟多大的value会导致big key问题,并没有统一的规范。

例如,对于String类型的value,有时分超越5M归于big key,有时分保险起见,超越10K就能够算作Bigey。

Big Key会导致哪些问题呢?

1、因为value值很大,序列化和反序列化时刻过长,网络时延也长,然后导致操作Big Key的时分耗时很长,降低了Redis的功能。

2、在集群模式下无法做到负载均衡,导致负载倾斜到某个实例上,单实例的QPS会比较高,内存占用比较多。

3、因为Redis是单线程,假如要对这个大Key进行删去操作,被操作的实例或许会被block住,然后导致无法响应恳求。

Big Key是怎么发生的呢?

一般是程序规划者对于数据的规划意料不妥,或规划考虑遗漏导致的Big Key的发生。

在某些事务场景下,很简单发生Big Key,例如KOL或者流量明星的粉丝列表、投票的统计信息、大批量数据的缓存,等等。

1.2.Hot Key

Hot Key,也叫Hotspot Key,即热门Key。假如某个特定Key突然有许多恳求,流量集中到某个实例,甚至导致这台Redis服务器因为到达物理网卡上线而宕机,这个时分其实便是遇到了热门Key 问题。

热门key会导致许多系统问题:

1、流量过度集中,无法发挥集群优势,假如到达该实例处理上限,导致节点宕机,进而冲击数据库,有导致缓存雪崩,让整个系统挂掉的危险。

2、因为Redis是单线程操作,热门Key会影响地点示例其他Key的操作。

2.怎么发现BigKey和HotKey

2.1.发现BigKey

1、经过Redis指令查询BigKey。

以下指令能够扫描Redis的整个Key空间不同数据类型中最大的Key。-i 0.1 参数能够在扫描的时分每100次指令执行sleep 0.1 秒。

Redis自带的bigkeys的指令能够很便利的在线扫描大key,对服务的功能影响很小,单缺陷是信息较少,只要每个类型最大的Key。

$ redis-cli -p 999 --bigkeys -i 0.1

2、经过开源东西查询BigKey。

运用开源东西,长处在于获取的key信息具体、可选参数多、支持定制化需求,后续处理便利,缺陷是需要离线操作,获取成果时刻较长。

比方,redis-rdb-tools 等等。

$ git clone https://github.com/sripathikrishnan/redis-rdb-tools
$ cd redis-rdb-tools
$ sudo python setup.py install 
$ rdb -c memory dump-10030.rdb > memory.csv

2.2.发现HotKey

1、hotkeys 参数

Redis 在 4.0.3 版本中添加了 hotkeys (github.com/redis/redis…)查找特性,能够直接运用 redis-cli –hotkeys 获取当前 keyspace 的热门 key,完成上是经过 scan + object freq 完成的。

2、monitor 指令

monitor 指令能够实时抓取出 Redis 服务器接收到的指令,经过 redis-cli monitor 抓取数据,一起结合一些现成的剖析东西,比方 redis-faina,统计出热 Key。

3.BigKey问题的处理办法

发现和处理BigKey问题,能够参阅以下思路:

1、在规划程序之初,预估value的巨细,在事务规划中就防止过大的value的出现。

2、经过监控的方式,尽早发现大Key。

3、假如真实无法防止大Key,那么能够将一个Key拆分为多个部分别离存储到不同的Key里。

下面以List类型的value为例,演示一下拆分处理大Key问题的办法。

有一个User Id列表,有1000万数据,假如全部存储到一个Key下面,会非常大,能够经过分页拆分的方式存取数据。

下面是存取数据的代码完成:

/**
 * 将用户数据写入Redis缓存
 *
 * @param userIdList
 */
public void pushBigKey(List<Long> userIdList) {
    // 将数据1000个一页进行拆分
    int pageSize = 1000;
    List<List<Long>> userIdLists = Lists.partition(userIdList, pageSize);
    // 遍历所有分页,每页数据存到1个Key中,经过后缀index进行区别
    Long index = 0L;
    for (List<Long> userIdListPart : userIdLists) {
        String pageDataKey = "user:ids:data:" + (index++);
        // 运用管道pipeline,削减获取衔接次数
        redisTemplate.executePipelined((RedisCallback<Long>) connection -> {
            for (Long userId : userIdListPart) {
                connection.lPush(pageDataKey.getBytes(), userId.toString().getBytes());
            }
            return null;
        });
        redisTemplate.expire(pageDataKey, 1, TimeUnit.DAYS);
    }
    // 存完数据,将数据的页数存到一个独自的Key中
    String indexKey = "user:ids:index";
    redisTemplate.opsForValue().set(indexKey, index.toString());
    redisTemplate.expire(indexKey, 1, TimeUnit.DAYS);
}
/**
 * 从Redis缓存读取用户数据
 *
 * @return
 */
public List<Long> popBigKey() {
    String indexKey = "user:ids:index";
    String indexStr = redisTemplate.opsForValue().get(indexKey);
    if (StringUtils.isEmpty(indexStr)) {
        return null;
    }
    List<Long> userIdList = new ArrayList<>();
    Long index = Long.parseLong(indexStr);
    for (Long i = 1L; i <= index; i++) {
        String pageDataKey = "user:ids:data:" + i;
        Long currentPageSize = redisTemplate.opsForList().size(pageDataKey);
        List<Object> dataListFromRedisOnePage = redisTemplate.executePipelined((RedisCallback<Long>) connection -> {
            for (int j = 0; j < currentPageSize; j++) {
                connection.rPop(pageDataKey.getBytes());
            }
            return null;
        });
        for (Object data : dataListFromRedisOnePage) {
            userIdList.add(Long.parseLong(data.toString()));
        }
    }
    return userIdList;
}

4.HotKey问题的处理办法

假如出现了HotKey,能够考虑以下处理方案:

1、运用本地缓存。比方在服务器缓存需要恳求的热门数据,这样经过服务器集群的负载均衡,能够防止将大流量恳求到Redis。

但本地缓存会引入数据一致性问题,一起糟蹋服务器内存。

2、HotKey将复制多份,随机打散,运用代理恳求。

/**
 * 将HotKey数据复制20份存储
 *
 * @param key
 * @param value
 */
public void setHotKey(String key, String value) {
    int copyNum = 20;
    for (int i = 1; i <= copyNum; i++) {
        String indexKey = key + ":" + i;
        redisTemplate.opsForValue().set(indexKey, value);
        redisTemplate.expire(indexKey, 1, TimeUnit.DAYS);
    }
}
/**
 * 随机从一个复制中获取一个数据
 *
 * @param key
 * @return
 */
public String getHotKey(String key) {
    int startInclusive = 1;
    int endExclusive = 21;
    String randomKey = key + ":" + RandomUtils.nextInt(startInclusive, endExclusive);
    return redisTemplate.opsForValue().get(randomKey);
}

Redis的BigKey和HotKey

5.参阅资料

  • 《Redis value is too large (key value is too large)》www.itworkman.com/redis-value…
  • 《7 Redis Worst Practices》redis.com/blog/7-redi…
  • 《Redis Hotspot Key Discovery and Common Solutions》dzone.com/articles/re…
  • 《edis中什么是Big Key(大key)问题?怎么处理Big Key问题?》blog.csdn.net/Weixiaohuai…
  • 《redis之bigkey(看这一篇就够)》www.cnblogs.com/szq95716/p/…
  • 《Redis hot Key 发现以及处理办法》zhuanlan.zhihu.com/p/473743398
  • 《京东零售 / hotkey》gitee.com/jd-platform…