O Cache de Segundo Nível no Hibernate – Parte 3

Neste artigo mostraremos como configurar um cache para atuar de forma replicada, analisando a influência que as otimizações desenvolvidas têm sobre o desempenho.

Fique por dentro
Dando continuidade à segunda parte do artigo, na qual avaliamos caches em disco e o overhead de serialização associado com as estruturas do Cache de 2º Nível (C2N) do Hibernate, nesta terceira parte veremos como utilizar as classes otimizadas desenvolvidas anteriormente.

Finalizando esta série de artigos do C2N, mostraremos também como configurar um cache para atuar de forma replicada, analisando a influência que as otimizações desenvolvidas têm sobre o desempenho do cache e volume do tráfego de dados na rede.

Deste modo, este artigo é útil para desenvolvedores e arquitetos que utilizam o EhCache como solução de C2N e pretendem estender a estrutura de cache para sincronizar dados entre diversas JVMs, entendendo em quais situações a replicação pode ou não ser adequada e qual impacto é gerado na rede pela transmissão de dados.

Ao longo desta série de artigos a respeito do C2N focamos em detalhes importantes que afetam de forma significativa o desempenho de leitura e escrita no EhCache. Verificamos na parte 2 do artigo que ao passar a armazenar os dados do C2N em disco, os recursos passarão a ser serializados, o que adiciona uma nova camada de I/O e processamento para consultar ou inserir elementos. Como alternativa para melhorar a taxa de transferência entre disco e memória, foram propostas alterações estruturais nas classes do Hibernate que compõem as chaves e valores que são mantidos em cache, de forma a promover uma representação mais compacta e eficiente nas traduções Objeto ↔ stream de bytes.

Nesta última parte, mostraremos como utilizar as classes desenvolvidas anteriormente sem precisar alterar o código fonte do Hibernate, empregando um Java Agent para transformar, em tempo de execução, as classes do C2N. Aproveitando-se das otimizações de serialização promovidas pelas representações compactas, vamos avaliar como as mesmas influenciam o comportamento do EhCache quando configurado para replicar seus dados entre diversas JVMs.

Configurando e instalando o Java Agent na JVM

Um Java Agent permite que sejam registrados na JVM objetos do tipo ClassFileTransformer (do pacote java.lang.instrument), que capturam o evento de carregamento de classes e possibilita que as mesmas sejam transformadas antes de serem efetivamente apresentadas para a aplicação.

A Figura 1 mostra o processo de ClassLoading quando um Java Agent é conectado à JVM e registra um transformador de classes. Toda classe que for carregada após a instalação do agente passará pelo ClassFileTransformer, que pode decidir se vai ou não modificá-la.

Figura 1. ClassLoading com transformação.

Um Java Agent nada mais é que uma classe empacotada em um arquivo JAR, o qual deve especificar no arquivo META-INF/MANIFEST.MF algumas propriedades para que a JVM reconheça e instale o agente.

O agente que vamos utilizar define quatro propriedades, indicadas na Listagem 1.

Listagem 1. Configuração do arquivo de manifesto para o Java Agent. Agent-Class: br.jm.agent.HibernateAgent Premain-Class: br.jm.agent.HibernateAgent Can-Redefine-Classes: false Can-Retransform-Classes: false

As classes declaradas como Agent-Class e Premain-Class determinam quem irá invocar os métodos agentmain() e premain() exibidos na Listagem 2. A chamada desses métodos é realizada diretamente pela JVM e funciona como a captura de um evento que disponibiliza uma instância de java.lang.instrument.Instrumentation, a qual permite que sejam registrados transformadores de classe através do método addTransformer(). Não é possível invocar diretamente estes métodos, pois é a API de instrumentação quem os executa quando necessário, passando os argumentos 'corretos'!

Na Listagem 1 definimos a mesma classe, HibernateAgent, para atender ambos os métodos, agentmain() e premain(), e sua implementação completa pode ser vista na Listagem 2.

Listagem 2. Implementação do Java Agent public class HibernateAgent{ static boolean registered; public static void agentmain(String args, Instrumentation inst){ //Ações quando o agente é instalado. No caso, apenas instalar o //otimizador se já não tiver sido instalado em premain if(!registered){ inst.addTransformer(new HibernateTransformer()); registered=true; } } public static void premain(String args, Instrumentation inst){ //Ações antes do método main de uma aplicação executar. No caso, //apenas instalar o otimizador inst.addTransformer(new HibernateTransformer()); registered=true; } //Apenas para instalação em tempo de execução public statics synchronized void install(){ if(registered){ return; } VirtualMachine vm = getVM(); String jar = createTempJar(); vm.loadAgent(jar); vm.detach(); } static VirtualMachine getVM() throws AttachNotSupportedException, IOException { String vmName = ManagementFactory.getRuntimeMXBean().getName(); int p = vmName.indexOf('@'); String pid = vmName.substring(0, p); return VirtualMachine.attach(pid); } //cria um arquivo jar com as informações do agente static String createTempJar() throws IOException { final File file = new File(System.getProperty("user.home"), "agent.jar"); if (file.exists()) { file.delete(); } file.createNewFile(); try (final ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(file));// final PrintWriter writer = new PrintWriter (new OutputStreamWriter(zout))) { zout.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); writer.println("Agent-Class: br.jm.agent.HibernateAgent"); writer.println("Premain-Class: br.jm.agent.HibernateAgent"); writer.println("Can-Redefine-Classes: false"); writer.println("Can-Retransform-Classes: false"); } return file.getAbsolutePath(); } }"

[...] continue lendo...

Artigos relacionados