Alterando as portas do JBoss AS 5.0

Bem, essa aqui é uma dica simples e rápida :). No meu último post falei sobre clusters, e por isso mencionei o serviço de bindings do jboss 4.2. Meu amigo Elton, fez uma pergunta se referindo ao 5.0, bem, aqui vai a resposta:

No JBoss AS 5.0, clustering ficou quase identico ao 4.2 (obrigado RH pela estratégia em time que esta vencendo agente não mexe), uma das poucas difereças é o VFS e o novo MicroContainer que injeta os serviços.

Assim sendo, toda configuração de bindigins, ficou centralizada no arquivo $SERVER_NAME/conf/bootstrap/bindings.xml

Abaixo um trecho

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="UTF-8"?>
 
<deployment xmlns="urn:jboss:bean-deployer:2.0">
 
   <classloader><inject bean="bindings-classloader:0.0.0"/></classloader>
 
   <classloader name="bindings-classloader" xmlns="urn:jboss:classloader:1.0" export-all="NON_EMPTY" import-all="true">
      <root>${jboss.common.lib.url}jboss-bindingservice.jar</root>
   </classloader>
 
   <bean name="ServiceBindingManager" class="org.jboss.services.binding.ServiceBindingManager">
 
      <annotation>@org.jboss.aop.microcontainer.aspects.jmx.JMX(name="jboss.system:service=ServiceBindingManager", exposedInterface=org.jboss.services.binding.ServiceBindingManagerMBean.class, registerDirectly=true)</annotation>
 
      <constructor>
         <!-- The name of the set of bindings to use for this server -->
         <parameter>${jboss.service.binding.set:ports-default}</parameter>
 
         <!-- The named sets of bindings -->
         <parameter>
            <bean name="ServiceBindingStore" class="org.jboss.services.binding.impl.PojoServiceBindingStore">
 
               <!-- Base bindings that are used to create bindings for each set -->
               <property name="standardBindings"><inject bean="StandardBindings"/></property>
 
               <!-- The sets of bindings -->
               <property name="serviceBindingSets">
                  <set>
                     <inject bean="PortsDefaultBindings"/>
                     <inject bean="Ports01Bindings"/>
                     <inject bean="Ports02Bindings"/>
                     <inject bean="Ports03Bindings"/>
                  </set>
               </property>
            </bean>
         </parameter>
      </constructor>
 
   </bean>

Note que nas linhas 27-32 estão definidas as configurações de portas, logo abaixo (omitido no post) estão cada uma das configurações com os respectivos nomes (ports-default,ports-01,ports-02,ports-03).

Para alterar as portas da sua instância, basta modificar a linha 17, após boss.service.binding.set. Pronto, agora ao iniciar seu JBoss ele já estará rodando em outra configuração de porta. Bem mais simples não é? Por default o AS agora já executa o binding service.

Espero que o pequeno post seja de ajuda.

Abraços

Cluster-Safe Timer no JBoss - sem usar EJBTimers ;)

O uso de timers no jboss não é nenhum mistério, você consegue achar vários links na internet explicando como criar um EJB Timer. Entretanto, quando temos um cluster de servidores encontramos um problema: Qual instância deverá executar o serviço de timer. Este tipo de serviço é do tipo Highlander (somente um pode existir), pois caso contrário podemos ter problemas de concorrência.

O Jboss oferece duas maneiras de se disponibilizar um serviço único dentro de um cluster (e isso é bem anterior a proposição de @Singleton do EJB 3.1). A primeira, seria colocarmos nosso arquivo no diretorio HA-Singleton, mas nem sempre funciona, uma vez que algumas dependências podem ainda não terem sido resolvidas no momento da inicialização da sua aplicação. A outra maneira, é criar uma dependência com o BarrierController.

Existe um link no wiki do jboss que demonstra como criar o serviço de timer em cluster. O que vou mostrar aqui é uma alternativa usando MDBs que na minha opinião é muito mais simples que a usando Timers, você pode comparar ambas e ver a diferença.

Criando um MDB sem JMS

