ToB企服应用市场:ToB评测及商务社交产业平台

标题: 优化系统性能:深入探究Web层缓存与Redis应用的挑战与对策 [打印本页]

作者: 大连密封材料    时间: 2024-8-15 08:43
标题: 优化系统性能:深入探究Web层缓存与Redis应用的挑战与对策
Web层缓存对于提高应用性能至关紧张,它通过减少重复的数据处置惩罚和数据库查询来加快响应时间。例如,如果一个用户哀求的数据已经缓存,服务器可以直接从缓存中返回结果,避免了每次哀求都进行复杂的盘算或数据库查询。这不仅提高了应用的响应速度,还减轻了后端系统的负担。
Redis是一个流行的内存数据结构存储系统,常用于实现高效的缓存层。它支持各种数据结构,如字符串、哈希、列表、集合等,能够迅速存取数据。通过将常用的数据缓存到Redis中,应用可以大幅度降低数据库负担,同时提升用户体验。
缓存题目详解

在本章中,我们将不深入探究Redis的基本缓存机制,而是专注于如何防范Redis失效可能带来的不须要丧失。我们将具体讨论缓存穿透、缓存击穿和缓存雪崩等题目的产生原因及其解决计谋。让我们开始深入相识这些内容。
缓存穿透

缓存穿透指的是查询一个根本不存在的数据时,缓存层和存储层都未能命中。这种情况通常出于容错思量,如果存储层未能找到数据,系统通常不会将其写入缓存层。结果就是每次哀求不存在的数据时,系统都需要直接访问存储层进行查询,从而失去了缓存保护后端存储的本质意义。这不仅增加了存储层的负担,也降低了系统的整体性能。
造成缓存穿透的基本原因主要有两个:
解决方案——缓存空对象

解决缓存穿透的有效方案之一是缓存空对象。这种方法涉及在缓存层中存储查询结果为“空”的标记或对象,以表明特定数据不存在。通过这种方式,当后续哀求查询相同的数据时,系统可以直接从缓存层获取“空对象”,而不必重新访问存储层。这不仅减少了对存储层的频繁访问,还提高了系统的整体性能和响应速度,从而有效缓解缓存穿透题目。
  1. String get(String key) {
  2.     // 从缓存中获取数据
  3.     String cacheValue = cache.get(key);
  4.     // 缓存命中
  5.     if (cacheValue != null) {
  6.         return cacheValue;
  7.     }
  8.     // 缓存未命中,从存储中获取数据
  9.     String storageValue = storage.get(key);
  10.     // 如果存储中数据为空,则设置缓存并设定过期时间
  11.     if (storageValue == null) {
  12.         cache.set(key, "");  // 存储空对象标记
  13.         cache.expire(key, 60 * 5);  // 设置过期时间(300秒)
  14.     } else {
  15.         // 存储中数据存在,则缓存该数据
  16.         cache.set(key, storageValue);
  17.     }
  18.     return storageValue;
  19. }
复制代码
解决方案——布隆过滤器

对于恶意攻击中通过哀求大量不存在的数据造成的缓存穿透题目,可以利用布隆过滤器来进行初步过滤。布隆过滤器是一种空间效率极高的概率型数据结构,它能有效地判断一个元素是否可能存在于集合中。具体而言,当布隆过滤器表现某个值可能存在时,现实情况可能是该值存在,也可能是布隆过滤器的误判;但当布隆过滤器表现某个值不存在时,则可以肯定该值确实不存在。

