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