您的位置:首页 > 数据库

缓存中常见的问题及解决方案

2017-11-10 14:23 429 查看
缓存技术是网站服务端经常用到的一种技术,在读多写少的业务场景中,通过使用缓存可以有效地提高网站的性能,支撑高并发的访问量,对数据库做到很好的保护。我们在使用缓存的时候,如Redis、Memcached,基本上都遇到以下几个问题:缓存穿透、缓存并发、缓存失效(缓存雪崩)、热点key、DB缓存一致性。

本案例结合平台开发过程中遇到的问题,简要总结了缓存中常见的问题并提供一些解决方案。

案例介绍

缓存穿透:

在项目开发中,我们常会遇到以下三种情形:

图一、查询缓存

图二、删除缓存

图三、更新缓存
在项目中使用缓存通常都是先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就查询数据库,然后再缓存查询结果返回。这个时候如果我们查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询DB,这样缓存就失去了意义,在流量很大时就会对DB造成一定的压力。这就是缓存穿透。如果有人利用不存在的key频繁攻击我们的应用,很可能直接对DB造成影响,这是在网站设计时需要考虑的问题。

有一个比较巧妙的做法是,将这个不存在的key预先设定一个值及过期时间,比如”key”-“null”等。当返回null值的时候,我们就认为这是一个不存在的key,然后决定是否继续等待访问取值,或者放弃操作。如果继续等待访问,过一个时间轮询点后再次请求这个key,如果取到的值不是”null”,则认为这时key有值了,从而避免了透传到数据库,把大量的类似请求挡在了缓存之中。

在我们搭建的平台中,有些地方需要特别注意防范缓存穿透问题,比如用户登录操作、找回密码操作等。我们可以分两步处理,在用户发起请求的时候,首先对请求内容的格式做校验,将非法内容的请求直接阻挡在前端或者服务层面;其次,在缓存中对频繁查询的字段做校验。比如最常用到的手机号,首先过滤非法手机号,然后再查询数据库,如果手机号不存在,则将该手机号放入缓存中,下次请求可直接从缓存中读取结果,减少数据库的压力。如果后续将手机号添加到了平台,同步更新对应的key即可。

缓存并发

有时候如果平台并发访问高,一个缓存失效了,可能出现多个进程同时查询DB,造成DB压力过大,还有缓存频繁更新的问题。

处理方案是对缓存查询加锁,如果KEY不存在则加锁,查询DB放入缓存,然后解锁;其他进程如果发现有锁就等待,等解锁后查询数据或者进入DB查询。这种情况和刚才说的预先设定值问题有些类似,不过利用锁的方式,会造成部分请求等待。

缓存失效

引起这个问题的主要原因也是高并发,平时我们可能将缓存设置一个过期时间(比如1分钟或5分钟),当并发很高时可能在某一个时间点同时生成很多缓存key,并且过期时间都一样,这个时候就可能引发在过期时间点到达时,缓存同时失效,请求全部转发到DB,DB就会有很大的压力,造成缓存雪崩。

关于此问题,方案之一就是将缓存失效时间均匀的分散开,防止同一时间点大量的缓存key失效。比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

热点key

         缓存中的某些key(比如某个促销商品或热点事件),对应value存储在集群中的一台机器,使得所有流量涌向同一机器,这样就形成系统的一个瓶颈。该问题的挑战在于它无法通过增加机器容量来解决。

         方案一:客户端热点key缓存:将热点key对应的value缓存在本地客户端,并设置一个失效时间。每次读取时,首先检查本地缓存中是否存在key,如果存在则直接返回,不存在再去访问分布式缓存集群。

方案二:将热点key分散为多个子key,存储到缓存集群上的不同机器上。这些子key对应的value都和热点key一样,当通过热点key去查询数据库时,通过某种hash算法随机选择一个子key,然后再去访问缓存机器。

DB和缓存一致性问题

如上图二、图三是对更新数据的两种不同操作,一种删除缓存(数据只写入数据库,不写入缓存)、一种更新缓存(数据不仅写入数据库,还要写入缓存),应该选用哪种更新方案呢,这里需要讨论下DB和缓存不一致的问题。

由于操作缓存与操作数据库不是原子的,中间某一过程非常有可能执行失败。假设先写数据库,再淘汰缓存:第一步写数据库操作成功,第二步淘汰缓存失败,则会出现DB中是新数据,Cache中是旧数据,两方数据不一致。

图四、db中是新数据,cache中是旧数据
假设先淘汰缓存,再写数据库:第一步淘汰缓存成功,第二步写数据库失败,则只会引发一次Cache miss,但是不存脏数据及缓存不一致的现象。

图五、cache中无数据,db中是旧数据
所以我们应该选用:先淘汰缓存,再写数据库

多级缓存也会存在一致性问题,如果我们需要强一致性的话,缓存与数据库同步是会存在时间差的,所以在具体开发的过程中,要根据场景来具体分析,二级缓存需要解决是缓存穿透与程序的健壮性,保证当集中式缓存出现问题的时候,我们的平台能继续运行。

 

注:缓存并发是针对同一个缓存来讲,缓存失效是针对很多缓存而言。

 

总结

一个好的项目不仅需要实现客户提出的功能需求,还需在性能、稳定性等方面花大量功夫进行测试,才能保证项目在现场环境较少的出现问题,也减少以后维护项目的成本。本案例总结了在使用缓存技术时经常遇到的几个问题,并提供了相应的解决方案,可供其它用到类似技术的同事学习借鉴。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  多线程 数据库 流量