缓存的基础用法:解决缓存击穿,缓存穿透,缓存雪崩等问题。

java 复制代码
  //查询key是否存在缓存中?
    String value = redis.getKey("key");
    //如果缓存没有,查询数据库
    if (StringUtils.isEmpty(value)){
      value = remoteQueryDB(key);
      //放入缓存中
      redis.addKey(key,value, RandomUtils.nextInt(1000,5000));
    }
    return value;

以上是在开发中我们经常用到的缓存的写法,然而在高并发场景下会暴露缓存击穿、缓存过期时间随机但无兜底、缺乏异常处理、无并发控制 等关键问题。

下面给出解决方案:

一、核心前提:Redisson 依赖准备

首先需要引入 Redisson 依赖(以 Maven 为例),并配置 Redisson 客户端:

复制代码
<!-- Redisson 核心依赖 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.23.3</version> <!-- 建议用最新稳定版 -->
</dependency>

二、重构后的完整代码(Redisson 实现分布式锁)

复制代码
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import java.util.Random;
import java.util.concurrent.TimeUnit;

// 基于 Redisson 的 Redis 工具类(简化版)
class RedisUtil {
    private final RedissonClient redissonClient;
    // 缓存操作的通用前缀(可选)
    private final String CACHE_PREFIX = "cache:";
    // 分布式锁前缀
    private final String LOCK_PREFIX = "lock:";

    // 初始化 Redisson 客户端(生产建议配置到Spring容器中)
    public RedisUtil() {
        Config config = new Config();
        // 单机 Redis 配置(集群/哨兵可参考Redisson官方文档)
        config.useSingleServer()
              .setAddress("redis://127.0.0.1:6379")
              .setPassword(null) // 无密码则设为null
              .setDatabase(0);
        this.redissonClient = Redisson.create(config);
    }

    // 查询缓存
    public String getKey(String key) {
        try {
            return redissonClient.getBucket(CACHE_PREFIX + key).get();
        } catch (Exception e) {
            // Redis 异常时返回null,降级查库
            System.err.println("Redis查询异常,key=" + key + ", error=" + e.getMessage());
            return null;
        }
    }

    // 写入缓存(带过期时间,单位:秒)
    public boolean addKey(String key, String value, int expireSeconds) {
        try {
            redissonClient.getBucket(CACHE_PREFIX + key)
                          .set(value, expireSeconds, TimeUnit.SECONDS);
            return true;
        } catch (Exception e) {
            System.err.println("Redis写入异常,key=" + key + ", error=" + e.getMessage());
            return false;
        }
    }

    // 获取 Redisson 分布式锁(核心:可重入、自动续期)
    public RLock getLock(String key) {
        return redissonClient.getLock(LOCK_PREFIX + key);
    }
}

// 业务逻辑类(基于Redisson锁重构)
public class CacheService {
    private final RedisUtil redis = new RedisUtil();
    // 缓存过期策略:基础30分钟 + 随机0-10分钟偏移
    private final int BASE_EXPIRE_SECONDS = 30 * 60;
    private final int RANDOM_OFFSET_SECONDS = 10 * 60;
    private final Random random = new Random();
    // 锁等待时间(5秒):超过则放弃抢锁,降级查库
    private final long LOCK_WAIT_TIME = 5;
    // 锁自动释放时间(10秒):Redisson会自动续期,避免死锁
    private final long LOCK_LEASE_TIME = 10;

    public String queryData(String key) {
        // 步骤1:查询缓存,命中则直接返回
        String value = redis.getKey(key);
        if (StringUtils.isNotEmpty(value)) {
            return value;
        }

        // 步骤2:缓存未命中,获取Redisson分布式锁
        RLock lock = redis.getLock(key);
        boolean locked = false;
        try {
            // 核心:尝试获取锁(非阻塞,最多等5秒,拿到锁后10秒自动释放)
            // Redisson 会自动续期(watch dog机制),只要线程没挂,锁不会过期
            locked = lock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS);
            
            if (locked) {
                // 双重检查:防止抢锁期间其他线程已写入缓存
                value = redis.getKey(key);
                if (StringUtils.isNotEmpty(value)) {
                    return value;
                }

                // 步骤3:锁内查库(仅一个线程执行)
                value = remoteQueryDB(key);

                // 步骤4:缓存穿透防护:数据库无值则写入空标记+短过期
                if (StringUtils.isEmpty(value)) {
                    redis.addKey(key, "", 5 * 60); // 空值缓存5分钟
                    return "";
                }

                // 步骤5:写入缓存(基础过期+随机偏移)
                int expireSeconds = BASE_EXPIRE_SECONDS + random.nextInt(RANDOM_OFFSET_SECONDS);
                redis.addKey(key, value, expireSeconds);
                return value;
            } else {
                // 步骤6:未抢到锁,自旋重试(最多3次)
                int retryCount = 0;
                while (retryCount < 3) {
                    TimeUnit.MILLISECONDS.sleep(100);
                    value = redis.getKey(key);
                    if (StringUtils.isNotEmpty(value)) {
                        return value;
                    }
                    retryCount++;
                }
                // 重试失败,降级查库(兜底)
                return remoteQueryDB(key);
            }
        } catch (Exception e) {
            // 步骤7:异常处理,降级查库
            System.err.println("缓存逻辑异常,key=" + key + ", error=" + e.getMessage());
            return remoteQueryDB(key);
        } finally {
            // 步骤8:释放锁(必须在finally中,Redisson锁释放是幂等的)
            if (locked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    // 模拟远程查库(生产需加超时、重试)
    private String remoteQueryDB(String key) {
        try {
            // 模拟查库耗时
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return "db_value_" + key;
    }

    // 测试方法
    public static void main(String[] args) {
        CacheService service = new CacheService();
        System.out.println(service.queryData("user_1001"));
    }
}
相关推荐
千里马学框架9 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台9 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone9 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc10 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo11 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun00770012 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼13 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone14 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen14 小时前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone14 小时前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui