什么是缓存穿透?
首先缓存穿透是在缓存查询中会出现的问题
指查询一个一定不存在的数据,那么缓存中肯定是没有的,将会去查数据库,数据库中也没有此记录。这将导致每次请求缓存不命中,数据库也无数据,但依然执行了数据建立连接操作数据的过程,在高并发场景下浪费了cpu和内存,影响了性能。
如何解决缓存穿透问题
1.固定值查询优化
针对某个固定的值的大并发查询,null值也要缓存值,在缓存中缓存一个占位符(任意,比如“x”),下次查询缓存时根据值的不同情况判断,不用再从数据库查询了。同时,这个占位符也需要给一个合适的过期时间,防止后续数据库中真的有这个数据了,但缓存中还一直判定为null。
2.随机值查询优化
在根据商品id查询商品详情的场景中,假如系统的商品id只有50w个,攻击者找到了这些id的规律后,大并发请求不存在的大量id的商品详情,虽然每一个id都可以固定值查询优化方法,在缓存中缓存一个占位符,但前提都还是需要先和数据库建立一次连接后才会发现没有数据才去缓存null值,大并发量请求下,数据库还是会出现问题。 解决这个场景的本质是:
数据库中没有的数据,就算缓存中没有,也不去查询数据库。
- 方案1:
- 全量缓存数据库里所有需要缓存的数据,这样只要缓存中没有,那么数据库中一定没有,就不用去查询数据库。
- 缺点:假如100w的数据,每个数据10kb,则需要占用10gb的内存空间,浪费内存,所以如果数据量小可以这么做。
- 方案2:
- 只全量缓存数据库所有商品的id,假设key是skuids,商品id是long类型,100w的数据才占用8mb
- 假设一个id的商品详情在缓存中没有,先不去数据库中查询,而是先去skuids里去看下这个id是不是在数据库中存在,存在的话才去数据库查询,不存在就返回空。
- 方案3:
- 布隆过滤器:快速判断一个值,是否存在。
- 类似一个数组结构,但每个位置是一个bit,只有0或1两种值,默认全是0,存储方法是对值进行hash计算后得到一个下表,将其值设置为1,假如是3次hash,那么会把3个位置的值设置成1,后续判断一个值是否存在时,就对这个值进行3次的hash运算,看得出的下表的值是否为1,是的话就代表这个值是在数据库存在的。
- 优点:省空间,啥都能放,id从原本的8byte变成了3个bit,大大减少了存储空间。
- 缺点1:容易误判,比如提前预热缓存数据时,49这个id将数组1,3,5下标设置成了1,50这个id将数组2,4,6下标设置成了1,后面如果查询一个不存在的996号id,经过hash运算是判断2,3,6这3个下标的值是否为1,恰巧还都是1,判定结果这id是存在的,实际是不存在的,属于误判了。所以可以将hash计算的次数增多,越不容易误判,但也越占用空间(但还是比存byte少),也越慢。最大特点:根据上面的内容可以得出,布隆过滤器说存在的,不一定存在,说不存在的,那一定不存在。
- 缺点2:只能增不能删。假如数据库中某一个值删掉了,但布隆过滤器中的值不可以删,因为一旦删了相应的位的值,可能将其他的值在此位上的值也删了,肯定会出问题。
- 缺点3:不能逆向给出存的是哪些值,只能给出是否存在这个值。
- 方案4:利用Redis的bitmap
- 将id存入bitmap的值,id的值是bitmap的下表索引,将索引的值置为1,假如要删除的话就将索引的值置为0,满足了既能增又能删的情况。
SETBIT key offset value
SETBIT skuids 49 1
SETBIT skuids 50 1
- 一个id只占用1bit,占用极小空间,能增能删。