spring-data-redis-cache usage and source code Walk-through

Prospective readers

  • Students preparing to use spring's data-redis-cache
  • Understand the use of @CacheConfig, @Cacheable, @CachePut, @CacheEvict, @Caching
  • Deeply Understanding the Realization Principle of data-redis-cache

Description of article content

  • How to use redis-cache
  • Customize keyGenerator and expiration time
  • Source code interpretation
  • Deficiencies of self-contained caching mechanism

quick get start

  1. maven joins the jar package

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

    spring.redis.host=127.0.0.1
  3. Open redis-cache

    @EnableCaching
  4. @ Functions of CacheConfig, @Cacheable, @CachePut, @CacheEvict, @Caching

    • @ Cacheable queries whether there is data in the cache, returns if there is data, or executes the method
    • @ CachePut executes methods each time and caches the results
    • @ CacheEvict deletes the contents of the cache
    • @ Caching is equivalent to the synthesis of the above three, which is used to configure the behavior of the three.
    • @ CacheConfig is configured on the class to configure the global cache configuration of the current class

Detailed configuration

With the above configuration, redis-cache is already available, but there are still some questions to ask yourself, such as

  • What does the key stored in redis look like? Can I customize the key?
  • How is the value stored in redis serialized
  • How long does the storage cache expire?
  • When accessing concurrently, does it penetrate directly and constantly modify the cached content?

The expiration time, serialization method determines RedisCacheConfiguration from this class, which can be overridden to achieve custom configuration. The default configuration is RedisCacheConfiguration.defaultCacheConfig(), which is never expired, the key is serialized as String, and a prefix is added as namespace, and the value is serialized as Jdk, so the class you want to store must implement java.io.Serializable.

The generation of stored key values is determined by KeyGenerator, which can be configured on cache annotations. By default, SimpleKey Generator stores keys in SimpleKey [parameter name 1, parameter name 2]. If two methods with the same parameter name collide in the same namespace, the deserialization fails.

When accessing concurrently, it is true that there are multiple accesses to the database without caching https://blog.csdn.net/clementad/article/details/52452119

Srping 4.3 provides a sync parameter. When the cache fails, in order to avoid multiple requests hitting the database, the system makes a concurrency control optimization, while only one thread will go to the database to fetch data and other threads will be blocked.

Custom storage key

According to the above instructions, there is a high probability that the stored keys will be consistent and the deserialization will fail. Therefore, we need to customize the stored keys. There are two ways to achieve this. One is to configure keys using metadata (simple but difficult to maintain), the other is to set keyGenerator globally.

Configuring key with metadata

    @Cacheable(key = "#vin+#name")
    public List<Vehicle> testMetaKey(String vin,String name){
        List<Vehicle> vehicles = dataProvide.selectAll();
        return vehicles.stream().filter(vehicle -> vehicle.getVin().equals(vin) && vehicle.getName().contains(name)).collect(Collectors.toList());
    }

This is a spel expression, you can use + sign to stitch parameters, constant use "" to include, more examples

@Cacheable(value = "user",key = "targetClass.name + '.'+ methodName")
@Cacheable(value = "user",key = "'list'+ targetClass.name + '.'+ methodName + #name ")

Note: The generated key cannot be null, otherwise the error Null key returned for cache operation will be reported.

Commonly used metadata information

Name position describe Example
methodName root Method name currently invoked #root.methodName
method root Called method object #root.method.name
target root Current instance #root.target
targetClass root List of currently invoked method parameters #root.targetClass
args root Method name currently invoked #root.args[0]
caches root Cache list used #root.caches[0].name
Argument Name Execution context Method parameter data #user.id
result Execution context Method Return Value Data #result.id

Using global keyGenerator

The use of metadata is simple, but difficult to maintain. If you need to configure more caching interfaces, you can configure a keyGenerator. This configuration can be multiple, just refer to its name.

@Bean
public KeyGenerator cacheKeyGenerator() {
    return (target, method, params) -> {
        return target + method + params;
    }
}

Customize serialization and configure expiration time

Because the default use value serialization is Jdk serialization, there are some problems, such as large volume, adding or subtracting fields will cause abnormal serialization, other serialization can be considered to override the default serialization.

@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory){
    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
    // Set expiration time to 30 days
    redisCacheConfiguration.entryTtl(Duration.ofDays(30));
    redisCacheConfiguration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new KryoRedisSerializer()));
    RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(redisCacheConfiguration)
                .withInitialCacheConfigurations(customConfigs)
                .build();
}

Personalized configuration expiration time and serialization

Above is the global configuration expiration time and serialization, which can be set individually for each cache Names, which is a Map configuration

Map<String, RedisCacheConfiguration> customConfigs = new HashMap<>();
customConfigs.put("cacheName1",RedisCacheConfiguration.defaultCacheConfig());

RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(redisCacheConfiguration)
                .withInitialCacheConfigurations(customConfigs)
                .build();

Source code reading

Source code walk-through only takes you to the beginning, the specific details need to be analyzed in detail.

Firstly, we don't need to look at the source code to know that this must be achieved by dynamic proxy. The proxy target method obtains the configuration, and then enhances the function of the method.

AOP is doing this, we often add some annotations to achieve log information collection, in fact, consistent with this principle, spring-data-cache-redis is also implemented using aop.

Starting with @EnableCaching, you can see that a configuration class that chooses to import configuration is imported (somewhat around, you can control which configuration classes are imported by yourself), and PROXY mode is used by default.

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> 

PROXY imports the following configuration classes

private String[] getProxyImports() {
    List<String> result = new ArrayList<>(3);
    result.add(AutoProxyRegistrar.class.getName());
    result.add(ProxyCachingConfiguration.class.getName());
    if (jsr107Present && jcacheImplPresent) {
        result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
    }
    return StringUtils.toStringArray(result);
}

The key configuration class for ProxyCaching Configuration is in this configuration class, which configures three beans

BeanFactory Cache OperationSource Advisor is an enhancer of Cache OperationSource

CacheOperationSource mainly provides the method of finding cached annotations on methods, findCacheOperations

CacheInterceptor is a Method Interceptor that executes its invoke method when calling a cached method

Let's look at CacheInterceptor's invoke method

// In one sentence, aopAllianceInvoker is a functional interface that executes your real method
execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());

Entering the execute method, you can see that this layer only retrieves all the caching operation sets, @CacheConfig, @Cacheable, @CachePut, @CacheEvict, @Caching and then binds its configuration and current execution context to CacheOperationContexts.

Class<?> targetClass = getTargetClass(target);
CacheOperationSource cacheOperationSource = getCacheOperationSource();
if (cacheOperationSource != null) {
    Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
    if (!CollectionUtils.isEmpty(operations)) {
        return execute(invoker, method,
                       new CacheOperationContexts(operations, method, args, target, targetClass));
    }
}

Entering the execute method, you can see that the former is specially dealing with sync, and the latter is dealing with annotations.

if (contexts.isSynchronized()) {
    // Here is a special treatment for sync, you can leave it alone first, then see how to deal with it, first look at the content behind. 
}

// Process any early evictions do the cache cleaning first
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
                   CacheOperationExpressionEvaluator.NO_RESULT);

// Check if we have a cached item matching the conditions query cache contents 
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

// Collect put from any@Cacheable miss, if no cached item is found, if the cache is not hit, collect put requests, then unified applications that need to be put into the cache will be unified.
List<CachePutRequest> cachePutRequests = new LinkedList<>();
if (cacheHit == null) {
    collectPutRequests(contexts.get(CacheableOperation.class),
                       CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}

Object cacheValue;
Object returnValue;

// CachePut processing with cache hit and not @CachePut
if (cacheHit != null && !hasCachePut(contexts)) {
    // If there are no put requests, just use the cache hit
    cacheValue = cacheHit.get();
    returnValue = wrapCacheValue(method, cacheValue);
}
else {
    // Invoke the method if we don't have a cache hit, execute the real method
    returnValue = invokeOperation(invoker);
    cacheValue = unwrapReturnValue(returnValue);
}

// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

// Process any collected put requests, either from @CachePut or a @Cacheable miss, caches all previously collected putRequest data
for (CachePutRequest cachePutRequest : cachePutRequests) {
    cachePutRequest.apply(cacheValue);
}

// Process any late evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

return returnValue;

After looking at the execution process, let's take a look at Cache Aspect Support, a superclass of Cache Interceptor, because I can use it without setting up the cache manager and see where the default cache manager is set up.

public abstract class CacheAspectSupport extends AbstractCacheInvoker
        implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
    // .... 
}

BeanFactory Aware is used to obtain BeanFactory

Initializing Bean s are used to manage the life cycle of beans, and logic can be added after the afterProperties Set

After Smart Initializing Singleton implements the interface, when all single bean s are initialized, the container calls back the interface's method afterSingletons Instantiated

In the afterSingletons Instantiated, the cacheManager was set up and a cacheManger was taken from the IOC container.

setCacheManager(this.beanFactory.getBean(CacheManager.class));

Who is this CacheManager? You can know the answer from the RedisCacheConfiguration class, where you configure a RedisCacheManager

@Configuration
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {} 
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory,
                                      ResourceLoader resourceLoader) {
    RedisCacheManagerBuilder builder = RedisCacheManager
        .builder(redisConnectionFactory)
        .cacheDefaults(determineConfiguration(resourceLoader.getClassLoader()));
    List<String> cacheNames = this.cacheProperties.getCacheNames();
    if (!cacheNames.isEmpty()) {
        builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
    }
    return this.customizerInvoker.customize(builder.build());
}

The default configuration of cacheManager is known from the determineConfiguration() method

Finally, let's see how the cut-off point is defined, when the invoke method of CacheInterceptor will be invoked

The configuration of pointcuts is in the BeanFactoryCacheOperationSourceAdvisor class, which returns such a pointcut, CacheOperationSourcePointcut, overrides matchs in MethodMatcher, and considers that it can be pointed in if there are annotations on the method.

Deficiencies of spring-data-redis-cache

Although the function is very powerful, it does not solve the problem of cache refresh. If the cache expires at a certain time, there will be a large number of requests into the database, which will cause great pressure on the database.

Version 4.3 does concurrency control in this respect, but it feels perfunctory. It simply locks other requests, load s data into the cache first, and then lets other requests go out of the cache.

Later I will customize the cache refresh, and make a cache enhancement control, try not to have too many intrusions into the original system, please pay attention to it.

A little promotion

Creation is not easy, I hope you can support my open source software, and my gadgets, welcome to gitee dot star, fork, bug.

Excel General Import and Export, Supporting Excel Formula
Blog address: https://blog.csdn.net/sanri1993/article/details/100601578
gitee: https://gitee.com/sanri/sanri-excel-poi

Use template code, generate code from database, and some small tools often used in projects
Blog address: https://blog.csdn.net/sanri1993/article/details/98664034
gitee: https://gitee.com/sanri/sanri-tools-maven

Keywords: Java Redis Database Spring Excel

Added by Felex on Sun, 13 Oct 2019 12:48:15 +0300