编程知识 cdmana.com

The thief is fierce, the spring boot cache system of the hand, the performance is balanced!

 

 

 

  • One 、 General cache interface

  • Two 、 Local cache

  • 3、 ... and 、 Distributed cache

  • Four 、 cache “ In time ” Overdue problem

  • 5、 ... and 、 Second level cache

 

Caching is one of the most direct and effective means to improve system performance . In my opinion, using cache is the basic quality of good programmers .

This paper combines with practical development experience , Start with simple concepts, principles and code , Step by step to build a simple secondary cache system .

One 、 General cache interface

1、 Cache basic algorithm

(1)、FIFO(First In First Out), fifo , and OS Inside FIFO The same way of thinking , If a data first enters the cache , When the cache is full , The first data in the cache should be removed .(2)、LFU(Least Frequently Used), The least used , If a data in the last period of time use few times , So the possibility of being used in the future is very small .(3)、LRU(Least Recently Used), Recently at least use , If a data has not been accessed in a recent period of time , So in the future it's very unlikely to be visited . in other words , When the limited space is full of data , Data that has not been accessed for the longest time should be removed .

2、 Interface definition

Simply define the cache interface , It can be abstracted as follows :

 

package com.power.demo.cache.contract;

import java.util.function.Function;

/**
 *  Cache provider interface 
 **/
public interface CacheProviderService {

    /**
     *  The query cache 
     *
     * @param key  Cache key   Do not empty 
     **/
    <T extends Object> T get(String key);

    /**
     *  The query cache 
     *
     * @param key       Cache key   Do not empty 
     * @param function  If there is no cache , Call the callable Function returns the object   May be empty 
     **/
    <T extends Object> T get(String key, Function<String, T> function);

    /**
     *  The query cache 
     *
     * @param key       Cache key   Do not empty 
     * @param function  If there is no cache , Call the callable Function returns the object   May be empty 
     * @param funcParm function Function call parameters 
     **/
    <T extends Object, M extends Object> T get(String key, Function<M, T> functionM funcParm);

    /**
     *  The query cache 
     *
     * @param key         Cache key   Do not empty 
     * @param function    If there is no cache , Call the callable Function returns the object   May be empty 
     * @param expireTime  Expiration time ( Company : millisecond )  May be empty 
     **/
    <T extends Object> T get(String key, Function<String, T> functionLong expireTime);

    /**
     *  The query cache 
     *
     * @param key         Cache key   Do not empty 
     * @param function    If there is no cache , Call the callable Function returns the object   May be empty 
     * @param funcParm   function Function call parameters 
     * @param expireTime  Expiration time ( Company : millisecond )  May be empty 
     **/
    <T extends Object, M extends Object> T get(String key, Function<M, T> functionM funcParmLong expireTime);

    /**
     *  Set cache key value 
     *
     * @param key  Cache key   Do not empty 
     * @param obj  Cache value   Do not empty 
     **/
    <T extends Objectvoid set(String key, T obj);

    /**
     *  Set cache key value 
     *
     * @param key         Cache key   Do not empty 
     * @param obj         Cache value   Do not empty 
     * @param expireTime  Expiration time ( Company : millisecond )  May be empty 
     **/
    <T extends Objectvoid set(String key, T obj, Long expireTime);

    /**
     *  Remove the cache 
     *
     * @param key  Cache key   Do not empty 
     **/
    void remove(String key);

    /**
     *  Is there a cache 
     *
     * @param key  Cache key   Do not empty 
     **/
    boolean contains(String key);
}

Be careful , What is listed here are just common cache function interfaces , Some statistical interfaces used in special scenarios 、 Distributed lock 、 Self increasing ( reduce ) And other functions are not in the scope of discussion .

Get Related methods , Note the case of multiple parameters , Cache interface inside the human Function, This is a Java8 The functional interface provided , Although the number of input parameters supported is limited ( You will miss it very much here .NET Under the Func entrust ), But only for Java In this language , This is really a big step forward ^_^.

Interface defined , The next step is to implement the cache provider . According to the storage type , This article simply implements the two most commonly used cache providers : Local cache and distributed cache .

Two 、 Local cache

Local cache , That is to say JVM Level cache ( Local caching can be thought of as a direct intra process communication call , The distributed cache needs to make cross process communication calls through the network ), There are many ways to implement it , Like direct use Hashtable、ConcurrentHashMap And other inherently thread safe collections as cache containers , Or use some mature open source components , Such as EhCache、Guava Cache etc. . In this paper, we choose to start with simple Guava cache .

1、 What is? Guava

Guava, In short, it is a development class library , And it's a very rich and powerful development kit , Claim to be able to use Java Language is more enjoyable , It mainly includes basic tool class library and interface 、 cache 、 Publish subscribe style event bus, etc . In actual development , The most I use is collections 、 Cache and common type help classes , A lot of people praise this library .

