使用redis的原因
性能和并发
一.缓存穿透
缓存穿透的概念很简单,用户想要查询一个数据,发现缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案:
1.简单校验
因为有时候可能是攻击者故意拿不存在的id攻击,可以在接口处添加基础校验,对不可能出现的数据进行拦截。
2.缓存空对象
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,同时设置一个过期时间,也就是缓存有效时间,如30秒(设置太长会导致正常情况也没法使用)。之后再访问这个数据将会从缓存中获取,保护了后端数据源;也可以防止攻击用户在短时间反复用同一个id暴力攻击。
但是这种方法会存在两个问题:
1.如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
2.即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
3.布隆过滤器bloomfilter
当大量访问不存在数据的请求到达时,先用布隆过滤器过滤,避免直接去查库,也就是说布隆过滤器只能判断数据是否一定不存在,而无法判断数据是否一定存在。是有误判率的问题的。
二、缓存击穿
某一个热点 key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。
解决方案:
1.互斥锁
在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。
简化代码如下:
这里用到了JVM锁:ReentrantLock。
分布式中一般会使用redis实现分布式锁。
单台服务器时也可以考虑用JVM锁,JVM 锁保证了在单台服务器上只有一个请求走到数据库,通常来说已经足够保证数据库的压力大大降低,同时在性能上比分布式锁更好。
2.热点数据不过期
三、缓存雪崩
缓存雪崩是指,就是在某一个时刻,缓存集大量失效。所有流量直接打到数据库上,对数据库造成巨大压力;
解决方案:
1.redis高可用
缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
2.限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。但是这种方式响应会很慢
4.数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
4.缓存过期时间设置
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。或者直接设置热点数据永远不过期。