redis缓存相关问题

redis缓存问题

缓存问题有哪些

redis作为一个基于内存的数据库,最常用的场景就是用来作为缓存,替数据库分担压力,当我们引入时,也必须考虑其带来的一些问题。

  • 缓存雪崩、击穿、穿透
  • 缓存污染
  • 缓存和数据库的一致性

缓存雪崩

  • 问题来源

    当缓存中的数据在同一时间大量过期,或者由于redis出现故障而导致的大量请求无法处理,从而都到达数据库,使得数据库的压力骤增,严重时可能引起数据库宕机,导致系统崩溃。

  • 解决方案

  1. 缓存数据的过期时间应该避免设置同一时间,可以通过给过期时间加随机数的方式进行处理

  2. 构建缓存时加互斥锁,也就是当请求发现缓存中没有数据时,只需要一个线程去从数据库读取数据,然后写入redis,其他线程等待缓存构建完成。

  3. 热点数据设置永不过期(此时需要考虑会不会被内存淘汰策略所淘汰)

  4. 针对redis故障引起的缓存雪崩,应该考虑建立基于主从的redis集群,避免单点故障,业务中可以加入请求限流或熔断机制,限制对数据库的并发请求数。

缓存击穿

  • 问题来源

某些热点数据过期时,若此时正好有大量请求要访问该数据,就会导致请求都落在数据库,引发数据库宕机风险,从造成的结果来看,缓存击穿和缓存雪崩所引发的问题基本一致,都是由于大量请求越过了redis,直接落到了数据库,区别在于一个是由于某些热点数据过期引起,一个是由于大量数据过期引起。

  • 解决方案

解决方法基本与之前的相似,构建缓存时加互斥锁、或者设置热点数据永不过期。

缓存穿透

  • 问题来源

当请求的数据,不存在于缓存和数据库中时,会导致每次请求都会查询数据库,而每次查询结果都为空,也没法构建缓存,相当于跳过了redis,直接请求到了数据库,若有大量这样的请求同时到来,就会引发数据库宕机风险。

  • 解决方案
  1. 接口层增加参数校验,如用户鉴权、参数合法性校验等,避免非法请求

  2. 缓存空值,对于数据库中取不到的数据设置一个默认值,然后存入缓存,过期时间可以设置一个较短时间

  3. 构建布隆过滤器,根据 bloomfilter 的特性,可以快速判断某个数据是否存在,但 bloomfilter 也存在部分误判性,若判断某个数据存在,其并不一定真的存在,若不存在就是真的不存在。

缓存污染

缓存污染指的是存在一些数据可能只会被访问一次或者几次,后续就不会在被访问到的情况,比如有个请求读取了大量数据,而这些数据只会被读取这么一次,后续就不再访问了,导致这些数据一直存在于缓存中,占用缓存空间。

缓存空间毕竟是有限的,如果缓存空间满了,那么每次写入数据时都要根据淘汰策略去淘汰数据,这样就会引入额外的操作时间开销,影响系统的性能。

Redis3.0 之前,默认的内存淘汰策略是 volatile-lru,会淘汰设置了过期时间的键值中,最近最久未使用的键值对,也就是根据数据的访问时间去淘汰数据,此时就无法解决缓存污染问题,比如应用在一次查询操作中读取了大量数据,而这些数据只会被读取一次,如果这些数据占满了缓存空间,之后有数据要写入缓存的话,就会发生内存淘汰,不仅会影响性能,而且可能会把某些热点数据淘汰出去。

因此,redis4.0中,引入了LFU策略,可以根据数据被访问的次数去淘汰数据,LFU策略会把那些访问次数低的数据淘汰出去,由此避免了缓存污染问题。

缓存和数据库的一致性

  • 问题来源

使用redis作为缓存,无非两种操作,读取缓存和更新缓存,读取缓存一般没有什么问题,但如果涉及到更新操作,则必须要考虑缓存和数据库之间的数据一致性问题。

假设我们考虑先更新数据库,后更新缓存,可以想象一下以下场景,请求A先到,首先将数据库中的数据更新为值A,然后在还没来得及更新缓存时,这时又来一个请求B,将数据库中的数据更新为值B,同时更新缓存为值B,然后请求A才开始更新缓存为值A,此时数据库中的值为B,而缓存值为A,发生了缓存和数据库中的数据不一致现象。

