资讯专栏INFORMATION COLUMN

本地缓存工具类

RichardXG / 788人阅读

摘要:时间转换成毫秒清空当前命名空间下的所有暴露指定命名空间下所有的缓存死亡时间纳秒值对更新缓存时旧的已有的会取消重新设置新的对于每个是单例的

package com.common.helper;


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.lang.Nullable;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * 本地缓存管理器
 * 
 *     v1.1
 *       - getByNameSpace
 *       - isExpired(..) 判断是否已经死亡, 包含物理死亡和逻辑死亡.
 *
 *     v1.0
 *       - 增加构造, 可指定线程数
 *
 *     v0.0.1
 *       - 测试期
 * 
* * @author Nisus Liu * @version 1.1 * @email liuhejun108@163.com * @date 2018/11/7 17:55 */ @Slf4j public abstract class LocalCacheManager { public static final String NAMESPACE_KEY_JOIN = "::"; /** * 缓存池子 */ protected Map caches = new ConcurrentHashMap(); /** * 清除缓存的定时延迟任务(刽子手) */ protected Map headsmans = new ConcurrentHashMap(); protected ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(5); public LocalCacheManager() { } /** * @param poolSize 指定用于清除定时任务的线程数 */ public LocalCacheManager(int poolSize) { scheduler.setCorePoolSize(poolSize); } /** * 获取指定key的缓存值 * * @param key * @return */ public T get(@Nullable String nameSpace, String key) { if (StringUtil.isEmpty(key)) { return null; } if (!StringUtil.isEmpty(nameSpace)) { //key实际上是原生key加上命名空间前缀 key = joinNameSpaceAndKey(nameSpace, key); } if (isExpired(key)) return null; return caches.get(key); } /** * 判断是否死亡(实际死亡/逻辑死亡) *

逻辑死亡: 时间上看已经过期, 但是由于秦楚线程阻塞, 还没有来得及清除

* * @param nskey joinNameSpaceAndKey * @return */ private boolean isExpired(String nskey) { //防止延迟清除任务阻塞, 导致生存时间误差, 这里实现逻辑清除 Headsman headsman = headsmans.get(nskey); if (headsman != null) { //死亡时间 <= 当前时间, 应该设置为逻辑死亡, 返回null if (headsman.triggerTime <= System.nanoTime()) { // ?已经逻辑死亡, 但实际没有死亡的值是否要在这里清除呢? return true; } else { return false; } } // else: 物理死亡 return !caches.containsKey(nskey); } private String joinNameSpaceAndKey(String nameSpace, String key) { if (StringUtils.isBlank(key)) { log.debug("`key` is NULL, return"); return null; } if (!StringUtil.isEmpty(nameSpace)) { return nameSpace + NAMESPACE_KEY_JOIN + key; } return key; } /** * 增加OR更新缓存 * * @param key * @param value */ public boolean put(@Nullable String nameSpace, @NotNull String key, T value) { key = joinNameSpaceAndKey(nameSpace, key); if (key == null) { return false; } caches.put(key, value); return true; } /** * 更加OR更新缓存(可设置生存时间TTL,Time To Live) *
     *     Note: 注意避免并发对同一个 key 设置TTL, 结果比较随机, 谁最后执行, 就以谁的TTL设置为准.
     * 
* * @param key * @param value * @param ttl * @param unit */ @Deprecated public void put(@Nullable String nameSpace, String key, T value, long ttl, TimeUnit unit) { String nsKey = joinNameSpaceAndKey(nameSpace, key); put(null, nsKey, value); long triggerTime = System.nanoTime() + unit.toNanos(ttl); //若此 key 已经有对应的杀死任务, 需要替换掉, 更新生存时间, 以最新的为准 Headsman headsman = headsmans.get(nsKey); if (headsman == null) { //增加缓存 headsman = new Headsman(); headsmans.put(nsKey, headsman); headsman.task = new Runnable() { @Override public void run() { //指定时间后清除此 nsKey T rm = caches.remove(nsKey); log.trace("cache expired, key: {}, value: {}", nsKey, rm); } }; } else { //更新缓存 //对于已经有设置过缓存的 nsKey, 任务用已有的, scheduledFuture 先取消旧的, 在new新的 /*如果任务运行之前调用了该方法,那么任务就不会被运行; 如果任务已经完成或者已经被取消,那么该方法方法不起作用; 如果任务正在运行,并且 cancel 传入参数为 true,那么便会去终止与 Future 关联的任务。*/ headsman.scheduledFuture.cancel(true); } headsman.scheduledFuture = new FutureTask(headsman.task, null); //时间转换成毫秒 headsman.triggerTime = triggerTime; scheduler.schedule(headsman.scheduledFuture, ttl, unit); } public void put(@Nullable String nameSpace, String key, T value, Duration ttl) { String nsKey = joinNameSpaceAndKey(nameSpace, key); put(null, nsKey, value); long triggerTime = System.nanoTime() + ttl.toNanos(); //若此 key 已经有对应的杀死任务, 需要替换掉, 更新生存时间, 以最新的为准 Headsman headsman = headsmans.get(nsKey); if (headsman == null) { //增加缓存 headsman = new Headsman(); headsmans.put(nsKey, headsman); headsman.task = new Runnable() { @Override public void run() { //指定时间后清除此 nsKey T rm = caches.remove(nsKey); log.trace("cache expired, key: {}, value: {}", nsKey, rm); } }; } else { //更新缓存 //对于已经有设置过缓存的 nsKey, 任务用已有的, scheduledFuture 先取消旧的, 在new新的 /*如果任务运行之前调用了该方法,那么任务就不会被运行; 如果任务已经完成或者已经被取消,那么该方法方法不起作用; 如果任务正在运行,并且 cancel 传入参数为 true,那么便会去终止与 Future 关联的任务。*/ headsman.scheduledFuture.cancel(true); } headsman.scheduledFuture = new FutureTask(headsman.task, null); //时间转换成毫秒 headsman.triggerTime = triggerTime; scheduler.schedule(headsman.scheduledFuture, ttl.toMillis(), TimeUnit.MILLISECONDS); } public T evictCache(String nameSpace, String key) { if (key == null) { return null; } String nsKey = joinNameSpaceAndKey(nameSpace, key); Headsman hsm = headsmans.remove(nsKey); if (hsm != null) hsm.scheduledFuture.cancel(true); return caches.remove(nsKey); } /** * 清空当前命名空间下的所有value */ public void evictCache(String nameSpace) { String prefix = nameSpace + NAMESPACE_KEY_JOIN; caches.forEach((k, v) -> { if (k.startsWith(prefix)) { evictCache(null, k); } }); } public void evictAllCache() { caches.clear(); } /** * 暴露指定命名空间下所有的缓存 * * @param nameSpace * @return */ public List getByNameSpace(String nameSpace) { String prefix = nameSpace + NAMESPACE_KEY_JOIN; List nsVals = new ArrayList<>(); caches.forEach((k, v) -> { if (k.startsWith(prefix)) { if (!isExpired(k)) { nsVals.add(v); } } }); return nsVals; } class Headsman { /** * 死亡时间, 纳秒值 */ public long triggerTime; /** * 对key更新缓存时, 旧的已有的会取消, 重新设置新的. */ public FutureTask scheduledFuture; /** * 对于每个 key, task是单例的 */ public Runnable task; } }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/62131.html

相关文章

  • 本地缓存工具

    摘要:时间转换成毫秒清空当前命名空间下的所有暴露指定命名空间下所有的缓存死亡时间纳秒值对更新缓存时旧的已有的会取消重新设置新的对于每个是单例的 package com.common.helper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.jetbrai...

    邹立鹏 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<