https://my.oschina.net/nalenwind/blog/897744

使用hashmap优化压缩Redis内存使用


背景

近来公司内部dsp架构升级,需要能够根据请求中的设备id实时的获取到该设备的用户画像相关信息,于是选用每天使用离线任务把用户数据灌入redis里面,供线上服务实时查询。

需求评估

需求是筛选出最近一个月活跃的设备,将其用户画像属性灌入redis中。于是筛选出30天的活跃设备总量有24亿。这么大的量如果直接使用设备id作为key直接写入redis,按value占用16字节来算,大概要用230G内存的redis集群,这成本还是比较可观的。

内存使用评估方法

为了评估我们设计的方法具体将占用多大的内存,上网查了写资料,发现跟redis内存容量使用和优化相关的只有这一篇文章《REDIS内存容量的预估和优化》,其他都是相互转载的,虽然不知道作者是谁,但是向作者致敬。

方案设计

大家知道Hash表空间大小和Key的个数决定了冲突率(或者用负载因子衡量),再合理的范围内,key越多自然hash表空间越大,消耗的内存自然也会很大。再加上大量指针本身是长整型,所以内存存储的膨胀十分可观。所以主要优化思路是考虑如何把key的个数减少。

在网上搜了下相关的博客,发现了这篇文章《Redis百亿级Key存储方案》,同样也是相互转载的特别多,已不知出处,里面bucket的思路启发了我,这里深深感谢作者。

原文的意思是设计一个映射函数,将设备id经过变换,映射到一个桶id上,桶的数量比原始数据的数量小2-3个数量级,而映射函数的作用是能够尽可能平均的将key映射到桶id上,使用桶id作为redis中的key,value使用hashmap结构,原始数据中的key和value作为hashmap中的subkey和value。这样能够大幅减少redis中全局key的数量。所以这里这个映射函数是一个比较核心的东西,要设计一个好的映射函数是很困难的。这里我经过多次尝试,方案如下:

选用CRC32算法对设备id做映射,再将结果对$2^{26}$取模作为桶id。这样能够将桶的数量控制在$2^{26}$ 这个数量上,同时使桶内的subkey分布尽可能均匀,减少数据倾斜的情况出现。同时使用BKDRHash算法对作为subkey的device id进行变换和压缩,从36字节变为4字节。

但是我们使用引用的第一篇文章中的公式来算一下: 首先我们有24亿条数据,这里桶的个数定为$2^{26}$个,平均每个桶里有35个子key。为了存储这$2^{26}$个key,我们需要key的长度为4个字节。 key字节数为4 + 9 ~ 16字节 subkey字节数为4字节 value字节数为16字节 则单个hashmap的大小为

35 × (4 + 16 + 3) + 2 = 807 字节 ~ 1024

总内存大小为

2 ^ 26 × (16 + 16 + 16 + 1024) + 2 ^ 26 × 4 = 67 G

可以看到总内存只使用了67G

总结

1.经过了上面的优化,从230G的内存占用到67G,内存使用减少了3.5倍,满满的成就感啊。 2.利用redis的hashmap可以极大的减少key的数量,和减少内存使用。