首页 > 编程语言 >Java中的缓存穿透与雪崩问题:解决方案与设计模式

Java中的缓存穿透与雪崩问题:解决方案与设计模式

时间:2024-09-11 14:14:04浏览次数:13  
标签:缓存 Java Object cache key return 设计模式 data

Java中的缓存穿透与雪崩问题:解决方案与设计模式

大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!在分布式系统中,缓存是提高性能的重要手段。然而,缓存系统在实际应用中常常会遇到缓存穿透和缓存雪崩这两种问题。本文将探讨这两种问题的成因以及在Java中解决它们的有效方案和设计模式。

一、缓存穿透

1. 缓存穿透的定义

缓存穿透指的是缓存未能拦截某些请求,这些请求直接穿透缓存访问数据库,导致数据库负担过重。通常,这是由于请求中的数据根本不存在于数据库中,导致缓存也无法缓存这些不存在的数据。

2. 缓存穿透的解决方案

2.1 使用缓存空对象

在缓存中存储一个空对象(例如一个特定的标识符),当查询的数据不存在时,将这个空对象缓存起来。这可以避免大量不存在的数据请求直接访问数据库。

package cn.juwatech.cache;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class CacheService {

    private final Map<String, Optional<Object>> cache = new HashMap<>();

    public Object get(String key) {
        if (!cache.containsKey(key)) {
            Object data = fetchFromDatabase(key);
            if (data == null) {
                cache.put(key, Optional.empty());
                return null;
            } else {
                cache.put(key, Optional.of(data));
                return data;
            }
        }
        return cache.get(key).orElse(null);
    }

    private Object fetchFromDatabase(String key) {
        // Simulate database fetch
        return null;
    }
}

2.2 请求参数校验

在服务层对请求参数进行校验,过滤掉无效的请求,以减少对数据库的无效访问。

package cn.juwatech.cache;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    private final CacheService cacheService;

    public UserService(CacheService cacheService) {
        this.cacheService = cacheService;
    }

    public User getUser(String userId) {
        if (userId == null || userId.isEmpty()) {
            throw new IllegalArgumentException("Invalid user ID");
        }
        return (User) cacheService.get(userId);
    }
}

二、缓存雪崩

1. 缓存雪崩的定义

缓存雪崩是指缓存中的数据在某一时刻大量失效,导致大量请求同时访问数据库,可能造成数据库的瞬时压力剧增,影响系统性能。

2. 缓存雪崩的解决方案

2.1 缓存预热

在系统启动时预先加载常用数据到缓存中,避免在高峰期数据大量失效时瞬时访问数据库。

package cn.juwatech.cache;

import org.springframework.stereotype.Component;

@Component
public class CachePreloader {

    private final CacheService cacheService;

    public CachePreloader(CacheService cacheService) {
        this.cacheService = cacheService;
    }

    public void preload() {
        // Load frequently accessed data into cache
        for (String key : getFrequentKeys()) {
            Object data = fetchFromDatabase(key);
            cacheService.cache.put(key, Optional.of(data));
        }
    }

    private List<String> getFrequentKeys() {
        // Retrieve keys of frequently accessed data
        return Arrays.asList("key1", "key2", "key3");
    }

    private Object fetchFromDatabase(String key) {
        // Simulate database fetch
        return new Object();
    }
}

2.2 设置不同的过期时间

对不同类型的数据设置不同的过期时间,避免大量数据在同一时间过期造成雪崩效应。

package cn.juwatech.cache;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class TimeBasedCacheService {

    private final ConcurrentMap<String, CacheEntry> cache = new ConcurrentHashMap<>();

    public Object get(String key) {
        CacheEntry entry = cache.get(key);
        if (entry == null || entry.isExpired()) {
            Object data = fetchFromDatabase(key);
            cache.put(key, new CacheEntry(data, calculateExpiration()));
            return data;
        }
        return entry.getData();
    }

    private Object fetchFromDatabase(String key) {
        // Simulate database fetch
        return new Object();
    }

    private long calculateExpiration() {
        // Implement expiration time calculation
        return System.currentTimeMillis() + 3600 * 1000; // 1 hour
    }

    private static class CacheEntry {
        private final Object data;
        private final long expirationTime;

        public CacheEntry(Object data, long expirationTime) {
            this.data = data;
            this.expirationTime = expirationTime;
        }

        public Object getData() {
            return data;
        }

        public boolean isExpired() {
            return System.currentTimeMillis() > expirationTime;
        }
    }
}

2.3 使用互斥锁

在缓存失效时,通过互斥锁(例如Redis的SETNX)避免多个请求同时去数据库获取数据。

