您的位置:首页 > 健康 > 养生 > 360免费wifi频繁掉线_百度广告服务商_seo网站推广教程_济南网站seo公司

360免费wifi频繁掉线_百度广告服务商_seo网站推广教程_济南网站seo公司

2025/7/1 11:06:03 来源:https://blog.csdn.net/q464042566/article/details/147465686  浏览:    关键词:360免费wifi频繁掉线_百度广告服务商_seo网站推广教程_济南网站seo公司
360免费wifi频繁掉线_百度广告服务商_seo网站推广教程_济南网站seo公司

所谓热点KEY,是指在缓存或数据库中被频繁访问的少量键值,这些键往往承载了系统中大部分的访问流量。

根据二八原则,通常20%的数据承担了80%的访问量,甚至在某些极端情况下,单个KEY可能会吸引系统超过50%的流量。

当这些热点KEY没有得到合理处理时,可能导致:

  • 缓存节点CPU使用率飙升
  • 网络带宽争用
  • 缓存服务响应延迟增加
  • 缓存穿透导致数据库压力骤增
  • 在极端情况下,甚至引发系统雪崩

本文将深入探讨SpringBoot中三种主流的热点KEY缓存优化策略,提升系统在面对热点KEY时的性能表现。

1. 分级缓存策略

1.1 原理解析

分级缓存策略采用多层次的缓存架构,通常包括本地缓存(L1)和分布式缓存(L2)。当访问热点KEY时,系统首先查询本地内存缓存,避免网络开销;仅当本地缓存未命中时,才请求分布式缓存。

开源实现有JetCache、J2Cache

这种策略能有效降低热点KEY对分布式缓存的访问压力,同时大幅提升热点数据的访问速度。

分级缓存的核心工作流程:

  1. 请求首先访问本地缓存(如Caffeine)
  2. 本地缓存命中直接返回数据(纳秒级)
  3. 本地缓存未命中,请求分布式缓存(如Redis)
  4. 分布式缓存命中,返回数据并回填本地缓存
  5. 分布式缓存未命中,查询数据源并同时更新本地和分布式缓存

1.2 实现方式

