普通缓存使用
当系统接收到一个获取数据的请求时,系统就会先从缓存中查找数据,而系统从缓存中查找数据时分两种情况:
- 如果缓存中有数据的话直接从缓存中读取数据,然后返回给请求方
- 如果缓存中没有那就从数据库中读取数据,然后在更新到缓存中,下次再获取这条数据时可以直接从缓存中读取
在并发较高时不建议使用缓存过期的策略,缓存一直存在,通过后台系统更新缓存系统中的数据,达到数据一致性的目的
上面的方案在并发较高的情况下还会出现缓存穿透的情况
缓存穿透
缓存穿透指的是查询一个一定不存在的数据,由于缓存不命中时,需要从数据库中查询,而数据库也没有这条数据,这时候无法更新缓存,这将导致这个不存在的数据每次请求时都要到数据库中查询
有人可以利用这个漏洞进行恶意攻击,将会对数据库造成非常大的压力,严重的话会导致数据库宕机
解决方案:
方案一:
在不存在的key预先设置一个值,比如设置为空字符串null,在返回这个空字符串null值的时候,我们应用就可以认为这是一个不存在的key,就可以决定是否需要等待还是继续访问,或者干脆放弃这次操作。如果继续等待访问,过一个时间轮询点后,再次请求这个key,如果取到的值不在是null,就可以认为这时候key是有值了。这就避免了穿透到数据库的情况
方案二:
布隆过滤器。用于检索一个元素是否在一个集合中,优点是空间效率和查询时间,缺失是有一定的误识别率并且删除困难
缓存并发
假设有多个获取同一条数据的请求,从缓存中没有查询到数据,这时所有的请求都会到数据库去查询,然后所有的请求再反复更新缓存数据,而且更新的是同一条数据,不仅增加查询数据库的压力,还会因为反复更新的问题,占用redis的请求资源,这就是缓存并发问题
解决方案:
请求从客户端发起,请求先从缓存中读取数据,然后判断是否能从缓存中读取到数据,如果读取到数据就直接返回给客户端,流程结束。如果没有读取到,那么就在redis中使用setNX方法设置一个状态位,表示这是一种锁定状态,要是设置成功了,表示已经锁定成功,这时候请求从数据库中读取数据,然后更新缓存,最后再将数据返回给客户端,要是设置没成功的话,表示这个状态位已经被其他请求锁定,这个请求等待一段时间,会重新发起数据查询,这样就能保证在同一时间,只有一个请求来查询数据库更新缓存,其他请求只能等待重新发起查询,再次查询发现有缓存中有数据直接返回数据即可