文章目录
一、缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起 id 为-1 的数据或者特别大的不存在的数据。有可能是黑客利用漏洞攻击从而去压垮应用的数据库。
解决方案
对于缓存穿透问题,常见的解决方案有以下三种:
-
验证拦截:接口层进行校验,如鉴定用户权限,对 ID 之类的字段做基础的校验,如 id<=0 的字段直接拦截;
-
缓存空数据:当数据库查询到的数据为空时,也将这条数据进行缓存,但缓存的有效性设置得要较短,以免影响正常数据的缓存;
-
使用布隆过滤器:布隆过滤器是一种比较独特数据结构,有一定的误差。当它指定一个数据存在时,它不一定存在,但是当它指定一个数据不存在时,那么它一定是不存在的。
方案1:缓存空数据
当数据库查询到的数据为空时,也将这条数据进行缓存,但缓存的有效性设置得要较短,以免影响正常数据的缓存;
-
优点:
- 该方案可以保证对于数据库的请求时一次性的,可以拦截后续重复的请求
- 实现简单、直接
-
不足:
- 如果攻击提供的查询值不重复,则无法有效缓存
- 不断累加的缓存 key 浪费缓存空间
方案2:验证拦截
接口层进行校验,如鉴定用户权限,对 ID 之类的字段做基础的校验,如 id<=0 的字段直接拦截;维护最大 id 缓存信息,超出的数据直接拒绝
- 优势:
- 在发布内容后需要维护最大 id 信息
- 实现也相对比较简单
- 不足:
- 仅满足关键值如 id 信息连续或数据不稀疏的场景
- 维护最大 id 等需要原子性以保持并发和递增等一致性
- 最大 id 缓存存在一定的初次写入成本
- 数据查询数据库时,多一次缓存 id 判定操作
方案3:使用布隆过滤器
布隆过滤器是一种比较独特数据结构,有一定的误差。当它指定一个数据存在时,它不一定存在,但是当它指定一个数据不存在时,那么它一定是不存在的。
而 BloomFilter 实现原理也很简单,首先用多个 bit 位去代替 HashMap 中的数组,这样的话储存空间就下来了,之后就是对 Key 进行多次哈希,将 Key 哈希后的值所对应的 bit 位置为 1。
当判断一个元素是否存在时,就去判断这个值哈希出来的比特位是否都为 1,如果都为 1,那么可能存在,也可能不存在(如下图 F)。但是如果有一个 bit 位不为 1,那么这个 Key 就肯定不存在。
注意:BloomFilter 并不支持删除操作,只支持添加操作。这一点很容易理解,因为你如果要删除数据,就得将对应的 bit 位置为 0,但是你这个 Key 对应的 bit 位可能其他的 Key 也对应着。
- 优势:
- 有开源的工具类可以使用且可实现 redis 分布式版本
- 空间占用少
- 不足:
- 多次哈希性能开销
- 对于存在的数据有一定误判率,且随着数据增加,误判率会增大
- 对于存在数据需要启动并热数据
- 无法删除数据,不适合数据删除场景较多的情况
- 缓存雪崩
二、缓存击穿
缓存击穿是指当前热点数据存储到期时,多个线程同时并发访问热点数据。因为缓存刚过期,所有并发请求都会到数据库中查询数据。主要是由于数据访问用户并发高,由于缓存失效等原因,并发请求同时发起数据库读取操作,引起数据库压力瞬间增大的情况。
解决方案
方案1:设置热点数据永不过期
- 优势:
- 实现最简单
- 不足:
- 需要 task 等其他方式维护数据更新
方案2:应用级别锁控制并发
单机并发时,应用级别锁控制并发,仅保留 1 个请求去查询并写入缓存,其他请求重试取缓存读取
- 优势:
- 可以减少同时发起请求的并发连接
- 应用级别支持,实现相对简单
- 不足:
- 未获取到锁的线程会阻塞,不适合缓存写入等逻辑复杂,用时较长的情况
方案3:使用分布式锁
查询数据库并写入缓存的操作请求需要获取到分布式锁
-
优势:
- 到数据库的并发请求最少
-
不足:
- 需要引入分布式锁操作
- 未获取到锁的请求需要定时重试或返回降级内容
三、缓存雪崩
缓存雪崩主要针对大量的缓存在同一时间集体失效,导致大量的查询直接透传到数据库层面,导致 CPU 和内存过载从而压垮数据库。
一个简单的雪崩过程:
- Redis 集群产生了大面积故障;
- 缓存失败,此时仍有大量请求去访问 Redis 缓存服务器;
- 在大量 Redis 请求失败后,这些请求将会去访问数据库;
- 由于应用的设计依赖于数据库和 Redis 服务,很快就会造成服务器集群的雪崩,最终导致整个系统的瘫痪。
解决方案
方案1:高可用缓存
高可用缓存是防止出现整个缓存故障。即使个别节点,机器甚至机房都关闭,系统仍然可以提供服务,Redis 哨兵(Sentinel) 和 Redis 集群(Cluster) 都可以做到高可用;
方案2:缓存降级(临时支持)
当访问次数急剧增加导致服务出现问题时,我们如何确保服务仍然可用。在国内使用比较多的是 Hystrix,它通过熔断、降级、限流三个手段来降低雪崩发生后的损失。只要确保数据库不死,系统总可以响应请求,每年的春节 12306 我们不都是这么过来的吗?只要还可以响应起码还有抢到票的机会;
方案3:Redis 备份和快速预热
Redis 数据备份和恢复、快速缓存预热。
四、缓存与数据库双写时的数据一致性
你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
解决方案
一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况
串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。