【缓存】解决缓存击穿、穿透、雪崩的示例代码(令牌桶、布隆过滤器、读写锁等)

说明

当前的类实现了雪崩、击穿、穿透的常规解决方案,基本能够避免上述的问题,但是实际应用场景中还需要优化下,而且在没必要的情况下,也不一定要使用当前的类,里面使用到了Redis令牌桶限流、分布式读写锁、分布式缓存、布隆过滤器等分布式场景。
若觉得还不错,可以加个关注,你的关注是我分享干货的动力。我会按照之前的课程大纲或者新的内容,不定时的分享工作的干货。

mvn 依赖
        <dependency>
          <groupId>org.redissongroupId>
            <artifactId>redisson-spring-boot-starterartifactId>
            <version>3.8.2version>
        dependency>
        <dependency>
            <groupId>org.redissongroupId>
            <artifactId>redissonartifactId>
            <version>3.8.2version>
        dependency>
配置文件
spring:
  redis:
    open: true
    database: 0
    host: 127.0.0.1
    port: 6379
    password: root
    timeout: 6000ms
公共抽象类
package com.demo.cache;

import com.demo.cache.constant.ConstantUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.*;
import org.redisson.client.codec.StringCodec;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * 缓存的通用抽象类,解决了 雪崩、击穿、穿透的问题
 *
 * @author 王思勤
 */
@Slf4j
public abstract class AbstractCache<T> {

    /**
     * 缓存前缀,必须包含占位符 %s
     */
    protected static final String CACHE_PREFIX = "demo:cache:%s";

    /**
     * 限流的速率类型
     */
    protected static RateType RATE_LIMITER_RATE_TYPE = RateType.OVERALL;

    /**
     * 限流的速率
     */
    protected static long RATE_LIMITER_RATE = 2;

    /**
     * 限流速率间隔
     */
    protected static long RATE_LIMITER_RATE_INTERVAL = 1;

    /**
     * 限流速率间隔单位
     */
    protected static RateIntervalUnit RATE_LIMITER_RATE_INTERVAL_UNIT = RateIntervalUnit.SECONDS;

    /**
     * 限流尝试获取超时时间
     */
    protected static long RATE_LIMITER_ACQUIRE_TIMEOUT = 1;

    /**
     * 限流尝试获取超时时间单位
     */
    protected static TimeUnit RATE_LIMITER_RATE_ACQUIRE_TIMEUNIT = TimeUnit.SECONDS;

    /**
     * 布隆过滤器预期元素插入量
     */
    protected static long BLOOM_FILTER_EXPECTED_INSERTIONS = 2 ^ 32;

    /**
     * 布隆过滤器预期的错误概率
     */
    protected static double BLOOM_FILTER_FALSE_PROBABILITY = 0.01;

    /**
     * 空值是最小的过期时间
     */
    protected static long NULL_VALUE_MIN_EXPIRE_TIME = 1;

    /**
     * 空值是最大的过期时间
     */
    protected static long NULL_VALUE_MAX_EXPIRE_TIME = 2;

    /**
     * 空值是过期时间的单位
     */
    protected static TimeUnit NULL_VALUE_MIN_EXPIRE_TIMEUNIT = TimeUnit.MINUTES;

    /**
     * 非空值是最小的过期时间
     */
    protected static long NONNULL_VALUE_MIN_EXPIRE_TIME = 3;

    /**
     * 非空值是最大的过期时间
     */
    protected static long NONNULL_VALUE_MAX_EXPIRE_TIME = 10;

    /**
     * 非空值是过期时间的单位
     */
    protected static TimeUnit NONNULL_VALUE_MIN_EXPIRE_TIMEUNIT = TimeUnit.MINUTES;

    @Resource
    private RedissonClient redissonClient;

    /**
     * 待缓存的数据,当前的方法需要重写
     *
     * @param param 参数
     * @return 返回 待缓存的值
     */
    public abstract String willCacheData(T param);

    /**
     * 限流
     *
     * @param key   key
     * @param param 查询的参数
     * @return 返回 值
     */
    public String getValueRateLimiter(String key, T param) {
        String rateLimiterKey = String.format(CACHE_PREFIX, ConstantUtil.CACHE_RATE_LIMITER);
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(rateLimiterKey);
        if (!rateLimiter.isExists()) {
            // 设置 速率模式,速率,间隔,间隔单位
            rateLimiter.trySetRate(RATE_LIMITER_RATE_TYPE, RATE_LIMITER_RATE
                    , RATE_LIMITER_RATE_INTERVAL, RATE_LIMITER_RATE_INTERVAL_UNIT);
        }
        // 获取 访问许可rateLimiter acquire();
        if (!rateLimiter.tryAcquire(RATE_LIMITER_ACQUIRE_TIMEOUT, RATE_LIMITER_RATE_ACQUIRE_TIMEUNIT)) {
            log.error("获取不到访问许可", key);
            return null;
        }
        return getValueBloomFilter(key, param);
    }

