这是我参与更文挑战的第7天,活动详情查看: 更文挑战
前言
在我们系统访问量不高的时候,通常我们的架构很简单,服务接收到请求,直接往数据库上怼进去就可以了。
但是随着用的人数越来越多,并发访问量越来越高,我们通常会考虑使用redis作为数据缓存来提升系统性能。
什么是缓存穿透
如果有人恶意攻击你的系统,用一个redis和mysql中都不存在的数据去大量的请求你的服务,那么这些请求相当于穿透了redis服务,又会全部打到数据库上去。如果高频请求,在达到mysql瓶颈的时候就会造成宕机等服务不可用的问题。
怎么解决缓存穿透问题
1. 缓存空值。
面对访问redis和mysql都查不到对应记录的key(比如id = -1),我们可以将空值也写入到缓存中,那么下次相同的请求再进来的时候,redis中就可以找到这条记录,redis就可以帮我们抵挡这些无效的请求,防止这些请求进入数据库。
看上去完美地解决了我们的问题,但是这种做法其实有个弊端。如果这种恶意攻击每次带的key都是不同的情况,比如用UUID。这时候我们的缓存中就会缓存到大量的无用数据,redis的数据是保存在我们的内存中的,我们知道redis不可能让数据无限的膨胀下去的,它是有内存淘汰策略的。在内存占用达到指定值,会根据设定的淘汰策略,删除掉即将过期的或者是最少被使用的数据。
使用这种方法解决,可能会导致redis缓存的都是无效的数据,导致我们正常的数据都被删除掉,结果可能适得其反。
2. 布隆过滤器
第二种方法就是在redis和数据库中间再加一层过滤器,如果过滤器发现不存在该数据,直接返回无效请求。
关于布隆过滤器你可以直接简单粗暴地理解为:它说没有的肯定没有,它说有的也可能没有。
布隆过滤器是由一个bitSet和三个hash函数组成的。bit占用的空间很小,所以在布隆过滤器中可以存储非常多的数据。
它的hash函数只需要满足尽可能的散列(必然会存在hash碰撞问题,这也是它有一定错误率的原因。),以及hash值要在数组长度之间。
在初始化时每个bit都是0,如果存在数据的hash值在这个下标位置上,那么这个下标的值就改为1.
我们上面说到布隆过滤器会存在一定错误率,那么如何减小这个错误率呢?
- 我们可以加大数组的长度,这样出现冲突的概率就会降低
- 还有就是我们可以用不止三个hash函数,越多次hash,每个值对应的bit也就越多,也就越不容易出现误判。
这里多说一句,正常新增数据,我们不推荐立刻往布隆过滤器中写入,可以写一个定时任务去定时异步写入,这样系统的性能会更好。
以上,感谢阅读。