同样的,先更新缓存,后更新数据库也会有数据不一致的问题,主要原因是两个并发的写操作会导致脏数据,因此,就引出了以下几种缓存更新模式。

  • 缓存更新模式
  1. 旁路缓存(Cache Aside Pattern)

    旁路缓存是最常用的缓存更新模式了,其逻辑如下,当读取数据的时候,如果命中缓存,则直接返回,如果未命中,则从数据库读取出来,然后写入缓存,当更新数据的时候,先更新数据库,然后删除缓存

    那如果我们先删除缓存,再更新数据库是否也可以呢?可以设想一下,假如有一个更新请求A,先删除缓存,然后在还没来得及更新数据库时,此时又来一个读请求B,查询数据发现未命中,则从数据库读取放入缓存中,之后请求A再更新数据库,于是,缓存中的数据就是更新之前的脏数据。

    先更新数据库,再删除缓存就真的没有问题了吗?可以再设想一下,假如一个读请求访问时,此时缓存中恰好没有数据,然后就去读数据库,在还没来的及将读取的数据写入缓存时,此时来了一个写请求,更新数据库,并删除缓存,然后之前的读请求才开始把之前读到的数据写入缓存,结果还是会造成脏数据。

    虽然理论上还是会存在数据不一致问题,但是实际中出现的概率非常低,因为这个条件首先需要读缓存时缓存未命中,同时有一个并发写操作,并且这个写操作要在读操作读取数据之后进行,同时又要在读操作写入缓存之前,更新数据库,而我们都知道数据库的写入要比缓存的写入要慢,因此满足这些条件的概率非常小。

    当然最好还是为缓存设置上过期时间,这样即使有小概率事件脏数据的发生,也能在一定时间之后,由于数据过期淘汰,重新从数据库读取,从而达到最终一致性的效果,如果业务上真的需要强一致性保证,则需要通过2PC或者Paxos相关协议来保证。

    到这里为止,我们还需要考虑一个问题,缓存删除失败的影响,因此更新数据库和删除缓存并非一个原子操作,存在数据库更新成功,但缓存删除失败的可能性,此时就又出现了数据一致性问题,解决方法其实也很简单,添加重试机制即可,比如引入消息队列,将删除失败的key发送消息队列,然后消费重试删除操作,或者读取binlog日志,拿到其更新数据再操作缓存,这样业务中可以只操作数据库,对缓存的修改可以交给读取binlog日志的程序。

  2. 读穿/写穿策略(Read/Write Through)

    在旁路缓存中,我们需要同时操作数据库和缓存,而对Read/Write Through策略而言,我们只需要和缓存进行交互,对数据库的更新操作则交给缓存自己去执行。

    Read Through指的是当查询数据时如果数据存在则直接返回,不存在则由缓存自己去数据库查询,然后写入缓存并返回。

    Write Through指的是当更新数据时如果数据存在则直接更新缓存中的数据,然后由缓存自己去同步更新到数据库中,如果数据不存在缓存中则直接更新数据库,然后返回。

  3. 写回策略(Write behind caching)

    Write Behind 又叫 Write Back,该策略指的是在更新数据的时候,只更新缓存,不更新数据库,对于数据库的更新,会通过异步批量的方式进行。比如Linux文件系统的Page Cache算法,好处是写文件很快,不需要写磁盘就可以返回,但是带来的问题是,数据不是强一致性的,存在数据丢失的风险。

总结

Redis作为我们最常用的缓存组件,想要正确的用好它也并不容易,本文也只是浅尝辄止的总结了一些在缓存层面的使用问题及解决方案,当我们使用某种方式解决某个问题时,也必然也会带来新的问题,比如上面所说的一些缓存更新模式。软件设计从来都是trade-off,no silver bullet

参考文章

缓存更新的套路 | 酷 壳 - CoolShelll

https://www.cnblogs.com/rjzheng/p/9041659.html

你可能感兴趣的:(redis,缓存,redis,数据库)