Message Driven Beans são amplamente utilizados em aplicações que usam batch processing de forma que possam ser executados de forma assíncrona. A sua forma mais tradicional é ouvindo uma fila JMS, mas o que muitos não sabem, é que um MDB pode ouvir outros eventos, desde que exista um Resource Adapter configurado para ele. Você pode criar um RA para um CICS, ERP ou outro sistema legado que possa notificar um MDB por exemplo.

O Jboss oferecer um RA chamado quartz-ra.rar que permite que eventos gerados pelo quartz possam notificar um MDB. Com esta abordagem teremos o nosso timer, so que ao invés de escutarmos uma fila JMS vamos escutar um Job quartz.

O código de nosso MDB se encontra na listagem abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.furiousbob.timer;
 
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
 
public class ConsoleTimer implements Job {
 
	public void execute(JobExecutionContext ctx) throws JobExecutionException {
		System.out.println("WAKE UP!!!!!!");
 
	}
 
}

A primeira coisa que vocês devem ter notado é que esta classe não implementa javax.jms.MessageListener, pode soar estranho, mas como havia dito um MDB não precisa depender de uma fila JMS e como não vamos ouvir uma fila, então por que devemos implementar uma interface de JMS?

Por isso implementamos então a interface de execução de jobs do quartz.

Neste primeiro exemplo não vamos usar anotações. Isso pois acredito que a frequência de execução deveria ficar em um arquivo xml e não dentro do MDB, uma segunda listagem será apresentada mostrando a alternativa com anotações

Em termos de código é só isso :), precisamos agora configurar nosso ejb-jar.xml para informar que usamos um MDB:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd">
<enterprise-beans>
	<message-driven>
		<ejb-name>ConsoleTimer</ejb-name>
		<ejb-class>com.furiousbob.timer.ConsoleTimer</ejb-class>
		<messaging-type>org.quartz.Job</messaging-type>
		<transaction-type>Container</transaction-type>
		<activation-config>
			<activation-config-property>
				<activation-config-property-name>cronTrigger</activation-config-property-name>
				<activation-config-property-value><![CDATA[30 * * * * ?]]></activation-config-property-value>				
			</activation-config-property>
		</activation-config>
	</message-driven>
</enterprise-beans>
</ejb-jar>

O que temos de diferente neste arquivo são as linhas 10 e 12-17. Na linha 10 informamos ao container que o tipo de interface que vamos usar, em anotações teríamos algo como @MessageDriven(messageListenerInterface=Job.class). As linhas 12-17 são configurações do bean, onde normalmente informaríamos o nome da queue/topic a ser consumida, aqui informamos a expressão do cronTrigger (para maiores informações consulte este link)

Agora precismos informar ao JBoss qual resource adapter vamos usar para este bean, para isso modifique o arquivo jboss.xml:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<jboss>
<enterprise-beans>
	<message-driven>
		<ejb-name>ConsoleTimer</ejb-name>
		<resource-adapter-name>quartz-ra.rar</resource-adapter-name>
	</message-driven>
</enterprise-beans>
</jboss>

Pronto, agora basta fazer deploy de seu novo MDB que é na verdade um timer. Você irá notar o console tendo saídas a cada minuto sempre aos 30s de cada minuto.

Cluster Safe Timers

Ok, agora você tem seu timer, ele acorda a cada X segundos, mas você precisa de um cluster de alta disponibilidade, ou seja você precisa que o serviço esteja sempre disponível, mesmo quando uma máquina falhe. A primeira idéia seria colocar este serviço replicado em cada nó do cluster, como fazemos com um SLSB ou SFSB. O problema dessa abordagem é que no caso de SLSB como não possuem estado tanto faz onde o cliente “aterrisa”, no caso de SFSB o JBoss (e sinceramente, o mecanismo de cluster mais fantástico de todos App Servers que conheço), replica o estado entre os nós usando JBossCache/JGroups para você. Agora e no caso de um serviço que pode existir apenas uma instância em execução? Neste caso, adicionamos ao nosso serviço uma dependência com o servico BarrierController como o arquivo jboss.xml modificado abaixo:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<jboss>
<enterprise-beans>
	<message-driven>
		<ejb-name>ConsoleTimer</ejb-name>
		<resource-adapter-name>quartz-ra.rar</resource-adapter-name>
		<depends>jboss.ha:service=HASingletonDeployer,type=Barrier</depends>  
	</message-driven>
