Official account: Java Xiaokaxiu, website: Javaxks.com

Author: xiaolyuh, link: my.oschina.net/xiaolyuh/bl…

Problem Description:

By using Redis and Caffeine for caching, we found some problems.

If only redis is used for caching, we will have a large number of requests to Redis, but the data of each request is the same. If this part of the data is stored locally in the application server, then the network overhead of requesting Redis is saved, and the request speed will be much faster. But scaling horizontally with Redis is convenient. If Caffeine is used only for local cache, our application server has limited memory and it is not cost-effective to scale the application server solely for cache. So using only local caching is also very limited. Do we have an idea at this point? Use both. Hot data is placed in the local cache (level 1 cache) and non-hot data is placed in the Redis cache (level 2 cache).

Cache selection

Level 1 Cache: Caffeine is a high-performance Java cache library; The Window TinyLfu recycle strategy provides a near-optimal hit ratio. Level 2 Cache: Redis is a high-performance, highly available key-value database that supports multiple data types, supports clustering, and is deployed separately from application servers for horizontal scaling.

solution

Spring provides Cache support, the core of which is to implement the Cache and CacheManager interfaces.

Cache interface

It mainly realizes the operation of cache, such as add, delete and check.

public interface Cache { String getName(); Object getNativeCache(); <T> T get(Object key, Class<T> type); <T> T get(Object key, Callable<T> valueLoader); void put(Object key, Object value); ValueWrapper putIfAbsent(Object key, Object value); void evict(Object key); void clear(); . }Copy the code

CacheManager interface

Manage the Cache by Cache name. The core method is to fetch the Cache by Cache name.

public interface CacheManager {

 Cache getCache(String name);

 Collection<String> getCacheNames();

}
Copy the code

The LayeringCache class contains the operations for Caffeine and Redis. The LayeringCache class contains the operations for Caffeine and Redis. Write a LayeringCacheManager to manage LayeringCache.

The redis cache here uses my extended RedisCache.

  • www.jianshu.com/p/275cb4208…
  • www.jianshu.com/p/e53c1b60c…

LayeringCache

The LayeringCache class needs to integrate Caffeine and Redis, so it needs to have at least name, CaffeineCache and CustomizedRedisCache properties. A switch usedFirstCache has also been added to enable or disable level 1 caching. The LayeringCache class calls methods that operate on level 1 cache and level 2 cache respectively.

Here is a special note:

  • In the query method such as get, first to query the level 1 cache, if not to check the level 2 cache.
  • There is no order required for the PUT method, but it is recommended that the operation of level 1 caching be placed first.
  • If the deletion methods such as EVict and clear are used, the data in level-2 cache must be deleted first, and then the data in Level-2 cache must be deleted; otherwise, concurrency problems may occur.
  • Deleting the level-1 cache requires redis Pub/Sub mode, otherwise level-1 cache data of other server nodes in the cluster cannot be deleted.
  • Redis Pub/Sub (subscribe and publish) mode sends messages stateless, which may cause level 1 caches on some application servers to be unable to be deleted for network reasons. If L1 and L2 data synchronization is required, MQ can be used to do this.

Complete code:

/** * @author yuhao.wang */ public class LayeringCache extends AbstractValueAdaptingCache { Logger logger = LoggerFactory.getLogger(LayeringCache.class); /** * Cache name */ private final String name; /** * Private Boolean usedFirstCache = true; /** * private final CustomizedRedisCache redisCache; /** * Caffeine */ Private Final Caffeache Caffeinache; RedisOperations<? extends Object, ? extends Object> redisOperations; /** * @param name Cache name * @param prefix Cache prefix * @param redisOperations Operate RedisTemplate of Redis * @param expiration Redis cache expiration time * @param preloadSecondTime Redis cache automatic refresh time * @param allowNullValues Whether NULL is allowed, The default is false * @param usedFirstCache specifies whether to use level 1 cache. The default is true * @param forceRefresh specifies whether to force a refresh. Default is false * @Param Caffeine */ public LayeringCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations, long expiration, long preloadSecondTime, boolean allowNullValues, boolean usedFirstCache, boolean forceRefresh, com.github.benmanes.caffeine.cache.Cache<Object, Object> caffeineCache) { super(allowNullValues); this.name = name; this.usedFirstCache = usedFirstCache; this.redisOperations = redisOperations; this.redisCache = new CustomizedRedisCache(name, prefix, redisOperations, expiration, preloadSecondTime, forceRefresh, allowNullValues); this.caffeineCache = new CaffeineCache(name, caffeineCache, allowNullValues); } @Override public String getName() { return this.name; } @Override public Object getNativeCache() { return this; } public CustomizedRedisCache getSecondaryCache() { return this.redisCache; } public CaffeineCache getFirstCache() { return this.caffeineCache; } @Override public ValueWrapper get(Object key) { ValueWrapper wrapper = null; If (usedFirstCache) {// Query the first level cache wrapper = caffeinecache.get (key); Logger.debug (" Query level 1 cache key:{}, return :{}", key, json.tojsonString (wrapper)); } if (wrapper == null) {// Query secondary cache wrapper = rediscache.get (key); Logger.debug (" Query secondary cache key:{}, return :{}", key, json.tojsonString (wrapper)); } return wrapper; } @Override public <T> T get(Object key, Class<T> type) { T value = null; If (usedFirstCache) {if (usedFirstCache) {value = Caffeinecache. get(key, type); Logger.debug (" Query level 1 cache key:{}, return :{}", key, json.tojsonString (value)); } if (value == null) {value = rediscache. get(key, type); caffeineCache.put(key, value); Logger.debug (" Query secondary cache key:{}, return :{}", key, json.tojsonString (value)); } return value; } @Override public <T> T get(Object key, Callable<T> valueLoader) { T value = null; If (usedFirstCache) {getForSecondaryCache(k, usedFirstCache) { ValueLoader) query cache value = 2 (T) caffeineCache. GetNativeCache () get (key, k - > getForSecondaryCache (k, valueLoader)); Value = (T) getForSecondaryCache(key, valueLoader); } if (value instanceof NullValue) { return null; } return value; } @Override public void put(Object key, Object value) { if (usedFirstCache) { caffeineCache.put(key, value); } redisCache.put(key, value); } @Override public ValueWrapper putIfAbsent(Object key, Object value) { if (usedFirstCache) { caffeineCache.putIfAbsent(key, value); } return redisCache.putIfAbsent(key, value); } @override public void evict(Object key) {rediscache.evict (key); If (usedFirstCache) {// If (usedFirstCache) { Otherwise, level-1 cache data of other server nodes in the cluster cannot be deleted. Map<String, Object> message = new HashMap<>(); message.put("cacheName", name); message.put("key", key); RedisPublisher = new RedisPublisher(redisOperations, ChannelTopicEnum.REDIS_CACHE_DELETE_TOPIC.getChannelTopic()); RedisPublisher. Publisher (message); } } @Override public void clear() { redisCache.clear(); If (usedFirstCache) {// If (usedFirstCache) { Otherwise, level-1 cache data of other server nodes in the cluster cannot be deleted. Map<String, Object> message = new HashMap<>(); message.put("cacheName", name); RedisPublisher = new RedisPublisher(redisOperations, ChannelTopicEnum.REDIS_CACHE_CLEAR_TOPIC.getChannelTopic()); RedisPublisher. Publisher (message); } } @Override protected Object lookup(Object key) { Object value = null; if (usedFirstCache) { value = caffeineCache.get(key); Logger.debug (" Query level 1 cache key:{}, return :{}", key, json.tojsonString (value)); } if (value == null) { value = redisCache.get(key); Logger.debug (" Query secondary cache key:{}, return :{}", key, json.tojsonString (value)); } return value; } private <T> Object getForSecondaryCache(Object key, Callable<T> valueLoader) { T value = redisCache.get(key, valueLoader); Logger.debug (" Query secondary cache key:{}, return :{}", key, json.tojsonString (value)); return toStoreValue(value); }}Copy the code

LayeringCacheManager

Since we need to manage the cache in CacheManager, we need to define a container in CacheManager to store the cache. Create a New ConcurrentMap<String, Cache> cacheMap to store the Cache. Both getCache and getCacheNames of CacheManager operate on this cacheMap.

Map<String, FirstCacheSetting> firstCacheSettings The secondaryCacheSettings property is configured specifically for each cache, such as expiration time configuration for level 1 cache, expiration time configuration for level 2 cache, and automatic refresh time configuration. The rest of the attributes are not introduced, can directly look at the source code below.

GetCache method

This is the core method of CacheManager, around which all CacheManager operations are conducted.

@Override
public Cache getCache(String name) {
 Cache cache = this.cacheMap.get(name);
 if (cache == null && this.dynamic) {
  synchronized (this.cacheMap) {
   cache = this.cacheMap.get(name);
   if (cache == null) {
    cache = createCache(name);
    this.cacheMap.put(name, cache);
   }
  }
 }
 return cache;
}
Copy the code

From the logic we can see that this method is according to the name from the cache, if not found and dynamically create cache switch dynamic to true, createCache method is invoked dynamically create the cache.

CreateCache method

To create a LayeringCache

protected Cache createCache(String name) {

 return new LayeringCache(name, (usePrefix ? cachePrefix.prefix(name) : null), redisOperations,
   getSecondaryCacheExpirationSecondTime(name), getSecondaryCachePreloadSecondTime(name),
   isAllowNullValues(), getUsedFirstCache(name), getForceRefresh(name), createNativeCaffeineCache(name));
}
Copy the code

When creating the cache we will call getSecondaryCacheExpirationSecondTime, getSecondaryCachePreloadSecondTime and getForceRefresh method to get the second level cache expiration time, automatic brush The value of the new time and whether to force a refresh is obtained in the secondaryCacheSettings property; Call createNativeCaffeineCache method to create a level 1 cache instance of Caffeine.

CreateNativeCaffeineCache in this method will be called to read l1 getCaffeine method of dynamic configuration, and according to the configuration to create the cache, if not found the special configuration, use the default configuration, The special configuration is obtained in the firstCacheSettings property.

getCaffeine

Dynamically get level 1 cache configuration and create Caffeine objects.

private Caffeine<Object, Object> getCaffeine(String name) { if (! CollectionUtils.isEmpty(firstCacheSettings)) { FirstCacheSetting firstCacheSetting = firstCacheSettings.get(name); if (firstCacheSetting ! = null && StringUtils. IsNotBlank (firstCacheSetting getCacheSpecification ())) {/ / return to the name of the cache access level cache configuration Caffeine.from(CaffeineSpec.parse(firstCacheSetting.getCacheSpecification())); } } return this.cacheBuilder; }private Caffeine<Object, Object> getCaffeine(String name) { if (! CollectionUtils.isEmpty(firstCacheSettings)) { FirstCacheSetting firstCacheSetting = firstCacheSettings.get(name); if (firstCacheSetting ! = null && StringUtils. IsNotBlank (firstCacheSetting getCacheSpecification ())) {/ / return to the name of the cache access level cache configuration Caffeine.from(CaffeineSpec.parse(firstCacheSetting.getCacheSpecification())); } } return this.cacheBuilder; }Copy the code

SetFirstCacheSettings and setSecondaryCacheSettings

We borrowed the idea from RedisCacheManager’s setExpires(Map<String, Long> Expires) method. With setFirstCacheSettings and setSecondaryCacheSettings method for level 1 cache and the special configuration of the second level cache set value.

/** * Set the valid time and refresh time of level 1 cache according to the cache name. Public void setFirstCacheSettings(Map<String, FirstCacheSetting> firstCacheSettings) { this.firstCacheSettings = (! CollectionUtils.isEmpty(firstCacheSettings) ? new ConcurrentHashMap<>(firstCacheSettings) : null); } /** * Set the valid time and refresh time of the level 2 cache according to the cache name. Unit s * * @ param secondaryCacheSettings * / public void setSecondaryCacheSettings (Map < String, SecondaryCacheSetting> secondaryCacheSettings) { this.secondaryCacheSettings = (! CollectionUtils.isEmpty(secondaryCacheSettings) ? new ConcurrentHashMap<>(secondaryCacheSettings) : null); }Copy the code

Complete code:

/**
 * @author yuhao.wang
 */
@SuppressWarnings("rawtypes")
public class LayeringCacheManager implements CacheManager {
    // 常量
    static final int DEFAULT_EXPIRE_AFTER_WRITE = 60;
    static final int DEFAULT_INITIAL_CAPACITY = 5;
    static final int DEFAULT_MAXIMUM_SIZE = 1_000;

    private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);


    /**
     * 一级缓存配置
     */
    private Map<String, FirstCacheSetting> firstCacheSettings = null;

    /**
     * 二级缓存配置
     */
    private Map<String, SecondaryCacheSetting> secondaryCacheSettings = null;

    /**
     * 是否允许动态创建缓存,默认是true
     */
    private boolean dynamic = true;

    /**
     * 缓存值是否允许为NULL
     */
    private boolean allowNullValues = false;

    // Caffeine 属性
    /**
     * expireAfterWrite:60
     * initialCapacity:5
     * maximumSize:1_000
     */
    private Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder()
            .expireAfterWrite(DEFAULT_EXPIRE_AFTER_WRITE, TimeUnit.SECONDS)
            .initialCapacity(DEFAULT_INITIAL_CAPACITY)
            .maximumSize(DEFAULT_MAXIMUM_SIZE);

    // redis 属性
    /**
     * 操作redis的RedisTemplate
     */
    private final RedisOperations redisOperations;

    /**
     * 二级缓存使用使用前缀,默认是false,建议设置成true
     */
    private boolean usePrefix = false;
    private RedisCachePrefix cachePrefix = new DefaultRedisCachePrefix();

    /**
     * redis缓存默认时间,默认是0 永不过期
     */
    private long defaultExpiration = 0;

    public LayeringCacheManager(RedisOperations redisOperations) {
        this(redisOperations, Collections.<String>emptyList());
    }

    public LayeringCacheManager(RedisOperations redisOperations, Collection<String> cacheNames) {
        this(redisOperations, cacheNames, false);
    }

    public LayeringCacheManager(RedisOperations redisOperations, Collection<String> cacheNames, boolean allowNullValues) {
        this.allowNullValues = allowNullValues;
        this.redisOperations = redisOperations;

        setCacheNames(cacheNames);
    }

    @Override
    public Cache getCache(String name) {
        Cache cache = this.cacheMap.get(name);
        if (cache == null && this.dynamic) {
            synchronized (this.cacheMap) {
                cache = this.cacheMap.get(name);
                if (cache == null) {
                    cache = createCache(name);
                    this.cacheMap.put(name, cache);
                }
            }
        }
        return cache;
    }

    @Override
    public Collection<String> getCacheNames() {
        return Collections.unmodifiableSet(this.cacheMap.keySet());
    }

    @SuppressWarnings("unchecked")
    protected Cache createCache(String name) {
        return new LayeringCache(name, (usePrefix ? cachePrefix.prefix(name) : null), redisOperations,
                getSecondaryCacheExpirationSecondTime(name), getSecondaryCachePreloadSecondTime(name),
                isAllowNullValues(), getUsedFirstCache(name), getForceRefresh(name), createNativeCaffeineCache(name));
    }

    /**
     * Create a native Caffeine Cache instance for the specified cache name.
     *
     * @param name the name of the cache
     * @return the native Caffeine Cache instance
     */
    protected com.github.benmanes.caffeine.cache.Cache<Object, Object> createNativeCaffeineCache(String name) {
        return getCaffeine(name).build();
    }

    /**
     * 使用该CacheManager的当前状态重新创建已知的缓存。
     */
    private void refreshKnownCaches() {
        for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
            entry.setValue(createCache(entry.getKey()));
        }
    }

    /**
     * 在初始化CacheManager的时候初始化一组缓存。
     * 使用这个方法会在CacheManager初始化的时候就会将一组缓存初始化好,并且在运行时不会再去创建更多的缓存。
     * 使用空的Collection或者重新在配置里面指定dynamic后,就可重新在运行时动态的来创建缓存。
     *
     * @param cacheNames
     */
    public void setCacheNames(Collection<String> cacheNames) {
        if (cacheNames != null) {
            for (String name : cacheNames) {
                this.cacheMap.put(name, createCache(name));
            }
            this.dynamic = cacheNames.isEmpty();
        }
    }

    /**
     * 设置是否允许Cache的值为null
     *
     * @param allowNullValues
     */
    public void setAllowNullValues(boolean allowNullValues) {
        if (this.allowNullValues != allowNullValues) {
            this.allowNullValues = allowNullValues;
            refreshKnownCaches();
        }
    }

    /**
     * 获取是否允许Cache的值为null
     *
     * @return
     */
    public boolean isAllowNullValues() {
        return this.allowNullValues;
    }

    /**
     * 在生成key的时候是否是否使用缓存名称来作为缓存前缀。默认是false,但是建议设置成true。
     *
     * @param usePrefix
     */
    public void setUsePrefix(boolean usePrefix) {
        this.usePrefix = usePrefix;
    }

    protected boolean isUsePrefix() {
        return usePrefix;
    }

    /**
     * 设置redis默认的过期时间(单位:秒)
     *
     * @param defaultExpireTime
     */
    public void setSecondaryCacheDefaultExpiration(long defaultExpireTime) {
        this.defaultExpiration = defaultExpireTime;
    }


    /**
     * 根据缓存名称设置一级缓存的有效时间和刷新时间,单位秒
     *
     * @param firstCacheSettings
     */
    public void setFirstCacheSettings(Map<String, FirstCacheSetting> firstCacheSettings) {
        this.firstCacheSettings = (!CollectionUtils.isEmpty(firstCacheSettings) ? new ConcurrentHashMap<>(firstCacheSettings) : null);
    }

    /**
     * 根据缓存名称设置二级缓存的有效时间和刷新时间,单位秒
     *
     * @param secondaryCacheSettings
     */
    public void setSecondaryCacheSettings(Map<String, SecondaryCacheSetting> secondaryCacheSettings) {
        this.secondaryCacheSettings = (!CollectionUtils.isEmpty(secondaryCacheSettings) ? new ConcurrentHashMap<>(secondaryCacheSettings) : null);
    }


    /**
     * 获取过期时间
     *
     * @return
     */
    public long getSecondaryCacheExpirationSecondTime(String name) {
        if (StringUtils.isEmpty(name)) {
            return 0;
        }

        SecondaryCacheSetting secondaryCacheSetting = null;
        if (!CollectionUtils.isEmpty(secondaryCacheSettings)) {
            secondaryCacheSetting = secondaryCacheSettings.get(name);
        }
        Long expiration = secondaryCacheSetting != null ? secondaryCacheSetting.getExpirationSecondTime() : defaultExpiration;
        return expiration < 0 ? 0 : expiration;
    }

    /**
     * 获取自动刷新时间
     *
     * @return
     */
    private long getSecondaryCachePreloadSecondTime(String name) {
        // 自动刷新时间,默认是0
        SecondaryCacheSetting secondaryCacheSetting = null;
        if (!CollectionUtils.isEmpty(secondaryCacheSettings)) {
            secondaryCacheSetting = secondaryCacheSettings.get(name);
        }
        Long preloadSecondTime = secondaryCacheSetting != null ? secondaryCacheSetting.getPreloadSecondTime() : 0;
        return preloadSecondTime < 0 ? 0 : preloadSecondTime;
    }

    /**
     * 获取是否使用二级缓存,默认是true
     */
    public boolean getUsedFirstCache(String name) {
        SecondaryCacheSetting secondaryCacheSetting = null;
        if (!CollectionUtils.isEmpty(secondaryCacheSettings)) {
            secondaryCacheSetting = secondaryCacheSettings.get(name);
        }

        return secondaryCacheSetting != null ? secondaryCacheSetting.getUsedFirstCache() : true;
    }

    /**
     * 获取是否强制刷新(走数据库),默认是false
     */
    public boolean getForceRefresh(String name) {
        SecondaryCacheSetting secondaryCacheSetting = null;
        if (!CollectionUtils.isEmpty(secondaryCacheSettings)) {
            secondaryCacheSetting = secondaryCacheSettings.get(name);
        }

        return secondaryCacheSetting != null ? secondaryCacheSetting.getForceRefresh() : false;
    }

    public void setCaffeineSpec(CaffeineSpec caffeineSpec) {
        Caffeine<Object, Object> cacheBuilder = Caffeine.from(caffeineSpec);
        if (!ObjectUtils.nullSafeEquals(this.cacheBuilder, cacheBuilder)) {
            this.cacheBuilder = cacheBuilder;
            refreshKnownCaches();
        }
    }

    private Caffeine<Object, Object> getCaffeine(String name) {
        if (!CollectionUtils.isEmpty(firstCacheSettings)) {
            FirstCacheSetting firstCacheSetting = firstCacheSettings.get(name);
            if (firstCacheSetting != null && StringUtils.isNotBlank(firstCacheSetting.getCacheSpecification())) {
                // 根据缓存名称获取一级缓存配置
                return Caffeine.from(CaffeineSpec.parse(firstCacheSetting.getCacheSpecification()));
            }
        }

        return this.cacheBuilder;
    }
}
Copy the code

