我们先对比一下在数据库中,数据存放在内存和硬盘上的性能对比。
考虑SATA硬盘:10000rpm、150MB/s传输率、9ms寻道时间
MySQL Innodb pagesize:16kB
如果每一次数据请求,内存中都未缓存,则吞吐量为:60qps
如果数据均在内存中,查询语句为简单主键查询,返回结果集很小,则吞吐量为:50000qps。
从测试结果可知,纯内存的访问吞吐量,大约为硬盘的1000倍,这与我们的常识一致。那紧接着的问题是,在我们线上产品部署时,为何不直接使用数据库的块缓存来存储数据,而是要使用专门的数据缓存产品来存储热门数据呢。假设每条记录50bytes,数据库页大小 16kB,综合考虑每条记录的额外存储开销,则在内存中每个page大约存放260条记录。如果该页中只有一条热门数据,而其他均是冷门数据,则空间利用率为0.3%
官方网站: http://memcached.org/
产品提供的主要操作类型:
产品优点:
产品缺点:
官方网站: http://redis.io/ 。产品特点如下:
其实MySQL自身也带了热门数据缓存机制,某些场景下也能满足产品需求。首先在服务器上设置开启查询缓存以及缓存占用内存大小,然后可在用户SQL中设定此语句是否走查询缓存。另外,在服务器上还可设定当用户SQL没有明确说明查询缓存操作策略时,默认行为是什么。MySQL查询缓存的实现原理很简单,不做复杂语法分析,以用户SQL语句为key进行查找,如果匹配到,则直接返回上次查询获得的结果集。需要格外注意的是,当一张表中的任何一条数据被更新或插入删除后,整张表相关的查询缓存都会被清空。因此,查询缓存只适用于查询量非常频繁而更新操作很少发生的场景。
NTSE是网易杭州研究院自主研发的一款数据库产品,可作为底层存储引擎安装到MySQL中。NTSE的一个特色是自带了热门数据行级缓存,其最大优点是用户使用非常方便,只需要在服务器上开启开关即可打开此功能,并且能保证缓存数据与实际数据强一致。根据项目组测试结果,对数据访问热点明显的应用与InnoDB相比通常可获得3~5倍甚至更高的性能提升。
数据保存在外部缓存服务器中,受限于服务器的内存容量及处理能力。下面讨论一下当我们决定使用memcached或redis时,有哪些水平扩展方案。
一致性hash是大家都非常熟悉的一种水平扩展方案,优点是不需要服务器做任何改动即可使用,而且主流客户端如spymemcached、jedis都支持。缺点是一旦服务器数量发生变化,数据迁移的成本非常高。一致性hash的部署架构图如下:
除去一致性hash,下面介绍几个其他业界常用水平扩展方案:
这些方案的共同优点是都以中间件形式管理底层多台数据缓存服务器,对产品开发人员来说,屏蔽分布式水平扩展的各种复杂问题。具体每种方案的优缺点及使用注意事项,可参考附件中的《开源数据缓存服务器扩展方案》。
如果我们用了NTSE或者MySQL查询缓存,那就不存在数据一致性问题,如果数据存储在memcached或redis之类的外部缓存系统中,则数据一致性需要我们格外关注。下面列举几种目前主流数据同步方案。
此算法的基本思想,由客户端通过特定算法来保证当更新数据库记录时,同步更新缓存中的数据。由于同步算法只和SQL语句有关,带有普遍性,所以我们公司的DAO框架以及DDB中,都已经自带缓存数据同步算法。
更新数据库语句
对更新数据库的SQL语句来说,update语句或delete语句在操作memcached上面没有本质不同。下面以delete语句为例来说明。
根据SQL语句中的缓存key更新缓存
a) 从SQL语句或数据库中提取缓存key
b) 把缓存中与缓存key相关的数据删除
c) 在数据库中执行delete语句
查询数据库语句
向memcached中添加记录的SQL,操作流程如下:
a) 从SQL语句或数据库中提取缓存key
b) 尝试从缓存中获取数据,如果能够得到则直接返回结果
c) 从数据库中获取数据并返回给用户
d) 把新获得的数据插入到缓存中
需要额外注意的是,即使我们应用了缓存数据同步算法,还是无法保证缓存数据与数据库中的数据严格一致。产生问题的原因有以下几种:
附件的《Memcache数据一致性方案讨论》,对各种场景展开详细讨论,有兴趣读者可进一步查看。另外,文档中还提到一种基于CAS操作的算法,能够保证单机情况下数据强一致。
阅读前面的客户端一致算法可以发现,要保证缓存服务器数据一致性代价非常昂贵。在实际生产使用时,还有另一种方案:通过异步采集回放数据库binlog日志来更新缓存服务器数据。基本流程为:
此方案的缺点是缓存数据最终一致性而非强一致。
主流缓存服务器产品都带有数据过期时间判断机制。在插入缓存服务器时,数据带有一个时间戳。当再次获取时,服务器将比对过期时间条件是否满足,如果已过期则返回数据不存在。一般情况下,此方案可与客户端同步算法联合使用,能大大降低数据不一致的概率,将数据一致性级别提升为最终一致性。
如果数据存放在外部缓存服务器,则很难保证数据强一致。因此,我们在编写程序时,需要注意以下原则:
业界常有一句话,热门数据缓存是万金油,如果发现哪里性能顶不住了就抹一下。这个万金油虽然好使,但用起来也需要格外注意。我们产品在发布时,都会做几轮性能测试,但回想一下我们以前做的测试,都是在系统运行一段时间稳定后产出的性能指标。数据一缓存,性能指标立马提升,但千万小心在某些情况下这只是一个假象。除了前面讲到的数据一致性问题,数据预热也需要考虑。
再回顾一下前面讲到的memcached行缓存和数据库块缓存之间的区别。行缓存虽然大大提高内存利用率,但也有副作用。当加载一条数据时,就需要一次数据库操作,至少一次IO,数据完全预热的时间非常长。而块缓存,最大好处是利用了热门数据局部性原理,往往可以在较短时间填充满整个buffer。当前端缓存服务器还在数据预热阶段,无法提供热门数据时,大量操作被穿透到底层数据库服务器上。尤其当前端缓存服务器宕机时,此情况很容易引发雪崩效应。
一般使用时为了尽量避免数据预热的风险,有以下措施:
最后稍微提一下非结构化数据缓存方案。
对于图片视频等数据,如果用户访问很大,而且呈现二八特征,第一想到的应该是CDN。采用CDN后不仅可以有效降低后端服务器压力,还能大大改善用户响应时间;
如果需要在服务器端进行热点数据缓存,可选方案有:
本文来自网易实践者社区,经作者邱似峰授权发布。