缓存概述
缓存在互联网架构中是十分重要的一部分,良好设计的缓存可以有效缓解系统压力,提高系统整体性能。
但是,使用缓存也会提高技术复杂度,一般情况下从两个方面来确认是否需要缓存。
- CPU占用高的行为
某些过程需要消耗大量的cpu资源进行计算,可以根据业务考虑将结果保存缓存。 - 数据库访问频繁
如果系统的访问量十分巨大,全部请求指到数据库中可能会导致连接不够甚至数据库崩溃,那么使用缓存是一种有效的暂时手段。
注意,这意味着使用缓存可以随意应对高并发数据量,一旦发生缓存雪崩,而后端没有相应的处理方案的话,数据库肯定会陷入超负荷状态。
缓存加速原理
数据库访问数据,磁盘IO,慢
缓存里访问数据,存操作,快
数据库里的热数据,可在缓存冗余一份
先访问缓存,如果命中,能大大的提升访问速度,降低数据库压力
使用基本方法 -Cache Aside 模式
- 先访问缓存,如果命中直接返回结果
- 如果没命中缓存,去数据库查询结果,返回并保存到缓存中一份记录
如果读没有命中,或者涉及写操作,系统流程将会变得更复杂。
读操作没有命中缓存
image尝试从缓存get数据,结果没有命中
从数据库获取数据,读从库,读写分离
把数据set到缓存,未来能够命中缓存
写操作
写需要修改数据库的值和缓存中的值,那么就会有两种修改方式
- 先操作数据库,再写缓存
- 先写缓存,再操作数据库
缓存的两种更新方法
- 更新
- 淘汰
相比于更新,淘汰会多一次cache miss。
更新操作可能会需要额外的IO操作去查库或者重新计算。
所以在绝大部分场景下,淘汰比更新需要的性能更小,所以推荐直接淘汰,在下次请求cache miss之后再进行操作放入缓存。
先操作缓存还是先操作数据库?
先操作缓存
更新缓存
先更新缓存,然后在缓存设置成功的情况下,如果数据库操作失败,产生了事务不一致。此时缓存与数据库中的数据不一致。
淘汰缓存
先淘汰缓存,在淘汰成功的情况下,再去操作数据库。
问题出在两个线程并发读写的情况下:线程1写操作去修改缓存成功,再去试图写入数据库,即锁表
线程2读操作是在线程1缓存修改成功之后进入,因为cache miss,回去数据库中查询数据。此时有可能出现线程2先读到了还未更新的数据库中数据,最终导致缓存中设置的是还未更新的值,缓存与数据库中的数据不一致,而且出现的几率很高。
并发读写导致脏数据
先操作数据库
更新缓存
操作完数据库之后,更新数据库。
并发写的场景下,可能出现两个线程因为时间先后顺序不同导致缓存出现脏数据。
线程1在更新完数据库早于线程2,但是线程2的更新缓存晚于线程1,此时缓存你中的值将会不是最新的值,即缓存中出现脏数据。
并发写导致脏数据
淘汰缓存
先操作数据库再淘汰缓存,是所有情况下最为稳妥的方式。
可能出现并发的场景为:线程1读缓存未命中,去数据库中获取到数据之后,然后线程2锁表开始写数据,写完成后将缓存删除,此时线程1再去更新缓存中的值(因为读是读得后再去更新),这时候缓存的数据就是之前的脏数据
。
并发读写导致脏数据
这种场景出现的比较苛刻,要在读失效的前提下,读数据库操作发生在写数据库之前,然后写缓存发生在淘汰缓存之后,因为数据库中读的速度远快于写,读完立即去写入,理论上来说要发生在写完后的操作是十分困难的。
总结
根据上文分析,操作缓存最好使用先操作数据库再淘汰缓存
是最好的使用方式。
缓存是通过牺牲强一致性来提高性能的。所以使用缓存提升性能,就是会有数据更新的延迟。这需要我们在设计时结合业务仔细思考是否适合用缓存。然后缓存一定要设置过期时间,这个时间太短太长都不好,太短的话请求可能会比较多的落到数据库上,这也意味着失去了缓存的优势。太长的话缓存中的脏数据会使系统长时间处于一个延迟的状态,而且系统中长时间没有人访问的数据一直存在内存中不过期,浪费内存。
注:文章图片采用使用缓存的正确姿势
网友评论