开源项目halo个人博客源码学习--halo的缓存模式(三)

halo的缓存模式

为什么要把halo的缓存模式拿出来说说呢?虽然halo个人博客是一个比较小的项目,但是缓存也必不可少。halo的缓存用到了作者自己编写的缓存组件,也帮忙配置了level-db cache 和redis cluster cache。深入了解halo的缓存模式有助于我自己对于缓存组件的运行与编写,增强对cache的了解!

一、包结构

在这里插入图片描述

二、自定义注解和AOP

cache包下有一个lock包,lock包中定义了@CacheLock与@CacheParam两个注解。我们进入CacheLockItercepter拦截器来看看它到底做了什么?

@Slf4j
@Aspect
@Configuration
public class CacheLockInterceptor {
    ...
}

首先,看到@Aspect@Configuration两个注解我们可以判断这是一个切面类,那么我们看看这个切面到底增强了什么!

 @Around("@annotation(run.halo.app.cache.lock.CacheLock)")
    public Object interceptCacheLock(ProceedingJoinPoint joinPoint) throws Throwable {
        // Get method signature
        //获取被注解的方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        log.debug("Starting locking: [{}]", methodSignature.toString());

        // Get cache lock
        //获取CacheLock注解的实例
        CacheLock cacheLock = methodSignature.getMethod().getAnnotation(CacheLock.class);

        // Build cache lock key
        //生成缓存的key
        String cacheLockKey = buildCacheLockKey(cacheLock, joinPoint);

        log.debug("Built lock key: [{}]", cacheLockKey);


        try {
            // Get from cache
            Boolean cacheResult = cacheStore.putIfAbsent(cacheLockKey, CACHE_LOCK_VALUE, cacheLock.expired(), cacheLock.timeUnit());

            if (cacheResult == null) {
                throw new ServiceException("Unknown reason of cache " + cacheLockKey).setErrorData(cacheLockKey);
            }

            if (!cacheResult) {
                throw new FrequentAccessException("访问过于频繁,请稍后再试!").setErrorData(cacheLockKey);
            }

            // Proceed the method
            return joinPoint.proceed();
        } finally {
            // Delete the cache
            if (cacheLock.autoDelete()) {
                cacheStore.delete(cacheLockKey);
                log.debug("Deleted the cache lock: [{}]", cacheLock);
            }
        }
    }

很明显这是对被注解方法的增强,我们首先通过反射获取方法对象和注解的对象按照buildCacheLockKey(cacheLock, joinPoint)方法生成缓存的key,并将缓存信息存储到一个缓存空间中。

那么你可能会问,缓存的空间是怎么实现的?我猜测是一个map,因为他用了put方法。不相信?那我们来看看他的缓存实现。

三、缓存容器的实现

在这里插入图片描述

顶层定义了CacheStore接口,一层层实现,三个实现类都继承了AbstractStringCacheStore的抽象类,这个抽象类被halo作为解耦的一种方式。在HaloConfiguration中向容器中注入的是AbstractStringCacheStore,而并不是某个具体实现类,给与了用户自定义的选择。这种模式是值得我们学习的!!!

    @Bean
    @ConditionalOnMissingBean
    public AbstractStringCacheStore stringCacheStore() {
        AbstractStringCacheStore stringCacheStore;
        switch (haloProperties.getCache()) {
            case "level":
                stringCacheStore = new LevelCacheStore();
                break;
            case "redis":
                stringCacheStore = new RedisCacheStore(this.haloProperties);
                break;
            case "memory":
            default:
                //memory or default
                //默认的缓存实现
                stringCacheStore = new InMemoryCacheStore();
                break;

        }
        log.info("halo cache store load impl : [{}]", stringCacheStore.getClass());
        return stringCacheStore;

    }

从配置类我们可以看出默认情况下,我们采取的是 InMemoryCacheStore()的实现。这个类中定义了缓存过期的时间,缓存的容器ConcurrentHashMap<>,定时清理内存,保证线程安全的同步锁属性,以及如何增加或删除单个缓存的方法。

1、成员变量

/**
     * Cleaner schedule period. (ms)
     */
//过期时间
    private final static long PERIOD = 60 * 1000;

    /**
     * Cache container.
     */
//缓存容器
    private final static ConcurrentHashMap<String, CacheWrapper<String>> CACHE_CONTAINER = new ConcurrentHashMap<>();

    private final Timer timer;

    /**
     * Lock.
     */
//同步锁
    private final Lock lock = new ReentrantLock();

2、构造方法与成员方法

 public InMemoryCacheStore() {
        // Run a cache store cleaner
        timer = new Timer();
        timer.scheduleAtFixedRate(new CacheExpiryCleaner(), 0, PERIOD);
    }

    @Override
    Optional<CacheWrapper<String>> getInternal(String key) {
        return Optional.ofNullable(CACHE_CONTAINER.get(key));
    }

    @Override
    void putInternal(String key, CacheWrapper<String> cacheWrapper) {
        Assert.hasText(key, "Cache key must not be blank");
        Assert.notNull(cacheWrapper, "Cache wrapper must not be null");

        // Put the cache wrapper
        CacheWrapper<String> putCacheWrapper = CACHE_CONTAINER.put(key, cacheWrapper);

        log.debug("Put [{}] cache result: [{}], original cache wrapper: [{}]", key, putCacheWrapper, cacheWrapper);
    }

    @Override
    Boolean putInternalIfAbsent(String key, CacheWrapper<String> cacheWrapper) {
        Assert.hasText(key, "Cache key must not be blank");
        Assert.notNull(cacheWrapper, "Cache wrapper must not be null");
        lock.lock();
        try {
            // Get the value before
            Optional<String> valueOptional = get(key);

            if (valueOptional.isPresent()) {
                log.warn("Failed to put the cache, because the key: [{}] has been present already", key);
                return false;
            }

            // Put the cache wrapper
            putInternal(key, cacheWrapper);
            log.debug("Put successfully");
            return true;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void delete(String key) {
        Assert.hasText(key, "Cache key must not be blank");

        CACHE_CONTAINER.remove(key);
        log.debug("Removed key: [{}]", key);
    }

    @PreDestroy
    public void preDestroy() {
        log.debug("Cancelling all timer tasks");
        timer.cancel();
        clear();
    }

    private void clear() {
        CACHE_CONTAINER.clear();
    }

    /**
     * Cache cleaner.
     *
     * @author johnniang
     * @date 03/28/19
     */
    private class CacheExpiryCleaner extends TimerTask {

        @Override
        public void run() {
            CACHE_CONTAINER.keySet().forEach(key -> {
                if (!InMemoryCacheStore.this.get(key).isPresent()) {
                    log.debug("Deleted the cache: [{}] for expiration", key);
                }
            });
        }
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值