31 January 2018
原文: 节约内存:Instagram 的 Redis 实践

Instagram 可以说是网拍 App 的始祖级应用,也是当前最火热的拍照 App 之一,Instagram 的照片数量已经达到 3 亿,而在 Instagram 里,我们需要知道每一张照片的作者是谁,下面就是 Instagram 团队如何使用 Redis 来解决这个问题并进行内存优化的。

首先,这个通过图片 ID 反查用户 UID 的应用有以下几点需求:

  • 查询速度要足够快
  • 数据要能全部放到内存里,最好是一台 EC2 的 high-memory 机型就能存储(17GB 或者 34GB 的,68GB 的太浪费了)
  • 要合适 Instagram 现有的架构(Instagram 对 Redis 有一定的使用经验,比如这个应用)
  • 支持持久化,这样在服务器重启后不需要再预热
  • Instagram 的开发者首先否定了数据库存储的方案,他们保持了 KISS 原则(Keep It Simple and Stupid),因为这个应用根本用不到数据库的 update 功能,事务功能和关联查询等等牛 X 功能,所以不必为这些用不到的功能去选择维护一个数据库。

于是他们选择了 Redis,Redis 是一个支持持久化的内存数据库,所有的数据都被存储在内存中(忘掉 VM 吧),而最简单的实现就是使用 Redis 的 String 结构来做一个 key-value 存储就行了。像这样:

redis> SET media:1155315 939
redis> GET media:1155315
redis> 939

其中 1155315 是图片 ID,939 是用户 ID,我们将每一张图片 ID 为作 key,用户 uid 作为 value 来存成 key-value 对。然后他们进行了测试,将数据按上面的方法存储,1,000,000 数据会用掉 70MB 内存,300,000,000 张照片就会用掉 21GB 的内存。对比预算的 17GB 还是超支了。

于是 Instagram 的开发者向 Redis 的开发者之一 Pieter Noordhuis 询问优化方案,得到的回复是使用 Hash 结构。具体的做法就是将数据分段,每一段使用一个 Hash 结构存储,由于 Hash 结构会在单个 Hash 元素在不足一定数量时进行压缩存储,所以可以大量节约内存。这一点在上面的 String 结构里是不存在的。而这个一定数量是由配置文件中的 hash-zipmap-max-entries 参数来控制的。经过开发者们的实验,将 hash-zipmap-max-entries 设置为 1000 时,性能比较好,超过 1000 后 HSET 命令就会导致 CPU 消耗变得非常大。

于是他们改变了方案,将数据存成如下结构:

redis> HSET "mediabucket:1155" "1155315" "939"
redis> HGET "mediabucket:1155" "1155315"
redis> "939"

通过取 7 位的图片 ID 的前四位为 Hash 结构的 key 值,保证了每个 Hash 内部只包含 3 位的 key,也就是 1000 个。

再做一次实验,结果是每 1,000,000 个 key 只消耗了 16MB 的内存。总内存使用也降到了 5GB,满足了应用需求。

redis> HSET "1155" "315" "939"
redis> HGET "1155" "315"
redis> "939"

简单的说,redis 维护一个 key-value 的开销有点大,优化的方法就是将多个 key-value 合并成一个 hash 对应多个 value

下面的内容来自:http://www.restran.net/2015/02/17/redis-practice/
Redis 默认使用 ziplist(压缩列表)来存储 HASH(哈希),LIST(列表),ZSET(有序集)这些数据结构。
当某些条件被满足时,自动转换成 hash table(哈希表),linkedlist(双端列表),skiplist(跳表)。

➜ vi redis.conf
hash-max-ziplist-entries 512
hash-max-ziplist-value   64

list-max-ziplist-entries 512
list-max-ziplist-value   64

zset-max-ziplist-entries 128
zset-max-ziplist-value   64