How to use ehcache and deal with cache ineffectiveness

How to use ehcache and deal with cache ineffectiveness

what is ehcache

Ehcache is a pure Java in-process caching framework, which is fast and capable. Java's most widely-used cache framework (java's most widely-used cache)
ehcache is a standards-based, open source, high-performance cache that is easy to expand. Because it is robust, proven, full-featured, and easy to integrate with third-party frameworks and libraries. Ehcache scales from in-process caching all the way to hybrid in-process/out-of-process deployments with terabyte size caches.

(ehcache is an open source, standards-based cache that boosts performance, offloads your databases, and simplifies scalability, it’s the most
widely-used java-based cache because it’s robust, proven, full-featured, and integrates with other popular libraries and frameworks, ehcache
from in-prrocess caching, all the way to mixed in process/out-of process deployments with terabyte-sized caches.)

Advantages and Disadvantages

advantage:

 1.纯java开发,便于学习,集成进主流的spring boot
 2.不依赖中间件
 3.快速,简单
 4.缓存数据由两级: 内存和磁盘,无需担心容量问题

shortcoming:

 1.多节点不能同步,缓存共享麻烦
 2.一般在架构中应用于二级缓存(一级缓存redis ---->二级缓存ehcache  -> database)

how to use

Here we use ehcache3

Introduce dependency packages

        <!-- ehcache 缓存 -->
		<dependency>
			<groupId>org.ehcache</groupId>
			<artifactId>ehcache</artifactId>
		</dependency>
		<dependency>
			<groupId>javax.cache</groupId>
			<artifactId>cache-api</artifactId>
		</dependency>

ehcache.xml configuration

<config
		xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
		xmlns='http://www.ehcache.org/v3'
		xmlns:jsr107='http://www.ehcache.org/v3/jsr107'>

	<cache-template name="heap-cache">
			<listeners>
			</listeners>
			<resources>
				<heap unit="entries">2000</heap>
				<offheap unit="MB">100</offheap>
			</resources>
	</cache-template>

	<cache-template name="defaultCache">
		<heap unit="entries">100</heap>
	</cache-template>

	<cache alias="allViewScenesCount" uses-template="defaultCache">
		<expiry>
			<ttl unit="hours">4</ttl>
		</expiry>
	</cache>

</config>

Integrated into springboot

ehcache is integrated into springboot. Springboot startup class adds annotation @EnableCaching

@Configuration
public class CacheConfig {
    
    

    @Bean
    public CacheManager cacheManager() throws FileNotFoundException, URISyntaxException {
    
    
        return new JCacheCacheManager(jCacheManager());
    }

    @Bean
    public javax.cache.CacheManager jCacheManager() throws FileNotFoundException, URISyntaxException {
    
    
        EhcacheCachingProvider ehcacheCachingProvider = new EhcacheCachingProvider();
        URI uri = ResourceUtils.getURL("classpath:ehcache.xml").toURI();
        javax.cache.CacheManager cacheManager  = ehcacheCachingProvider.getCacheManager(uri, this.getClass().getClassLoader());
        return cacheManager;
    }
}

cache results

@Slf4j
@RestController
@RequestMapping("/api/statistics")
public class StatisticsController {
    
    

	
    @RequestMapping("/allViewScenesCount")
    public RestResult<Object> allViewScenesCount(@RequestBody StatisticsDTO statisticsDTO) {
    
    
        StatisticsViewAllVO viewAllVO = statisticsService.allViewScenesCount(statisticsDTO);
        return RestResult.ok(viewAllVO);
    }
}

@Service
@Slf4j
public class StatisticsService {
    
    
	// 这里会使用allViewScenesCount作为key, statisticsDTO的所有属性作为field, StatisticsViewAllVO的所有属性作为value
	// 这里注意StatisticsViewAllVO 需要实现 Serializable 接口,否则无法序列化到磁盘
    @Cacheable(value = "allViewScenesCount")
    public StatisticsViewAllVO allViewScenesCount(StatisticsDTO statisticsDTO){
    
    
        //do something
    }
}

principle

Request process

用户发起请求---> StatisticsController.allViewScenesCount() ---> CglibAopProxy 动态代理 ---> cacheInterceptor 缓存查询与存储 ---> 

StatisticsService.allViewScenesCount() -->  cacheInterceptor 缓存存储  -->  CglibAopProxy 代理返回结果

CglibAopProxy(spring)
-------cacheInterceptor(ehcache)
-----------StatisticsService(我们的代码)
-------cacheInterceptor(ehcache)
CglibAopProxy(spring)		

ehcache data structure

ehcache的数据结构:  key field  value  (类似redis的hash)

key: 为注解 @Cacheable(value = "diffMoneyTrend")
filed: 为@Cacheable(value = "diffMoneyTrend", key = '')的key, 如果没有则是方法的参数

spring cglib proxy

CglibAopProxy
	retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();