package cn.juwatech.cache;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class DistributedCacheService {

    private final StringRedisTemplate redisTemplate;

    public DistributedCacheService(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public Object get(String key) {
        String cacheKey = "cache:" + key;
        String value = redisTemplate.opsForValue().get(cacheKey);
        if (value != null) {
            return deserialize(value);
        }

        synchronized (this) {
            value = redisTemplate.opsForValue().get(cacheKey);
            if (value == null) {
                Object data = fetchFromDatabase(key);
                redisTemplate.opsForValue().set(cacheKey, serialize(data), 1, TimeUnit.HOURS);
                return data;
            }
        }

        value = redisTemplate.opsForValue().get(cacheKey);
        return value != null ? deserialize(value) : null;
    }

    private Object fetchFromDatabase(String key) {
        // Simulate database fetch
        return new Object();
    }

    private String serialize(Object data) {
        // Serialize data
        return data.toString();
    }

    private Object deserialize(String value) {
        // Deserialize data
        return new Object();
    }
}

总结

在Java服务端开发中,缓存穿透和缓存雪崩问题是影响系统性能的常见挑战。通过实施合理的解决方案,如使用缓存空对象、请求参数校验、缓存预热、设置不同过期时间和使用互斥锁,可以有效地减少这些问题对系统的影响。选择合适的方案并结合设计模式实现,将有助于提升系统的稳定性和性能。

本文著作权归聚娃科技微赚淘客系统开发者团队,转载请注明出处!

标签:缓存,Java,Object,cache,key,return,设计模式,data
From: https://www.cnblogs.com/szk123456/p/18408155

相关文章

  • Java中的负载测试:从单元测试到集成测试的完整覆盖策略
    Java中的负载测试:从单元测试到集成测试的完整覆盖策略大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!今天我们来聊一聊Java中的负载测试。负载测试是保证系统性能和稳定性的重要手段,而完整的测试策略不仅包括单元测试,还要覆盖到集成测试。本文将从单......
  • 如何在Java中实现应用的动态扩展:基于热插拔与插件机制的实现
    如何在Java中实现应用的动态扩展:基于热插拔与插件机制的实现大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!在现代应用开发中,为了应对不断变化的需求和快速迭代的要求,应用的动态扩展能力变得尤为重要。实现动态扩展的关键技术包括热插拔和插件机制。......
  • 清理C盘缓存,你知道清理C盘缓存有哪些方法吗
    清理C盘缓存是维护Windows系统性能的重要步骤之一。以下是一些常用的清理C盘缓存的方法:一、使用Windows内置工具磁盘清理打开“设置”应用,选择“系统”>“存储”。在“存储”页面中,找到并点击“C盘”的详细信息。点击“临时文件”,系统将列出可清理的临时文件,如系统更新文......
  • 清理C盘缓存,教你清理C盘缓存的方法
    清理C盘缓存是提升Windows系统性能和释放存储空间的有效方法。以下是一些详细的步骤和技巧,帮助你有效地清理C盘缓存:一、使用Windows内置工具磁盘清理打开磁盘清理:通过“设置”>“系统”>“存储”进入C盘存储详情页面,或直接在文件资源管理器中右键点击C盘选择“属性”>“......
  • Java服务端开发中的请求优化:从HTTP/1.1到HTTP/2与gRPC的升级
    Java服务端开发中的请求优化:从HTTP/1.1到HTTP/2与gRPC的升级大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!在现代Java服务端开发中,提升请求性能是至关重要的。随着HTTP/2和gRPC的引入,优化请求性能变得更加有针对性和高效。本文将探讨如何从HTTP/1.1......
  • Java中的元编程:使用反射与代理模式实现代码的动态增强
    Java中的元编程:使用反射与代理模式实现代码的动态增强大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!在Java开发中,元编程是指在程序运行时对程序进行修改和扩展的技术。反射和代理模式是实现Java元编程的两种常用技术。本文将探讨如何使用反射与代理......
  • Java中的安全编码实践:如何防止SQL注入与XSS攻击
    Java中的安全编码实践:如何防止SQL注入与XSS攻击大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!在Java开发中,安全编码是确保应用程序免受攻击的关键因素。SQL注入和跨站脚本攻击(XSS)是最常见的安全漏洞之一。本文将介绍如何在Java中防止这两种攻击,并提......
  • Javascript应用(轮播图进阶)
    <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title>Document</title>......
  • 【JAVA开源】基于Vue和SpringBoot员工绩效考核系统
    本文项目编号T021,文末自助获取源码\color{red}{T021,文末自助获取源码}......
  • 【JAVA开源】基于Vue和SpringBoot大学生入学审核系统
    本文项目编号T022,文末自助获取源码\color{red}{T022,文末自助获取源码}......