高并发下缓存常见问题及处理

1 背景

在日常开发过程中,为了提高查询速度,我们通常会用到本地缓存或分布式缓存。特别是在高并发场景下使用分布式缓存会遇到很多问题。主要可以归为如下几种:

  • 一致性
  • 击穿
  • 雪崩

接下来就看一看如何处理这些常见问题。

2 缓存一致性

缓存通常对数据库数据缓存。而分布式缓存,还会存在主从结点。从结点会再同步主结点数据。因此在这种情况下访问缓存就可能导致缓存中数据与数据库数据不一致。或从结点数据与主几点数据不一致。

对于一致性问题,通常会结合业务特点进行一定折衷,只要确保在业务可接收范围内,实现最终一致即可。

对于写缓存更新,通常使用如下方式:

  • 应用将数据写到数据库中
  • 数据库数据变更产生binlog
  • 将变更的binlog发送到mq中
  • 监听mq变更信息,将变更信息更新到缓存中。

对于读取缓存,通常使用如下方式:

  • 缓存数据中保存过期时间
  • 读取时若未过期,则直接读取
  • 读取时若过期则从数据库中读取,随后再更新缓存。

3 热点KEY并发访问

使用缓存,我们会设置过期时间。可能某些key会在某一时刻被高频访问。那么当缓存过期失效,会产生大量请求数据库的请求。因此我们希望这种请求下只有一个请求是去重新加载数据库数据再写缓存,而其它请求仅仅去读缓存。这样减少对数据库的冲击。

这时常用的方法如下:

互斥锁:一个线程去加载数据库数据,再更新缓存。其它线程等待刷新完毕,重新从缓存读取数据。

本机缓存可以使用锁,而分布式缓存可以根据具体使用的产品来决定。例如使用redis,可以考虑使用setnx。

以redis为例,一段伪代码如下:

public String get(String redisKey) {
        String value = redisClient.get(redisKey);
   		// 已过期
        if (value == null) {
            //设置一个过期时间,避免删除失败时,无法再次从执行db加载逻辑
            if (redisClient.setnx(mutexKey,1,"过期时间")) {  
                // setnx执行成功
                value = db.get(redisKey);
                redisClient.set(redisKey, value, “过期时间");
                redisClient.delete(key_mutex);
            } else {
            	// 一个线程进入else说明有一个线程已经在加载db数据,并回写到缓存中,此时该线程重试即可得到缓存数据
                sleep(10);
                return get(key);  
            }
        }
        return value;
    }

预先过期数据并使用互斥锁:第一个使用互斥锁的例子中,是利用了缓冲设置的超时时间。此外我们可以给缓存数据额外增加一个过期时间参数。这个过期时间比缓存中设置的过期时间小。当参读取数据中参数的过期时间发现超时。这时提前利用锁的方式去更新数据。

后台线程刷新:我们将过期时间记录在缓存值中,通过一个后台线程去重新刷新缓存。这样出现热点key过期,不会出现全部请求访问数据库的情况。

接着对上述提到的几种方式进行简单的比较

方法优点缺点
互斥锁尽快保证一致性1.存在死锁风险 2.存在线程池阻塞风险
后台线程刷新异步刷新,不会阻塞线程1.不一致时间窗口会增大 2.需要在数据上维护额外的过期时间

4.缓存穿透

缓存穿透指查询一个不存在的数据,导致每次都不命中缓存,最终都会去查询数据库,从而使缓存失去意义。在流量大时,大量不存在的key导致DB无法承受流量冲击,可能会导致数据库挂调。

常用解决方式如下:

  • 缓存空数据: 如果缓存不存在,数据库也不存在,那么对这个key缓存一个空数据。并设置一个较短的过期时间。这样避免短时间内对数据库的频繁冲击。
  • 使用过滤层:将所有存在的key更新到一个过滤器中,例如布隆过滤器。访问时先查过滤器,如果不存在则拦截掉,这样避免对数据库的频繁访问。
方法优点缺点
缓存空数据有效应对缓存命中率不高的场景,即数据频繁变化实时性强。需要更多的空间,可能会有数据不一致问题
布隆过滤器应用缓存数据相对稳定的场景开发量与空数据比较较多

5.缓存雪崩

缓存层由于某些原因异常,无法提供服务,最终所有的请求都会达到数据库层,所有数据库的调用量会暴增,最终可能导致数据库也挂掉。

这个问题就是要保证缓存系统的可靠性。

  • 使用分布式结构,是结点挂掉后可以故障转移。
  • 组件隔离,例如使用hystrix。
  • 系统预防演练,提前模拟,发现可能产生的问题,做好准备预案。

6.总结

缓存常见问题就是数据不一致,以及缓存失效或故障最终造成对后端数据库造成冲击使系统不可用。因此要明确原因,通过高质量代码保证系统可靠。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值