项目中很多地方都会使用缓存,由于其直接与内存交互,可以有效的提高并发量,减少程序与数据库等的交互,但是使用缓存也会存在诸多问题,这里根据平时自己涉及到的以及了解到做个分享,缓存的比如redis的工作原理这些就不说了,能遇到标题中的问题的都是会使用的,有问题的地方欢迎指出。
存在即合理,每个方案都以自己适用的场景,之后不断的学习摸索,才能找到最优的解决方法。
一、缓存穿透
产生过程及原因:
用户访问的时候,缓存和数据库(或者其他存储件)都不存在记录,如果并发量不大,那么最多就是给客户端返回null,但是当并发相当大的时候,后台就会不停的向数据库查询,我们知道,程序性能的提升有很大一部分是从数据库优化入手的,高并发的访问数据库必然会造成系统性能问题,甚至,如果遭到恶意攻击,那么系统很可能就直接“炸”了。
解决方案:
1、增加缓存空值:这个很好理解,其实就是按条件查询数据库时未查询到对应值也就是null,此时,不是直接返回,而是按一定的规则(比如查询条件最为key)将查询的空值缓存起来,下次就知道这个条件是查不到值的。但是这个缓存时间不能过长,因为有可能后台数据更新,可以查到值了,具体时间视系统情况来约定,比如我们之间有个系统就允许一天的时间,有的更新频繁的就30s。
2、增加布布隆过滤器:大体的流程可解释为:系统中的数据在初始化的时候(或者在进行DML之后)将数据库的数据映射到布隆过滤器中,下次获取数据先经过布隆过滤器的判断,验证通过了才到缓存中或者是到数据库获取。大体流程如下:
此时有人会问了,我的数据既然能放到布隆过滤器,为啥不直接放到缓存里面,还省了使用布隆过滤器。你要知道存在即合理,固然可以直接放到缓存,但是,比如一条数据100B,一千万数据就得1G了,况且有些数据都不止100B,而布隆过滤器只需要占用很小的一部分空间。但是使用布隆过滤器也会有一些问题,比如因为他是使用hash来计算映射的,那么就会存在hash冲突问题,也可以使用多次计算hash值来减少对撞的可能性。再者就是过滤器不支持删除。
3、较强的用户、权限等验证,避免遭到恶意攻击:这个就不说了,一个系统必不可少的就是用户、权限验证。
二、缓存击穿
产生过程和原因:
当缓存中没有目标数据,但是数据库存在,而此时用户的访问量特别大(高并发,缓存过期或者没有缓存),那么数据库的压力就会非常大甚至会因为连接数超载而宕机。
解决方案:
1、热点数据缓存更新或者永久缓存:
永久缓存:这个很好理解。
缓存更新:分为两种方式:第一个是后台定时检查超时缓存,然后将数据库的数据查询后更新到缓存,比如key5分钟过期,那么定时任务定4分钟;第二种是检查也就是将key的过期时间也缓存,每次请求获取数据时都用其缓存时间与系统时间做比对,这里的是要得到两个时间的差值比如允许在缓存超时前一分钟甚至是几秒钟更新时间(一定是在缓存超时前更新,在之后就没有意义)。
2、加锁
加锁有很多种,这里只介绍一个互斥锁,因为其他的锁都会有些许小问题,有兴趣的可以自己查一下。互斥锁原理流程:
按道理在不能获取锁之后(也就是已经被锁定了),要休眠一点时间,避免一定的资源浪费,因为锁定肯定不是立马就能完成的。
三、缓存雪崩
产生过程和原因:
雪崩并非是缓存系统蹦了,而是应用系统对缓存依赖很深,大量数据在缓存中存放,而在某一时刻或者时间段,大面积的缓存都是超时失效了,此时,这些失效的缓存就必须到数据库取数据,它和击穿不一样的就是,击穿专注于某一个或某一些的缓存值的高并发访问,雪崩是大面的超时。 其实大体的解决方案和之前两种情况差不多,主要目的就是降低对数据库的访问。
解决方案:
1、缓存超时时间尽量设置的离散一点。
2、能设置成永不过期的就设置成永不过期,比如静态缓存,热点数据。
3、设置过期标识,定期检查是否,若过期了就重新设置。
4、加锁排队,一是从用户流量上排队,二是访问数据库排队,限流啊,降级等。
5、二级缓存,比如L1和L2,用户先从L1获取,如果没有再从L2获取,L1的超时时间短,L2的超时时间较长一点。
6、消息中间件,其实就和排队差不多但是比加锁的性能要高很多。