缓存常见问题及解决方案
在上一篇文章性能设计之缓存中,已经讲了缓存的设计,在这一篇中主要讲一下关于缓存使用中常见的问题以及处理方案。
雪崩
雪崩:缓存中大量key在同一时间过期,紧接着的一大波请求同时落在数据库上导致数据库连接异常,甚至崩溃。
从上面的描述可知,问题只要出在大量key同时过期,那我们可以翻过来思考一下,如果我们能避免key不在同一时间过期,那雪崩问题不就解决了么。通常的做法有以下两种:
设置过期时间是加上一个随机数。一般为5~10分钟不等,可以从一定程度上避免雪崩问题。
让key永不过期。后台通过一个定时任务检查key是否快要过期,如果快要过期了,就重新从数据库里加载出来,重置过期时间。这个方案只适合用在热点数据上,如果非热点数据使用该方案,会导致非热点数据常驻缓存,导致缓存空间资源浪费。
通常根据实际情况结合这两种方案,能基本解决缓存雪崩问题。
穿透
穿透:用户请求一个缓存和数据库都没有的数据,导致所有的请求都落在数据库上,数据库压力增大,严重的话导致数据库崩溃。
既然知道了穿透是请求缓存和数据库的没有的数据,那有没有什么手段是可以知道缓存和数据库中是否存在数据库而不用额外访问数据库的呢,答案是有点。
增加参数校验。比如用户请求Id=-1的数据,如果API做了数据合法性校验,那么请求直接可以返回。
那你可能会说,万一用户请求的参数是合法的并且缓存和数据库都没有的数据怎么办?那你可能会想到我们可以把null的结果也加到缓存中不就不用每次都请求数据库了么,比如当用户请求Id=-1的数据,直接从缓存中拿到null返回,次方法看似可行,实则不可取。试想一下,当用户请求一个不存在的数据,缓存就要增加一条记录,那如果是一千万,甚至更多呢,是不是缓存就炸了。那还有什么方法可以解决这个问题呢?
布隆过滤器(Bloom Filter)。
这里简单说一下布隆过滤器的原理,布隆过滤器可以用于检索一个元素是否在一个集合中。如果元素存在集合中,就请求缓存,如果缓存存在则直接返回数据,如果不存在,则请求数据库后并加入缓存。通过了这个布隆过滤器后,就能有效的过滤数据库不存在数据的请求。
是不是觉得很强大,但布隆过滤器存在一个缺点:存在一定的误判率。也就是说实际上数据库不存在的数据,布隆过滤器会觉得他存在。从而导致部分请求还是会落到数据库上。可幸的是这个误判率并不算高,所以还是能有效的降低缓存穿透的概率。
击穿
击穿:是指缓存中有一个key非常热点,在抗着大量并发请求,一旦这个key过期了,这些请求全部落在数据库上,就像一个桶被击穿了一个洞。
通常面对缓存击穿有两种解决方案:
设置key永不过期。不过期就不会击穿了。
互斥锁。加锁流程如下图所示。
你可能会问,加锁之后问什么要再次访问redis?
加锁之后增加了一次redis访问,当其他线程已经把数据加载到缓存中,可以减少一次数据库的查询,如果有100个线程同时阻塞在加锁那里,就能较少100次查询数据库操作,在高并发情况下,这一步还是很有必要的。
小结
这篇讲了关于缓存常见问题并给出了推荐的解决方案,关于其他的一些问题,例如缓存一致性、淘汰策略等,可参考性能设计之缓存中的方案。
山东掌趣网络科技