Redis缓存三兄弟解决方案及实现

1. 缓存穿透

定义:缓存穿透是指查询一个不存在的数据,缓存中没有,数据库中也没有,导致每次请求都会直接打到数据库上。

解决方案

  • 布隆过滤器:使用布隆过滤器来判断数据是否存在,如果布隆过滤器返回不存在,则直接返回,不再查询数据库。
  • 缓存空值:将不存在的数据也缓存一段时间,避免频繁查询数据库。

使用布隆过滤器和缓存空值的结合方案:

import com.google.common.hash.Funnel;
import com.google.common.hash.Hashing;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

@Service
public class CachePenetrationService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    private final RBloomFilter<String> bloomFilter;

    public CachePenetrationService() {
        // 初始化布隆过滤器
        this.bloomFilter = redissonClient.getBloomFilter("bloomFilter");
        this.bloomFilter.tryInit(1000000, 0.03); // 预期插入 100 万个元素,误判率为 3%
    }

    /**
     * 获取数据,处理缓存穿透
     *
     * @param key 键
     * @return 数据
     */
    public String getData(String key) {
        // 使用布隆过滤器检查数据是否存在
        if (!bloomFilter.contains(key)) {
            return "数据不存在";
        }

        // 存在则从缓存中获取数据
        String data = stringRedisTemplate.opsForValue().get(key);
        if (data != null) {
            return data;
        }

        // 加锁,确保只有一个请求去查询数据库
        String lockKey = "lock:" + key;
        while (stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS)) {
            try {
                // 从数据库中获取数据
                data = fetchDataFromDatabase(key);

                // 如果数据不存在,缓存空值
                if (data == null) {
                    stringRedisTemplate.opsForValue().set(key, "", 60, TimeUnit.SECONDS);
                    return "Data does not exist";
                }

                // 将数据写回缓存
                stringRedisTemplate.opsForValue().set(key, data, 60 * 60, TimeUnit.SECONDS);
                return data;
            } finally {
                stringRedisTemplate.delete(lockKey);
            }
        }

        // 等待其他请求完成并获取结果
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 从数据库中获取数据
     *
     * @param key 键
     * @return 数据
     */
    private String fetchDataFromDatabase(String key) {
        // 模拟从数据库中获取数据
        // 实际应用中应替换为真实的数据库查询逻辑
        return "Data from database";
    }
}

2. 缓存击穿

定义:缓存击穿是指某个热点数据在缓存中过期的一瞬间,大量请求同时打到数据库上,造成数据库压力骤增。

解决方案

  • 加锁:在缓存失效后,通过加锁机制确保只有一个请求去查询数据库,并将结果写回缓存。
  • 设置随机过期时间:为缓存设置一个随机的过期时间,避免大量缓存在同一时间点过期。

使用加锁和随机过期时间的结合方案:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class CacheBreakdownService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 获取数据,处理缓存击穿
     *
     * @param key 键
     * @return 数据
     */
    public String getData(String key) {
        // 从缓存中获取数据
        String data = stringRedisTemplate.opsForValue().get(key);
        if (data != null) {
            return data;
        }

        // 加锁,确保只有一个请求去查询数据库
        String lockKey = "lock:" + key;
        while (stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS)) {
            try {
                // 从数据库中获取数据
                data = fetchDataFromDatabase(key);

                // 将数据写回缓存,并设置随机过期时间
                int randomExpireTime = 60 * 60 + (int) (Math.random() * 3600); // 1 小时到 2 小时之间
                stringRedisTemplate.opsForValue().set(key, data, randomExpireTime, TimeUnit.SECONDS);
                return data;
            } finally {
                stringRedisTemplate.delete(lockKey);
            }
        }

        // 等待其他请求完成并获取结果
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 从数据库中获取数据
     *
     * @param key 键
     * @return 数据
     */
    private String fetchDataFromDatabase(String key) {
        // 模拟从数据库中获取数据
        // 实际应用中应替换为真实的数据库查询逻辑
        return "Data from database";
    }
}

3. 缓存雪崩

定义:缓存雪崩是指大量缓存在同一时间点集体失效,导致大量请求直接打到数据库上,造成数据库压力剧增。

解决方案

  • 设置随机过期时间:为缓存设置一个随机的过期时间,避免大量缓存在同一时间点过期。
  • 限流:在请求到达时进行限流,避免过多请求同时打到数据库上。
  • 降级:在缓存和数据库都不可用时,返回默认值或降级处理。

使用随机过期时间和限流的结合方案:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class CacheAvalancheService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 获取数据,处理缓存雪崩
     *
     * @param key 键
     * @return 数据
     */
    public String getData(String key) {
        // 从缓存中获取数据
        String data = stringRedisTemplate.opsForValue().get(key);
        if (data != null) {
            return data;
        }

        // 限流,确保每秒不超过 100 个请求
        if (isRateLimited()) {
            return "请求过多,请稍后再试";
        }

        // 从数据库中获取数据
        data = fetchDataFromDatabase(key);

        // 将数据写回缓存,并设置随机过期时间
        int randomExpireTime = 60 * 60 + (int) (Math.random() * 3600); // 1 小时到 2 小时之间
        stringRedisTemplate.opsForValue().set(key, data, randomExpireTime, TimeUnit.SECONDS);

        return data;
    }

    /**
     * 从数据库中获取数据
     *
     * @param key 键
     * @return 数据
     */
    private String fetchDataFromDatabase(String key) {
        // 模拟从数据库中获取数据
        // 实际应用中应替换为真实的数据库查询逻辑
        return "Data from database";
    }

    /**
     * 限流逻辑
     *
     * @return 是否超过限制
     */
    private boolean isRateLimited() {
        // 模拟限流逻辑,实际应用中可以使用 Redis 的限流功能或其他限流组件
        // 例如:使用 Redis 的 INCR 和 EXPIRE 命令实现每秒 100 个请求的限流
        return false;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值