</enterprise-beans>
</jboss>

Note que agora temos uma mudança na linha 7 indicando a dependência com o serviço. Pronto, seu serviço esta pronto para rodar em clusters de forma consistente. Sempre que um nó cair, outro toma seu lugar e faz o deploy do serviço.

Esta solução é mais simples que a apresentada pelo wiki do jboss, e já usei em produção em sistemas e garanto que funciona ;)

Tirando onda com seus amigos

O que vou mostrar aqui é um pequeno passo a passo de como subir duas instâncias do jboss em cluster, na mesma máquina, com replicação de estado e com o serviço de timer, isto aqui é só para você mostrar aos seus amigos como é simples criar clusters com jboss :)

Vamos usar o jboss 4.2.3.GA rodando em um Ubuntu 8.10. O primeiro passo é você fazer uma cópia da pasta all pois teremos 2 configurações distintas, eu normalmente copio a pasta all para node1 e node2:

vinicius@cybertron:~/java/jboss-4.2.3.GA/server$ cp -R all/ node1
vinicius@cybertron:~/java/jboss-4.2.3.GA/server$ cp -R all/ node2

Bem, vamos subir 2 instâncias de jboss na mesma máquina, aqui você tem 2 alternativas:

  • Subir cada instância em IPs distintos, mas mantendo as portas
  • Alterar as portas de uma instância e subir ambas na mesma interface


  • Vamos adotar a segunda abordagem. E vamos fazer o seguinte, node1 irá subir nas portas padrão (8080,1099,8009) e node2 vai subir em portas alternativas (8180,1199,8109). Se você não sabe como fazer, vai aqui mais uma dica

    Alterando as portas do JBossAS



    Como vamos alterar as portas do node2, entre em node2/conf e edite o arquivo jboss-service.xml. Procure pelo trecho mostrado abaixo:

    1
    2
    3
    4
    5
    6
    7
    8
    
     <mbean code="org.jboss.services.binding.ServiceBindingManager"
         name="jboss.system:service=ServiceBindingManager">
         <attribute name="ServerName">ports-01</attribute>
         <attribute name="StoreURL">${jboss.home.url}/docs/examples/binding-manager/sample-bindings.xml</attribute>
         <attribute name="StoreFactoryClassName">
           org.jboss.services.binding.XMLServicesStoreFactory
         </attribute>
       </mbean>

    O trecho apresentado estará comentado, basta remover o comentário do mesmo. O que isto faz é ler um arquivo de bindings (dê uma lida no arquivo para entender sua estrutura) e então atribuir as portas de serviço do jboss de forma dinâmica. Note que no arquivo existem várias pre-configurações (ports-default, ports-01,ports-02….) de forma a simplificar sua vida :)

    Bem, agora quando você subir a instância node2 ela não terá conflito de portas com node1.

    Cluster e Multicast



    Toda comunicação entre os nós do cluster no jboss é feita por multicasting, portanto você precisa habilitar uma rota de multicast em seu linux (no windows isso não é necessário). Eu recomendo que vocês dêem uma lida neste artigo, mas para simplificar as coisas vou passar o comando que devem executar em seu linux ;)


    route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0

    O -net é necessário apenas em algumas distro (no ubuntu é necessário)

    Pronto, agora você esta pronto para executar suas instâncias de jboss. Abra duas janelas de terminal e execute os comando abaixo no diretório bin/

    vinicius@cybertron:~/java/jboss-4.2.3.GA/bin$ ./run.sh -c node1
    vinicius@cybertron:~/java/jboss-4.2.3.GA/bin$ ./run.sh -c node2
    

    Para verificar se suas instâncias estão se comunicando procure pela mensagem abaixo em um dos consoles

    -------------------------------------------------------
    GMS: address is 127.0.0.1:42880
    -------------------------------------------------------
    10:55:32,391 INFO  [DefaultPartition] Number of cluster members: 2
    10:55:32,391 INFO  [DefaultPartition] Other members: 1
    10:55:32,391 INFO  [DefaultPartition] Fetching state (will wait for 30000 milliseconds):
    10:55:32,504 INFO  [DefaultPartition] state was retrieved successfully (in 112 milliseconds)
    10:55:32,622 INFO  [HANamingService] Started ha-jndi bootstrap jnpPort=1200, backlog=50, bindAddress=/127.0.0.1
    10:55:32,627 INFO  [DetachedHANamingService$AutomaticDiscovery] Listening on /127.0.0.1:1102, group=230.0.0.4, HA-JNDI address=127.0.0.1:1200
    10:55:32,877 INFO  [TreeCache] No transaction manager lookup class has been defined. Transactions cannot be used
    10:55:33,235 INFO  [STDOUT]
    -------------------------------------------------------
    GMS: address is 127.0.0.1:56144
    -------------------------------------------------------
    

    Note a existência de 2 clusters :)

    Agora pegue seu arquivo modificado com a dependência do BarrierController e faça deploy em uma instância, depois realize o procedimento na outra instância. Note que em apenas um dos consoles o timer será executado. Agora, mate o processo no console que esta rodando o serviço, note que logo após ter matado o serviço o outro console irá assumir controle e registrará a seguinte mensagem:

    10:59:27,627 INFO  [EJBContainer] STARTED EJB: com.furiousbob.timer.ConsoleTimer ejbName: ConsoleTimer
    

    A partir daí o novo nó será responsável pela execução do servico. Muito legal né? Espero que tenham curtido o post.

    Grande Abraço

    Coletando métricas da sua aplicação Hibernate

    Bem, esta é uma dica que apesar de simples, muita gente não conhece. O Hibernate pode ser configurado para gerar estatísticas de uso da SessionFactory. Em meus projetos já é comum rodar estas estatísticas uma vez que o projeto entra em homologação e ate mesmo em produção. Estas estatísticas vão ajudá-lo em alguns pontos:

    • Encontrar queries que podem estar consumindo muito tempo
    • Identificar possíveis objetos que podem ser colocados em cache
    • Verificar se os caches de 2nd level estão realmente sendo utilizados
    • Identificar possíveis campos que podem ser definidos como índices em suas tabelas

    Por maior que seja nosso esforço como arquitetos de software, é muito difícil prevermos quais consultas serão mais usadas, quais objetos deveremos manter em cache, e quando sua equipe for grande, por mais que você tente, peça, xingue, sempre vai existir um desenvolvedor que não executa as queries no Hibernate tools e verifica se o SQL gerado é realmente a versão mais otimizada. Se eu ganhasse 1 real para cada join desnecessário que já encontrei em queries de projetos quando faço revisão de código, eu provavelmente poderia adiantar minha aposentadoria.

    Fica aqui então uma forma de como verificar as estatísticas do hibernate. A abordagem que vou adotar é de disponibilizar um ManagedBean que irá exportar como serviço o HibernateStatistics. Vamos lá.

    A aplicação de exemplo a ser monitorada, é uma aplicação de aprovação de horas (usando seam + jbpm_ que foi demonstrada neste blog neste link

    Primeiro precisamos da nossa interface de MBean:

    package com.furiousbob.hibernate.stats;
     
    public interface HibernateStatsMBean {
    	public void start() throws Exception;
    	public void stop() throws Exception;
    	public void create()throws Exception;
    	public void destroy();
    }

    Bem, alguns poderão questionar aqui que poderíamos usar o ServiceMBeanSupport do JBoss, mas eu preferi manter a forma tradicional de mbeans ;)

    Agora nossa implementação:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    
    package com.furiousbob.hibernate.stats;
     
    import javax.management.MBeanServer;
    import javax.management.ObjectName;
    import javax.naming.InitialContext;
    import javax.persistence.EntityManager;
     
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.jmx.StatisticsService;
    import org.jboss.mx.util.MBeanServerLocator;
    import org.jboss.system.ServiceMBeanSupport;
     
     
    public class HibernateStats implements HibernateStatsMBean {
     
    	private final String  SessionFactoryName = "java:/AprovacaoHorasSessionFactory";
     
     
    	public void start() throws Exception {
    		InitialContext ctx = new InitialContext();
    		SessionFactory sessionFactory = (SessionFactory)ctx.lookup(SessionFactoryName);
    		MBeanServer server = MBeanServerLocator.locateJBoss();
    		ObjectName on = new ObjectName("Hibernate:type=statistics,application=AprovacaoHoras");
    		StatisticsService service = new StatisticsService();
    		service.setSessionFactory(sessionFactory);
    		server.registerMBean(service, on);
    	}
     
    	public void stop() throws Exception {
    		MBeanServer server = MBeanServerLocator.locateJBoss();
    		ObjectName on = new ObjectName("Hibernate:type=statistics,application=AprovacaoHoras");
    		server.unregisterMBean(on);
    	}
     
    	public void create() throws Exception {
    	}
     
    	public void destroy() {
    	}

    Como podem notar é realmente muito simples registrar o servico de stats do Hibernate. Note que estamos usando o nome da SessionFactory de forma fixa dentro do código, você poderia por exemplo, definir isto como um atributo de seu MBean de forma que este código possa ser usado em várias aplicações. O nome do MBean também reflete o nome da aplicação a ser monitorada, e é outro candidato a se tornar uma propriedade configurável.

    A última parte de nosso exemplo é o arquivo jboss-service.xml que deve ser colocado na pasta META-INF de sua aplicação

    1
    2
    3
    4
    5
    6
    7
    8
    
    <?xml version="1.0" encoding="UTF-8"?>
    <server>
     
       <mbean name="MyApp:Hibernate=Stats" code="com.furiousbob.hibernate.stats.HibernateStats">
     		<depends>jboss:service=Naming</depends>
     		<depends>persistence.units:ear=AprovacaoHoras.ear,unitName=AprovacaoHoras</depends>
       </mbean>
    </server>

    Este arquivo irá informar que nosso Mbean possui uma dependência com a PersistenceUnit. E esta também deve sofrer uma pequena modificação, devemos adicionar as propriedades abaixo em nosso arquivo de configuração de persistencia:

    <property name="hibernate.generate_statistics" value="true"/>
    <property name="hibernate.session_factory_name" value="java:/AprovacaoHorasSessionFactory"/>

    Agora precisamos de empacotar nossas classes como um Service ARchive (SAR). Um SAR é nada mais que um JAR com o jboss-service.xml em seu META-INF. Ao final da operação você deverá ter um arquivo SAR com a seguinte estrutura:

    vinicius@cybertron:~/workspace/HibernateStats/dist$ jar -tvf HibernateStats.sar
         0 Wed Mar 04 09:09:40 BRT 2009 META-INF/
       106 Wed Mar 04 09:09:38 BRT 2009 META-INF/MANIFEST.MF
       305 Wed Mar 04 09:02:46 BRT 2009 META-INF/jboss-service.xml
         0 Wed Mar 04 09:03:02 BRT 2009 com/
         0 Wed Mar 04 09:03:02 BRT 2009 com/furiousbob/
         0 Wed Mar 04 09:03:02 BRT 2009 com/furiousbob/hibernate/
         0 Wed Mar 04 09:03:02 BRT 2009 com/furiousbob/hibernate/stats/
      2082 Wed Mar 04 09:03:34 BRT 2009 com/furiousbob/hibernate/stats/HibernateStats.class
       290 Wed Mar 04 09:03:02 BRT 2009 com/furiousbob/hibernate/stats/HibernateStatsMBean.class
    

    Agora, acesse seu jmx-console de você deverá encontrar uma tela contendo um link para seu novo mbean. Existem dois mebans novos o primeiro:

    AprovacaoHoras:Hibernate=Stats é o seu MBean que registra o serviço de stats

    Hibernate:application=AprovacaoHoras,type=statistics: é o serviço de stats que você poderá coletar as métricas

    Pronto, agora basta acessar o serviço e começar a coletar algumas métricas de seu Hibernate.

    Aproveitem