缓存与数据库 一致性分析(双写)
1、1 Read Pattern 读模式
总结: 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
1、2 Write Pattern 写模式
更新与write是一样的,先写数据库,然后在写缓存,为什么? 下面我用一张图说明
缓存写
总结: 更新的时候,先更新数据库,然后再删除缓存
缓存删除
异常分析:
1、删除缓存失败,也不会出现不一致的现象
2、删除数据库失败,但是缓存的数据也就删除,不会出现不一致的现象,下次发送请求的时候loading到缓存中。
高并发下场景分析:
当线程1已经把环境数据删除了。线程2发送读请求,后台因为现实线程2执行在前面,将数据库中的数据读取到缓存中,然后删除数据,那么缓存中的数据与数据库中的数据就会出现不一致的情况。
在高并发下,由于时许的问题导致缓存中的数据与数据库中的数据不一致那么我们应该如何解决呢?
1、 锁能解决这种问题吗,不行为什么呢。 锁解决的是竞争问题。
2、单纯的消息中间件能解决吗,中间件可以保证时序,但是不能判断是否存在该消息
3、数据库的事务是否能解决呢? 事务解决的也是数据一致性的问题(业务层与数据库层面的一致性),看来这个也跟数据库的事务没有关系。
4、我们可以采用队列来实现,read的时候判断当前队列中是否存在删除操作,如果存在直接等待,如果没有直接执行,这个看样子是可以解决问题,当前这样会出现大量的线程阻塞、有可能还是出现timeout。
出现这种问题,应该如何注意呢?
- 读请求长时阻塞
解说: 为什么加了缓冲队列,主要在读请求的是否判断缓冲队列中是否存在update操作,如果有就阻塞。那么队列中长时间有数据,那么会一直阻塞这种情况如何解决,请看下图
我将从前端后后台的架构画了下,这种架构就不存在问题吗?
- 通过nginx 通过ip hash的规则转发到后台 (单机过热的问题)
- 单机缓存队列不足的问题,可以扩容
读并发的问题
这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能扛的住,需要多少机器才能扛住最大的极限情况的峰值。
但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。
- 多服务实例部署的请求路由
可能这个服务部署了多个实例,那么必须保证说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 Nginx 服务器路由到相同的服务实例上。
比如说,对同一个商品的读写请求,全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的 hash 路由,也可以用 Nginx 的 hash 路由功能等等。
- 热点商品的路由问题,导致请求的倾斜
万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大。就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以其实要根据业务系统去看,如果更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些。
总结: 先删除缓存,然后在删除数据库
1、3 高并发缓存思考
-
在高并发缓存分为两种数据,热点与非热点数据。热点数据瞬间失效了怎么办?
非热点数据: 其实对于并发影响不大, 那么热点数据呢? 列如秒杀系统查询商品信息,在这种大流量的请求下首先对商品入热操作(就是将热点商品loading到缓存中)
热点数据瞬间失效了怎么办? 请看后面
-
如何保证高并发下面时序造成的影响
-
如何解决缓存击穿、穿透、雪崩问题
雪崩–> 对你当前的你应该不会陌生,例如我们使用springcloud, 使用feign组件的调用,如果调用的其他服务没有容错的话,很有可能导致服务宕机造成阻塞导致大量的请求阻塞到这里。那么缓存雪崩是啥子东西呢请看下面的图
当前当3000个请求到达时候,导致环境宕机全部到数据库,而这时数据库也支撑不住,导致数据库宕机。
那么这种请求,我们应该怎么办呢?
其实也简单,第一种如果远程缓存挂了走本地缓存,那么数据库怎么保护被,数据库可以加上限流开关,当超过这个值的时候,走降级逻辑
穿透
及时大量的恶意的请求,导致数据库宕机,如何解决呢
- 布隆过滤机
- 将恶意的请求参数也写入缓存
击穿
大量的缓存key(非常热)在同一时间失效
场景分析:
- 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。
- 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 Redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。
- 若缓存的数据更新频繁或者在缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动地重新
- 构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。