【笔记】并发查询缓存工具类,自用

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;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
嗨!很高兴回答你关于Java并发编程的问题。请问你想知道什么方面的内容呢?我可以分享一些学习笔记和建议给你。 1. 并发编程基础:了解并发编程的基本概念,如线程、进程、锁、同步等。学习Java中的并发编程模型以及相关的API,如Thread、Runnable、Lock、Condition等。 2. 线程安全性:学习如何保证多线程环境下的数据安全性,了解共享资源的问题以及如何使用同步机制来防止数据竞争和并发问题。 3. 线程间的通信:掌握线程间的通信方式,如使用wait/notify机制、Lock/Condition等来实现线程的协调与通信。 4. 并发容器:学习并发容器的使用,如ConcurrentHashMap、ConcurrentLinkedQueue等。了解它们的实现原理以及在多线程环境下的性能特点。 5. 并发工具类:熟悉Java提供的并发工具类,如CountDownLatch、CyclicBarrier、Semaphore等,它们可以帮助你更方便地实现线程间的协作。 6. 并发编程模式:学习一些常见的并发编程模式,如生产者-消费者模式、读者-写者模式、线程池模式等。了解这些模式的应用场景和实现方式。 7. 性能优化与调试:学习如何分析和调试多线程程序的性能问题,了解一些性能优化的技巧和工具,如使用线程池、减少锁竞争、避免死锁等。 这些只是一些基本的学习笔记和建议,Java并发编程是一个庞大而复杂的领域,需要不断的实践和深入学习才能掌握。希望对你有所帮助!如果你有更具体的问题,欢迎继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值