使用EhCache和Redis搭建二级缓存

雪域幽狐 2018-01-25 10:42 阅读:17650


在普通应用中,要么使用本地缓存(如EhCache)或直接使用缓存服务(远程缓存,如Redis)。
    在较高并发下,需要把本地缓存和远程缓存结合起来使用。期望达到效果是:先从本地缓存读取,如果有,直接返回;如果本地没有,从远程缓存读取,远程缓存有,把值写到本地,再返回;如果远程缓存也没有,就需要查数据库了。从数据库查到数据后,再把结果放入到缓存。
    Spring提供了一个org.springframework.cache.support.CompositeCacheManager,可以实现多个缓存依次查找,只要查找到就返回。但是在本地缓存没有,远程缓存有的情况下,每次都需要从远程缓存获取,不符合期望。
    因此需要自己实现org.springframework.cache.CacheManager和org.springframework.cache.Cache来达到目标,本文以EhCache为本地缓存,Redis为远程缓存为例来进行说明。
    首先定义Exception,根据业务需要,定义CacheManager,该CacheManager需要从org.springframework.cache.CacheManager继承,如果不添加方法,该接口可以去掉。
package com.nowfox.commons.cache;

/**
 * 
 * Title: CacheManager

 * Description: 缓存管理器泛型接口

 * Copyright: Copyright (c) 2017

 * 
 * @version 1.0
 */
public interface CacheManager extends org.springframework.cache.CacheManager {

    /**
     * 创建缓存
     * 
     * @param name
     * @param keyType
     * @param valueType
     * @return
     * @throws CacheException
     */
    public <K, V> Cache<K, V> createCache(String name, Class<K> keyType, Class<V> valueType) throws CacheException;

    /**
     * 创建缓存
     * 
     * @param name
     * @param keyType
     * @param valueType
     * @param expireTimeOut
     *            失效时长,单位:秒
     * @return
     * @throws CacheException
     */
    public <K, V> Cache<K, V> createCache(String name, Class<K> keyType, Class<V> valueType, long expireTimeOut)
            throws CacheException;
}

    接下来定义Cache。
package com.nowfox.commons.cache;

/**
 * 
 * Title: Cache

 * Description: 缓存相关操作接口

 * Copyright: Copyright (c) 2017

 * 
 * @version 1.0
 */
public interface Cache<K, V> extends org.springframework.cache.Cache{
    /**
     * 根据key移除缓存中对象,spring中定义为evict
     * 
     * @param key
     * @throws CacheException
     */
    public void remove(K key) throws CacheException;
}

    编写Cache抽象类,实现EhCache和Redis Cache公用部分。
package com.nowfox.commons.cache;

import java.util.concurrent.Callable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.support.SimpleValueWrapper;

public abstract class AbstractCache<K, V> implements Cache<K, V> {
    protected final Logger logger = LoggerFactory.getLogger(getClass());
    protected String name;

    @Override
    public String getName() {
        return name;
    }

    @SuppressWarnings("unchecked")
    @Override
    public ValueWrapper get(Object key) {
        if (key == null) {
            return null;
        }
        Object value = lookup((K) key);
        return toValueWrapper(value);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T get(Object key, Class<T> type) {
        if (key == null) {
            return null;
        }
        Object value = lookup((K) key);
        if (value != null && type != null && !type.isInstance(value)) {
            throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value);
        }
        return (T) value;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T get(Object key, Callable<T> valueLoader) {
        // TODO 待完善
        Object value = lookup((K) key);
        return (T) value;
    }

    @SuppressWarnings("unchecked")
    @Override
    public void evict(Object key) {
        remove((K) key);
    }

    public abstract V lookup(K key);

    protected ValueWrapper toValueWrapper(Object value) {
        return (value != null ? new SimpleValueWrapper(value) : null);
    }
}

    EhCacheCacheManager的实现
package com.nowfox.commons.cache.ehcache;

import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.config.units.EntryUnit;
import org.ehcache.expiry.Duration;
import org.ehcache.expiry.Expirations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.nowfox.commons.cache.Cache;
import com.nowfox.commons.cache.CacheException;
import com.nowfox.commons.cache.CacheManager;

public class EhCacheCacheManager implements CacheManager {
    private static final Logger logger = LoggerFactory.getLogger(EhCacheCacheManager.class);
    @SuppressWarnings("rawtypes")
    private final ConcurrentMap<String, Cache> map = new ConcurrentHashMap<String, Cache>();

    private long expireTimeOut = 600;
    private org.ehcache.CacheManager localCacheManager;
    private CacheManager remoteCacheManager;

    public EhCacheCacheManager() {
        localCacheManager = CacheManagerBuilder.newCacheManagerBuilder().build(true);
    }

    public void setExpireTimeOut(long expireTimeOut) {
        this.expireTimeOut = expireTimeOut;
    }

    public void setRemoteCacheManager(CacheManager remoteCacheManager) {
        this.remoteCacheManager = remoteCacheManager;
    }

