为什么要用缓存?

主要有两个用途:高性能、高并发

高性能

假设这么个场景,有个操作,一个请求过来,耗时 600ms 操作 mysql查出来一个结果,但是这个结果可能接下来几个小时都不会变了,或者变了也可以不会立即反馈给用户。那么此时咋办?

将折腾 600ms 查出来的结果放入缓存里,一个 key 对应一个 value,下次查找时不经过 mysql,直接从缓存里通过一个 key 查出来一个 value,2ms 搞定,性能提升 300 倍。

所以对于一些需要复杂操作耗时查出来的结果,确定后面不怎么变化,但是有很多读请求,直接将查询出来的结果放在缓存中,后面直接读缓存就好。

高并发

mysql 数据库对于高并发来说天然支持不好,mysql 单机支撑到 2000QPS 也开始容易报警了。

所以若是系统高峰期一秒钟有1万个请求,那么一个 mysql 单机绝对会死掉。这个时候就只能上缓存,把很多数据放入缓存,别放入 mysql。缓存功能简单,说白了就是 key-value 式操作,单机支撑的并发量一秒可达几万十几万,单机承载并发量是 mysql 单机的几十倍。

缓存是走内存的,内存天然就支撑高并发。

分布式缓存系统面临的问题

 

image

 

 

缓存与数据库双写不一致

一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统不是严格要求 “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:读请求和写请求串行化,串到一个内存队列里去。

串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。

最经典的就是缓存+数据库读写的模式(Cache Aside Pattern)。

  • 读的时候,先读缓存,缓存没有的话,再读数据库,然后取出数据后放入缓存,同时返回响应。
  • 更新的时候,先更新数据库,然后再删除缓存。

缓存穿透和缓存雪崩

缓存穿透

概念:

访问一个不存在的 key,缓存不起作用,请求会穿透到 DB,流量大时 DB 会挂掉。

举个栗子。系统A,每秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。数据库 id 是从 1 开始的,而黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。

 

image

 

 

解决方案:

(1)对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该 key 对应的数据 insert 之后再清理缓存。

(2)对一定不存在的 key 进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该Bitmap过滤。

缓存雪崩

概念:

对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。

 

image

 

 

解决方案

(1)在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。

(2)不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

(3)做二级缓存,A1 为原始缓存,A2为拷贝缓存,A1 失效时,可以访问 A2,A1 缓存失效时间设置为短期,A2 设置为长期(此点为补充)

缓存击穿

概念:

某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

解决方案:

(1)使用互斥锁 (mutex key):感知到缓存失效,去查询 DB 时,使用分布式锁,使得只有一个线程去数据库加载数据,加锁失败的线程,等待即可。

(2)手动过期:redis 上从不设置过期时间,功能上将过期时间存在 key 对应的 value 里,如果发现要过期,通过一个后台的异步线程进行缓存的构建,也就是“手动”过期。

缓存并发竞争

某个时刻,多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。

 

image

 

 

要写入缓存的数据都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也要查出来。

每次要写之前,先判断一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。


 

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值