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

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"));
    }
}
相关推荐
TheNextByte11 小时前
如何将Android联系人导出为 Excel 格式
android·excel
某zhuan1 小时前
Flutter环境搭建(VS Code和Android Studio)
android·flutter·android studio
yi诺千金1 小时前
Android U 自由窗口(浮窗)——补充
android
4Forsee2 小时前
【增强现实】快速上手 Vuforia Unity Android AR 应用开发
android·unity·ar
2501_944521592 小时前
Flutter for OpenHarmony 微动漫App实战:列表项组件实现
android·开发语言·javascript·flutter·ecmascript
小风呼呼吹儿2 小时前
Flutter 框架跨平台鸿蒙开发 - 种子发芽记录器:记录植物成长的每一刻
android·flutter·华为·harmonyos
一起养小猫2 小时前
Flutter for OpenHarmony 实战:Dart类与面向对象编程
android·flutter
2501_944526423 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 多语言国际化实现
android·java·开发语言·javascript·flutter·游戏