2、 Add dependency

 

     

<dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
        </dependency>

3、 Implementation interface

 

package com.power.demo.cache.impl;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Maps;
import com.power.demo.cache.contract.CacheProviderService;
import com.power.demo.common.AppConst;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;

import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;

/*
 *  Local cache provider service  (Guava Cache)
 * */
@Configuration
@ComponentScan(basePackages = AppConst.BASE_PACKAGE_NAME)
@Qualifier("localCacheService")
public class LocalCacheProviderImpl implements CacheProviderService {

    private static Map<String, Cache<StringObject>> _cacheMap = Maps.newConcurrentMap();

    static {

        Cache<StringObject> cacheContainer = CacheBuilder.newBuilder()
                .maximumSize(AppConst.CACHE_MAXIMUM_SIZE)
                .expireAfterWrite(AppConst.CACHE_MINUTE, TimeUnit.MILLISECONDS)// A period of time after the last write 
                //.expireAfterAccess(AppConst.CACHE_MINUTE, TimeUnit.MILLISECONDS) // Move out a while after the last visit 
                .recordStats()// Turn on the statistics function 
                .build();

        _cacheMap.put(String.valueOf(AppConst.CACHE_MINUTE), cacheContainer);
    }

    /**
     *  The query cache 
     *
     * @param key  Cache key   Do not empty 
     **/
    public <T extends Object> T get(String key) {
        T obj = get(key, nullnull, AppConst.CACHE_MINUTE);

        return obj;
    }