步骤1:添加相关依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId>
</dependency>
步骤2:配置分级缓存管理器
@Configuration
@EnableCaching
public class LayeredCacheConfig {@Beanpublic CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {LayeredCacheManager cacheManager = new LayeredCacheManager(createLocalCacheManager(), createRedisCacheManager(redisConnectionFactory));return cacheManager;}private CacheManager createLocalCacheManager() {CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();// 本地缓存配置 - 为热点KEY特别优化caffeineCacheManager.setCaffeine(Caffeine.newBuilder().initialCapacity(100)                  // 初始大小.maximumSize(1000)                     // 最大缓存对象数.expireAfterWrite(1, TimeUnit.MINUTES) // 写入后1分钟过期.recordStats());                       // 开启统计return caffeineCacheManager;}private CacheManager createRedisCacheManager(RedisConnectionFactory redisConnectionFactory) {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10))      // Redis缓存10分钟过期.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();}
}
步骤3:实现自定义分级缓存管理器
public class LayeredCacheManager implements CacheManager {private final CacheManager localCacheManager;  // 本地缓存(L1)private final CacheManager remoteCacheManager; // 分布式缓存(L2)private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>();public LayeredCacheManager(CacheManager localCacheManager, CacheManager remoteCacheManager) {this.localCacheManager = localCacheManager;this.remoteCacheManager = remoteCacheManager;}@Overridepublic Cache getCache(String name) {return cacheMap.computeIfAbsent(name, this::createLayeredCache);}@Overridepublic Collection<String> getCacheNames() {Set<String> names = new LinkedHashSet<>();names.addAll(localCacheManager.getCacheNames());names.addAll(remoteCacheManager.getCacheNames());return names;}private Cache createLayeredCache(String name) {Cache localCache = localCacheManager.getCache(name);Cache remoteCache = remoteCacheManager.getCache(name);return new LayeredCache(name, localCache, remoteCache);}// 分级缓存实现static class LayeredCache implements Cache {private final String name;private final Cache localCache;private final Cache remoteCache;public LayeredCache(String name, Cache localCache, Cache remoteCache) {this.name = name;this.localCache = localCache;this.remoteCache = remoteCache;}@Overridepublic String getName() {return name;}@Overridepublic Object getNativeCache() {return this;}@Overridepublic ValueWrapper get(Object key) {// 先查本地缓存ValueWrapper localValue = localCache.get(key);if (localValue != null) {return localValue;}// 本地未命中,查远程缓存ValueWrapper remoteValue = remoteCache.get(key);if (remoteValue != null) {// 回填本地缓存localCache.put(key, remoteValue.get());return remoteValue;}return null;}@Overridepublic <T> T get(Object key, Class<T> type) {// 先查本地缓存T localValue = localCache.get(key, type);if (localValue != null) {return localValue;}// 本地未命中,查远程缓存T remoteValue = remoteCache.get(key, type);if (remoteValue != null) {// 回填本地缓存localCache.put(key, remoteValue);return remoteValue;}return null;}@Overridepublic <T> T get(Object key, Callable<T> valueLoader) {// 先查本地缓存ValueWrapper localValue = localCache.get(key);if (localValue != null) {return (T) localValue.get();}// 本地未命中,查远程缓存ValueWrapper remoteValue = remoteCache.get(key);if (remoteValue != null) {// 回填本地缓存T value = (T) remoteValue.get();localCache.put(key, value);return value;}// 远程也未命中,调用值加载器try {T value = valueLoader.call();if (value != null) {// 同时更新本地和远程缓存put(key, value);}return value;} catch (Exception e) {throw new ValueRetrievalException(key, valueLoader, e);}}@Overridepublic void put(Object key, Object value) {localCache.put(key, value);remoteCache.put(key, value);}@Overridepublic void evict(Object key) {localCache.evict(key);remoteCache.evict(key);}@Overridepublic void clear() {localCache.clear();remoteCache.clear();}}
}
步骤4:在服务中使用分级缓存
@Service
public class ProductService {private final ProductRepository productRepository;public ProductService(ProductRepository productRepository) {this.productRepository = productRepository;}// 使用自定义缓存处理热点商品数据@Cacheable(value = "products", key = "#id", cacheManager = "cacheManager")public Product getProductById(Long id) {// 模拟数据库访问延迟try {Thread.sleep(200);} catch (InterruptedException e) {Thread.currentThread().interrupt();}return productRepository.findById(id).orElseThrow(() -> new ProductNotFoundException("Product not found: " + id));}// 处理热门商品列表@Cacheable(value = "hotProducts", key = "'top' + #limit", cacheManager = "cacheManager")public List<Product> getHotProducts(int limit) {// 复杂查询获取热门商品return productRepository.findTopSellingProducts(limit);}// 更新商品信息 - 同时更新缓存@CachePut(value = "products", key = "#product.id", cacheManager = "cacheManager")public Product updateProduct(Product product) {return productRepository.save(product);}// 删除商品 - 同时删除缓存@CacheEvict(value = "products", key = "#id", cacheManager = "cacheManager")public void deleteProduct(Long id) {productRepository.deleteById(id);}
}

1.3 优缺点分析

优点

  • 显著降低热点KEY的访问延迟,本地缓存访问速度可达纳秒级
  • 大幅减轻分布式缓存的负载压力,提高系统整体吞吐量
  • 减少网络IO开销,节约带宽资源
  • 即使分布式缓存短暂不可用,本地缓存仍可提供服务,增强系统弹性

缺点

  • 增加了系统复杂度,需管理两层缓存
  • 存在数据一致性挑战,不同节点的本地缓存可能不同步
  • 本地缓存占用应用服务器内存资源
  • 适合读多写少的场景,写入频繁场景效果有限

适用场景

  • 高频访问且相对稳定的热点数据(如商品详情、用户配置)
  • 读多写少的业务场景
  • 对访问延迟敏感的关键业务
  • 分布式缓存面临高负载的系统

2. 缓存分片策略

2.1 原理解析

缓存分片策略针对单个热点KEY可能导致的单点压力问题,通过将一个热点KEY拆分为多个物理子KEY,将访问负载均匀分散到多个缓存节点或实例上。这种策略在不改变业务逻辑的前提下,有效提升了系统处理热点KEY的能力。

其核心原理是:

