自己的话:查缓存和数据库都没有的数据

产生原因:访问的数据即不在缓存,也不在数据库。

解决方案

  1. 非法请求的限制
  2. 缓存空值或者默认值
  3. 使用缓存过滤器快速判断数据是否存在;

缓存穿透(查不到导致的)

概述

缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久化层数据库查询,发现也没有,于是本次查询失败,当用户很多的时候,缓存都没有命中(秒杀!),于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。

解决方案

1. 数据校验

https://cdn.nlark.com/yuque/0/2022/png/576791/1642787125351-70f53c73-bb26-4f7f-bf76-934a8b14cf9c.png#clientId=u52f8001b-14eb-4&from=paste&height=870&id=ufc466b86&originHeight=1740&originWidth=3173&originalType=binary&ratio=1&size=1580734&status=done&style=none&taskId=u1efd2e76-1446-4281-8780-fc1409b9804&width=1586.5

要解决这类问题最首先的思路是在接入层做好数据的校验,比如 id 是否正整数、key 是什么类型、参数的范围等。在入口层就把所有规则上不合法的请求拦在 Redis 之外,自然就没有穿透的现象发生了。

2.缓存空值

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;

https://cdn.nlark.com/yuque/0/2021/png/576791/1621514529415-00c841c9-6c1c-40a8-a400-8956b95995d4.png#height=235&id=XCi8k&originHeight=470&originWidth=400&originalType=binary&ratio=1&size=96685&status=done&style=none&width=200

但是这种方法会存在两个问题:

1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键; 2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

3.频控

https://cdn.nlark.com/yuque/0/2022/png/576791/1642787332748-1da771c0-320b-4867-869f-a3ff1f8a7032.png#clientId=ud404ed51-0a85-4&from=paste&height=870&id=u2ab7a2e0&originHeight=1739&originWidth=3213&originalType=binary&ratio=1&size=1035749&status=done&style=none&taskId=ue0fd4c35-53db-45d0-9da9-a5f467a6065&width=1606.5

缓存空值这方案看似很完美,但实际上只能防止正常用户的穿透行为。如果是真正的攻击者,他并不会一直用同一个值去请求,例如 id 最大值是 999999,攻击者可能会从 9999991 开始,9999992、9999993……逐个试,这样既能通过我们的合法检查,又能穿透我们的缓存。如果只用缓存空值的方案,很可能整个缓存会一直被这种“空数据”污染。

https://cdn.nlark.com/yuque/0/2022/png/576791/1642787415216-ebcbb2ea-e762-49dc-829a-354941da606f.png#clientId=ub6d82918-cd9e-4&from=paste&id=u3729b00c&originHeight=646&originWidth=1194&originalType=url&ratio=1&size=51452&status=done&style=none&taskId=u9806c993-055b-458b-be33-12ed0072d7d

不断缓存空值可能会造成空数据污染

所以如果采取空数据缓存,一定要在上游就针对恶意的攻击做好对应的频控(例如 ip 维度),侦测到类似的恶意请求,则直接拒绝,或者直接返回一些假数据/降级数据等,以保护缓存不被穿透。