这让我想起了校招回厦门的第二站,当时气血上头应聘5年经验的分布式网络工程师,由于实习期间负责支付模块中用到redis
的缓存一致性,所以展开了一场持续1小时的缓存之争
### 项目中缓存是如何使用的?
根据缓存一致性,在查询支付数据的时候,为了及时反馈订单,就使用redis保存订单支付和用户在线状态
### 为什么要用缓存?
用缓存,主要有两个用途:**高性能**、**高并发**。
#### 高性能
假设这么个场景,你有个操作,一个请求过来耗时 600ms查数据库,但是这个结果不会经常变动,或者变了也可以不用立即反馈给用户。应该怎么做?
用缓存,每次先写数据库,再写缓存。每次先查缓存,再查数据库。
#### 高并发
但这样有个问题,缓存如果某一时刻为null,那岂不是所有请求都打到数据库?那如果数据库为null,那岂不是所有请求都打到缓存?
enmm缓存是走内存的一部分.....好吧我想不出来
其实要根据具体需求跟业务方协商能不能加机器
但实际上机器也是要维护成本的,所以我在想,如果只有2-3台机器的话,那我们如何应对呢?
常见的缓存场景有以下几个
双写不一致
就像自己之前提到的
- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
- 更新的时候,先更新数据库,然后再删除缓存。
缺点在于:
一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。随后数据变更的程序完成了数据库的修改。
方案1:我自己想的是一般是先放入队列,然后起一个短任务轮询最新的请求,如果是写超时没关系,毕竟需要等待最新的写请求,到时间后自动更新。但是读请求超时,就会发生缓存与数据库不一致,所以我的方案是做读写分离,并行化请求,需要频繁访问的接口先读主库,其他接口走从库。
方案2:一位前辈的建议是更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个 jvm 内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部队列中。
缓存雪崩、缓存穿透
- 缓存雪崩
对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库报警完就挂了。此时,如果重启数据库,数据库立马又被新的流量给打死了。
方案1:使用熔断器Hystrix
对重要的资源 ( 例如 Redis、 MySQL、 Hbase、外部接口 ) 都进行隔离,让每种资源都单独运行在自己的线程池中,即使个别资源出现了问题,对其他服务没有影响。
方案2:使用redis持久化机制
一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
-
缓存穿透
当前 key 是一个热点 key( 例如一个热门的娱乐新闻),并发量非常大。
重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的 SQL、多次 IO、多个依赖等。
33.png
方案1:避免key过期,可以将热点数据设置为永远不过期。但如果重建缓存不能再短时间完成,又会出现数据不一致的情况
这里需要避免下面三个问题
- 减少重建缓存的次数
- 数据尽可能一致
- 较少的潜在危险
方案2:基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。
缓存并发竞争
多客户端同时并发写一个 key,可能本来应该先到的数据后到了,导致数据版本错了;或者是多客户端同时获取一个 key,修改值之后再写回去,只要顺序错了,数据就错了。
方案1:基于时间戳的乐观锁
你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。
每次要写之前,先判断一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。但缺点在于假设客户A,B,C并发写key,假设有一个审核场景,必须限制A,B,C顺序写。那么就会出现乱序的情况
方案2:基于redis的setnx的悲观锁
当要写入key的时候,给每个要访问的定时任务一定的逻辑时间间隔,先进入一个内存队列等待锁,然后排队写入Key
网友评论