布隆过滤器是一种高效的概率型数据结构,由一个大型位数组和多个独立的无偏哈希函数组成。无偏哈希函数的特点是能够将输入元素的哈希值均匀地分布到位数组中,减少哈希冲突。添加一个键(key)到布隆过滤器时,首先利用这些哈希函数对键进行哈希运算,每个哈希函数天生一个整数索引值。然后,这些索引值经过对位数组长度的取模运算,确定在位数组中的具体位置。接着,将这些位置的值设置为1,标记该键的存在。
当查询布隆过滤器中某个键(key)是否存在时,操作过程与添加键时类似。首先,利用多个哈希函数对键进行哈希运算,得到多个位置索引。然后,查抄这些索引对应的位数组位置。如果所有相关位置的值都是1,那么可以推测该键可能存在;否则,如果有任意一个位置的值为0,则可以确定该键肯定不存在。值得注意的是,即使所有相关位置的值均为1,这也仅仅意味着该键“可能”存在,而不能绝对确认,因为这些位置可能已经被其他键置为1。通过调解位数组的巨细和哈希函数的数量,可以优化布隆过滤器的性能,达到较好的正确性与效率平衡。
这种方法特殊适用于数据命中率不高、数据集相对固定、对实时性要求不高的应用场景,尤其是在数据集较大时,布隆过滤器可以显著减少缓存空间的占用。只管布隆过滤器的实现可能会增加代码维护的复杂度,但其带来的内存效率和查询速度的优势通常值得投入。
布隆过滤器在这类场景中的有效性得益于其能处置惩罚大规模数据集而只占用较少的内存空间。为了实现布隆过滤器,可以利用Redisson,这是一个支持分布式布隆过滤器的Java客户端。要在项目中引入Redisson,可以添加以下依靠项:
  1. <dependency>
  2.     <groupId>org.redisson</groupId>
  3.     <artifactId>redisson</artifactId>
  4.     <version>3.16.2</version>
  5. </dependency>
复制代码
示例伪代码:
  1. package com.redisson;
  2. import org.redisson.Redisson;
  3. import org.redisson.api.RBloomFilter;
  4. import org.redisson.api.RedissonClient;
  5. import org.redisson.config.Config;
  6. public class RedissonBloomFilter {
  7.     public static void main(String[] args) {
  8.         // 配置Redisson客户端,连接到Redis服务器
  9.         Config config = new Config();
  10.         config.useSingleServer().setAddress("redis://localhost:6379");
  11.         // 创建Redisson客户端
  12.         RedissonClient redisson = Redisson.create(config);
  13.         // 获取布隆过滤器实例,名称为 "nameList"
  14.         RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");
  15.         // 初始化布隆过滤器,预计元素数量为100,000,000,误差率为3%
  16.         bloomFilter.tryInit(100_000_000L, 0.03);
  17.         // 将元素 "zhuge" 插入到布隆过滤器中
  18.         bloomFilter.add("xiaoyu");
  19.         // 查询布隆过滤器,检查元素是否存在
  20.         System.out.println("Contains 'huahua': " + bloomFilter.contains("huahua")); // 应为 false
  21.         System.out.println("Contains 'lin': " + bloomFilter.contains("lin")); // 应为 false
  22.         System.out.println("Contains 'xiaoyu': " + bloomFilter.contains("xiaoyu")); // 应为 true
  23.         // 关闭Redisson客户端
  24.         redisson.shutdown();
  25.     }
  26. }
复制代码
利用布隆过滤器时,首先需要将所有预期的数据元素提前插入布隆过滤器中,以便它能够通过其位数组结构和哈希函数有效地检测元素的存在性。在进行数据插入时,也必须实时更新布隆过滤器,以保证其数据的正确性。
以下是布隆过滤器缓存过滤的伪代码示例,展示了如何在初始化和数据添加过程中操作布隆过滤器:
  1. // 初始化布隆过滤器
  2. RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");
  3. // 设置布隆过滤器的期望元素数量和误差率
  4. bloomFilter.tryInit(100_000_000L, 0.03);
  5. // 将所有数据插入布隆过滤器
  6. void init(List<String> keys) {
  7.     for (String key : keys) {
  8.         bloomFilter.add(key);  
  9.     }
  10. }
  11. // 从缓存中获取数据
  12. String get(String key) {
  13.     // 检查布隆过滤器中是否存在 key
  14.     if (!bloomFilter.contains(key)) {
  15.         return ""; // 如果布隆过滤器中不存在,返回空字符串
  16.     }
  17.     // 从缓存中获取数据
  18.     String cacheValue = cache.get(key);
  19.     // 如果缓存值为空,则从存储中获取
  20.     if (StringUtils.isBlank(cacheValue)) {
  21.         String storageValue = storage.get(key);
  22.         if (storageValue != null) {
  23.             cache.set(key, storageValue); // 存储非空数据到缓存
  24.         } else {
  25.             cache.expire(key, 300); // 设置过期时间为300秒
  26.         }
  27.         return storageValue;
  28.     } else {
  29.         // 缓存值非空,直接返回
  30.         return cacheValue;
  31.     }
  32. }