FirstCacheSettings

Level 1 cache configuration class

Public class firstCacheesetting {/** * {@Link Caffeinespecespecl #configure(String, String)} * @param cacheSpecification */ public FirstCacheSetting(String cacheSpecification) { this.cacheSpecification = cacheSpecification; } private String cacheSpecification; public String getCacheSpecification() { return cacheSpecification; }}Copy the code

SecondaryCacheSetting

Special configuration classes for level 2 caching

/** * @author yuhao. Wang */ public class secondaryrequetting {/** * @param expirationSecondTime * @param preloadSecondTime Sets the automatic refresh time of the Redis cache. Public SecondaryCacheSetting(Long expirationSecondTime, long preloadSecondTime) { this.expirationSecondTime = expirationSecondTime; this.preloadSecondTime = preloadSecondTime; } /** * @param usedFirstCache enables level 1 cache, default true * @param expirationSecondTime sets redis cache valid time, * @param preloadSecondTime Sets the automatic refresh time of the Redis cache. Public secondaryRequetting (Boolean type first Cache, long expirationSecondTime, long preloadSecondTime) { this.expirationSecondTime = expirationSecondTime; this.preloadSecondTime = preloadSecondTime; this.usedFirstCache = usedFirstCache; } /** * @param preloadSecondTime Sets the expiration time of the redis cache. * @param forceRefresh whether to use forceRefresh (go database), Default false */ public SecondaryCacheSetting(Long expirationSecondTime, Long preloadSecondTime, boolean forceRefresh) { this.expirationSecondTime = expirationSecondTime; this.preloadSecondTime = preloadSecondTime; this.forceRefresh = forceRefresh; } /** * @param preloadSecondTime Sets the expiration time of the redis cache. Unit of second * @param usedFirstCache whether to enable level 1 cache, default true * @param forceRefresh whether to use forceRefresh (go to database), Default false */ public secondaryEtting (Long expirationSecondTime, Long preloadSecondTime, Boolean usedFirstCache, boolean forceRefresh) { this.expirationSecondTime = expirationSecondTime; this.preloadSecondTime = preloadSecondTime; this.usedFirstCache = usedFirstCache; this.forceRefresh = forceRefresh; } / / private long expirationSecondTime; Private Long preloadSecondTime = 0; private Long preloadSecondTime = 0; /** * Private Boolean usedFirstCache = true; */ private Boolean forceRefresh = false; public long getPreloadSecondTime() { return preloadSecondTime; } public long getExpirationSecondTime() { return expirationSecondTime; } public boolean getUsedFirstCache() { return usedFirstCache; } public boolean getForceRefresh() { return forceRefresh; }}Copy the code

