package com.test.common.redis;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.gexin.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
/**
* 并发查询引入redis缓存
**/
public class RedisCacheCommon<T> {
private final static Logger logger = LoggerFactory.getLogger(RedisCacheCommon.class);
/**
* 缓存读取,等待自旋,每次等待毫秒
*/
private static final int CACHE_READ_SLEEP_MILLIS = 20;
/**
* 缓存读取,等待自旋次数
*/
private static final int CACHE_READ_COUNTS = 10;
/**
* 缓存读取,等待自旋次数最大次数
*/
private static final int CACHE_READ_COUNTS_MAX = 100;//查询db的耗时最大2s
/**
* 默认失效时间:默认5分钟
*/
private static final int CACHE_EXPIRE_TIME_DEFAULT = 5*60;
/**
* 空值缓存失效时间:30秒
*/
private static final int EMPTY_CACHE_EXPIRE_TIME = 30;
/**
* 并发锁失效时间:3s
*/
public static final int DEFAULT_LOCK_SECOND = 3;
/**
* T 的数据类型
*/
private Class<T> clazz;
/**
* 是否缓存
*/
private boolean isCacheFlag = false;
public boolean isCacheFlag() {
return isCacheFlag;
}
public void setCacheFlag(boolean cacheFlag) {
isCacheFlag = cacheFlag;
}
/**
* 私有化构造方法,防止外部实例化
*/
private RedisCacheCommon(){
// PS:经验证该方法获取不到数据类型,所以私有化,使用加参数的构造方法进行设置
clazz =(Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
logger.info(">>>>>>>>>>clazz={}",clazz);
}
/**
* 实例化
*/
public RedisCacheCommon(Class<T> clazz){
this.clazz = clazz;
logger.info(">>>>>>>>>>clazz={}",clazz);
}
public static void main(String[] args) {
RedisCacheCommon<InfoMiniVO> cacheCommon1 = new RedisCacheCommon<>();
RedisCacheCommon<InfoMiniVO> cacheCommon2 = new RedisCacheCommon<>(InfoMiniVO.class);
}
/**
* 并发查询列表:优先使用缓存,没有缓存进行db查询;最大超时时间为默认为200ms
* @methodName getListDataByCache
* @param callable 查询db的方法
* @param callableMaxTime 查询db的方法最大耗时时间: 为空时默认200ms,最大不超过2000ms
* @param redisUtils
* @param cacheKey 缓存的key
* @param cacheExpireTime 缓存失效时间: 为空时默认5分钟
* @return java.util.List<T>
*/
public List<T> getListDataByCache(Callable<List<T>> callable, Integer callableMaxTime, RedisUtils redisUtils, String cacheKey, Integer cacheExpireTime){
if (clazz == null){
//这里不进行异常抛出,clazz约定必须设置值
logger.error(("<<并发查询列表>>:clazz为空"));
return null;
}
String name = clazz.getName();
logger.info("<<并发查询列表>>:start---->{}", name);
//类名
name = name.substring(name.lastIndexOf(".")+1);
long start = System.currentTimeMillis();
List<T> res = null;
// 先从缓存获取
Object obj = redisUtils.get(cacheKey);
if (obj != null) {
res = JSON.parseArray(String.valueOf(obj), clazz);
logger.info("<<并发查询列表:{}>>:[使用缓存],key={}", name,cacheKey);
//使用了缓存数据
setCacheFlag(true);
} else {
if (redisUtils.getLock(cacheKey + ":lock", DEFAULT_LOCK_SECOND)) {
// 获取到锁,进行查db并存入缓存中
logger.info("<<并发查询列表:{}>>:[使用db],key={}", name,cacheKey);
// 没缓存,使用db
try {
res = callable.call();
// 将数据存进缓存
if (CollectionUtil.isNotEmpty(res)) {
// 失效时间判断
if (cacheExpireTime == null || cacheExpireTime <=0){
cacheExpireTime = CACHE_EXPIRE_TIME_DEFAULT;
}
redisUtils.set(cacheKey, res, cacheExpireTime);
} else {
// 空数据,设置较短的失效时间 30s
redisUtils.set(cacheKey, res, EMPTY_CACHE_EXPIRE_TIME);
}
// 手动释放锁
redisUtils.releaseLock(cacheKey);
} catch (Exception e) {
logger.error("查询db发生异常", e);
throw new SystemException("服务器繁忙,请重试!");
}
} else {
// 未获取到锁,进行自旋
boolean flag = true;
int i = 0;
//设置次数
int count = getCount(callableMaxTime);
while (flag) {
i++;
// 休眠20ms
sleep();
// 自旋查询
res = getListDataByCache(callable,null, redisUtils, cacheKey, cacheExpireTime);
// 获取到数据就退出
flag = res == null;
if (flag) {
logger.info("<<并发查询列表:{}>>:自旋次数---->{}", name,i);
// 尝试10次后还是未获取到数据,退出循环返回重试
flag = i < count;
if (!flag) {
//200ms的时间还未
logger.info("<<并发查询列表:{}>>:自旋{}次后未获取到数据,返回繁忙!", name,count);
throw new SystemException("服务繁忙,请重试!");
}
}
}
}
}
logger.info("<<并发查询列表:{}>>:end---size={}----耗时={}ms", name,Optional.ofNullable(res).orElse(new ArrayList<>()).size(),System.currentTimeMillis() - start);
return res;
}
public int getCount(Integer callableMaxTime) {
if(callableMaxTime==null) {
return CACHE_READ_COUNTS;
}else {
int calculateCount = callableMaxTime / CACHE_READ_SLEEP_MILLIS;
if(calculateCount <= CACHE_READ_COUNTS){
return CACHE_READ_COUNTS;
}else {
if(calculateCount <= CACHE_READ_COUNTS_MAX){
return calculateCount;
}else {
return CACHE_READ_COUNTS_MAX;
}
}
}
}
public void sleep() {
try {
TimeUnit.MILLISECONDS.sleep(CACHE_READ_SLEEP_MILLIS);
} catch (InterruptedException e) {
logger.error("线程休眠发生异常!", e);
throw new SystemException("服务繁忙,请重试!");
}
}
/**
* 并发查询详情:优先使用缓存,没有缓存进行db查询;最大超时时间为默认为200ms
* @methodName getDataByCache
* @param callable 查询db的方法
* @param callableMaxTime 查询db的方法最大耗时时间: 为空时默认200ms,最大不超过2000ms
* @param redisUtils
* @param cacheKey 缓存的key
* @param cacheExpireTime 缓存失效时间: 为空时默认5分钟
* @return T
*/
public T getDataByCache(Callable<T> callable,Integer callableMaxTime, RedisUtils redisUtils, String cacheKey, Integer cacheExpireTime) {
if (clazz == null){
// 这里不进行异常抛出,clazz约定必须设置值
logger.error(("并发查询详情:clazz为空"));
return null;
}
String name = clazz.getName();
logger.info("并发查询详情:start---->{}", name);
//类名
name = name.substring(name.lastIndexOf(".")+1);
long start = System.currentTimeMillis();
T res = null;
// 先从缓存获取
Object obj = redisUtils.get(cacheKey);
// 是否进行db查询
if (obj != null) {
res = (T) obj;
logger.info("<<并发查询详情:{}>>:[使用缓存],key={}", name,cacheKey);
//使用了缓存数据
setCacheFlag(true);
} else {
if (redisUtils.getLock(cacheKey + ":lock", DEFAULT_LOCK_SECOND)) {
// 获取到锁,进行查db并存入缓存中
logger.info("<<并发查询详情:{}>>:[使用db],key={}", name,cacheKey);
//没缓存,使用db
try {
res = callable.call();
// 将数据存进缓存
if (ObjectUtil.isNotNull(res)) {
// 失效时间判断
if (cacheExpireTime == null || cacheExpireTime <= 0) {
cacheExpireTime = CACHE_EXPIRE_TIME_DEFAULT;
}
redisUtils.set(cacheKey, res, cacheExpireTime);
} else {
// 空数据,设置较短的失效时间 30s
redisUtils.set(cacheKey, res, EMPTY_CACHE_EXPIRE_TIME);
}
// 手动释放锁
redisUtils.releaseLock(cacheKey);
} catch (Exception e) {
logger.error("查询db发生异常", e);
throw new SystemException("服务器繁忙,请重试!");
}
} else {
// 未获取到锁,进行自旋
boolean flag = true;
int i = 0;
//设置次数
int count = getCount(callableMaxTime);
while (flag) {
i++;
// 休眠20ms
sleep();
// 自旋查询
res = getDataByCache(callable,callableMaxTime, redisUtils, cacheKey, cacheExpireTime);
// 获取到数据就退出
flag = res == null;
if (flag) {
logger.info("<<并发查询详情:{}>>:自旋次数---->{}", name,i);
// 尝试10次后还是未获取到数据,退出循环返回重试
flag = i < count;
if (!flag) {
logger.info("<<并发查询详情:{}>>:自旋{}次后未获取到数据,返回繁忙!", name,count);
throw new SystemException("服务繁忙,请重试!");
}
}
}
}
}
logger.info("<<并发查询详情:{}>>:耗时={}ms", name,System.currentTimeMillis() - start);
return res;
}
}