Redis——BigKey

BigKey

1 多大算 BigKey?

阿里云 Redis 开发规范:

  • string 类型的数据控制在 10KB 以内,hash, list, set, zset 元素数量不要超过 5000
  • 非字符串的 BigKey,不要使用 del 删除,而是使用 hsacn, sscan, zscan 方式 渐进式删除。同时,要防止 BigKey 过期时自动删除,因为自动删除会使用 del 指令

2. BigKey 有什么危害?

  • 如果没有配置 Redis 非阻塞删除,则在过期自动删除 BigKey 时会导致 Redis 主进程阻塞。
  • 如果在分片集群中,则会造成某个节点被频繁访问,单点流量过高的问题。
  • 如果有主从复制,则可能延长主从不一致的时间。
  • 在查询 BigKey 时,可能导致网络设备带宽耗尽,从而超时。

3. BigKey 是如何产生的?

BigKey 一般不是突然就产生的,而是 逐步积累 的。比如粉丝列表、统计报表。

如果使用 Redis 不当,也可能会造成 BigKey,例如:

  • 把大文件直接存入 Redis。
  • 把对象的键作为 hash 数据结构内部的键,从而往一个 hash 数据结构中存储大量对象。

4. 如何发现 BigKey?

  • redis-cli --bigkeys:在 shell 中执行这个命令后,可以分析出最大的数据。
  • memory useage xxx:在 redis-cli 里执行这个命令后,可以获取 xxx 所占的字节数。

5. 如何删除 BigKey?

5.1 string 类型

一般用 del,保险起见,可以使用 unlink

5.2 hash 类型

使用 hscan + hdel,每次删除 100 个键值对,使用 redisTemplate 来操作是这样的:

final int BATCH_SIZE = 100;
ScanOptions options = ScanOptions.scanOptions().count(BATCH_SIZE).build();
Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(KEY, options);
while (cursor.hasNext()) {
    Object[] keys = new Object[BATCH_SIZE];
    int elementCount = 0;
    while (cursor.hasNext() && elementCount < BATCH_SIZE) {
        Map.Entry<Object, Object> entry = cursor.next();
        keys[elementCount++] = entry.getKey();
    }
    redisTemplate.opsForHash().delete(KEY, keys);
}
cursor.close();
redisTemplate.delete(KEY);

5.3 list 类型

使用 ltrim,每次删除 100 个元素,使用 redisTemplate 来操作是这样的:

final int BATCH_SIZE = 100;
long size = Optional.ofNullable(redisTemplate.opsForList().size(KEY)).orElse(0L);
long end = size - 1;
while (end > 0) {
    end -= BATCH_SIZE;
    redisTemplate.opsForList().trim(KEY, 0, end);
}
redisTemplate.delete(KEY);

注:这些操作不是原子的,按理应该写一个 lua 脚本来保证操作的原子性,但是 lua 脚本在 Redis 中执行时会阻塞其他操作,还不如直接使用 del 指令。一般情况下,删除这个 list 时需要确保没有人访问它,所以不是原子的也可以

5.4 set 类型

使用 sscan + srem,每次删除 100 个元素,使用 redisTemplate 来操作是这样的:

final int BATCH_SIZE = 100;
ScanOptions scanOptions = ScanOptions.scanOptions().count(BATCH_SIZE).build();
Cursor<Object> cursor = redisTemplate.opsForSet().scan(KEY, scanOptions);
while (cursor.hasNext()) {
    Object[] keys = new Object[BATCH_SIZE];
    int elementCount = 0;
    while (cursor.hasNext() && elementCount < BATCH_SIZE) {
        keys[elementCount++] = cursor.next();
    }
    redisTemplate.opsForSet().remove(KEY, keys);
}
cursor.close();
redisTemplate.delete(KEY);

5.5 zset 类型

使用 zscan + zrem,每次删除 100 个元素,使用 redisTemplate 来操作是这样的:

final int BATCH_SIZE = 100;
ScanOptions scanOptions = ScanOptions.scanOptions().count(BATCH_SIZE).build();
Cursor<TypedTuple<Object>> cursor = redisTemplate.opsForZSet().scan(KEY, scanOptions);
while (cursor.hasNext()) {
    Object[] keys = new Object[BATCH_SIZE];
    int elementCount = 0;
    while (cursor.hasNext() && elementCount < BATCH_SIZE) {
        TypedTuple<Object> typedTuple = cursor.next();
        keys[elementCount++] = typedTuple.getValue();
    }
    redisTemplate.opsForZSet().remove(KEY, keys);
}
cursor.close();
redisTemplate.delete(KEY);

6. BigKey 优化

Redis 默认的过期删除策略用的删除指令是 del,这个指令是 阻塞 的,所以在删除 BigKey 时会造成 Redis 卡顿。此外,Redis 还提供了一个指令 unlink,这个指令是 非阻塞 的,可以通过配置来让 Redis 使用 unlink 删除过期的数据,配置如下:

# 当 Redis 服务器主动删除对象(如 过期键、内存淘汰)时,是否使用异步线程执行实际内存释放
lazyfree-lazy-server-del yes
# 当执行 FLUSHDB 或 FLUSHALL 命令清空数据库时,是否使用异步线程执行实际内存释放
lazyfree-lazy-flush yes
# 当用户通过 DEL 命令主动删除键时,是否使用异步线程执行实际内存释放
lazyfree-lazy-user-del yes

7. 总结

本文对 BigKey 的定义做了诠释,介绍了 BigKey 的危害,提到了 BigKey 的产生原因,主要讲解了如何发现和删除 BigKey,除此之外,还介绍了可以通过配置文件让 Redis 在删除数据时使用非阻塞的方式。

你可能感兴趣的:(Java,面试,#,Redis,redis,java,BigKey)