use

We’ve defined LayeringCacheManager and LayeringCache above and now we’re going to use it.

Create a new configuration class called CacheConfig, where you specify a LayeringCacheManager Bean. My cache will kick in. The complete code is as follows:

/** * @author yuhao.wang */ @Configuration @EnableConfigurationProperties(CacheProperties.class) public class CacheConfig {/ / redis cache effectively @ Value is seconds (" ${redis. Default. Expiration: 3600} ") private long redisDefaultExpiration; / / query cache valid time @ Value (" ${select. Cache. The timeout: 1800} ") private long selectCacheTimeout; / / query cache refresh automatically time @ Value (" ${select. Cache. Refresh: 1790} ") private long selectCacheRefresh; @Autowired private CacheProperties cacheProperties; @Bean @Primary public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) { LayeringCacheManager layeringCacheManager = new LayeringCacheManager(redisTemplate); // Caffeine set setFirstCacheConfig(layeringCacheManager); // Redis cache set setSecondaryCacheConfig(layeringCacheManager); return layeringCacheManager; } private void setFirstCacheConfig(LayeringCacheManager LayeringCacheManager) {// Set the default Level-1 Cache configuration. String Specification = this.cacheProperties.getCaffeine().getSpec(); if (StringUtils.hasText(specification)) { layeringCacheManager.setCaffeineSpec(CaffeineSpec.parse(specification)); } // Set expiration time and automatic refresh time for each level of cache Map<String, FirstCacheSetting> firstCacheSettings = new HashMap<>(); firstCacheSettings.put("people", new FirstCacheSetting("initialCapacity=5,maximumSize=500,expireAfterWrite=10s")); firstCacheSettings.put("people1", new FirstCacheSetting("initialCapacity=5,maximumSize=50,expireAfterAccess=10s")); layeringCacheManager.setFirstCacheSettings(firstCacheSettings); } private void setSecondaryCacheConfig(LayeringCacheManager LayeringCacheManager) {// Set the cache name (value attribute) as the redis cache prefix layeringCacheManager.setUsePrefix(true); / / here you can set a default expiration time unit is seconds layeringCacheManager setSecondaryCacheDefaultExpiration (redisDefaultExpiration); Map<String, SecondaryCacheSetting> secondaryCacheSettings = new HashMap<>(); secondaryCacheSettings.put("people", new SecondaryCacheSetting(selectCacheTimeout, selectCacheRefresh)); secondaryCacheSettings.put("people1", new SecondaryCacheSetting(selectCacheTimeout, selectCacheRefresh, true)); secondaryCacheSettings.put("people2", new SecondaryCacheSetting(false, selectCacheTimeout, selectCacheRefresh)); secondaryCacheSettings.put("people3", new SecondaryCacheSetting(selectCacheTimeout, selectCacheRefresh, false, true)); layeringCacheManager.setSecondaryCacheSettings(secondaryCacheSettings); } /** * display declaration cache key generator ** @return */ @bean public KeyGenerator KeyGenerator() {return new SimpleKeyGenerator(); }}Copy the code

When you specify a Bean in cacheManager, We by calling LayeringCacheManager setFirstCacheSettings and setSecondaryCacheSettings method for cache cache Settings level special configuration and the second level cache.

All that remains is to annotate the Service method as follows:

@Override @Cacheable(value = "people1", key = "#person.id", sync = true)//3 public Person findOne1(Person person, String a, String[] b, List<Long> c) { Person p = personRepository.findOne(person.getId()); Logger. info(" for ID, key :" + p.getid () + "data cached "); return p; }Copy the code

You are advised to set the sync attribute of @cacheable to true.

test

Finally, through jMeter test, 50 threads, using multi-level cache, the performance of more than 2 times than only using Redis level cache, only using Redis throughput at about 1243, using multi-level cache at about 2639.

The source code

Github.com/wyh-spring-…