Redis的key和value大小限制及应对策略

6,646 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情

Redis的key和value大小限制

redis官方所定义各个数据结构KEY允许的数据大小:

最多容纳 2^32 个key

redis的key和string类型value限制均为512MB。

String类型:一个String类型的value最大可以存储512M

List类型:list的元素个数最多为2^32-1个,也就是4294967295个。

Set类型:元素个数最多为2^32-1个,也就是4294967295个。

Hash类型:键值对个数最多为2^32-1个,也就是4294967295个。

Sorted set类型:跟Set类型相似。

redis中大Value问题的解决

1、压缩

日常在使用redis的时候, 有时会碰到大Value的问题, 超级大的一个Value存到redis中去, 这样其实不好, 我们可以把value进行压缩. 

下面我们使用java自带的压缩, 对字符串进行压缩。

/**
     * 使用gzip压缩字符串
     *
     * @param originString 要压缩的字符串
     * @return 压缩后的字符串
     */
    public static String compress(String originString) {
        if (originString == null || originString.length() == 0) {
            return originString;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try (
                GZIPOutputStream gzip = new GZIPOutputStream(out);
        ) {
            gzip.write(originString.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new sun.misc.BASE64Encoder().encode(out.toByteArray());
    }
 
    /**
     * 使用gzip解压缩
     *
     * @param compressedString 压缩字符串
     * @return
     */
    public static String uncompress(String compressedString) {
        if (compressedString == null || compressedString.length() == 0) {
            return null;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] compressedByte = new byte[0];
        try {
            compressedByte = new sun.misc.BASE64Decoder().decodeBuffer(compressedString);
        } catch (IOException e) {
            e.printStackTrace();
        }
        String originString = null;
        try (
                ByteArrayInputStream in = new ByteArrayInputStream(compressedByte);
                GZIPInputStream ginzip = new GZIPInputStream(in);
        ) {
            byte[] buffer = new byte[1024];
            int offset = -1;
            while ((offset = ginzip.read(buffer)) != -1) {
                out.write(buffer, 0, offset);
            }
            originString = out.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return originString;
    }

2、拆解几个key-value

如果对象需要每次都整存整取,可以尝试将对象分拆成几个key-value, 使用multiGet获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多个redis实例中,降低对单个redis的IO影响;

由于redis是单线程运行的,如果一次操作的value很大会对整个redis的响应时间造成负面影响,所以,业务上能拆则拆,下面举几个典型的分拆方案。

3、换数据格式——hash 可以将这个存储在一个hash中,每个field代表一个具体的属性,使用hget,hmget来获取部分的value,使用hset,hmset来更新部分属性。

4、hash, set,zset,list 中存储过多的元素——元素分拆

以hash为例,原先的正常存取流程是 hget(hashKey, field) ; hset(hashKey, field, value)

现在,固定一个桶的数量,比如 10000, 每次存取的时候,先在本地计算field的hash值,模除 10000, 确定了该field落在哪个key上,再根据对应的hashkey取值。

Redis大Key问题

数据量大的 key ,由于其数据大小远大于其他key,导致经过分片之后,某个具体存储这个 big key 的实例内存使用量远大于其他实例,造成内存不足,拖累整个集群的使用。

什么是 big key

  1. 字符串类型:一般认为超过 10k 的就是 bigkey,但是这个值和具体的 OPS 相关。
  2. 非字符串类型:体现在哈希,列表,集合类型元素过多。

带来的问题

bigkey 通常会导致内存空间不平衡,超时阻塞,如果 key 较大,redis 又是单线程,操作 bigkey 比较耗时,那么阻塞 redis 的可能性增大。每次获取 bigKey 的网络流量较大,假设一个 bigkey 为 1MB,每秒访问量为 1000,那么每秒产生 1000MB 的流量,对于普通千兆网卡,按照字节算 128M/S 的服务器来说可能扛不住。

优化big key

当存储量特别大的时候,可以将key进行hash分散处理,可以减少存储内存。

优化big key的原则就是string减少字符串长度,list、hash、set、zset等减少成员数。