本文主要讨论了在使用memcache时的一些技巧,以及这些技巧的优缺点。

1. 缓存的数据

缓存存储的数据按照计算(读取)的结果类型,可以分为

  1. 存储直接从数据库中读取的数据,降低对数据库的压力,可称为数据源型缓存
  2. 存储从数据库读取并且业务处理完毕的数据,也可称为 最终结果型缓存
  3. 存储进行了部分业务处理的数据,称为中间结果型缓存

数据源型缓存可以直接降低对DB的压力,扩展性高,这也是缓存应用很普遍的形式。但是如果这些数据需要经过复杂而消耗资源的处理,则数据源缓存只能解决了部分的性能问题,这个时候就可以考虑最终结果性缓存,也就是存储经过算法处理之后的结果,这样,每次程序直接根据key从缓存读取结果即可,能够节省大量的时间。

最终结果性缓存,比较适合需要消耗大量计算时间的程序,但是其缺点是缓存的扩展和可移植性很差,一旦程序的key有变化,就会导致所有的缓存都不再适合使用;而且在缓存失效的情况下,会导致程序的时间曲线出现毛刺。

中间结果性缓存,是出于数据源缓存与最终结果型缓存的一种形式,中和了两种缓存的优缺点,但是会使得缓存变多,一次计算需要读取多个缓存。

在实际使用过程中,优先使用数据源型缓存。

2.缓存有效期设置

memcached中所有的缓存都有缓存时间,最长有效时间为72小时。

缓存有效期时间越长,其命中率也就越高,越能有效降低对DB的压力,同时提高程序的处理性能;但是其数据的有效性也就会降低,而且有效期很长的缓存导致程序bug之后,会等到很久之后才会被发现,在问题定位及debug都会造成较大的困难。

原则上在业务允许的范围内,可尽量将有效期设置更长;在使用过程中,综合程序的可测性和线上bug处理的便利性,一般缓存的有效期设置时间会小于2个小时。

3.缓存更新策略

我们在程序中通常的方式是,首先从缓存中读取数据,如果缓存中的数据已经过期,则我们会直接向DB请求数据,并将正确的数据存储到缓存中。这种更新策略也就做被动式更新策略

在程序中需要的缓存的数据一般是更新频率很低的数据,我们可以在数据发生改变时,主动的将改变后的程序更新到缓存中,这样缓存数据的有效期就可以基本处于有效的状态中,这是主动式更新策略,可以将这种缓存的有效期设置的长些。但是这种方式会使得缓存在两处的程序中更新。

对于一些可预见的高QPS的缓存,我们可以在程序启动的时候将这部分缓存加载(缓存预热),这样就可以避免由于冷启动而对DB造成瞬时过大压力。

4.使用热点缓存

在某些场景之下,cache的qps极高,会出现一瞬间缓存miss,导致过多的请求直接压到数据库中,造成数据库雪崩,这也叫做stampeding herd问题。我们可以采用热点缓存的方式来解决。

热点缓存在缓存失效时,总共有n个请求在读取缓存,对其中n-1个请求返回刚刚过期的缓存数据,只允许其中一个请求直接访问DB,并更新缓存。这样可以有效降低对数据库的访问数量,同时对于业务也不会造成太大的影响。

5.批量读取缓存(multiget)

在一些情况,需要一次读取多个key的缓存。这个时候可以使用批量读取的方式一次向memcached请求多个key的缓存,这样可以有效避免循环读取单个key缓存的网络问询时间。

批量缓存导致过multiget 无底洞问题。可以考虑把multiget的key分布到一个节点上,来避免这个问题,这样就需要自己定制memcache的客户端,按一定的规则(比如:相同的前缀)把一类key分布到同一个节点上,来避免这个问题,同时这样也可以提高性能,不用在多个节点之间等待数据。

6.缓存内容大小

multiget主要为了解决网络轮训的时间,但是网络传输时间还包括了传输数据量的大小,需要传输的数据量越大,需要的传输时间也就越大。

所以,我们在设计缓存的时候,尽量将缓存的value变小,比如采用自己序列化value的方式,这样可以有效降低网络传输时间。有效避免网络的拥堵。

另外还存在一个问题:当某个key的QPS过高,会出现短时间内网络拥堵的情况,例如value的大小为1k,而其QPS 为100000,而服务器内网的最大带宽只有1000M,这时候就会出现网络拥堵的情况。实际上,因为缓存的网络带宽是很多key共用的,所以在热点key QPS还没有达到临界点的时候,就已经超时了。

所以,缩小缓存value不仅可有效减低网络传输时间,同时也可以降低发生网络拥堵的几率。

总结

缓存的使用有很多小技巧,但是很多技巧都有其优缺点,我们在实际的业务中根据现状选择适合自己的使用方式,才能有效提高网站的性能。

Reference

也谈如何构建高性能服务端程序
Memcached应用总结