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);}