Java Caching System

por


Publicado en: marzo 9, 2018



En este artículo vamos a explicar el uso del Java Caching System (JCS), como una opción para implementar un cache en nuestras aplicaciones.

Problemática

En ocasiones los sistemas que desarrollados hacen consultas constantes a datos que nunca o muy rara vez cambian en el tiempo. El ejemplo más evidente son las listas de códigos, o de países, o de provincias/estados, entre otros. Usualmente nuestro código invoca algún tipo de servicio (por ejemplo: un EJB o un WebService) para obtener los valores que tenemos almacenados -tradicionalmente- en una base de datos.

Pero hay momentos, en que luego de analizar la aplicación, se concluye que es necesario minimizar estas consultas para suprimir un ‘cuello de botella’. Para atender este requerimiento se hace necesario emplear un mecanismo de cache, esto nos permita tener disponibles los datos que son accesados de manera más frecuente. En este artículo usaremos el Java Caching System JCS de Apache Commons.

Java Caching System


Java Caching System o JCS, es sistema de cache distribuido que está escrito en Java y es parte de Apache Commons. Como es general para casi todas las soluciones de cache; resulta óptimo en situaciones donde se tienen muchas lecturas y pocas escrituras.

JCS es mucho más que un simple cache en memoria; pues tiene elementos muy avanzados como: expiración de los datos, auto descubrimiento por UDP, una configuración muy granular, dependencias mínimas, entre muchas otras bondades que pueden leer en el sitio oficial de JCS.

Lo que es importante tener presente, es que JCS es un cache compuesto y que existen 4 tipos de cache que pueden emplearse en cualquier región: memoria, disco, lateral y remoto. Además debemos tener claro que JCS es un cache de elementos y que estos son referenciados por medio de una llave; muy parecido a un hashtable. Finalmente, cada colección de mapas se referencian por nombre y estos mapas se les conoce como regiones, las cuales podemos configurar de manera independiente.

¿Cómo usarlo?

Primeramente debemos agregar a nuestro POM las siguientes dependencias:

  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-jcs-core</artifactId>
    <version>2.2</version>
  </dependency>

  <dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
  </dependency>

Luego, debemos crear un archivo llamado cache.ccf, como el que se muestra de seguido.

# DEFAULT CACHE REGION
# sets the default aux value for any non configured caches
jcs.default=DC
jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
jcs.default.cacheattributes.MaxObjects=1000
jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
jcs.default.elementattributes.IsEternal=false
jcs.default.elementattributes.MaxLife=3600
jcs.default.elementattributes.IdleTime=1800
jcs.default.elementattributes.IsSpool=false
jcs.default.elementattributes.IsRemote=false
jcs.default.elementattributes.IsLateral=false


# CACHE REGIONS AVAILABLE
# La region de ciudades, por ejemplo,
jcs.region.ciudadesCache=DC
jcs.region.ciudadesCache.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
jcs.region.ciudadesCache.cacheattributes.MaxObjects=500
jcs.region.ciudadesCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
jcs.region.ciudadesCache.elementattributes.IsEternal=true
jcs.region.ciudadesCache.elementattributes.MaxLife=7200
jcs.region.ciudadesCache.elementattributes.IdleTime=1800
jcs.region.ciudadesCache.elementattributes.IsSpool=false
jcs.region.ciudadesCache.elementattributes.IsRemote=false
jcs.region.ciudadesCache.elementattributes.IsLateral=false

Observemos que creamos una región llamada ciudadesCache que contiene un cache de Ciudades. Las mismas rara vez cambian de nombre, por lo cual definimos que:

  • MaxObjects: tiene un máximo de 500 elementos
  • IsEternal: no expiran, es decir; una vez colocados en el cache no tienen un plazo para expirar.
  • MaxLife: acá no se usa, pues indicamos que los elementos son eternos, pero este parámetro nos permitiría controlar la cantidad de segundos que un elemento puede vivir en el cache antes de ser retirado. Lo cual es útil en el caso de de objetos que se actualizan con cierta frecuencia.
  • IdleTime: es el tiempo en segundos que un elemento puede estar en el cache sin ser accedido.
  • IsSpool: quiere decir si estos elementos se pueden ir o no a un cache en disco.
  • IsRemote:quiere decir si estos elementos se pueden enviar o no a un servidor remoto.
  • IsLateral: quiere decir si estos elementos se pueden distribuir lateralmente.

