SpringCache of grain mall

Posted by hitman6003 on Wed, 01 Sep 2021 20:14:23 +0200

summary

spring has defined Cache and CacheManager interfaces since 3.1 to unify different caching technologies. It also supports the use of JCache(JSR-107) annotations to simplify our development
The implementation of Cache interface includes RedisCache, EhCacheCache, ConcurrentMapCache, etc

Each time a method requiring caching is called, spring will check whether the specified target method of the specified parameter has been called; If yes, get the result of the method call directly from the cache. If not, call the method and cache the result and return it to the user. The next call is taken directly from the cache.

When using Spring cache abstraction, we need to pay attention to the following two points:

  • Identify the methods that need caching and their caching policies
  • Read the data stored in the previous cache from the cache

gulimall-product

pom.xml

<dependency>
    <groupId>org.springframework.b oot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

Specify the cache type and annotate @ EnableCaching on the main configuration class

application.yml

spring:
  cache:
  	#The specified cache type is redis
    type: redis
    redis:
      # Specify the expiration time in redis as 1h
      time-to-live: 3600000

Here, we do not use the default configuration of redisCacheManger in spring cache

Through source code

Cache auto fit class

// Cache auto configuration source code
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
                     HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import({ CacheConfigurationImportSelector.class, // See what CacheConfiguration to import
         CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
public class CacheAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public CacheManagerCustomizers cacheManagerCustomizers(ObjectProvider<CacheManagerCustomizer<?>> customizers) {
        return new CacheManagerCustomizers(customizers.orderedStream().collect(Collectors.toList()));
    }

    @Bean
    public CacheManagerValidator cacheAutoConfigurationValidator(CacheProperties cacheProperties,
                                                                 ObjectProvider<CacheManager> cacheManager) {
        return new CacheManagerValidator(cacheProperties, cacheManager);
    }

    @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
    @ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
    static class CacheManagerEntityManagerFactoryDependsOnPostProcessor
        extends EntityManagerFactoryDependsOnPostProcessor {

        CacheManagerEntityManagerFactoryDependsOnPostProcessor() {
            super("cacheManager");
        }

    }

It is to determine what type of configuration is imported and load it

We use the redis configuration here

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {

    @Bean // Put into cache manager
    RedisCacheManager cacheManager(CacheProperties cacheProperties, 
                                   CacheManagerCustomizers cacheManagerCustomizers,
                                   ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
                                   ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
                                   RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
        RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
            determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
        List<String> cacheNames = cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
        }
        redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
        return cacheManagerCustomizers.customize(builder.build());
    }

We configure the desired redis configuration in config and overwrite the default redisCacheConfiguration with the new redisCacheConfiguration

@EnableConfigurationProperties(CacheProperties.class)
@EnableCaching
@Configuration
public class MyCacheConfig {



	/**
	 * The TTL setting in the configuration file is not used
	 *
	 * original:
	 * @ConfigurationProperties(prefix = "spring.cache")
	 * public class CacheProperties
	 *
	 * Now let this configuration file take effect 	: @ EnableConfigurationProperties(CacheProperties.class)
	 *
	 */
	@Bean
	RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){

		RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();

		// Set kv's serialization mechanism
		config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
		config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
		CacheProperties.Redis redisproperties = cacheProperties.getRedis();

		// set configuration
		if(redisproperties.getTimeToLive() != null){
			config = config.entryTtl(redisproperties.getTimeToLive());
		}
		if(redisproperties.getKeyPrefix() != null){
			config = config.prefixKeysWith(redisproperties.getKeyPrefix());
		}
		if(!redisproperties.isCacheNullValues()){
			config = config.disableCachingNullValues();
		}
		if(!redisproperties.isUseKeyPrefix()){
			config = config.disableKeyPrefix();
		}
		return config;
	}

}

Common notes

  • @Cacheable: triggers the operation of saving data to the cache;
  • @CacheEvict: triggers the deletion of data from the cache;
  • @CachePut: does not affect method execution to update the cache;
  • @Caching: combine the above operations;
  • @CacheConfig: share the same configuration of the cache at the class level;
/**
 * 1,For each data to be cached, we specify the cache to be put into that name[ Cached partitions(By business type)]
 * 2,@Cacheable The results representing the current method need to be cached. If there are methods in the cache, they do not need to be called. If there are no methods in the cache, they will be called. Finally, put the result of the method into the cache
 * 3,Default behavior
 *   3.1 If there is in the cache, the method is no longer called
 *   3.2 key Is generated by default:Cache name::SimpleKey::[](Automatic generation key value)
 *   3.3 Cached value Value, used by default jdk Serialization mechanism to save the serialized data to redis in
 *   3.4 The default time is -1: 
 *
 *   Custom actions: key Generation of
 *    1. Specifies the of the generated cache key: key Property to receive a SpEl
 *    2. Specifies the lifetime of cached data:Modify the lifetime in the configuration document ttl
 *    3. Save data as json format: Custom configuration class MyCacheManager
 * <p>
 * 4,Spring-Cache Shortcomings:
 * 1),Read mode
 * Cache penetration: query a null data Solution: cache empty data
 * Cache breakdown: a large number of concurrent queries come in and query an expired data at the same time. Solution: lock ? The default is unlocked;use sync = true To solve the breakdown problem
 * Cache avalanche: massive key Expired at the same time. Solution: add random time. Plus expiration time
 * 2),Write mode: (CACHE consistent with database)
 * 1),Read write lock.
 * 2),introduce Canal,Perceived MySQL Update to update Redis
 * 3),Read more and write more. Just go to the database to query
 * <p>
 * Summary:
 * Conventional data (data with more reading and less writing, real-time and low consistency requirements) can be used Spring-Cache): Write mode(It is sufficient as long as the cached data has an expiration time)
 * Special data: special design
 * <p>
 * Principle:
 * CacheManager(RedisCacheManager)->Cache(RedisCache)->Cache Responsible for cache read and write
 *
 * @return

// When this method is called, the result will be cached. The cache name is category and the key is the method name
// sync means that the cache of this method will be locked when it is read. / / value is equivalent to cacheNames // key if it is a string "''"
@Cacheable(value = {"category"},key = "#root.methodName",sync = true)
public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithSpringCache() {
    return getCategoriesDb();
}

//Calling this method will delete all caches under the cache category. If you want to delete a specific, use key = ''
@Override
@CacheEvict(value = {"category"},allEntries = true)
public void updateCascade(CategoryEntity category) {
    this.updateById(category);
    if (!StringUtils.isEmpty(category.getName())) {
        categoryBrandRelationService.updateCategory(category);
    }
}

If you want to empty multiple caches, use@Caching(evict={@CacheEvict(value="")})


Principle and deficiency of SpringCache

1) , read mode

Cache penetration: query a null data. Solution: cache empty data through spring. Cache. Redis. Cache null values = true
Cache breakdown: a large number of concurrent queries come in and query an expired data at the same time. Solution: lock? It is unlocked by default;
Use sync = true to solve the breakdown problem
Cache avalanche: a large number of Keys expire at the same time. Solution: add random time.
2) . write mode: (cache is consistent with database)

Read write lock.
Introduce Canal to sense the update of MySQL and update Redis
Read more and write more. Just go to the database to query
3) . summary:

General data (for data with more reads and less writes, timeliness and low consistency requirements, spring cache can be used):

Write mode (as long as the cached data has an expiration time)

Special data: special design

Topics: Java Redis Spring