    @Override
    public <K, V> Cache<K, V> createCache(String name, Class<K> keyType, Class<V> valueType) throws CacheException {
        return createCache(name, keyType, valueType, expireTimeOut);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <K, V> Cache<K, V> createCache(String name, Class<K> keyType, Class<V> valueType, long expireTimeOut)
            throws CacheException {
        //根据自己需要修改config
        CacheConfigurationBuilder<K, V> cacheConfig = CacheConfigurationBuilder
                .newCacheConfigurationBuilder(keyType, valueType,
                        ResourcePoolsBuilder.newResourcePoolsBuilder().heap(100, EntryUnit.ENTRIES))
                .withDispatcherConcurrency(4)
                .withExpiry(Expirations.timeToLiveExpiration(Duration.of(expireTimeOut, TimeUnit.SECONDS)));
        org.ehcache.Cache<K, V> localCache = localCacheManager.createCache(name, cacheConfig);
        Cache<K, V> cache = map.get(name);
        if (cache == null) {
            Cache<K, V> remoteCache = null;
            if (remoteCacheManager != null) {
                remoteCache = (Cache<K, V>) remoteCacheManager.createCache(name, keyType, valueType, expireTimeOut);
            }
            cache = new EhCacheCache<K, V>(name, localCache, remoteCache);
            map.put(name, cache);
            if (logger.isInfoEnabled()) {
                logger.info("创建本地缓存:{}", name);
            }
        }
        return cache;
    }

    @Override
    public Cache<?, ?> getCache(String name) {
        return map.get(name);
    }

    @Override
    public Collection<String> getCacheNames() {
        return map.keySet();
    }
}

    以下是EhCacheCache的实现,主要关键为lookup方法
package com.nowfox.commons.cache.ehcache;

import com.nowfox.commons.cache.AbstractCache;
import com.nowfox.commons.cache.Cache;
import com.nowfox.commons.cache.CacheException;

public class EhCacheCache<K, V> extends AbstractCache<K, V> {
    private org.ehcache.Cache<K, V> localCache;
    private Cache<K, V> remoteCache;

    public EhCacheCache(String name, org.ehcache.Cache<K, V> localCache, Cache<K, V> remoteCache) {
        this.name = name;
        this.localCache = localCache;
        this.remoteCache = remoteCache;
    }

    @Override
    public void remove(K key) throws CacheException {
        localCache.remove(key);
        if (remoteCache != null) {
            remoteCache.remove(key);
        }
    }

    @Override
    public void clear() throws CacheException {
        localCache.clear();
    }

    @Override
    public Object getNativeCache() {
        return localCache;
    }

    @SuppressWarnings("unchecked")
    @Override
    public void put(Object key, Object value) {
        if (key != null && value != null) {
            localCache.put((K) key, (V) value);
            if (remoteCache != null) {
                remoteCache.put(key, value);//根据情况使用异步
            }
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public ValueWrapper putIfAbsent(Object key, Object value) {
        if (value == null) {
            return get(key);
        }
        Object existingValue = localCache.putIfAbsent((K) key, (V) value);
        if (remoteCache != null) {
            remoteCache.putIfAbsent(key, existingValue);
        }
        return toValueWrapper(existingValue);
    }

    /**
     * 先查找本地,没有再查找远程缓存,如果有,写到本地缓存
     * 
     * @param key
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public V lookup(K key) {
        V object = localCache.get(key);
        if (object != null) {
            return object;
        }
        if (remoteCache == null) {
            return null;
        }
        ValueWrapper remoteValueWrapper = remoteCache.get(key);
        if (remoteValueWrapper == null) {
            return null;
        } else {
            localCache.put((K) key, (V) remoteValueWrapper.get());
            return (V) remoteValueWrapper.get();
        }
    }
}

    XML的缓存配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:cache="http://www.springframework.org/schema/cache"
    xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.2.xsd">
    <cache:annotation-driven cache-manager="localCacheManager"
        proxy-target-class="true" />

    <bean id="remoteCacheManager" class="com.nowfox.commons.cache.redis.RedisCacheManager">
        <property name="namespace" value="agent"></property>
        <property name="template" ref="redisTemplate"></property>
    </bean>
    <bean id="localCacheManager" class="com.nowfox.commons.cache.ehcache.EhCacheCacheManager">
        <property name="expireTimeOut" value="600"></property>
        <property name="remoteCacheManager" ref="remoteCacheManager"></property>
    </bean>
</beans>



    在使用时,EhCache必需先创建对应的Cache。
@Configuration
public class CacheConfig {
    @Autowired
    private CacheManager localCacheManager;
    @Autowired
    private CacheManager remoteCacheManager;

    @PostConstruct
    public void init() {
        localCacheManager.createCache("agent", String.class, AgentVO.class);
    }
}

    在具体的agentService实现类中,结合@Cacheable注解,即可实现本地缓存->远程缓存->数据库的读取方式,在加上@Cacheable注解后,如果第一次是从数据库获取的数据,可自动把缓存放入到本地和远程缓存。后续再获取时,会自动从本地缓存获取。
    本文使用的EhCache为3.4.0,pom.xml节选如下
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.8.2</version>
        </dependency>
        <dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>3.4.0</version>
        </dependency>

0条评论

登陆后可评论