Showing posts with label Ehcache. Show all posts
Showing posts with label Ehcache. Show all posts

Thursday, July 8, 2010

Ehcache 2.1 Monitoring

In my previous post Spring, AspectJ, Ehcache Method Caching Aspect

I demo'ed and described how and why to cache the results of methods. Now to enhance that, I did some more investigation into the latest release of ehcache available at Terracotta

With 2.1 they released a monitoring api, to allow you to monitor cache's real time within your runtime environment, this will allow to fine tune and manage any cache you may have... be that custom caching with something like my MethodCacheAspect or your applications' Hibernate caches. To setup and use the monitoring you will need to do the following (I am setting this up on weblogic 10.3)

I placed the following 4 jars into my domain/lib, this will be different for every application server, but as long as the following are in the relative classpath you should be ok.
  • ehcache-core-2.1.0.jar
  • ehcache-probe-1.0.0.jar
  • slf4j-api-1.5.11.jar
  • slf4j-jdk14-1.5.11.jar

So now for the configuration:

The ehcache.xml, needs to be in your classpath and have the following details:


Take note of the following that is added to the cache you want to monitor.



In the start up of the application server... in my case weblogic 10.3 you will see the following:
(it is a licensed product from terracotta, and I am only using in a development environment to tune my cache settings)

INFO: Initializing EHCache CacheManager
04 Jul 2010 6:18:24 PM net.sf.ehcache.distribution.RMICacheManagerPeerListener
WARNING: Explicitly setting the listener hostname to 'localhost' is not recommended. It will only work if all CacheManager peers are on the same machine.
04 Jul 2010 6:18:25 PM org.terracotta.ehcachedx.monitor.probe.ProbeDxService startBackend
INFO: Started probe at http://localhost:51115/monitor/list
04 Jul 2010 6:18:25 PM org.terracotta.ehcachedx.license.LicenseResolver resolveLicense
WARNING: No Ehcache Monitor license key found. This monitoring probe software is not licensed for production usage, and is only licensed for development usage. See LICENSE.txt for details. A temporary key will be generated for development usage. When the temporary key expires, the Ehcache monitoring probe capability will be suspended but your ability to continue to use Ehcache will not be affected. Please contact sales@terracottatech.com to request a license.
04 Jul 2010 6:18:25 PM org.terracotta.ehcachedx.license.LicenseResolver logLicense
INFO:
--------- Ehcache Monitor license key ---------
License type = DevOnly
License number = 0
Licensee = DevOnly User
Product = Ehcache Monitor
Capabilities = monitor
Expiration date = 2010-07-07
-----------------------------------------------
04 Jul 2010 6:18:25 PM org.terracotta.ehcachedx.monitor.probe.ProbeDxService$RegisterWithServer run
INFO: Null license registered


Now that it is up and running, you can browse to:
http://localhost:51115/monitor/list

There you will get a list of methods that allow you to query you live caches, or even check the status of the CacheManager with, "probeGetCacheManagerStatus( )" very handy if you want to monitor the health of your live application. We have had a couple occasions where we we getting exceptions calling another teams' services and it took a while to discover that the "Cache Manager is not alive" exception.

The Ehcache API is REST based service. You Should be able to to use a simple RestTemplate like I used in my Spring 3 RESTFul example to manipulate and query the API, or you can just use a standard web browser:

http://localhost:51115/monitor/probeGetCacheManagerStatus
Result:
STATUS_ALIVE

http://localhost:51115/monitor/probeGetCacheNames
Result:
testCache
restCache

http://localhost:51115/monitor/probeGetCacheElementMemorySize?cache=testCache
Result:
584

The full list of functions are:

AmountOfHistoryStoredOnMonitor( )
getSampledStatisticNames( )
getSamplingHistory( )
getSamplingSeconds( )
getVersion( )
probeEvictExpiredElements( cache )
probeFlushCache( cache )
probeGetAllCachesConfigAllValues( )
probeGetAllCachesSampledStatisticValues( name )
probeGetCacheConfigAllValues( cache )
probeGetCacheConfigNames( )
probeGetCacheConfigValue( cache, name )
probeGetCacheConfigValues( cache, name )
probeGetCacheCount( )
probeGetCacheElementCount( cache )
probeGetCacheElementCountDisk( cache )
probeGetCacheElementCountMemory( cache )
probeGetCacheElementMemorySize( cache )
probeGetCacheElementMetaDataAllValues( cache, key )
probeGetCacheElementMetaDataNames( )
probeGetCacheElementMetaDataValue( cache, key, name )
probeGetCacheElementMetaDataValues( cache, key, name )
probeGetCacheKeys( cache, count, [query] )
probeGetCacheManagerName( )
probeGetCacheManagerStatus( )
probeGetCacheNames( )
probeGetReplicationMode( )
probeGetSampledStatisticAllValues( cache )
probeGetSampledStatisticHistory( cache, name, [fromDateTime], [timeInterval], [offset] )
probeGetSampledStatisticValue( cache, name )
probeGetSampledStatisticValues( cache, name )
probeRemoveAllFromCache( cache )
probeRemoveFromCache( cache, key )
probeRemoveQueryFromCache( cache, query )


