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
-
maven joins the jar package
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
Configure redis
spring.redis.host=127.0.0.1
-
Open redis-cache
@EnableCaching
-
@ 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