首先,这个通过图片id反查用户uid的应用有以下几点需求:
查询速度要足够快
数据要能全部放到内存里,最好是一台ec2的 high-memory 机型就能存储(17gb或者34gb的,68gb的太浪费了)
支持持久化,这样在服务器重启后不需要再预热
首先否定了数据库存储的方案,他们保持了kiss原则(keep it simple and stupid),因为这个应用根本用不到数据库的update功能,事务功能和关联查询等等牛x功能,所以不必为这些用不到的功能去选择维护一个数据库。
于是他们选择了redis,redis是一个支持持久化的内存数据库,所有的数据都被存储在内存中(忘掉vm吧),而最简单的实现就是使用redis的string结构来做一个key-value存储就行了。像这样:
set media:1155315 939get media:1155315> 939
其中1155315是图片id,939是用户id,我们将每一张图片id为作key,用户uid作为value来存成key-value对。然后他们进行 了测试,将数据按上面的方法存储,1,000,000数据会用掉70mb内存,300,000,000张照片就会用掉21gb的内存。对比预算的17gb 还是超支了。
(nosqlfan:其实这里我们可以看到一个优化点,我们可以将key值前面相同的media去掉,只存数字,这样key的长度就减少了,减少key值对内存的开销【注:redis的key值不会做字符串到数字的转换,所以这里节省的,仅仅是media:这6个字节的开销】。经过实验,内存占用会降到50mb,总的内存占用是15gb,是满足需求的,但是instagram后面的改进任然有必要)
于是instagram的开发者向redis的开发者之一pieter noordhuis询问优化方案,得到的回复是使用hash结构。具体的做法就是将数据分段,每一段使用一个hash结构存储,由于hash结构会在单个hash元素在不足一定数量时进行压缩存储,所以可以大量节约内存。这一点在上面的string结构里是不存在的。配置文件中的hash-zipmap-max-entries参数控制着一定数量。经过开发者们的实验,将hash-zipmap-max-entries设置为1000时,性能比较好,超过1000后hset命令就会导致cpu消耗变得非常大。
于是他们改变了方案,将数据存成如下结构:
hset mediabucket:1155 1155315 939hget mediabucket:1155 1155315> 939
通过取7位的图片id的前四位为hash结构的key值,保证了每个hash内部只包含3位的key,也就是1000个。
在进行另一次试验后,发现每1,000,000个key仅消耗了16mb的内存。总内存使用也降到了5gb,满足了应用需求。
(nosqlfan: 同样的,这里我们还是可以再进行优化,首先是将hash结构的key值变成纯数字,这样key长度减少了12个字节,其次是将hash结构中的 subkey值变成三位数,这又减少了4个字节的开销,如下所示。经过实验,内存占用量会降到10mb,总内存占用为3gb)
hset 1155 315 939hget 1155 315> 939
以上就是redis如何节约内存的详细内容。