话不多说先贴代码

/**
 * 缓存工具
 */
public class ConcurrentHashMapCacheUtils{

    /**
     * 当前缓存个数
     */
    public static Integer CURRENT_SIZE = 0;

    /**
     * 时间一分钟
     */
    static final Long ONE_MINUTE = 60 * 1000L;

    /**
     * 缓存超时
     */
    private static final Long TTL_TIME = 60 * 1000L;

    /**
     * 缓存对象
     */
    private static final ConcurrentHashMap<String, CacheObj> CACHE_OBJECT_MAP = new ConcurrentHashMap<>();

    /**
     * 清理过期缓存是否在运行
     */
    private static volatile Boolean CLEAN_THREAD_IS_RUN = false;

    /**
     * 设置缓存
     */
    public static void setCache(String cacheKey, String cacheValue, long cacheTime) {
        Long ttlTime = null;
        if (cacheTime <= 0L) {
            if (cacheTime == -1L) {
                ttlTime = -1L;
            } else {
                return;
            }
        }
        CURRENT_SIZE = CURRENT_SIZE + 1;
        if (ttlTime == null) {
            ttlTime = System.currentTimeMillis() + cacheTime;
        }
        CacheObj cacheObj = new CacheObj(cacheValue, ttlTime);
        CACHE_OBJECT_MAP.put(cacheKey, cacheObj);
    }

    /**
     * 设置缓存
     */
    public static void setCache(String cacheKey, String cacheValue) {
        setCache(cacheKey, cacheValue, TTL_TIME);
    }

    public static long getCurrentSize(){
        return CACHE_OBJECT_MAP.mappingCount();
    }

    public static List<String> getRecentApp(){
        List<String> list = new ArrayList<>(16);
        for (String key:CACHE_OBJECT_MAP.keySet()){
            list.add(key);
        }
        return list;
    }

    /**
     * 获取缓存
     */
    public static String getCache(String cacheKey) {
        startCleanThread();
        if (checkCache(cacheKey)) {
            return CACHE_OBJECT_MAP.get(cacheKey).getCacheValue();
        }
        return null;
    }

    /**
     * 删除某个缓存
     */
    public static void deleteCache(String cacheKey) {
        Object cacheValue = CACHE_OBJECT_MAP.remove(cacheKey);
        if (cacheValue != null) {
            CURRENT_SIZE = CURRENT_SIZE - 1;
        }
    }
    /**
     * 判断缓存在不在,过没过期
     */
    private static boolean checkCache(String cacheKey) {
        CacheObj cacheObj = CACHE_OBJECT_MAP.get(cacheKey);
        if (cacheObj == null) {
            return false;
        }
        if (cacheObj.getTtlTime() == -1L) {
            return true;
        }
        if (cacheObj.getTtlTime() < System.currentTimeMillis()) {
            deleteCache(cacheKey);
            return false;
        }
        return true;
    }

    /**
     * 删除过期的缓存
     */
    static void deleteTimeOut() {
        List<String> deleteKeyList = new LinkedList<>();
        for(Map.Entry<String, CacheObj> entry : CACHE_OBJECT_MAP.entrySet()) {
            if (entry.getValue().getTtlTime() < System.currentTimeMillis() && entry.getValue().getTtlTime() != -1L) {
                deleteKeyList.add(entry.getKey());
            }
        }
        for (String deleteKey : deleteKeyList) {
            deleteCache(deleteKey);
        }
    }


    /**
     * 设置清理线程的运行状态为正在运行
     */
    static void setCleanThreadRun() {
        CLEAN_THREAD_IS_RUN = true;
    }

    /**
     * 开启清理过期缓存的线程
     */
    private static void startCleanThread() {
        if (!CLEAN_THREAD_IS_RUN) {
            ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNamePrefix("clean-cache-pool-").build();
            ThreadPoolExecutor cleanThreadPool = new ThreadPoolExecutor(
                    8,
                    16,
                    60L,
                    TimeUnit.SECONDS,
                    new ArrayBlockingQueue<>(8),
                    namedThreadFactory
            );
            cleanThreadPool.execute(new CleanTimeOutThread());

        }

    }

}

class CacheObj {
    /**
     * 缓存对象
     */
    private String cacheValue;
    /**
     * 缓存过期时间
     */
    private Long ttlTime;

    CacheObj(String cacheValue, Long ttlTime) {
        this.cacheValue = cacheValue;
        this.ttlTime = ttlTime;
    }

    String getCacheValue() {
        return cacheValue;
    }

    Long getTtlTime() {
        return ttlTime;
    }

    @Override
    public String toString() {
        return "CacheObj {" +
                "cacheValue = " + cacheValue +
                ", ttlTime = " + ttlTime +
                '}';
    }
}

/**
 * 每一分钟清理一次过期缓存
 */
class CleanTimeOutThread implements Runnable{

    private static Logger logger = LoggerFactory.getLogger(CleanTimeOutThread.class);

    @Override
    public void run() {
        ConcurrentHashMapCacheUtils.setCleanThreadRun();
        while (true) {
            ConcurrentHashMapCacheUtils.deleteTimeOut();
            try {
                Thread.sleep(ConcurrentHashMapCacheUtils.ONE_MINUTE);
            } catch (InterruptedException e) {
                logger.error("Time-out Cache has not been cleaned!{}", e.getMessage());
            }
            if(1==2){
                break;
            }
        }
    }

}

  

1、背景

在公司对某个开源组件的使用中,频繁出现客户端无法请求到数据的情况,经排查是发生了并发数过大数据库性能瓶颈的情况。

于是有了对服务端的优化喝如下的思考。

2、设计思考

2.1、是否选择缓存

直接查询DB还是添加缓存,这个取决于系统的并发数,如果系统并发数数据库性能足以支持,则无使用缓存的必要。

如果选择使用缓存,则需要面对的一个风险是:

服务启动/重启的瞬间会出现大量对于数据库的请求,容易发生缓存的击穿/雪崩情况。

关于这种情况我做了专门的优化来避免出现缓存击穿/雪崩,这一段的代码后面优化后再上

2.2、缓存种类的选择

2.2.1、Java内存

优点:

  • 速度快
  • 无额外网络开销
  • 系统复杂度低

缺点:

  • 受限于热点数据数量,对应用内存大小有要求
  • 大量缓存同时失效会发生雪崩导致服务性能瞬间下降
  • 存在击穿风险
  • 多实例存在缓存一致性问题,可能出现对一条数据的重复查询

2.2.2、redis

优点:

  • 支持大量数据缓存,扩展性好
  • 在多实例时不需要考虑缓存一致性问题

缺点:

  • 系统依赖redis,如果redis不可用会导致系统不可用
  • 存在击穿风险