  1. 将一个逻辑上的热点KEY映射为多个物理子KEY
  2. 访问时,随机或按某种规则选择一个子KEY进行操作
  3. 写入时,同步更新所有子KEY,保证数据一致性
  4. 通过分散访问压力,避免单个缓存节点的性能瓶颈

2.2 实现方式

步骤1:创建缓存分片管理器
@Component
public class ShardedCacheManager {private final RedisTemplate<String, Object> redisTemplate;private final Random random = new Random();// 热点KEY分片数量private static final int DEFAULT_SHARDS = 10;// 分片KEY的有效期略有差异,避免同时过期private static final int BASE_TTL_MINUTES = 30;private static final int TTL_VARIATION_MINUTES = 10;public ShardedCacheManager(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}/*** 获取分片缓存的值*/public <T> T getValue(String key, Class<T> type) {// 随机选择一个分片String shardKey = generateShardKey(key, random.nextInt(DEFAULT_SHARDS));return (T) redisTemplate.opsForValue().get(shardKey);}/*** 设置分片缓存的值*/public void setValue(String key, Object value) {// 写入所有分片for (int i = 0; i < DEFAULT_SHARDS; i++) {String shardKey = generateShardKey(key, i);// 计算略有差异的TTL,避免同时过期int ttlMinutes = BASE_TTL_MINUTES + random.nextInt(TTL_VARIATION_MINUTES);redisTemplate.opsForValue().set(shardKey, value, ttlMinutes, TimeUnit.MINUTES);}}/*** 删除分片缓存*/public void deleteValue(String key) {// 删除所有分片List<String> keys = new ArrayList<>(DEFAULT_SHARDS);for (int i = 0; i < DEFAULT_SHARDS; i++) {keys.add(generateShardKey(key, i));}redisTemplate.delete(keys);}/*** 生成分片KEY*/private String generateShardKey(String key, int shardIndex) {return String.format("%s:%d", key, shardIndex);}
}
步骤2:创建热点KEY识别和处理组件
@Component
public class HotKeyDetector {private final RedisTemplate<String, Object> redisTemplate;private final ShardedCacheManager shardedCacheManager;// 热点KEY计数器的Hash名称private static final String HOT_KEY_COUNTER = "hotkey:counter";// 热点判定阈值 - 每分钟访问次数private static final int HOT_KEY_THRESHOLD = 1000;// 热点KEY记录private final Set<String> detectedHotKeys = ConcurrentHashMap.newKeySet();public HotKeyDetector(RedisTemplate<String, Object> redisTemplate,ShardedCacheManager shardedCacheManager) {this.redisTemplate = redisTemplate;this.shardedCacheManager = shardedCacheManager;// 启动定时任务,定期识别热点KEYscheduleHotKeyDetection();}/*** 记录KEY的访问次数*/public void recordKeyAccess(String key) {redisTemplate.opsForHash().increment(HOT_KEY_COUNTER, key, 1);}/*** 检查KEY是否是热点KEY*/public boolean isHotKey(String key) {return detectedHotKeys.contains(key);}/*** 使用合适的缓存策略获取值*/public <T> T getValue(String key, Class<T> type, Supplier<T> dataLoader) {if (isHotKey(key)) {// 使用分片策略处理热点KEYT value = shardedCacheManager.getValue(key, type);if (value != null) {return value;}// 分片中没有找到,从数据源加载并更新分片value = dataLoader.get();if (value != null) {shardedCacheManager.setValue(key, value);}return value;} else {// 对于非热点KEY,使用常规方式处理T value = (T) redisTemplate.opsForValue().get(key);if (value != null) {return value;}// 缓存未命中,记录访问并从数据源加载recordKeyAccess(key);value = dataLoader.get();if (value != null) {redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);}return value;}}/*** 定期识别热点KEY的任务*/private void scheduleHotKeyDetection() {ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();executor.scheduleAtFixedRate(() -> {try {// 获取所有KEY的访问计数Map<Object, Object> counts = redisTemplate.opsForHash().entries(HOT_KEY_COUNTER);// 清空之前识别的热点KEYSet<String> newHotKeys = new HashSet<>();// 识别新的热点KEYfor (Map.Entry<Object, Object> entry : counts.entrySet()) {String key = (String) entry.getKey();int count = ((Number) entry.getValue()).intValue();if (count > HOT_KEY_THRESHOLD) {newHotKeys.add(key);// 对新发现的热点KEY,预热分片缓存if (!detectedHotKeys.contains(key)) {preloadHotKeyToShards(key);}}}// 更新热点KEY集合detectedHotKeys.clear();detectedHotKeys.addAll(newHotKeys);// 清除计数器,开始新一轮计数redisTemplate.delete(HOT_KEY_COUNTER);} catch (Exception e) {// 异常处理e.printStackTrace();}}, 1, 1, TimeUnit.MINUTES);}/*** 预热热点KEY到分片缓存*/private void preloadHotKeyToShards(String key) {// 获取原始缓存中的值Object value = redisTemplate.opsForValue().get(key);if (value != null) {// 将值复制到所有分片shardedCacheManager.setValue(key, value);}}
}
步骤3:在服务中集成热点KEY处理
@Service
public class EnhancedProductService {private final ProductRepository productRepository;private final HotKeyDetector hotKeyDetector;public EnhancedProductService(ProductRepository productRepository, HotKeyDetector hotKeyDetector) {this.productRepository = productRepository;this.hotKeyDetector = hotKeyDetector;}/*** 获取商品信息,自动处理热点KEY*/public Product getProductById(Long id) {String cacheKey = "product:" + id;return hotKeyDetector.getValue(cacheKey, Product.class, () -> {// 从数据库加载产品信息return productRepository.findById(id).orElseThrow(() -> new ProductNotFoundException("Product not found: " + id));});}/*** 获取热门商品列表,自动处理热点KEY*/public List<Product> getHotProducts(int limit) {String cacheKey = "products:hot:" + limit;return hotKeyDetector.getValue(cacheKey, List.class, () -> {// 从数据库加载热门商品return productRepository.findTopSellingProducts(limit);});}/*** 更新商品信息,同时处理缓存*/public Product updateProduct(Product product) {Product savedProduct = productRepository.save(product);// 清除所有相关缓存String cacheKey = "product:" + product.getId();if (hotKeyDetector.isHotKey(cacheKey)) {// 如果是热点KEY,清除分片缓存hotKeyDetector.getShardedCacheManager().deleteValue(cacheKey);} else {// 常规缓存清除redisTemplate.delete(cacheKey);}return savedProduct;}
}

2.3 优缺点分析

优点

  • 有效分散单个热点KEY的访问压力
  • 不依赖于特定的缓存架构,可适用于多种缓存系统
  • 对客户端透明,无需修改调用方代码
  • 可动态识别和调整热点KEY的处理策略
  • 通过错峰过期时间,避免缓存雪崩问题

缺点

  • 增加写入开销,需同步更新多个缓存分片
  • 实现复杂度较高,需维护热点KEY检测和分片逻辑
  • 额外的内存占用(一个值存储多份)
  • 可能引入短暂的数据不一致窗口

适用场景

  • 特定KEY访问频率远高于其他KEY的场景
  • 读多写少的数据(商品详情、活动信息等)
  • 大型促销活动、爆款商品等可预见的流量突增场景
  • Redis集群面临单个KEY访问热点问题的系统

两种策略对比

特性分级缓存策略缓存分片策略
主要解决问题热点KEY访问延迟热点KEY单点压力
实现复杂度中等
额外存储开销中等
写入性能影响中等
一致性保障最终一致最终一致
对原有代码改动中等
适用热点类型通用热点超级热点

总结

在实际应用中,我们可以根据业务特点和系统架构选择合适的策略,甚至将多种策略组合使用,构建更加健壮的缓存体系。

无论选择哪种策略,都应当结合监控、预热、降级等最佳实践,才能真正发挥缓存的价值,保障系统在面对热点KEY时的性能和稳定性。

最后,缓存优化是一个持续改进的过程,随着业务发展和流量变化,需要不断调整和优化缓存策略,才能确保系统始终保持高性能和高可用性。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com