A nivel del código Java; es bastante simple de emplear. Primero instanciamos la región, usando el nombre que definimos en el archivo cache.ccf. Observen que en este ejemplo, el cache tiene como llave un String y como elemento o valor un String. Pero perfectamente puede ser una clase más elaborado.

 private CacheAccess<String, String> ciudadesCache = JCS.getInstance("ciudadesCache");

Para guardar un elemento en el cache; solo hacemos uso del método put

    ciudadesCache.put(codigo, nombre);

Y para verificar si un elemento esta en el cache, hacemos lo siguiente:

    ciudadesCache.get(codigo);

Si eso no retorna null, quiere decir que el elemento no está en el cache.

Usando lo anterior; podemos programar un algoritmo similar al siguiente pseudocódigo:

   buscar(codigo) {
    if !existeEnCache(codigo) then
        invocar API para traer el dato (sea un API REST, EJB, etc)
        agregarEnCache(codigo, dato);
    else
       traerCache(codigo)
    end if;
  }

De esta forma, invocaciones posteriores harían uso de este cache y evitarían accesar a un recurso externo.

¿Cómo sabemos que el cache esta siendo realmente utilizado?

JCS provee de un JMXBean que nos permite tomar métricas respecto al uso de cada región definida en tu archivo cache.ccf. Entonces, una buena manera de exponer esa información es por medio de un REST API. Les presento el código seguidamente:

@Path("cache")
public class CacheResource {

    @Inject
    private CacheService cache;
    private static final Logger LOG = Logger.getLogger(CacheResource.class.getName());

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response estadisticas() {

        JsonObjectBuilder builder = Json.createObjectBuilder();
        JCSAdminBean jcsBean = new JCSAdminBean();
        try {
            JsonArrayBuilder jsonArrayBuilderInfo = Json.createArrayBuilder();
            CacheRegionInfo[] listSelect = (CacheRegionInfo[]) jcsBean.buildCacheInfo();
            JsonObject jsonObject;
            for (CacheRegionInfo record : listSelect) {
                jsonObject = Json.createObjectBuilder()
                        .add("cacheName", record.getCacheName())
                        .add("cacheSize", record.getCacheSize())
                        .add("byteCount", record.getByteCount())
                        .add("cacheStatus", record.getCacheStatus())
                        .add("hitCountRAM", record.getHitCountRam())
                        .add("hitCountAux", record.getHitCountAux())
                        .add("missCountNotFound", record.getMissCountNotFound())
                        .add("missCountExpired", record.getMissCountExpired())
                        .build();
                jsonArrayBuilderInfo.add(jsonObject);
            }

            builder.add("info", jsonArrayBuilderInfo.build());

        } catch (Exception ex) {
            Logger.getLogger(CacheResource.class.getName()).log(Level.SEVERE, null, ex);
        }

        return Response.ok(builder.build()).build();

    }

Y una invocación a este REST nos brindaría los datos de uso de cada región; con lo cual fácilmente podemos tomar decisiones respecto a su efectividad y permitirnos aplicar ajustes basados en su comportamiento, como el tamaño de la región, tiempos de vigencia, entre otros. Por ejemplo:

{
  "info": [{
    "cacheName": "ciudadesCache",
    "cacheSize": 327,
    "byteCount": 1308,
    "cacheStatus": "ALIVE",
    "hitCountRAM": 199472,
    "hitCountAux": 0,
    "missCountNotFound": 334,
    "missCountExpired": 0
  }]
}
Conclusión

Esperamos que con este artículo puedan conocer de manera básica el Java Caching System y que ustedes determinen de que manera pueden beneficiarse de este en sus desarrollos actuales o futuros.

Referencias

Autor del Artículo

Gerardo Arroyo Arce

Gerardo Arroyo Arce

Chief Technology Officer.

  • Ingeniero en Software. ITCR.
  • Master en Computación en Informática. UCR.
  • AWS Certified Solutions Architect - Associate
  • Oracle Certified Expert, JEE 6 Web Services Developer.
  • Oracle Certified Professional, Java SE 7 Programmer.
  • Oracle WebLogic Server 12c Certified Implementation Specialist.