Some of these could be very valuable to fine tune you caching strategy. Knowing things like "probeGetCacheElementCountDisk", "probeGetCacheElementMemorySize" and being able to monitor them may allow you to tailor your Ehcache config for optimal usage and consequently better application performance.

Also "probeFlushCache" or "probeRemoveFromCache" allow you to clear out the cache in the case of a data change without having to reinitialize / restart your application.

Monday, June 7, 2010

Spring, AspectJ, Ehcache Method Caching Aspect

I know this is not a new concept, and I am pretty sure there a couple implementations of method caching out there, possibly some much more thorough, but I feel quite strongly about it and I think that it is still one of the most beneficial things ever to come out of the AOP design philosophy. It is also something that if implemented correctly can vastly improve the performance for a lot of systems out there.

A good couple years back, I stumbled across a blog post describing the concept of a method caching interceptor, that would allow you to cache method calls without changing code and thereby make some dramatic performance improvements on existing systems. I jumped on the concept, took some code from the blog, fixed an issue or 2, changed a couple things to my requirements, demo'ed it to the system architect at the time and implemented it for the next release cycle. That bit of code has been running for about 4 years now and had a huge impact on our performance and the pressures our legacy system placed on other internal teams.

Due to the joys that are large corporate organisations, the use of the latest version of Spring and especially AspectJ are not allowed by the standards and redtape, but that allows me to write a whole new neat version without infringing on too many IP issues :) and publish it here.

Full source code, maven pom and eclipse files are available for download:
Source Code
or through SVN:
MethodCacheAspect trunk

In the project there are 3 main java files of interest:
MethodCacheAspect.java
CacheKeyStrategy.java
DefaultCacheKeyStrategy.java

The CacheKeyStrategy interface came about when I noticed that even though objects were "equal" there were 2 separate items in ehcache. This was because of 2 issues actually, order of items and just using .toString() to build up a key for ehcache. Not wanting to enforce the order or method of generating a key, I created a the interface and a default implementation. If future users want to handle it any different all they need to do is implement it and specify the implementation in Spring. I have 2 custom implementations in my live project, but for this blog posting I have just created a simple default.

note:I removed all JavaDoc just to save space, the downloadable code has docs.

package javaitzen.spring.interceptors;

public interface CacheKeyStrategy {

    String generateKey() throws IllegalStateException;

    String classForStrategy();

    void setObject(final Object object);
}


Below is the simple default key generator, just for convenience, that does a Collections.sort and uses the objects' hashcode method.

package javaitzen.spring.interceptors;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.aspectj.lang.ProceedingJoinPoint;

public class DefaultCacheKeyStrategy implements CacheKeyStrategy {

    private Logger logger = Logger.getLogger(DefaultCacheKeyStrategy.class.getName());
    private ProceedingJoinPoint pjp;

    public DefaultCacheKeyStrategy() {
    }

    public DefaultCacheKeyStrategy(final ProceedingJoinPoint pjp) {
        this.pjp = pjp;
    }

    public String generateKey() throws IllegalStateException {

        String targetName = pjp.getSignature().getDeclaringTypeName();
        String methodName = pjp.getSignature().getName();
        Object[] arguments = pjp.getArgs();

        return getCacheKey(targetName, methodName, arguments);
    }

    private String getCacheKey(final String targetName, final String methodName, final Object[] arguments) {
        StringBuilder sb = new StringBuilder();
        sb.append(targetName).append(".").append(methodName);

        for (Object arg : arguments) {
            if (arg instanceof Map< ?, ? >) {
                sb.append(expandMap((Map) arg));
            } else if (arg instanceof Collection< ? >) {
                sb.append(buildBuffer((Collection< ? >) arg));
            } else {
                sb.append(".").append(arg.hashCode());
            }
        }

        logger.log(Level.INFO, "Cache key is: [" + sb.toString() + "]");
        return sb.toString();
    }