    /**
     * 布隆过滤器
     *
     * @param key   key
     * @param param 查询的参数
     * @return 返回 值
     */
    public String getValueBloomFilter(String key, T param) {
        String readWriteKey = String.format(CACHE_PREFIX,String.format(ConstantUtil.CACHE_LOCK, key));
        // 读写锁
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(readWriteKey);
        RLock readLock = readWriteLock.readLock();
        readLock.lock();
        RBucket<String> rBucket = null;
        try {
            String cacheKey = String.format(CACHE_PREFIX, String.format(ConstantUtil.CACHE_KEY, key));
            // 获取缓存
            rBucket = redissonClient.getBucket(cacheKey, StringCodec.INSTANCE);
        } catch (Exception e) {
            log.error(key, e);
        } finally {
            readLock.unlock();
        }
        if (rBucket == null) {
            // 获取数据异常
            return null;
        }
        if (StringUtils.isEmpty(rBucket.get())) {
            //获取 数据库的数据,并且缓存数据
            return this.getAndCacheData(key, readWriteLock, rBucket, param);
        }
        return rBucket.get();
    }

    /**
     * 获取 数据库的数据,并且缓存数据
     *
     * @param key           关键字
     * @param readWriteLock 锁
     * @param rBucket       redis
     * @param param         查询的参数
     * @return 返回 数据
     */
    private String getAndCacheData(String key, RReadWriteLock readWriteLock, RBucket<String> rBucket, T param) {
        String bloomFilterKey = String.format(CACHE_PREFIX,ConstantUtil.CACHE_BLOOM_FILTER);
        // 布隆过滤器,存在则可能存在,不存在,则一定不存在
        RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter(bloomFilterKey, StringCodec.INSTANCE);
        // expectedInsertions - - 每个元素的预期插入量 falseProbability - - 预期的错误概率
        bloomFilter.tryInit(BLOOM_FILTER_EXPECTED_INSERTIONS, BLOOM_FILTER_FALSE_PROBABILITY);
        if (!bloomFilter.contains(key)) {
            // 不存在则返回, 则一定不存在
            return null;
        }
        RLock writeLock = readWriteLock.writeLock();
        // 一直等待 writeLock tryLock(1, 1, TimeUnit.SECONDS);
        writeLock.lock();
        try {
            // 再次验证是否已经缓存,用于并发验证
            String value = rBucket.get();
            if (StringUtils.isNotBlank(value)) {
                return value;
            }
            // 待缓存的数据
            value = this.willCacheData(param);
            // 缓存数据
            this.cacheData(rBucket, value);
            // 返回 数据
            return value;
        } catch (Exception e) {
            log.error(key, e);
        } finally {
            writeLock.unlock();
        }
        return null;
    }

    /**
     * 缓存数据
     *
     * @param rBucket bucket
     * @param value   缓存数据
     */
    private void cacheData(RBucket<String> rBucket, String value) {
        if (StringUtils.isBlank(value)) {
            long timeout = RandomUtils.nextLong(NULL_VALUE_MIN_EXPIRE_TIME, NULL_VALUE_MAX_EXPIRE_TIME);
            rBucket.set(null, timeout, NULL_VALUE_MIN_EXPIRE_TIMEUNIT);
        } else {
            long timeout = RandomUtils.nextLong(NONNULL_VALUE_MIN_EXPIRE_TIME, NONNULL_VALUE_MAX_EXPIRE_TIME);
            // 缓存
            rBucket.set(value, timeout, NONNULL_VALUE_MIN_EXPIRE_TIMEUNIT);
        }
    }
}
实现类
package com.demo.cache;

import com.demo.cache.domain.entity.SaasRequestLog;
import com.demo.cache.domain.service.SaasRequestLogService;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * CacheDemoBiz
 *
 * @author 王思勤
 */
@Component
public class CacheDemoBiz extends AbstractCache<Long> {

    @Resource
    private SaasRequestLogService saasRequestLogService;

    @Override
    public String willCacheData(Long id) {
        SaasRequestLog requestLog = saasRequestLogService.getById(id);
        if (requestLog == null) {
            return null;
        }
        return requestLog.getRequestUrl();
    }
}
常量类
package com.demo.cache.constant;

/**
 * ConstantUtil
 *
 * @author 王思勤
 */
public class ConstantUtil {

    public static final String CACHE_KEY = "key:%s";

    public static final String CACHE_LOCK = "lock:%s";

    public static final String CACHE_BLOOM_FILTER = "bloomFilter";

    public static final String CACHE_RATE_LIMITER = "rateLimiter";
}
单元测试
package com.demo.cache;

import com.demo.Application;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class CacheDemoBizTest {

    @Resource
    private CacheDemoBiz cacheDemoBiz;

    @Test
    public void willCacheData() {
        String value = cacheDemoBiz.getValueBloomFilter("13", 13L);
        System.out.println(value);
        Assert.assertNotNull(value);
    }
}

你可能感兴趣的:(课程培训,分布式,java,redis,spring,boot,缓存)