Spring boot integrates redis to cache some knowledge points

Preface

Recently, we are working on a smart home platform. Considering that the control of home needs fast response, we plan to use redis cache. On the one hand, it can reduce the pressure of database, on the other hand, it can improve the response speed. The technology stack used in the project is basically the familiar springboot family bucket. After springboot2.x, the client operating redis recommends lettuce (lettuce) instead of jedis.

The disadvantage of jedis lies in its direct connection with redis and its inability to achieve elastic contraction.

I. configuration file

Content in the application.yml file

spring:
  application:
    name: simple-lettuce
  cache:
    type: redis
    redis:
      # Cache timeout ms
      time-to-live: 60000
      # Whether to cache null values
      cache-null-values: true
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    # Connection timeout (MS)
    timeout: 60000
    # Redis has 16 partitions by default. The specific partition is configured here. The default is 0
    database: 1
    # Spring 2. X redis client uses lettuce (lettuce) instead of jedis
    lettuce:
      # Shutdown timeout
      shutdown-timeout: 30000
      pool:
        # Connection pool maximum connections (negative for no limit) default 8
        max-active: 30
        # Connection pool maximum block wait time (use a negative value to indicate no limit) default -1
        max-wait: -1
        # Maximum free connections in connection pool default 8
        max-idle: 8
        # Minimum free connections in connection pool default 0
        min-idle: 0

Explain:

  • spring.cache.type: redis

It has been shown that redis is used as the caching method.

  • spring.cache.redis.cache-null-values: true

Indicates whether to cache null values, which is generally allowed. Because this involves three major issues of cache: cache penetration, cache avalanche, and cache breakdown.

If you set false, you will not be allowed to cache null values, which will result in many requests for data that the database does not have, and will not cache to redis, resulting in requests to the database every time. This is the case: cache penetration.

For a preliminary understanding of these concepts, please refer to the article: Cache three major problems and solutions!

II. config configuration class


@Configuration
@EnableCaching
public class RedisTemplateConfig extends CachingConfigurerSupport {

    private static Map<String, RedisCacheConfiguration> cacheMap = Maps.newHashMap();

    @Bean(name = "stringRedisTemplate")
    @ConditionalOnMissingBean(name = "stringRedisTemplate") //If the container already has redisTemplate bean No more injection
    public StringRedisTemplate stringRedisTemplate(LettuceConnectionFactory redisConnectionFactory) {return new StringRedisTemplate(redisConnectionFactory);
    }

