常见的几种缓存软件以及使用缓存需要考虑的问题

目录

几种缓存

缓存使用中存在的问题

缓存不足

缓存击穿问题(热点数据单个key)也叫缓存失效

缓存雪崩

缓存穿透

热点缓存

 缓存与数据库一致性问题

缓存与数据库双写不一致

几种缓存

EnCache:
● 优点:
基于Java开发的,被Apache认证
基于JVM缓存的(Ehcache的缓存占用的存储空间,和JAVA虚拟机是在一块儿的,也就是说随着缓存的增多,java虚拟机所消耗的内存也会变大)
简单轻巧,方便,广泛应用于hibernate MyBatis

● 缺点:
不支持集群 单点
不支持分布式 存储容量不支持扩展
Memcache
●  优点:
简单的Key-value存储
内存使用率比较高
支持多核多线程
● 缺点:
无法容灾
无法持久化
Redis
● 优点:
丰富的数据结构 
持久化 RDB AOF
主从同步 故障转移(Mysql 主从)
内存数据库
● 缺点:
单线程 不建议进行大量数据的存储
单核 无法充分利用CPU多核性能,建议使用多实例

缓存使用中存在的问题

缓存不足

缓存击穿问题(热点数据单个key)也叫缓存失效

   缓存击穿是指数据库原本有得数据,但是缓存中没有,一般是缓存突然失效了,
这时候如果有大量用户请求该数据,缓存没有则会去数据库请求,会引发数据库压力增大,可能会瞬间打垮

解决方案

1.加锁
,在未命中缓存时,通过加锁避免大量请求访问数据库
2.不允许过期
。物理不过期,也就是不设置过期时间。而是逻辑上定时在后台异步的更新数据。
3.采用二级缓存
。L1缓存失效时间短,L2缓存失效时间长。请求优先从L1缓存获取数据,如果
未命中,则加锁,保证只有一个线程去数据库中读取数据然后再更新到L1和L2中。然后其他线
程依然在L2缓存获取数据。

缓存雪崩

      缓存雪崩是指缓存中有大量的数据,在同一个时间点,或者较短的时间段内,全部过期了,这个时候请求过来,缓存没有数据,都会请求数据库,则数据库的压力就会突增,扛不住就会宕机。

解决方案

1、
缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列
的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。
这里分享一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增
加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发
集体失效的事件。
2、事前
:这种方案就是在发生雪崩前对缓存集群实现高可用,如果是使用 Redis,可以使用 主
从+哨兵 ,Redis Cluster 来避免 Redis 全盘崩溃的情况。
3、事中
:使用 Hystrix进行限流 & 降级 ,比如一秒来了5000个请求,我们可以设置假设只能
有一秒 2000个请求能通过这个组件,那么其他剩余的 3000 请求就会走限流逻辑。然后去调用
我们自己开发的降级组件(降级),比如设置的一些默认值呀之类的。以此来保护最后的
MySQL 不会被大量的请求给打死。
4、事后
:开启Redis持久化机制,尽快恢复缓存集群

缓存穿透

    缓存穿透是指查询一个根本不存在的数据, 缓存层和存储层都不会命中, 通常出于容错的考虑, 如果从存储层查不到数据则不写入缓存层。缓存穿透将导致不存在的数据每次请求都要到存储层去查询, 失去了缓存保护后端存储的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频 繁攻击我们的应用,这就是漏洞。

造成缓存穿透的基本原因有两个:

  1. 自身业务代码或者数据出现问题。
  2. 一些恶意攻击、 爬虫等造成大量空命中。

注意:穿透的意思是,都没有,直接一路打到数据库。

解决方案

1:接口增加业务层级的Filter,进行合法校验,这可以有效拦截大部分不合法的请求。作为第一点的补充,最常见的是使用布隆过滤器,针对一个或者多个维度,把可能存在的数据值hash到bitmap中,bitmap证明该数据不存在则该数据一定不存在,但是bitmap证明该数据存在也只能是可能存在,因为不同的数值hash到的bit位很有可能是一样的,hash冲突会导致误判,多个hash方法也只能是降低冲突的概率,无法做到避免。

2、另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回
的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的
过期时间会很短,最长不超过五分钟。

热点缓存

        开发人员使用“缓存+过期时间”的策略既可以加速数据读写, 又保证数据的定期更新, 这种模式基本能够满足绝大部分需求。 但是有两个问题如果同时出现, 可能就会对应用造成致命的危害:

  • 当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大。
  • 重建缓存不能在短时间完成, 可能是一个复杂计算, 例如复杂的SQL、 多次IO、 多个依赖等。

在缓存失效的瞬间, 有大量线程来重建缓存, 造成后端负载加大, 甚至可能会让应用崩溃。

解决方案

我们可以利用互斥锁来解决,此方法只允许一个线程重建缓存, 其他线程等待重建缓存的线程执行完, 重新从缓存获取数据即可。

  1. 双重检测锁机制:
  2. 用分布式锁控制访问的线程
  3. 使用redis的setnx互斥锁先进行判断,这样其他线程就处于等待状态,保证不会有大并发操作去操作数据库。

 缓存与数据库一致性问题

 且只要引入缓存,就要考虑缓存和DB数据一致性问题
    一般两种情况:
      1:强一致性/实时一致性方案: 每次更新操作都先删除缓存
      2:最终一致性  设置超时时间来解决 
如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。另外,我们所做的方案其实从根本上来说,只能说降 低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存。

对于分布式高并发场景下使用缓存,优化策略

1:采用本地缓存(对于数据一致性要求不高的场景) + 分布式锁 +缓存
使用分布式锁是为了防止本地缓存和分布式缓存查找不到数据,数据库压力过大

缓存与数据库双写不一致

在大并发下,同时操作数据库与缓存会存在数据不一致性问题

1、双写不一致情况

《常见的几种缓存软件以及使用缓存需要考虑的问题》

2、读写并发不一致

《常见的几种缓存软件以及使用缓存需要考虑的问题》

解决方案

1、对于并发几率很小的数据(如个人维度的订单数据、用户数据等),这种几乎不用考虑这个问题,很少会发生缓存不一致,可以给缓存数据加上过期时间,每隔一段时间触发读的主动更新即可。

2、就算并发很高,如果业务上能容忍短时间的缓存数据不一致(如商品名称,商品分类菜单等),缓存加上过期时间依然可以解决大部分业务对于缓存的要求。

3、如果不能容忍缓存数据不一致,可以通过加读写锁保证并发读写或写写的时候按顺序排好队,读读的时候相当于无锁

4、也可以用阿里开源的canal通过监听数据库的binlog日志及时的去修改缓存,但是引入了新的中间件,增加了系统的复杂度。

《常见的几种缓存软件以及使用缓存需要考虑的问题》

总结:

         以上我们针对的都是读多写少的情况加入缓存提高性能,如果写多读多的情况又不能容忍缓存数据不一致,那就没必要加缓存了,可以直接操作数据库。放入缓存的数据应该是对实时性、一致性要求不是很高的数据。切记不要为了用缓存,同时又要保证绝对的一致性做大量的过度设计和控制,增加系统复杂性!

    原文作者:追夢鹿少年
    原文地址: https://blog.csdn.net/huanglu0314/article/details/124586314
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