目录
几种缓存
EnCache:
● 优点:
基于Java开发的,被Apache认证
基于JVM缓存的(Ehcache的缓存占用的存储空间,和JAVA虚拟机是在一块儿的,也就是说随着缓存的增多,java虚拟机所消耗的内存也会变大)
简单轻巧,方便,广泛应用于hibernate MyBatis
● 缺点:
不支持集群 单点
不支持分布式 存储容量不支持扩展
Memcache
● 优点:
简单的Key-value存储
内存使用率比较高
支持多核多线程
● 缺点:
无法容灾
无法持久化
Redis
● 优点:
丰富的数据结构
持久化 RDB AOF
主从同步 故障转移(Mysql 主从)
内存数据库
● 缺点:
单线程 不建议进行大量数据的存储
单核 无法充分利用CPU多核性能,建议使用多实例
缓存使用中存在的问题
缓存不足
缓存击穿问题(热点数据单个key)也叫缓存失效
缓存击穿是指数据库原本有得数据,但是缓存中没有,一般是缓存突然失效了,
这时候如果有大量用户请求该数据,缓存没有则会去数据库请求,会引发数据库压力增大,可能会瞬间打垮
解决方案
缓存雪崩
缓存雪崩是指缓存中有大量的数据,在同一个时间点,或者较短的时间段内,全部过期了,这个时候请求过来,缓存没有数据,都会请求数据库,则数据库的压力就会突增,扛不住就会宕机。
解决方案
缓存穿透
缓存穿透是指查询一个根本不存在的数据, 缓存层和存储层都不会命中, 通常出于容错的考虑, 如果从存储层查不到数据则不写入缓存层。缓存穿透将导致不存在的数据每次请求都要到存储层去查询, 失去了缓存保护后端存储的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频 繁攻击我们的应用,这就是漏洞。
造成缓存穿透的基本原因有两个:
- 自身业务代码或者数据出现问题。
- 一些恶意攻击、 爬虫等造成大量空命中。
注意:穿透的意思是,都没有,直接一路打到数据库。
解决方案
1:接口增加业务层级的Filter,进行合法校验,这可以有效拦截大部分不合法的请求。作为第一点的补充,最常见的是使用布隆过滤器,针对一个或者多个维度,把可能存在的数据值hash到bitmap中,bitmap证明该数据不存在则该数据一定不存在,但是bitmap证明该数据存在也只能是可能存在,因为不同的数值hash到的bit位很有可能是一样的,hash冲突会导致误判,多个hash方法也只能是降低冲突的概率,无法做到避免。
热点缓存
开发人员使用“缓存+过期时间”的策略既可以加速数据读写, 又保证数据的定期更新, 这种模式基本能够满足绝大部分需求。 但是有两个问题如果同时出现, 可能就会对应用造成致命的危害:
- 当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大。
- 重建缓存不能在短时间完成, 可能是一个复杂计算, 例如复杂的SQL、 多次IO、 多个依赖等。
在缓存失效的瞬间, 有大量线程来重建缓存, 造成后端负载加大, 甚至可能会让应用崩溃。
解决方案
我们可以利用互斥锁来解决,此方法只允许一个线程重建缓存, 其他线程等待重建缓存的线程执行完, 重新从缓存获取数据即可。
- 双重检测锁机制:
- 用分布式锁控制访问的线程
- 使用redis的setnx互斥锁先进行判断,这样其他线程就处于等待状态,保证不会有大并发操作去操作数据库。
缓存与数据库一致性问题
且只要引入缓存,就要考虑缓存和DB数据一致性问题
一般两种情况:
1:强一致性/实时一致性方案: 每次更新操作都先删除缓存
2:最终一致性 设置超时时间来解决
如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。另外,我们所做的方案其实从根本上来说,只能说降 低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存。
对于分布式高并发场景下使用缓存,优化策略
1:采用本地缓存(对于数据一致性要求不高的场景) + 分布式锁 +缓存
使用分布式锁是为了防止本地缓存和分布式缓存查找不到数据,数据库压力过大
缓存与数据库双写不一致
在大并发下,同时操作数据库与缓存会存在数据不一致性问题
1、双写不一致情况
2、读写并发不一致
解决方案
1、对于并发几率很小的数据(如个人维度的订单数据、用户数据等),这种几乎不用考虑这个问题,很少会发生缓存不一致,可以给缓存数据加上过期时间,每隔一段时间触发读的主动更新即可。
2、就算并发很高,如果业务上能容忍短时间的缓存数据不一致(如商品名称,商品分类菜单等),缓存加上过期时间依然可以解决大部分业务对于缓存的要求。
3、如果不能容忍缓存数据不一致,可以通过加读写锁保证并发读写或写写的时候按顺序排好队,读读的时候相当于无锁。
4、也可以用阿里开源的canal通过监听数据库的binlog日志及时的去修改缓存,但是引入了新的中间件,增加了系统的复杂度。
总结:
以上我们针对的都是读多写少的情况加入缓存提高性能,如果写多读多的情况又不能容忍缓存数据不一致,那就没必要加缓存了,可以直接操作数据库。放入缓存的数据应该是对实时性、一致性要求不是很高的数据。切记不要为了用缓存,同时又要保证绝对的一致性做大量的过度设计和控制,增加系统复杂性!