复制代码
注意:布隆过滤器不能删除数据,如果要删除得重新初始化数据。
缓存失效(击穿)

由于在同一时间大量缓存失效可能会导致大量哀求同时穿透缓存,直接访问数据库,这种情况可能会导致数据库瞬间承受过大的压力,乃至可能引发数据库崩溃。
解决方案——随机过期时间

为了缓解这一题目,我们可以采取一种计谋:在批量增加缓存时,将这一批数据的缓存过期时间设置为一个时间段内的不同时间。具体来说,可以对每个缓存项设置不同的过期时间,这样可以避免所有缓存项在同一时刻失效,从而减少瞬时哀求对数据库的打击。
以下是具体的示例伪代码:
  1. String get(String key) {
  2.     // 从缓存中获取数据
  3.     String cacheValue = cache.get(key);
  4.     // 如果缓存为空
  5.     if (StringUtils.isBlank(cacheValue)) {
  6.         // 从存储中获取数据
  7.         String storageValue = storage.get(key);
  8.         
  9.         // 如果存储中的数据存在
  10.         if (storageValue != null) {
  11.             cache.set(key, storageValue);
  12.             // 设置一个过期时间(300到600秒之间的随机值)
  13.             int expireTime = 300 + new Random().nextInt(301); // Random range: 300 to 600
  14.             cache.expire(key, expireTime);
  15.         } else {
  16.             // 存储中没有数据时,设置缓存的默认过期时间(300秒)
  17.             cache.expire(key, 300);
  18.         }
  19.         return storageValue;
  20.     } else {
  21.         // 返回缓存中的数据
  22.         return cacheValue;
  23.     }
  24. }
复制代码
缓存雪崩

缓存雪崩是指在缓存层出现故障或负载过高的情况下,导致大量哀求直接涌向后端存储层,从而引发存储层的过载或宕机现象。通常,缓存层的作用是有效地承载和分担哀求流量,保护后端存储层免受高并发哀求的压力。
然而,当缓存层由于某些原因无法继续提供服务时,比如遇到超大并发的打击或者缓存计划不当(例如,访问一个极大的缓存项 bigkey 导致缓存性能急剧降落),大量的哀求将会转发到存储层。此时,存储层的哀求量会急剧增加,可能会导致存储层也发生过载或宕机,从而引发系统级的故障。这种现象被称为“缓存雪崩”。
解决方案

为了有效防备和解决缓存雪崩题目,可以从以下三个方面着手:
通过综合运用这些措施,可以显著降低缓存雪崩带来的风险,提升系统的稳固性和性能。
总结

Web层缓存显著提高了应用性能,通过减少重复的数据处置惩罚和数据库查询来加快响应时间。Redis作为高效的内存数据结构存储系统,在实现缓存层中发挥了紧张作用,它支持各种数据结构,能够迅速存取数据,从而减少数据库负担,提升用户体验。
然而,缓存机制也面临挑战,如缓存穿透、缓存击穿和缓存雪崩等题目。缓存穿透通过缓存空对象和布隆过滤器来解决,前者避免了每次查询都访问数据库,后者有效减少了恶意哀求的影响。缓存击穿则通过设置随机过期时间来缓解,这样可以避免大量哀求同时涌向数据库。对于缓存雪崩,保证缓存层的高可用性、采用限流和熔断机制,以及制定充分的预案是关键。
有效的缓存管理不仅提升了系统性能,还增强了系统的稳固性。相识并解决这些缓存题目,能确保系统在高并发环境下保持高效、稳固的运行。精心计划和实施缓存计谋是优化应用性能的底子,持续关注和调解这些计谋可以帮助系统应对各种挑战,保持良好的用户体验。
我是努力的小雨,一名 Java 服务端码农,潜心研究着 AI 技能的奥秘。我热爱技能交流与分享,对开源社区充满热情。同时也是一位掘金优秀作者、腾讯云内容共创官、阿里云专家博主、华为云云享专家。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4