Redis 数据存储优化

325 阅读3分钟

背景:公司信息流业务需要对接字节RTA,RTA作为广告主先'选'后'买'的重要环节,对提高广告主投放质量,降低成本有较大作用,但是RTA具有极高流量(50WQps)超低延迟(60ms)海量数据(十亿级别)特质,这对于我们在接入时存在很大的挑战。

挑战

  1. 服务如何实现高并发(50wQPS)
  2. 服务接口如何实现超低延迟(60ms以下)
  3. 海量设备数据如何低成本、高效存储

本次主要讲的是第三点(海量设备数据如何低成本、高效存储)

Redis支持五种数据类型:string(字符串)、hash(哈希)、list(列表)、set(集合)及zset(有序集合)

我们的数据结构主要是存储设备和对应的质量分,用 JSON 格式表示 如下:

字符串长度为 214

{
  "deviceId": "7371f6d7040883b214561be6811b9a15",
  "score": {
    "1125": 0.0,
    "1126": 0.0,
    "1127": 0.0,
    "1128": 0.0,
    "1129": 0.0
  }
}

对于上述数据结构,reids可以 选择 string或者 hash 两种数据类型来进行存储,下面我们看下这两种数据类型在20亿数据下的内存占用情况

注意:reids底层会对数字类型进行编码优化、字符串直接存储对应字节

直接存储数据原始值,不做任何处理

1、string(字符串)

key: 设备ID的MD5值 value:设备对应的所有模型分的JSON字符串

上面是按照字符串结构存储时redis的数据内容,因此可以粗略计算出20亿设备占用多少redis内存

214(B) * 2000000000 / 1024(k) / 1024(M) / 1024(G) = 400G

2、hash (哈希)

key: 设备ID的MD5值 filed: rtaScore(固定字符串) value:设备对应的所有模型分的JSON字符串

类似于字符串的计算方式,使用hash结构我们还要多一些额外的属性,因此内存占用要大于字符串结构的

不直接存储原始数据,对原始数据进行编码和转化

下面我们采取hash(哈希)结构来对此进行分析

1、key 处理

将 32位的MD5字符串经过 CRC32 算法转成一个整数作为key,然后在对 key % 500w 取模 作为hash的key,这样就将key分散到500w个桶中

2、filed 处理

将 32位的MD5字符串经过 BKDR_Hash 算法转成一个非负整数 作为filed 进行存储

3、value 处理

将 质量分对象进行 ProtoBuffer 编码,生成短小、紧凑的字节。

上面这些紧紧是对代码层面的改造,还有一点就是还要对redis的参数进行设置,如下:

hash-max-ziplist-entries = 1024

hash-max-ziplist-value = 128

以上两个配置是强制 hash(哈希)底层使用ziplist结构

按照上面逻辑,将20亿数据存储到redis中,实测下来占用内存50G, 通过计算可以发现节省了80%的内存。

补充说明:

redis为了节约内存空间使用,zset和hash容器在元素个数比较小的时候,采用压缩列表(ziplist)进行存储,上面对redis的参数进行配置就是为了一点。

压缩列表是由一系列特殊编码的连续内存块组成的顺序型数据结构,有点类似 java 中 ArrayList 的底层数组实现,能够很好地利用空间局部性提升内存访问效率。 另外它通过对数据的动态编码,能够很大程度节省内存使用,这对 Redis 来说非常重要。

ziplist的结构:

ziplist.webp