proxy:   CglibAopProxy
target: statistiocsService
chain:  cacheInterceptor
 @Nullable
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    
    
            Object oldProxy = null;
            boolean setProxyContext = false;
            Object target = null;
            TargetSource targetSource = this.advised.getTargetSource();

            Object var16;
            try {
    
    
                if (this.advised.exposeProxy) {
    
    
                    oldProxy = AopContext.setCurrentProxy(proxy);
                    setProxyContext = true;
                }
				// 代理目标bean,我们的statistiocsService
                target = targetSource.getTarget();
                Class<?> targetClass = target != null ? target.getClass() : null;
				// 缓存切面
                List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
                Object retVal;
				// 判断是否有其他调用链,没有,直接代理我们bean
                if (chain.isEmpty() && CglibAopProxy.CglibMethodInvocation.isMethodProxyCompatible(method)) {
    
    
                    Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                    retVal = CglibAopProxy.invokeMethod(target, method, argsToUse, methodProxy);
                } else {
    
    
					//有其他调用链 
                    retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();
                }
				// 返回结果
                retVal = CglibAopProxy.processReturnType(proxy, target, method, retVal);
                var16 = retVal;
            } finally {
    
    
                if (target != null && !targetSource.isStatic()) {
    
    
                    targetSource.releaseTarget(target);
                }

                if (setProxyContext) {
    
    
                    AopContext.setCurrentProxy(oldProxy);
                }

            }

            return var16;
        }

CacheAspectSupport cache aspect

	@Nullable
	private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
    
    
		// Special handling of synchronized invocation
		if (contexts.isSynchronized()) {
    
    
			CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
			if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
    
    
				Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
				Cache cache = context.getCaches().iterator().next();
				try {
    
    
					return wrapCacheValue(method, handleSynchronizedGet(invoker, key, cache));
				}
				catch (Cache.ValueRetrievalException ex) {
    
    
					// Directly propagate ThrowableWrapper from the invoker,
					// or potentially also an IllegalArgumentException etc.
					ReflectionUtils.rethrowRuntimeException(ex.getCause());
				}
			}
			else {
    
    
				// No caching required, only call the underlying method
				return invokeOperation(invoker);
			}
		}


		// Process any early evictions
		processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
				CacheOperationExpressionEvaluator.NO_RESULT);

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

		// Collect puts from any @Cacheable miss, if no cached item is found
		List<CachePutRequest> cachePutRequests = new ArrayList<>();
		if (cacheHit == null) {
    
    
			collectPutRequests(contexts.get(CacheableOperation.class),
					CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
		}

		Object cacheValue;
		Object returnValue;

		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
			// 没有查到缓存,调用我们的代码,执行实际逻辑
			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
		for (CachePutRequest cachePutRequest : cachePutRequests) {
    
    
			cachePutRequest.apply(cacheValue);
		}

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

		return returnValue;
	}


//CacheAspectSupport 
private Object execute(){
    
    
	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
			// 没有查到缓存,调用我们的代码,执行实际逻辑
			returnValue = invokeOperation(invoker);
			cacheValue = unwrapReturnValue(returnValue);
		}
}

@cacheable does not take effect, handle it

Note: Because the controller calls the service through the cglib proxy, only the proxy method is generated for the call: Therefore, ehcahce only takes effect for the first-level method: for example, serviceA has A and B methods, A calls the B method internally, and B plus @cacheable cache does not take effect. .

For example:

B method annotation does not take effect

@Slf4j
@RestController
@RequestMapping("/api/statistics")
public class StatisticsController {
    
    
	
    @RequestMapping("/allViewScenesCount")
    public RestResult<Object> allViewScenesCount(@RequestBody StatisticsDTO statisticsDTO) {
    
    
        StatisticsViewAllVO viewAllVO = statisticsService.A(statisticsDTO);
        return RestResult.ok(viewAllVO);
    }
}

@Service
@Slf4j
public class StatisticsService {
    
    

    public StatisticsViewAllVO A(StatisticsDTO statisticsDTO){
    
    
		// 这里调用不会有cglib动态代理,因此也就没有缓存切面了
		return b();
    }


	 @Cacheable(value = "B")
	  public StatisticsViewAllVO b(StatisticsDTO statisticsDTO){
    
    
        //do something
    }
}

Modify to make B method caching effective

@Slf4j
@RestController
@RequestMapping("/api/statistics")
public class StatisticsController {
    
    
	
    @RequestMapping("/allViewScenesCount")
    public RestResult<Object> allViewScenesCount(@RequestBody StatisticsDTO statisticsDTO) {
    
    
        StatisticsViewAllVO viewAllVO = statisticsService.A(statisticsDTO);
        return RestResult.ok(viewAllVO);
    }
}

@Service
@Slf4j
public class StatisticsService {
    
    

    public StatisticsViewAllVO A(StatisticsDTO statisticsDTO){
    
    
        // 我们强制这里用动态代理, 这里执行就会走切面了
		StatisticsService statisticsService = SpringUtil.getBean(StatisticsService.class);
		return  statisticsService.b();
    }


	 @Cacheable(value = "B")
	  public StatisticsViewAllVO b(StatisticsDTO statisticsDTO){
    
    
        //do something
    }
}

reference

https://www.jianshu.com/p/154c82073b07

https://www.ehcache.org/

https://www.ehcache.org/documentation/3.10/xml.html

Guess you like

Origin blog.csdn.net/u013565163/article/details/128308131