Reddit是著名的社交新闻网站,光是在2012年,它的独立访客就达到了4000万,页面浏览量达到了370亿次。几年过去了,网站用户有增无减,而随着用户的增多,网站的响应速度却一直在改进。这要得益于Reddit使用了大量的缓存。而随着网站规模不断增长,缓存数量也随着增加,那么Reddit是如何做到在增大缓存规模的同时又能保证它们的响应速度的?
我们知道,缓存的命中率越高,整体速度就越快,因为不需要重新从数据源加载数据。除此之外,如何管理缓存,比如缓存的过期时间,新旧缓存的交换,以及缓存的设计等等,它们对缓存的整体性能都有很大影响。来自Reddit的工程师Daniel Ellis在Reddit官方博客上分享了他们是如何使用Memcached集群来存储网站的缓存数据的。
Reddit的缓存规模和基本策略
Reddit目前使用了54个规格为r3.2xlarge的AWS EC2实例,每个实例拥有61GB内存,也就是说总的缓存大小差不多是3.3TB,而且这些缓存并不包括应用程序的本地缓存。Reddit的缓存包含了多种类型的数据,包括数据库对象、查询结果集、函数调用,还有一些看起来不太像缓存的东西,比如限定速率、分布式锁等等。如何管理这么大规模的缓存是一件很挑战性的事情,Reddit采用的是“不要把所有鸡蛋放在同一个篮子里”的基本策略。也就是说,他们并不是把3.3TB的内存看成一个总的大缓存池,而是按照负载类型对缓存进行分类,每种类型占用一定数量的缓存空间。这样做有几个好处:
首先,按照负载类型对缓存进行分区,每种类型的缓存可以独立地伸缩。例如,对于数据库缓存来说,如果它的命中率降低,交换率变高,同时数据库变慢,那么就要考虑对数据库缓存进行扩展,而它的扩展不会影响到其它类型的缓存。
其次,按照负载类型对缓存进行分区,可以有针对性地对某种类型的缓存进行负载测试,从而预测该类型缓存的使用规模,并作出权衡。
第三个好处跟Memcached的内存分配模型有关系。Memcached按照板块(slab)来分配内存,例如,1至96字节的对象可能被放到板块1,97至120字节的对象被放到板块2,并依此类推。这样做可以避免出现内存碎片。不过,Memcached的这种分配机制不能动态变化,也就是说一旦设定好了这种模式就不能对其进行修改。如果一开始设定了用来存储1KB的对象,但后来想用它来存储500KB的对象,那么交换率就会变得很高。而按照负载类型来区分缓存,那么就可以根据实际数据类型的大小类设定板块大小。
新版本的Memcached可能支持slab_automove功能,不过这是后话了。
Reddit的缓存类型
接下来我们来看看Reddit的几种缓存类型。
数据库对象缓存(thing-cache)
Instances 16 r3.2xlargeMemcached Version
1.4.30
Total RAM 976 GB
Get Rate ~800k/s
Set Rate ~13k/s
Miss % 1.2-2%
Typical Object Size 384-1184 bytes
数据库对象缓存是Reddit最大的缓存池。这些对象是无schema的,开发人员可以很容易地对这些对象添加新属性,而无需对数据库schema进行变更。这些对象包括用户评论、链接和账户等等。该类型缓存是Reddit最繁忙也最有用的缓存,命中率高达99%。
主缓存(cache-main)
Instances 11 r3.2xlargeMemcached Version
1.4.30
Total RAM
671 GB
Get Rate
~82k/s
Set Rate ~10k/s
Miss %
~75%
Typical Object Size <96 bytes
主缓存是Reddit第二大缓存池。这个缓存是一般性的缓存,里面存放的所有用来展示/r/all的结果集。不过从表格中可以看到,这个缓存的命中率并不高,大概只有25%左右。
渲染缓存(cache-render)
Instances 8 r3.2xlargeMemcached Version
1.4.30
Total RAM
488 GB
Get Rate
~224k/s
Set Rate ~103k/s
Miss %
~45-55%
Typical Object Size 240-2320 bytes