测试开发面试题:缓存击穿和缓存雪崩

缓存击穿和缓存雪崩是缓存体系中常见的问题,,它们都可能导致系统性能下降,甚至瘫痪,了解它们的机制和预防措施对于提高系统性能和稳定性非常重要。

一、缓存击穿
1. 产生机制:

缓存击穿发生在缓存中不存在某个请求的结果,且这个请求的结果在数据库中也是不存在的情况下。当大量并发请求同一key,而该key在缓存中为空时,由于缓存中没有该结果,所有请求都会直接查询数据库,这种情况会导致数据库瞬时承受巨大的压力。

2. 后果:
  • 数据库负载剧增,可能导致数据库崩溃或者响应缓慢。

  • 影响用户体验,可能会导致服务不可用。

  • 造成资源浪费,重复查询数据库增加了不必要的开销。

3. 预防措施:
  • 缓存空值:对于查询结果为空的key,也进行缓存,设置较短的失效时间,这样后续相同请求就能直接从缓存中获取。

  • 使用布隆过滤器:在请求数据库之前,先通过布隆过滤器检查请求的key是否存在,存在才会去数据库查询,可以有效减少无效请求直接访问数据库。

  • 加锁机制:对于热点数据,可以在查询之前加锁,直到数据被加载完成,这样可以避免大量请求同时打到数据库。

二、缓存雪崩
1. 产生机制:

缓存雪崩是指在某一时刻大量缓存同时失效,导致大量请求直接访问后端数据库。尤其是在高峰期,如果多个缓存项在同一时间过期,会集中对数据库发起请求。

2. 后果:
  • 数据库承受巨大的压力,可能导致瞬时崩溃或长时间无法响应。

  • 影响系统的稳定性和可用性。

  • 增加后端处理时间,导致响应延迟。

3. 预防措施:
  • 设置随机过期时间:在设置缓存的过期时间时,可以引入一定的随机性,避免所有缓存同时失效。

  • 多级缓存:使用不同层次的缓存,如本地缓存、分布式缓存等,以降低单个缓存层的压力。

  • 限流:对访问数据库的请求进行限流,保护数据库的承载能力。

  • 提前预热缓存:在预计的高并发时段,提前将热点数据加载到缓存中。

三、布隆过滤器

**布隆过滤器(Bloom Filter)**是一种空间效率高的概率型数据结构,用于测试某个元素是否在一个集合中。它的特点是:

  • 快速查询:可以快速判断某个元素是否在集合中。

  • 可能产生误判:如果查询结果为“在集合中”,可能是正确的,也可能是误判(即元素实际上不在集合中)。如果查询结果为“不在集合中”,则一定不在集合中。

  • 不支持删除:一旦元素被添加到布隆过滤器中,无法删除。

使用场景:布隆过滤器常用于防止缓存击穿,尤其是在高并发情况下,能够有效减少对数据库的无效请求。

四、热点数据查询之前加锁

在并发环境中,为了避免多个请求同时查询数据库并导致压力过大,可以对热点数据加锁。具体步骤如下:

  1. 请求到达时,先检查缓存:

    • 如果命中,直接返回缓存结果。

    • 如果未命中,尝试获取锁。

  2. 获取锁:

    • 使用分布式锁(如Redis的SETNX命令)或本地锁(如Java的synchronized)来对该key加锁。

  3. 查询数据库:

    • 如果成功获得锁,查询数据库并更新缓存。

  4. 释放锁:

    • 查询完成后,释放锁。

五、对于查询结果为空的key进行缓存

对于查询结果为空的key进行缓存的操作可以通过以下方式实现:

  1. 设置缓存时,检查结果是否为空:

    • 如果结果为空,则将该key的值设置为一个特殊的标记(如null"EMPTY"),并设置较短的过期时间。

  2. 后续请求:

    • 当后续请求相同的key时,如果缓存中存在该标记,则直接返回空值,而不再查询数据库

示例代码:

  1. import redis

  2. import time

  3. import threading

  4. # 初始化Redis连接

  5. r = redis.StrictRedis(host='localhost', port=6379, db=0)

  6. # 锁的前缀

  7. LOCK_PREFIX = 'lock:'

  8. def get_data_from_db(key):

  9. # 模拟从数据库查询数据

  10. print(f"Querying database for key: {key}")

  11. time.sleep(2) # 模拟延迟

  12. return None # 假设数据库中没有该key

  13. def get_data(key):

  14. # 先检查缓存

  15. cached_data = r.get(key)

  16. if cached_data is not None:

  17. if cached_data == b'EMPTY':

  18. # 如果缓存中是空值标记,直接返回None

  19. return None

  20. return cached_data # 返回缓存中的数据

  21. # 尝试获取锁

  22. lock_key = LOCK_PREFIX + key

  23. if r.set(lock_key, 'LOCKED', nx=True, ex=5): # 设置锁,5秒后过期

  24. try:

  25. # 锁成功,查询数据库

  26. data = get_data_from_db(key)

  27. if data is None:

  28. # 如果结果为空,缓存一个空值标记

  29. r.set(key, 'EMPTY', ex=60) # 设置过期时间为60秒

  30. else:

  31. r.set(key, data, ex=60) # 正常缓存数据

  32. return data

  33. finally:

  34. # 释放锁

  35. r.delete(lock_key)

  36. else:

  37. # 如果锁被其他请求持有,等待一段时间后重试

  38. time.sleep(0.1)

  39. return get_data(key)

  40. # 示例调用

  41. result = get_data('some_key')

  42. print(f"Result: {result}")

六、提前将热点数据加载到缓存中

热点数据: 通常是访问频率极高、易于产生缓存穿透或击穿的数据。例如,用户信息、商品列表、统计数据等。

1. 如何选择热点数据:
  • 历史访问日志分析:通过分析历史日志,确定哪些数据被频繁访问。

  • 业务逻辑:识别业务中哪些数据可能是热点,例如高频交易、直播时的评论等。

  • A/B 测试:基于用户行为标记进行测试以及反馈分析,识别访问量高的特定数据。

2. 提前加载的操作:

可以在系统启动时或定期进行后台任务,将热点数据加载到缓存中。

示例代码:

  1. def preload_hot_data(hot_keys):

  2. for key in hot_keys:

  3. # 从数据库中查询数据

  4. data = get_data_from_db(key)

  5. if data is not None:

  6. # 将数据缓存

  7. r.set(key, data, ex=3600) # 设定过期时间为1小时

  8. # 定义待加载的热点数据key

  9. hot_keys = ['user:1', 'product:42', 'stats:monthly_sales']

  10. # 启动时预加载数据

  11. preload_hot_data(hot_keys)

总结

要有效预防缓存击穿和雪崩,可以综合使用上述多种策略,充分考虑系统的架构设计和业务逻辑,在高并发场景下保障系统的稳定性与良好的用户体验。同时,监测系统的运行状态,及时发现和调整潜在问题也是关键。

 感谢每一个认真阅读我文章的人!!!

作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

 

          视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行领取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值