前言
目前磁盘 IO 和网络 IO 相对于内存 IO 的成百上千倍的性能劣势。
通过将高频使用的数据存在离 cpu 更近的位置,以减少数据传输时间,从而提高处理效率,这就是缓存的意义。
浏览器缓存
浏览器会缓存页面的元素,这样在重复访问网页时,就避开了要从互联网上下载数据(例如大图片)
缺点:
客户端缓存减少了的服务器请求,避免了文件重复加载,显著地提升了用户地方。但是当网站发生了更新的时候(如替换了 css、js 以及图片文件),浏览器本地仍保存着旧版本的文件,从而导致无法预料后果。
CDN 缓存
浏览器本地缓存失效后,浏览器会向 CDN 边缘节点发起请求。类似浏览器缓存,CDN 边缘节点也存在着一套缓存机制。
CDN 可以理解为分布在每个县城的火车票代售点,用户在浏览网站的时候,CDN 会选择一个离用户最近的 CDN 边缘节点来响应用户的请求。
缺点:CDN 的分流作用不仅减少了用户的访问延时,也减少的源站的负载。但其缺点也很明显:当网站更新时,如果 CDN 节点上数据没有及时更新,即便用户再浏览器使用 Ctrl +F5 的方式使浏览器端的缓存失效,也会因为 CDN 边缘节点没有同步最新数据而导致用户访问异常。
数据库缓存
数据库会缓存查询,所以同一条查询第二次就是要比第一次快。
分布式 缓存
(如 redis Memcache)选择把数据存在内存而非硬盘里。
本地缓存
编程实现(成员变量、局部变量、静态变量)、Guava Cache
高并发下缓存的常见问题
- 缓存一致性
当数据时效性要求很高时,需要保证缓存中的数据与数据库中的保持一致,而且需要保证缓存节点和副本中的数据也保持一致,不能出现差异现象。这就比较依赖缓存的过期和更新策略。
一般会在数据发生更改的时,主动更新缓存中的数据或者移除对应的缓存。
- 缓存穿透
缓存穿透是说收到了一个请求,但是该请求缓存里没有,只能去数据库里查询,然后放进缓存。这里面有两个风险,一个是同时有好多请求访问同一个数据,然后业务系统把这些请求全发到了数据库;第二个是有人恶意构造一个逻辑上不存在的数据,然后大量发送这个请求,这样每次请求都会被发送到数据库,可能导致数据挂掉。
怎么应对这种情况呢?对于恶意访问,一个思路是事先做校验,对恶意数据直接过滤掉,不要发到数据库层;第二个思路是缓存空结果,就是对查询不存在的数据仍然记录一条该数据不存在在缓存里,这样能有效的减少查询数据库的次数。
- 缓存击穿
缓存击穿其实是缓存穿透中,非恶意访问的情况。比如缓存中某热点数据过期失效,所有请求都被下放到数据库去请求更新缓存,数据库被压垮。
怎么防范这种问题呢?一个思路是全局锁,就是所有访问某个数据的请求都共享一个锁,获得锁的那个才有资格去访问数据库,其他线程必须等待。但是现在的业务都是分布式的,本地锁没法控制其他服务器也等待,所以要用到全局锁,比如用 redis 的 setnx 实现全局锁。
另一个思路是对即将过期的数据主动刷新,做法可以有很多,比如起一个线程轮询数据,比如把所有数据划分为不同的缓存区间,定期分区间刷新数据等等。这第二个思路又和我们接下来要讲的缓存雪崩有关系。
- 缓存雪崩
缓存雪崩是指比如我们给所有的数据设置了同样的过期时间,然后在某一个历史性时刻,整个缓存的数据全部过期了,然后瞬间所有的请求都被打到了数据库,数据库就崩了。
解决思路要么是分治,划分更小的缓存区间,按区间过期;要么是给每个 key 的过期时间加个随机值,避免同时过期,达到错峰刷新缓存的目的。
- 缓存无底洞
该问题由 facebook 的工作人员提出的, facebook 在 2010 年左右,memcached 节点就已经达 3000 个,缓存数千 G 内容。他们发现了一个问题—memcached 连接频率,效率下降了,于是加 memcached 节点,添加了后,发现因为连接频率导致的问题,仍然存在,并没有好转,称之为”无底洞现象”。
总结
缓存无底洞情况并不常见,在绝大多数公司根本不会遇到。