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