    /**
     *  The query cache 
     *
     * @param key       Cache key   Do not empty 
     * @param function  If there is no cache , Call the callable Function returns the object   May be empty 
     **/
    public <T extends Object> T get(String key, Function<String, T> function{
        T obj = get(key, functionkeyAppConst.CACHE_MINUTE);

        return obj;
    }

    /**
     *  The query cache 
     *
     * @param key       Cache key   Do not empty 
     * @param function  If there is no cache , Call the callable Function returns the object   May be empty 
     * @param funcParm function Function call parameters 
     **/
    public <T extends Object, M extends Object> T get(String key, Function<M, T> functionM funcParm{
        T obj = get(key, functionfuncParmAppConst.CACHE_MINUTE);

        return obj;
    }

    /**
     *  The query cache 
     *
     * @param key         Cache key   Do not empty 
     * @param function    If there is no cache , Call the callable Function returns the object   May be empty 
     * @param expireTime  Expiration time ( Company : millisecond )  May be empty 
     **/
    public <T extends Object> T get(String key, Function<String, T> functionLong expireTime{
        T obj = get(key, functionkeyexpireTime);

        return obj;
    }

    /**
     *  The query cache 
     *
     * @param key         Cache key   Do not empty 
     * @param function    If there is no cache , Call the callable Function returns the object   May be empty 
     * @param funcParm   function Function call parameters 
     * @param expireTime  Expiration time ( Company : millisecond )  May be empty 
     **/
    public <T extends Object, M extends Object> T get(String key, Function<M, T> functionM funcParmLong expireTime{
        T obj = null;
        if (StringUtils.isEmpty(key) == true) {
            return obj;
        }

        expireTime = getExpireTime(expireTime);

        Cache<StringObject> cacheContainer = getCacheContainer(expireTime);

        try {
            if (function == null{
                obj = (T) cacheContainer.getIfPresent(key);
            } else {
                final Long cachedTime = expireTime;
                obj = (T) cacheContainer.get(key, () -> {
                    T retObj = function.apply(funcParm);
                    return retObj;
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return obj;
    }

    /**
     *  Set cache key value    Insert values directly into the cache , This will directly override the values mapped before the given key 
     *
     * @param key  Cache key   Do not empty 
     * @param obj  Cache value   Do not empty 
     **/
    public <T extends Objectvoid set(String key, T obj) {

        set(key, obj, AppConst.CACHE_MINUTE);
    }

    /**
     *  Set cache key value    Insert values directly into the cache , This will directly override the values mapped before the given key 
     *
     * @param key         Cache key   Do not empty 
     * @param obj         Cache value   Do not empty 
     * @param expireTime  Expiration time ( Company : millisecond )  May be empty 
     **/
    public <T extends Objectvoid set(String key, T obj, Long expireTime) {
        if (StringUtils.isEmpty(key) == true) {
            return;
        }

        if (obj == null) {
            return;
        }

        expireTime = getExpireTime(expireTime);

        Cache<StringObject> cacheContainer = getCacheContainer(expireTime);

        cacheContainer.put(key, obj);
    }

    /**
     *  Remove the cache 
     *
     * @param key  Cache key   Do not empty 
     **/
    public void remove(String key) {
        if (StringUtils.isEmpty(key) == true) {
            return;
        }

        long expireTime = getExpireTime(AppConst.CACHE_MINUTE);

        Cache<StringObject> cacheContainer = getCacheContainer(expireTime);

        cacheContainer.invalidate(key);
    }

    /**
     *  Is there a cache 
     *
     * @param key  Cache key   Do not empty 
     **/
    public boolean contains(String key) {
        boolean exists = false;
        if (StringUtils.isEmpty(key) == true) {
            return exists;
        }

        Object obj = get(key);

        if (obj != null) {
            exists = true;
        }

        return exists;
    }

    private static Lock lock = new ReentrantLock();

    private Cache<StringObject> getCacheContainer(Long expireTime) {

        Cache<StringObject> cacheContainer = null;
        if (expireTime == null) {
            return cacheContainer;
        }

        String mapKey = String.valueOf(expireTime);

        if (_cacheMap.containsKey(mapKey) == true) {
            cacheContainer = _cacheMap.get(mapKey);
            return cacheContainer;
        }

        try {
            lock.lock();
            cacheContainer = CacheBuilder.newBuilder()
                    .maximumSize(AppConst.CACHE_MAXIMUM_SIZE)
                    .expireAfterWrite(expireTime, TimeUnit.MILLISECONDS)// A period of time after the last write 
                    //.expireAfterAccess(AppConst.CACHE_MINUTE, TimeUnit.MILLISECONDS) // Move out a while after the last visit 
                    .recordStats()// Turn on the statistics function 
                    .build();

            _cacheMap.put(mapKey, cacheContainer);

        } finally {
            lock.unlock();
        }

        return cacheContainer;
    }

    /**
     *  Get expiration time   Company : millisecond 
     *
     * @param expireTime  The expiration time of the heirs   Unit millisecond   If less than 1 minute , The default is 10 minute 
     **/
    private Long getExpireTime(Long expireTime) {
        Long result = expireTime;
        if (expireTime == null || expireTime < AppConst.CACHE_MINUTE / 10) {
            result = AppConst.CACHE_MINUTE;
        }

        return result;
    }
}

4、 matters needing attention

Guava Cache When initializing the container , Support cache expiration policy , similar FIFO、LRU and LFU And so on .

expireAfterWrite: A period of time after the last write .

expireAfterAccess: Move out a while after the last visit .

Guava Cache The setting of cache expiration time is not friendly enough . Common application scenarios , such as , Some of the underlying data caching is almost constant 1 God , Some hot data caching 2 Hours , Some session data caching 5 Minutes, etc. .

Usually we think it is very easy to set the cache with the expiration time , And just a cache container instance can , such as .NET Under the ObjectCache、System.Runtime.Cache wait .

however Guava Cache It's not the realization idea , If the expiration time of the cache is different ,Guava Of CacheBuilder To initialize multiple copies Cache example .

Fortunately, I noticed this problem in the implementation , And it provides solutions , You can see getCacheContainer This function , The cache instance is judged according to the expiration time , Even if the multi instance cache with different expiration times is no problem at all .

3、 ... and 、 Distributed cache

There are a lot of distributed caching products , The general application of this article Redis, stay Spring Boot Use in application Redis It's simple .

1、 What is? Redis

Redis It's an open source (BSD The license ) Of 、 use C High performance keys written in language - Value store (key-value store). It is often called a data structure server (data structure server). It can be used as a cache 、 Message middleware and database , In many applications , Often see someone choose to use Redis Do the cache , Implementation of distributed lock and distributed Session etc. . As a caching system , And classic KV Structural Memcached Very similar , But there are many differences .Redis Support rich data types .Redis The key value of can include string (strings) type , It also includes hashes (hashes)、 list (lists)、 aggregate (sets) And ordered set (sorted sets) And so on . For these data types , You can do atomic operations . for example : Append strings (append); Increment the value in the hash ; Add elements... To the list ; Calculate the intersection of sets 、 Union and difference, etc .

Redis Data type of :Keys: Non binary safe character types ( not binary-safe strings ), because key No binary safe String , So it's like “my key” and “mykey\n” This includes spaces and line breaks key It's not allowed .Values:Strings、Hash、Lists、 Sets、 Sorted sets. in consideration of Redis Single thread operation mode ,Value The particle size should not be too large , The larger the cache value , The more likely it is to cause congestion and queuing .

In order to get excellent performance ,Redis In memory (in-memory) Data sets (dataset) The way . meanwhile ,Redis Support data persistence , You can transfer datasets to disk every once in a while (snapshot), Or append each operation command at the end of the log (append only file,aof).Redis It also supports master-slave replication (master-slave replication), And it has very fast non blocking first synchronization ( non-blocking first synchronization)、 Network disconnection, automatic reconnection, etc . meanwhile Redis There are other features , It includes simple things to support 、 Publish subscribe ( pub/sub)、 The Conduit (pipeline) And virtual memory (vm) etc. .

2、 Add dependency

 

   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

3、 To configure Redis

stay application.properties In profile , To configure Redis Common parameters :

 

# Redis Cache related configuration
#Redis Database index ( The default is 0)
spring.redis.database=0
#Redis Server address
spring.redis.host=127.0.0.1
#Redis Server port
spring.redis.port=6379
#Redis Server password ( The default is empty. )
spring.redis.password=123321
#Redis Connection timeout   Default :5 minute ( Company : millisecond )
spring.redis.timeout=300000ms
#Redis Maximum number of connections in connection pool ( Use a negative value to indicate that there is no limit )
spring.redis.jedis.pool.max-active=512
#Redis The smallest free connection in the connection pool
spring.redis.jedis.pool.min-idle=0
#Redis The maximum free connection in the connection pool
spring.redis.jedis.pool.max-idle=8
#Redis Connection pool maximum blocking wait time ( Use a negative value to indicate that there is no limit )
spring.redis.jedis.pool.max-wait=-1ms

The common thing to note is the maximum number of connections (spring.redis.jedis.pool.max-active ) And overtime (spring.redis.jedis.pool.max-wait).Redis The frequency of failure in a production environment is often related to these two parameters .

Then define an inheritance from CachingConfigurerSupport( Please note that cacheManager and keyGenerator These two methods are implemented in subclasses ) Of RedisConfig class :

 

package com.power.demo.cache.config;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis Cache configuration class 
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        return RedisCacheManager.create(connectionFactory);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();

        //Jedis Of Key and Value The default value of the serializer for is JdkSerializationRedisSerializer
        // By experiment ,JdkSerializationRedisSerializer adopt RedisDesktopManager The key value pairs you see cannot be parsed properly 

        // Set up key serializers 
        template.setKeySerializer(new StringRedisSerializer());

        //// Set up value serializers    The default value is JdkSerializationRedisSerializer
        // Use Jackson The problem with the serializer is , Complex objects may fail to serialize , such as JodaTime Of DateTime type 

        //        // Use Jackson2, Serialize the object to JSON
        //        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //        //json Transfer object class , If you don't set the default, it will json Turn into hashmap
        //        ObjectMapper om = new ObjectMapper();
        //        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //        jackson2JsonRedisSerializer.setObjectMapper(om);
        //        template.setValueSerializer(jackson2JsonRedisSerializer);

        // take redis Set the connection factory to the template class 
        template.setConnectionFactory(factory);

        return template;
    }

//    // Custom cache key Generation strategy 
//    @Bean
//    public KeyGenerator keyGenerator() {
//        return new KeyGenerator() {
//            @Override
//            public Object generate(Object target, java.lang.reflect.Method method, Object... params) {
//                StringBuffer sb = new StringBuffer();
//                sb.append(target.getClass().getName());
//                sb.append(method.getName());
//                for (Object obj : params) {
//                    if (obj == null) {
//                        continue;
//                    }
//                    sb.append(obj.toString());
//                }
//                return sb.toString();
//            }
//        };
//    }
}

stay RedisConfig Add... To this class @EnableCaching This annotation , This annotation will be Spring Find out , And it creates a facet (aspect) And trigger Spring Cache pointcuts for annotations (pointcut). Based on the annotations used and the state of the cache , This aspect gets the data from the cache , Add data to the cache or remove a value from the cache .cacheManager Method , Declare a cache manager (CacheManager) Of bean, Role is @EnableCaching This aspect will call the method of the cache manager when adding or deleting the cache .keyGenerator Method , You can customize the cache according to your needs key Generation strategy .

and redisTemplate Method , It's mainly about setting Redis Template class , Like serializers for keys and values ( You can see it here ,Redis Must be serializable )、redis Connecting factories, etc .

RedisTemplate The main types of serializers supported are as follows :

JdkSerializationRedisSerializer: Use Java serialize ;StringRedisSerializer: serialize String Type of key and value;GenericToStringSerializer: Use Spring Transform services for serialization ;JacksonJsonRedisSerializer: Use Jackson 1, Serialize the object to JSON;Jackson2JsonRedisSerializer: Use Jackson 2, Serialize the object to JSON;OxmSerializer: Use Spring O/X Mapped choreographer and unloader (marshaler and unmarshaler) Implement serialization , be used for XML serialize ;

Be careful :RedisTemplate Key and value serializers for , By default, it's all JdkSerializationRedisSerializer, They can all be customized to set the serializer . It is recommended to use string key StringRedisSerializer Serializer , Because it's easy to check problems during operation and maintenance ,JDK Serializers also recognize , But it's a little less readable ( It's because the cache server doesn't have JRE Do you ?), See the following effect :

 

 

The value serializer is much more complex , Many people recommend using Jackson2JsonRedisSerializer Serializer , But in the actual development process , People often encounter deserialization errors , After investigation, most of them are with Jackson2JsonRedisSerializer This serializer is about .

4、 Implementation interface

Use RedisTemplate, stay Spring Boot Call in Redis Interface is better than calling directly Jedis Much simpler .

 

package com.power.demo.cache.impl;

import com.power.demo.cache.contract.CacheProviderService;
import com.power.demo.common.AppConst;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Configuration
@ComponentScan(basePackages = AppConst.BASE_PACKAGE_NAME)
@Qualifier("redisCacheService")
public class RedisCacheProviderImpl implements CacheProviderService {

    @Resource
    private RedisTemplate<Serializable, Object> redisTemplate;

    /**
     *  The query cache 
     *
     * @param key  Cache key   Do not empty 
     **/
    public <T extends Object> T get(String key) {
        T obj = get(key, nullnull, AppConst.CACHE_MINUTE);

        return obj;
    }

    /**
     *  The query cache 
     *
     * @param key       Cache key   Do not empty 
     * @param function  If there is no cache , Call the callable Function returns the object   May be empty 
     **/
    public <T extends Object> T get(String key, Function<String, T> function{
        T obj = get(key, functionkeyAppConst.CACHE_MINUTE);

        return obj;
    }

    /**
     *  The query cache 
     *
     * @param key       Cache key   Do not empty 
     * @param function  If there is no cache , Call the callable Function returns the object   May be empty 
     * @param funcParm function Function call parameters 
     **/
    public <T extends Object, M extends Object> T get(String key, Function<M, T> functionM funcParm{
        T obj = get(key, functionfuncParmAppConst.CACHE_MINUTE);

        return obj;
    }

    /**
     *  The query cache 
     *
     * @param key         Cache key   Do not empty 
     * @param function    If there is no cache , Call the callable Function returns the object   May be empty 
     * @param expireTime  Expiration time ( Company : millisecond )  May be empty 
     **/
    public <T extends Object> T get(String key, Function<String, T> functionLong expireTime{
        T obj = get(key, functionkeyexpireTime);

        return obj;
    }

    /**
     *  The query cache 
     *
     * @param key         Cache key   Do not empty 
     * @param function    If there is no cache , Call the callable Function returns the object   May be empty 
     * @param funcParm   function Function call parameters 
     * @param expireTime  Expiration time ( Company : millisecond )  May be empty 
     **/
    public <T extends Object, M extends Object> T get(String key, Function<M, T> functionM funcParmLong expireTime{
        T obj = null;
        if (StringUtils.isEmpty(key) == true) {
            return obj;
        }

        expireTime = getExpireTime(expireTime);

        try {

            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            obj = (T) operations.get(key);
            if (function != null && obj == null{
                obj = function.apply(funcParm);
                if (obj != null) {
                    set(key, obj, expireTime);// Set cache information 
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return obj;
    }

    /**
     *  Set cache key value    Insert values directly into the cache , This will directly override the values mapped before the given key 
     *
     * @param key  Cache key   Do not empty 
     * @param obj  Cache value   Do not empty 
     **/
    public <T extends Objectvoid set(String key, T obj) {

        set(key, obj, AppConst.CACHE_MINUTE);
    }

    /**
     *  Set cache key value    Insert values directly into the cache , This will directly override the values mapped before the given key 
     *
     * @param key         Cache key   Do not empty 
     * @param obj         Cache value   Do not empty 
     * @param expireTime  Expiration time ( Company : millisecond )  May be empty 
     **/
    public <T extends Objectvoid set(String key, T obj, Long expireTime) {
        if (StringUtils.isEmpty(key) == true) {
            return;
        }

        if (obj == null) {
            return;
        }

        expireTime = getExpireTime(expireTime);

        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();

        operations.set(key, obj);

        redisTemplate.expire(key, expireTime, TimeUnit.MILLISECONDS);
    }

    /**
     *  Remove the cache 
     *
     * @param key  Cache key   Do not empty 
     **/
    public void remove(String key) {
        if (StringUtils.isEmpty(key) == true) {
            return;
        }

        redisTemplate.delete(key);
    }

    /**
     *  Is there a cache 
     *
     * @param key  Cache key   Do not empty 
     **/
    public boolean contains(String key) {
        boolean exists = false;
        if (StringUtils.isEmpty(key) == true) {
            return exists;
        }

        Object obj = get(key);

        if (obj != null) {
            exists = true;
        }

        return exists;
    }

    /**
     *  Get expiration time   Company : millisecond 
     *
     * @param expireTime  The expiration time of the heirs   Unit millisecond   If less than 1 minute , The default is 10 minute 
     **/
    private Long getExpireTime(Long expireTime) {
        Long result = expireTime;
        if (expireTime == null || expireTime < AppConst.CACHE_MINUTE / 10) {
            result = AppConst.CACHE_MINUTE;
        }

        return result;
    }
}

Be careful : Many tutorials talk about annotation (@Cacheable,@CachePut、@CacheEvict and @Caching) Implement data caching , According to practice , I personally don't recommend this way of using .

Four 、 cache “ In time ” Overdue problem

This is also a classic problem in the development and operation and maintenance process .

Some companies write cache clients , Each team will be given a definition of Area, But this can only do the distribution of cache keys , Caching is not guaranteed “ real time ” Valid expiration .

Many years ago, I wrote an article that combined with the actual situation , That is to add the cached version , Please hit here , It provides a relatively effective solution , Not too high concurrent site to be careful , Prevent avalanche effect .

Redis There are other common problems , such as :Redis String type of Key and Value There are limits , And they can't exceed 512M, Please hit here . There are also problems with the maximum number of connections and timeout settings , This article will not list them one by one .

5、 ... and 、 Second level cache

In the configuration file , Add the cache provider switch :

 

## Whether to enable local caching
spring.power.isuselocalcache=1
## Is it enabled? Redis cache
spring.power.isuserediscache=1

The cache providers are all implemented , We'll wrap a call facade class again PowerCacheBuilder, Plus caching version control , You can easily control and switch the cache ,code talks:

 

package com.power.demo.cache;

import com.google.common.collect.Lists;
import com.power.demo.cache.contract.CacheProviderService;
import com.power.demo.common.AppConst;
import com.power.demo.common.AppField;
import com.power.demo.util.ConfigUtil;
import com.power.demo.util.PowerLogger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;

/*
 *  Cache help class that supports multi cache provider multi cache 
 * */
@Configuration
@ComponentScan(basePackages = AppConst.BASE_PACKAGE_NAME)
public class PowerCacheBuilder {

    @Autowired
    @Qualifier("localCacheService")
    private CacheProviderService localCacheService;

    @Autowired
    @Qualifier("redisCacheService")
    private CacheProviderService redisCacheService;

    private static List<CacheProviderService> _listCacheProvider = Lists.newArrayList();

    private static final Lock providerLock = new ReentrantLock();

    /**
     *  Initialize cache provider   Default priority : Cache locally first , Post distributed caching 
     **/
    private List<CacheProviderService> getCacheProviders() {

        if (_listCacheProvider.size() > 0) {
            return _listCacheProvider;
        }

        // Thread safety 
        try {
            providerLock.tryLock(1000, TimeUnit.MILLISECONDS);

            if (_listCacheProvider.size() > 0) {
                return _listCacheProvider;
            }

            String isUseCache = ConfigUtil.getConfigVal(AppField.IS_USE_LOCAL_CACHE);

            CacheProviderService cacheProviderService = null;

            // Enable local caching 
            if ("1".equalsIgnoreCase(isUseCache)) {
                _listCacheProvider.add(localCacheService);
            }

            isUseCache = ConfigUtil.getConfigVal(AppField.IS_USE_REDIS_CACHE);

            // Enable Redis cache 
            if ("1".equalsIgnoreCase(isUseCache)) {
                _listCacheProvider.add(redisCacheService);

                resetCacheVersion();// Set the distributed cache version number 
            }

            PowerLogger.info(" Initialization of cache provider succeeded , share " + _listCacheProvider.size() + " individual ");
        } catch (Exception e) {
            e.printStackTrace();

            _listCacheProvider = Lists.newArrayList();

            PowerLogger.error(" Exception initializing cache provider :{}", e);
        } finally {
            providerLock.unlock();
        }

        return _listCacheProvider;
    }

    /**
     *  The query cache 
     *
     * @param key  Cache key   Do not empty 
     **/
    public <T extends Object> T get(String key) {
        T obj = null;

        //key = generateVerKey(key);// Construct cache key with version 

        for (CacheProviderService provider : getCacheProviders()) {

            obj = provider.get(key);

            if (obj != null) {
                return obj;
            }
        }

        return obj;
    }

    /**
     *  The query cache 
     *
     * @param key       Cache key   Do not empty 
     * @param function  If there is no cache , Call the callable Function returns the object   May be empty 
     **/
    public <T extends Object> T get(String key, Function<String, T> function{
        T obj = null;

        for (CacheProviderService provider : getCacheProviders()) {

            if (obj == null) {
                obj = provider.get(key, function);
            } else if (function != null && obj != null{// Query and set up other cache provider caches 
                provider.get(key, function);
            }

            // If callable Function is empty   The cache object is not empty   Jump out of the loop in time and return to 
            if (function == null && obj != null{
                return obj;
            }

        }

        return obj;
    }

    /**
     *  The query cache 
     *
     * @param key       Cache key   Do not empty 
     * @param function  If there is no cache , Call the callable Function returns the object   May be empty 
     * @param funcParm function Function call parameters 
     **/
    public <T extends Object, M extends Object> T get(String key, Function<M, T> functionM funcParm{
        T obj = null;

        for (CacheProviderService provider : getCacheProviders()) {

            if (obj == null) {
                obj = provider.get(key, functionfuncParm);
            } else if (function != null && obj != null{// Query and set up other cache provider caches 
                provider.get(key, functionfuncParm);
            }

            // If callable Function is empty   The cache object is not empty   Jump out of the loop in time and return to 
            if (function == null && obj != null{
                return obj;
            }
        }

        return obj;
    }

    /**
     *  The query cache 
     *
     * @param key         Cache key   Do not empty 
     * @param function    If there is no cache , Call the callable Function returns the object   May be empty 
     * @param expireTime  Expiration time ( Company : millisecond )  May be empty 
     **/
    public <T extends Object> T get(String key, Function<String, T> functionlong expireTime{
        T obj = null;

        for (CacheProviderService provider : getCacheProviders()) {

            if (obj == null) {
                obj = provider.get(key, functionexpireTime);
            } else if (function != null && obj != null{// Query and set up other cache provider caches 
                provider.get(key, functionexpireTime);
            }

            // If callable Function is empty   The cache object is not empty   Jump out of the loop in time and return to 
            if (function == null && obj != null{
                return obj;
            }
        }

        return obj;
    }

    /**
     *  The query cache 
     *
     * @param key         Cache key   Do not empty 
     * @param function    If there is no cache , Call the callable Function returns the object   May be empty 
     * @param funcParm   function Function call parameters 
     * @param expireTime  Expiration time ( Company : millisecond )  May be empty 
     **/
    public <T extends Object, M extends Object> T get(String key, Function<M, T> functionM funcParmlong expireTime{
        T obj = null;

        for (CacheProviderService provider : getCacheProviders()) {

            if (obj == null) {
                obj = provider.get(key, functionfuncParmexpireTime);
            } else if (function != null && obj != null{// Query and set up other cache provider caches 
                provider.get(key, functionfuncParmexpireTime);
            }

            // If callable Function is empty   The cache object is not empty   Jump out of the loop in time and return to 
            if (function == null && obj != null{
                return obj;
            }
        }

        return obj;
    }

    /**
     *  Set cache key value    Insert or override values directly into the cache 
     *
     * @param key  Cache key   Do not empty 
     * @param obj  Cache value   Do not empty 
     **/
    public <T extends Objectvoid set(String key, T obj) {

        //key = generateVerKey(key);// Construct cache key with version 

        for (CacheProviderService provider : getCacheProviders()) {

            provider.set(key, obj);

        }
    }

    /**
     *  Set cache key value    Insert or override values directly into the cache 
     *
     * @param key         Cache key   Do not empty 
     * @param obj         Cache value   Do not empty 
     * @param expireTime  Expiration time ( Company : millisecond )  May be empty 
     **/
    public <T extends Objectvoid set(String key, T obj, Long expireTime) {

        //key = generateVerKey(key);// Construct cache key with version 

        for (CacheProviderService provider : getCacheProviders()) {

            provider.set(key, obj, expireTime);

        }
    }

    /**
     *  Remove the cache 
     *
     * @param key  Cache key   Do not empty 
     **/
    public void remove(String key) {

        //key = generateVerKey(key);// Construct cache key with version 

        if (StringUtils.isEmpty(key) == true) {
            return;
        }

        for (CacheProviderService provider : getCacheProviders()) {

            provider.remove(key);

        }
    }

    /**
     *  Is there a cache 
     *
     * @param key  Cache key   Do not empty 
     **/
    public boolean contains(String key) {
        boolean exists = false;

        //key = generateVerKey(key);// Construct cache key with version 

        if (StringUtils.isEmpty(key) == true) {
            return exists;
        }

        Object obj = get(key);

        if (obj != null) {
            exists = true;
        }

        return exists;
    }

    /**
     *  Get the distributed cache version number 
     **/
    public String getCacheVersion() {
        String version = "";
        boolean isUseCache = checkUseRedisCache();

        // not enabled Redis cache 
        if (isUseCache == false) {
            return version;
        }

        version = redisCacheService.get(AppConst.CACHE_VERSION_KEY);

        return version;
    }

    /**
     *  Reset distributed cache version    If you enable distributed caching , Set cache version 
     **/
    public String resetCacheVersion() {
        String version = "";
        boolean isUseCache = checkUseRedisCache();

        // not enabled Redis cache 
        if (isUseCache == false) {
            return version;
        }

        // Set cache version 
        version = String.valueOf(Math.abs(UUID.randomUUID().hashCode()));
        redisCacheService.set(AppConst.CACHE_VERSION_KEY, version);

        return version;
    }

    /**
     *  If you enable distributed caching , Get cached version , Reset the cache for queries key, Can achieve relatively real-time cache expiration control 
     * <p>
     *  If distributed caching is not enabled , cache key No modification , Go straight back to 
     **/
    public String generateVerKey(String key) {

        String result = key;
        if (StringUtils.isEmpty(key) == true) {
            return result;
        }

        boolean isUseCache = checkUseRedisCache();

        // Distributed caching is not enabled , cache key No modification , Go straight back to 
        if (isUseCache == false) {
            return result;
        }

        String version = redisCacheService.get(AppConst.CACHE_VERSION_KEY);
        if (StringUtils.isEmpty(version) == true) {
            return result;
        }

        result = String.format("%s_%s", result, version);

        return result;
    }

    /**
     *  Verify that distributed caching is enabled 
     **/
    private boolean checkUseRedisCache() {
        boolean isUseCache = false;
        String strIsUseCache = ConfigUtil.getConfigVal(AppField.IS_USE_REDIS_CACHE);

        isUseCache = "1".equalsIgnoreCase(strIsUseCache);

        return isUseCache;
    }
}

Unit tests are as follows :

 

   

 @Test
    public void testCacheVerson() throws Exception {

        String version = cacheBuilder.getCacheVersion();
        System.out.println(String.format(" Current cache version :%s", version));

        String cacheKey = cacheBuilder.generateVerKey("goods778899");

        GoodsVO goodsVO = new GoodsVO();
        goodsVO.setGoodsId(UUID.randomUUID().toString());
        goodsVO.setCreateTime(new Date());
        goodsVO.setCreateDate(new DateTime(new Date()));
        goodsVO.setGoodsType(1024);
        goodsVO.setGoodsCode("123456789");
        goodsVO.setGoodsName(" My test product ");

        cacheBuilder.set(cacheKey, goodsVO);

        GoodsVO goodsVO1 = cacheBuilder.get(cacheKey);

        Assert.assertNotNull(goodsVO1);

        version = cacheBuilder.resetCacheVersion();
        System.out.println(String.format(" The cached version after reset :%s", version));


        cacheKey = cacheBuilder.generateVerKey("goods112233");

        cacheBuilder.set(cacheKey, goodsVO);

        GoodsVO goodsVO2 = cacheBuilder.get(cacheKey);

        Assert.assertNotNull(goodsVO2);

        Assert.assertTrue(" The primary keys of the two cache objects are the same ", goodsVO1.getGoodsId().equals(goodsVO2.getGoodsId()));
    }

A multi-level cache system that meets the basic functions is good .

stay Spring Boot The use of caching in applications is very simple , Select and call the cache interface packaged above .

 

String cacheKey = _cacheBuilder.generateVerKey("com.power.demo.apiservice.impl.getgoodsbyid." + request.getGoodsId());

GoodsVO goodsVO = _cacheBuilder.get(cacheKey, _goodsService::getGoodsByGoodsId, request.getGoodsId());

Come here Spring Boot Business system development in the most commonly used ORM, Cache and queue three axe on the end of the introduction .

In the process of development, you will find that ,Java It's really a very, very formal language , You need to be familiar with open source tools and middleware , The wheel of open source is too rich , Try a few more , True knowledge comes from practice .

Java We have a wide range of knowledge , The interview involves a wide range of questions , Key points include :Java Basics 、Java Concurrent ,JVM、MySQL、 data structure 、 Algorithm 、Spring、 Microservices 、MQ wait , What a huge amount of knowledge is involved , So we often have no way to start when we review , Today, I'm going to bring you a set of Java Interview questions , The question bank is very comprehensive , Include Java Basics 、Java aggregate 、JVM、Java Concurrent 、Spring Family bucket 、Redis、MySQL、Dubbo、Netty、MQ wait , contain Java Back end knowledge points 2000 + , Some of them are as follows :

Data acquisition method : Official account :“ Programmer Bai Nannan ” Access to the above information

 

 

版权声明
本文为[Programmer Bai Nannan]所创,转载请带上原文链接,感谢

Scroll to Top