    private StringBuilder buildBuffer(final Collection< ? > values) {
        StringBuilder bob = new StringBuilder();
        Collections.sort((List< ? >) values, new Comparator< Object >() {
            public int compare(final Object arg0, final Object arg1) {
                String thing1 = arg0.toString();
                String thing2 = arg1.toString();
                return thing1.compareTo(thing2);
            }
        });
        for(Object o : values){
            if (o != null) {
                bob.append(".").append(o.hashCode());
            }            
        }
        return bob;
    }

    private StringBuilder expandMap(final Map< ?, ? > args) {
        if (args == null) {
            return new StringBuilder();
        }

        List< Object > values = new ArrayList< Object >();
        for (Entry< ?, ? > entry : args.entrySet()) {
            values.add(entry.hashCode());
        }
        return buildBuffer(values);
    }

   public void setObject(final Object invocation) {
        this.pjp = (ProceedingJoinPoint) invocation;
    }

    public String classForStrategy() {
        return "*";
    }
}


Now for the juicy bit, the aspect checks if there are any cache key strategies compares types with information from the ProceedingJoinPoint, it extracts the relevant information and generates a key. It then checks if there is value for that key in the cache and returns that value if relevant, else it executes the method with "pjp.proceed()" and caches the result, so that for the next time the same method is called with the same parameters it can just be returned from the cache. I cant remember the exact numbers but with implementing this between our legacy system and another team we cut our daily calls to that teams web service from something like 65000 to around 6000.

package javaitzen.spring.interceptors;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class MethodCacheAspect {

    private Logger logger = Logger.getLogger(MethodCacheAspect.class.getName());
    private Cache cache;
    private CacheKeyStrategy defaultKeyStrat;
    private List< CacheKeyStrategy > keyStrategies = new LinkedList< CacheKeyStrategy >();

    public void setCacheKeyStrategies(final List< CacheKeyStrategy > cacheKeys) {
        this.keyStrategies = cacheKeys;
    }

    public void addCacheKeyStrategy(final CacheKeyStrategy cacheKey) {
        this.keyStrategies.add(cacheKey);
    }

    public Object aroundAdvice(final ProceedingJoinPoint pjp) throws Throwable {

        Object[] arguments = pjp.getArgs();
        Object result;
        StringBuilder cacheKey = new StringBuilder();
        defaultKeyStrat = new DefaultCacheKeyStrategy(pjp);

         if (!keyStrategies.isEmpty()) {
            logger.log(Level.INFO, "Have a Key Strategy to use...");
            for (CacheKeyStrategy strat : keyStrategies) {
                if ((arguments != null) && (arguments.length != 0)) {
                    logger.log(Level.INFO, "Have Arguments...");
                    for (Object arg : arguments) {
                        if (Class.forName(strat.classForStrategy()).isInstance(arg)) {
                            strat.setObject(arg);
                            logger.log(Level.INFO, "Using Strategy...");
                            cacheKey.append(strat.generateKey());
                        }
                    }
                }
            }
        }

        if (cacheKey.length() == 0) {
            logger.log(Level.INFO, "Using Default...");
            cacheKey.append(defaultKeyStrat.generateKey());
        }
        Element element = cache.get(cacheKey.toString());

        // not in cache
        if (element == null) {
            result = pjp.proceed();
            if (result != null && !(result instanceof Serializable)) {
                throw new RuntimeException("[" + result.getClass().getName() + "] is not Serializable");
            }
            logger.log(Level.INFO, ">>> caching result - " + cacheKey);
            element = new Element(cacheKey.toString(), (Serializable) result);
            cache.put(element);
        } else {
            logger.log(Level.INFO, ">>> returning result from cache");
            return element.getValue();
        }

        return result;
    }

    public Cache getCache() {
        return cache;
    }

    public void setCache(final Cache cache) {
        this.cache = cache;
    }

}


In the application context below you will notice execution(* theBusinessMethod(..)). That only attaches the aspect for theBusinessMethod, in my test class in the downloadable source.

Just for quick reference here are a couple more examples from the Spring docs:
The execution of any public method:
execution(public * *(..))
The execution of any method with a name beginning with "set":
execution(* set*(..))
The execution of any method defined by the AccountService interface:
execution(* com.xyz.service.AccountService.*(..))
The execution of any method defined in the service package:
execution(* com.xyz.service.*.*(..))
The execution of any method defined in the service package or a sub-package:
execution(* com.xyz.service..*.*(..))

Application Context:



 

 
  
   
  
 

 
  
   
   
  
 

 
 
  
   classpath:javaitzen/spring/interceptors/ehcache.xml
   
  
  
 

 
 
  
   
  
  
   testCache
  
 





And finally the ehcache.xml:

 
 

 


Building KubeSkippy: Learnings from a thought experiment

So, I got Claude Code Max and I thought of what would be the most ambitious thing I could try "vibe"? As my team looks after Kuber...