    @Bean(name = "redisTemplate")
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        System.out.println("RedisTemplateConfig.RedisTemplate");
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // key Serialization of StringRedisSerializer
        template.setKeySerializer(keySerializer());
        template.setHashKeySerializer(keySerializer());
        // value Value serialization takes fastJsonRedisSerializer
        template.setValueSerializer(valueSerializer()); //Use fastjson serialize
        template.setHashValueSerializer(valueSerializer()); //Use fastjson serialize
        template.setConnectionFactory(lettuceConnectionFactory);
        return template;
    }

    /**
     * Add custom cache exception handling
     * Ignore exception when cache read / write exception
     * Reference resources:https://blog.csdn.net/sz85850597/article/details/89301331
     */
    @Override
    public CacheErrorHandler errorHandler() {
        return new IgnoreCacheErrorHandler();
    }

    @SuppressWarnings("Duplicates")
    @Bean
    @Primary//When there are multiple managers, the annotation must be used on one manager to indicate that the manager is the default Manager
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // Default configuration
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(keyPair())
                .serializeValuesWith(valuePair())
                .entryTtl(Duration.ofSeconds(DEFAULT_TTL_SECS)) //Set expiration time
                .disableCachingNullValues();

        // Other configurations
        for(MyCaches cache : MyCaches.values()) {
            cacheMap.put(cache.name(),
                    RedisCacheConfiguration.defaultCacheConfig()
                            .serializeKeysWith(keyPair())
                            .serializeValuesWith(valuePair())
                            .entryTtl(cache.getTtl())
                            // .disableCachingNullValues() // Indicates that caching null values is not allowed
                            .disableKeyPrefix() // Do not use default prefix
                    // .prefixKeysWith("mytest") // Add custom prefix
            );
        }

        /** Traverse MyCaches to add cache configuration*/
        RedisCacheManager cacheManager = RedisCacheManager.builder(
                RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory)
        )
                .cacheDefaults(defaultCacheConfig)
                .withInitialCacheConfigurations(cacheMap)
                .transactionAware()
                .build();

        ParserConfig.getGlobalInstance().addAccept("mypackage.db.entity.");
        return cacheManager;
    }

    /**
     * key Serialization method
     * @return
     */
    private RedisSerializationContext.SerializationPair<String> keyPair() {
        RedisSerializationContext.SerializationPair<String> keyPair =
                RedisSerializationContext.SerializationPair.fromSerializer(keySerializer());
        return keyPair;
    }

    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    /**
     * value Serialization method
     * @return
     */
    private RedisSerializationContext.SerializationPair<Object> valuePair() {
        RedisSerializationContext.SerializationPair<Object> valuePair =
                RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer());
        return valuePair;
    }

    /**
     * Using fastjson serialization
     * @return
     */
    private RedisSerializer<Object> valueSerializer() {
        MyFastJsonRedisSerializer<Object> fastJsonRedisSerializer = new MyFastJsonRedisSerializer<>(Object.class);
        return fastJsonRedisSerializer;
    }

    @Getter
    private enum MyCaches {
        defaultCache(Duration.ofDays(1)),
        MyCaches(Duration.ofMinutes(10));

        MyCaches(Duration ttl) {
            this.ttl = ttl;
        }
        /** Failure time */
        private Duration ttl = Duration.ofHours(1);
    }
}

Explanation

1. Annotation @ EnableCaching on class

Indicates that caching is enabled.

2. extends CachingConfigurerSupport

This class is very rich. In fact, if there is no special operation, you can not inherit this class.

This class can support dynamic selection of caching methods. For example, there are more than one caching scheme in the project, and ehcache is possible. Then you can customize the use of ehcache when redis is used. There are also some exceptions to deal with. I don't know very well. Please refer to:

springboot(25) custom cache read / write mechanism cacheconfigurersupport

3. Use of stringredistemplate and RedisTemplate

(1) the main difference between the two is: if you only want to cache simple strings, it is a wise move to select StringRedisTemplate. If you want to use redis to cache some object data, you must choose RedisTemplate.
 
(2) RedisTemplate needs to pay attention to how to select serialization tools. By default, after using the serialized cache data of jdk, i.e. the value value value is binary data that cannot be read directly.
Usually we choose jackson or fastjson to serialize the objects and convert them to json format. After the objects are serialized, an object class path will be added to the header, such as @ type com.mypackage.entity.User. This is also a security strategy.
For example, using fastjosn will specify the white list of the package location of the serialized object in the cacheManager: ParserConfig.getGlobalInstance().addAccept("mypackage.db.entity.");
Official description of fastjson: https://github.com/alibaba/fastjson/wiki/enable'autotype
 
(3) it should also be noted that if value is of string type. RedisTemplate will add a pair of double quotation marks around the string, such as "abc". If you use RedisTemplate to read, you can get abc, but I use Jedis to read in the project, which becomes "abc", which causes these strings cannot be deserialized.
 
(4) the data of StringRedisTemplate and RedisTemplate are isolated from each other. If the data stored in StringRedisTemplate is used, it cannot be read or deleted.
 
 

3. Use of cache annotations

@Cacheable used in query methods

@CachePut is used in update and save methods

@CacheEvict used on delete methods

Note that the @ Cacheable, @ CachePut methods must return the cached object. Because the AOP facet used by annotation indicates that the cache object is null if there is no return value.

@CacheConfig annotation on the class, you can choose which cache, cache manager and Key generator to use

 

Well, the above is a summary of some knowledge points in the project recently. If I have new experience in using cache in the future, I will update it synchronously.

Keywords: Java Redis Database Jedis Spring

Added by colby.anderson on Sat, 09 Nov 2019 11:46:04 +0200