1.为什么要使用缓存呢?
在项目中使用缓存,主要是有两个用途,第一个就是高性能,第二个就是高并发.
第一个:高性能
①我们先假设一个场景,客户端发过来一个请求,操作数据库并查询结果耗时一秒钟,此后,这个数据基本不变,或者变了也不用立即反馈给客户,此时就需要使用缓存,将查出来的结果放入到缓存中,缓存功能简单,就是key-value式操作,一个key对应一个value,(可以使用redis非关系型数据库),如果此后还有请求那么就直接从缓存中获取数据,性能得到大大提高,这就是所谓的高性能.
②总结:就是把一些负责操作查出来的结果,如果这个结果后面基本不变或者变化很少,而且还有很多读请求需要这个数据,那么就把这个数据放入到缓存中,后面的请求直接读请求就可以了.
第二个:高并发:
①mysql支持高并发,但是对于高并发支持性不好,如果在高峰期每秒请求有一万,那么mysql服务器就会宕机,这个时候只能使用缓存,不放在mysql中.使用了缓存之后,单击支持的并发量轻松一秒几万几十万,可以轻松支持高并发,单击承载的并发量是mysql单机的几十倍.
②总结:对于短时间内请求过多的情况下,可以采用缓存,因为mysql本来就不是为了高并发而存在的,而缓存是走内存的,内存天然就支撑高并发。
缓存的意义:
通过将高频使用的数据存在离cpu更近的位置,以减少数据传输时间,从而提高处理效率,这就是缓存的意义。
2.如何使用缓存,
一般来说,系统的底层是数据库,中间放了一个redis,前面的业务系统所需的数据都直接从redis里取,然后计算出结果返回给发出请求的对象.此外,数据库和redis的同步另外有程序保证,避免redis的穿透,防止了程序里出现大量请求从redis里找不到,于是又一窝蜂的去查数据库,直接压垮数据库的情况。
3.缓存使用不当有什么后果.
缓存很有用,但是缓存使用不好也会埋很多坑.
①缓存穿透
缓存穿透是说收到了一个请求,但是该请求缓存里没有,只能去数据库里查询,然后放进缓存。这里面有两个风险,一个是同时有好多请求访问同一个数据,然后业务系统把这些请求全发到了数据库;第二个是有人恶意构造一个逻辑上不存在的数据,然后大量发送这个请求,这样每次请求都会被发送到数据库,可能导致数据挂掉。
怎么应对这种情况呢?对于恶意访问,一个思路是事先做校验,对恶意数据直接过滤掉,不要发到数据库层;第二个思路是缓存空结果,就是对查询不存在的数据仍然记录一条该数据不存在在缓存里,这样能有效的减少查询数据库的次数。
那么非恶意访问呢?这个要结合缓存击穿来讲。
②缓存击穿
上面提到的某个数据没有,然后好多请求都被发到数据库其实可以归为缓存击穿的范畴:对于热点数据,当数据失效的一瞬间,所有请求都被下放到数据库去请求更新缓存,数据库被压垮。
怎么防范这种问题呢?一个思路是全局锁,就是所有访问某个数据的请求都共享一个锁,获得锁的那个才有资格去访问数据库,其他线程必须等待。但是现在的业务都是分布式的,本地锁没法控制其他服务器也等待,所以要用到全局锁,比如用redis的setnx实现全局锁。
另一个思路是对即将过期的数据主动刷新,做法可以有很多,比如起一个线程轮询数据,比如把所有数据划分为不同的缓存区间,定期分区间刷新数据等等。这第二个思路又和我们接下来要讲的缓存雪崩有关系。
③缓存雪崩
缓存雪崩是指比如我们给所有的数据设置了同样的过期时间,然后在某一个历史性时刻,整个缓存的数据全部过期了,然后瞬间所有的请求都被打到了数据库,数据库就崩了。
解决思路要么是分治,划分更小的缓存区间,按区间过期;要么是给每个key的过期时间加个随机值,避免同时过期,达到错峰刷新缓存的目的。
④缓存刷新
刷新缓存的程序在请求时,收到了一个错误的结果,例如返回结果为Null,那么,刷新程序根据这个null,就清空了整个缓存,导致缓存中数据消失,而提供缓存中数据的程序可能会被压垮,那么缓存中的数据就消失了,而且在提供数据的程序正常运行之前,缓存中是没有这些数据的.
个人理解:谨慎使用
缓存刷新类似于缓存雪崩,即在极短时间内对缓存进行刷新,缓存中的数据会在短时间内全部消失,此时需要重新从数据库中读取数据,但是这些大量数据同时读取会对数据库造成极大的压力,导致数据库被压垮.缓存刷新一般是在失误情况下对缓存进行的全盘替换.
⑤缓存与数据库双写不一致
你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,即缓存中的数据与数据库中的数据不一致.
解决思路:第一个思路,也是最经典的缓存+数据库读写的模式,就是Cache Aside Pattern, 第一点,读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。第二点,更新的时候,先更新数据库,然后再删除缓存。第二个思路,如果系统不是严格要求 “缓存+数据库” 必须保持一致性的话,最好不要使用读请求和写请求串行化,串到一个内存中去.串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
详细实现可以参考:https://blog.csdn.net/fengqiangdu/article/details/96475383
⑥缓存并发竞争
这个也是线上非常常见的一个问题,就是多客户端同时并发写一个 key,可能本来应该先到的数据后到了,导致数据版本错了;或者是多客户端同时获取一个 key,修改值之后再写回去,只要顺序错了,数据就错了。但是 redis 自己就有天然解决这个问题的 CAS 类的乐观锁方案。某个时刻,多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。每次要写之前,先判断一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。
总之,想要做好高并发系统的缓存,就要考虑到各种边角情况,小心设计,任何细小的疏忽都可能导致系统崩溃.
引用来源:https://blog.csdn.net/fengqiangdu/article/details/96475383