业务场景往往存在各种大键和多键,例如:1:单个简单键存储一个大值。2:hash、set、zset、list存储的元素过多(以万计)。3:一个集群存储了上亿个key,过多的key本身会占用更多的空间。(如无意外,文中提到的hash、set等数据结构均指的是redis中的数据结构)。由于redis是单线程运行的,如果某个操作的值很大,会对整个redis的响应时间产生负面影响。所以,业务能分就分。下面介绍几种典型的分割方案。1:单个simplekey存储的值非常大i:每次都需要对对象进行保存和检索。可以尝试将对象拆分为多个key-value,使用multiGet获取值。这种拆分的意义在于拆分单个操作的压力,可以将其拆分到多个redis实例中,减少对单个redis的IO影响;ii:对象每次只需要访问部分数据,可以像第一种方法一样拆分成若干个。key-value,你也可以将其存储在哈希中,每个字段代表一个特定的属性。使用hget、hmget获取部分值,使用hset、hmset更新部分属性。2:value中存放过多元素与场景中的第一种做法类似,可以拆分这些元素。以hash为例,原来正常的访问过程是hget(hashKey,field);hset(hashKey,字段,值)。现在,固定bucket的个数,比如10000。每次访问,先在本地计算该字段的hash值,对10000取模,判断该字段落在哪个key上。newHashKey=hashKey+(set,zset,list也可以类似上面的做法。但是有些不适合的场景,比如要保证lpop的数据确实最先push到list,这个需要额外加一些属性,或者在key的拼接上做一些工作(比如按时间拆分list)3:一个cluster存储了上亿个key,如果key数量过多,会占用更多的内存空间i:thekey本身的职业(每个key都会有一个Category前缀)。ii:在集群模式下,服务器需要建立一些slot2key的映射关系,key多时指针占用空间大。这两个方面在key数量为亿级时会消耗内存(Redis3.2及以下版本均有此问题,4.0有优化);所以减少key的个数可以减少内存消耗,可以参考的解决方案是转为Hash结构存储,即直接使用RedisString的结构存储,现在将多个key存储在一个Hash结构中。具体场景如下:1:key本身有很强的相关性,比如多个key代表一个object,每个key是object的一个属性,可以直接根据的特性设置一个新的Key-Hash结构一个具体的对象,将原来的key作为新Hash的字段。例如:原来存储的三个key。user.zhangsan-id=123;user.zhangsan-age=18;user.zhangsan-country=中国;这三个key本身就具有很强的关联特性,像这样key=user.zhangsan转换成Hash存储。字段:id=123;字段:年龄=18;字段:国家=中国;即在r??edis中存储了一个key:user.zhangsan,它有三个字段,每个字段+key对应一个原始key。二:key本身没有关联。预估总量,采用与上述第二种情况类似的方案,预分配固定数量的桶。例如,估计密钥总数为2亿个。如果一个hash存储100个字段,需要2亿/100=200W个桶(200W个key占用的空间很小,2亿个可能有将近20G)。比如以前有3个key:user.123456789user.987654321user.678912345现在按照200W这个固定的bucket,先计算这个bucket的序号hash(123456789)%200W。这里,最好保证hash算法的值是正数,否则需要调整下模除法的规则;这样三个key的bucket分别计算为1、2、2。所以存储时调用APIhset(key,field,value),读取时使用hget(key,field)。注意两点:1、hash取模对负数的处理;2.pre-bucketing时,hash存储的值不要超过512,100左右比较合适。4:LargeBitmaporBloomfilter(布隆过滤器))使用位图或布隆过滤器分割场景,这种情况在数据量巨大的情况下经常出现。这种情况下Bitmap和Bloomfilter使用的空间比较大,比如公司userid匹配用的Bloomfilter,需要512MB的大小,这对于redis来说绝对是一个很大的值。在这种场景下,我们需要将其拆分成足够小的Bitmap,比如将一个512MB的大Bitmap拆分成1024个512KB的Bitmap。不过拆分的时候需要注意,每个key要放在一个Bitmap上。有的业务只是把Bitmap拆开,仍然把它当作一个整体的bitmap,所以一个key还是落在了多个Bitmap上,可能会导致一个key请求查询多个节点,查询多个Bitmap。如下图,请求的值被hash到多个Bitmap,也就是redis的多个key。这些密钥也可能位于不同的节点上。这样的拆分显然大大降低了查询的效率。所以我们要做的就是把所有被拆分出来的Bitmap都当成独立的bitmap,然后通过hash给不同的bitmap分配不同的key,而不是把所有的小Bitmap都当作一个整体来对待。这样做之后,你只需要为每个请求在redis中取一个key。可能有同学会问,这样拆分后Bitmap变小了,会不会增加Bloomfilter的误判率?其实不是的,Bloomfilter的误报率是由hash函数的个数k,集合元素的个数n,和Bitmap的大小m决定的,大致相等。因此,如果我们在第一步中能够尽可能均匀地拆分,即在将key分配给不同的Bitmap时,那么n/m的值几乎是一样的,误报率就不会发生变化。具体误判率推导可以参考wiki:Bloom_filter。同时客户端也提供了方便的api(>=2.3.4版本),setBits/getBits用于一次操作同一个key的多个bit值。建议:选择13k,单个bloomfilter控制在512KB以下。
