domingo, 7 de abril de 2013

JMS Bridge


Salve galera,


Hoje vou escrever sobre um assunto que não domino, mas estou pretendendo usar essa tecnologia em alguns projetos, então uma boa maneira de aprender sobre um assunto é escrever sobre ele, e ver se tudo o que aprendi até agora ficou claro. Vou falar sobre JMS.

JMS

O assunto JMS a muito tempo está na minha "lista" de coisas para aprender antes de morrer (sim, coisa de programador! hahahaha), mas por preguiça e falta de motivação, até agora só havia lido superficialmente sobre o assunto. Nesse post vou explicar o que é JMS e para o que ele serve.

JMS, ou Java Messages Service, segundo a Wikipédia, é uma API da linguagem Java para middleware orientado à mensagens. Através da API JMS duas ou mais aplicações podem se comunicar por mensagens (http://pt.wikipedia.org/wiki/JMS ). JMS é uma maneira simples a padronizada de trocar informações entre sistemas distribuídos, ou entre sistemas remotos. Um bom exemplo seria a replicação de dados entre sistemas, onde sistemas dependem de informações de outros sistemas para tomar alguma decisão.

Motivação

Existem diversas estruturas de sistemas onde a comunicação não pode ser em tempo real, ou não podemos ter um servidor central, seja por falta de internet confiável, alta disponibilidade e outras coisas mais. Nesses casos, cada ponto, ou nó tem sua unidade própria de funcionamento, mas precisa trocar e compartilhar informações com outras unidades.


Sobre JMS

Precisamos saber que uma estrutura JMS tem dois componentes principais. Uma fabrica de conexões com uma fila e uma fila.
As filas podem ser de dois tipos:

Consumidores X Produtores

Quando falamos sobre JMS temos dois papeis principais. Os produtores e os consumidores, que não precisam de muita explicação, pois os nomes produtor e consumidor já são bem sugestivos. Mas para não passar em branco:

O produtor é o cara que solicita uma conexão a fabrica de conexão e envia uma mensagem.
O consumidor é o cara que solicita uma conexão a fabrica de conexões e recebe as mensagens enviadas.

Tipos de Fila

Queue: Esse tipo de fila tem uma relação de 1 x 1 com seu consumidor, isso quer dizer que quando uma aplicação lê o dado da Queue, esse dado é marcado como lido e não é mais entregue para ninguém.

Topic: Esse tipo de fila tem uma relação de 1 x n consumidores, isso quer dizer que podemos ter vários consumidores recebendo a mesmo informação.

Persistente

As filas podem ser persistentes (Persistent) ou não. Uma fila com propriedade de persistência é mais segura que uma não persistente por motivos óbvios. Enquanto uma tem suas mensagens armazenada em um local seguro, como um banco de dados ou arquivo, a outra fica apenas em memória. Então, se os dados da fila não podem ser perdidos com uma queda de luz, ou alguma reinicialização do container, você deve usar uma fila persistente. Por padrão, se nada for configurado, os dados são persistentes para ambas as filas.

Time to Live

Ao escrever uma mensagem na fila, podemos definir um tempo de vida para ela (Time to Live), que define o tempo de vida até que ela expire e saia da fila. Isso também vai depender da importância da mensagem. Por padrão, elas têm tempo de vida infinita.

Durabilidade

O termo durabilidade só se aplica as filas do tipo Topic. Para acessar uma fila desse tipo temos que fazer uma inscrição de conteúdo no servidor JMS, claro que isso é apenas "formalidade" pois para uma Queue fazemos o mesmo processo, mas o que define um Topic são seus possíveis múltiplos leitores. Com isso, nós podemos informar qual a importância do conteúdo na perspectiva do assinante ou leitor. Quando dizemos que a assinatura é durável (Durable) estamos dizendo ao servidor JMS que todo conteúdo gerado deve ser entregue, independente de eu estar on-line ou não. Com isso, todo conteúdo gerado é "guardado" para que eu possa receber agora ou mais tarde, garantindo uma entrega segura e sem perdas. Já uma assinatura não durável (NonDurable) diz que quando eu estiver off-line o conteúdo gerado não precisa ser guardado, logo quando eu ficar on-line vou receber apenas o conteúdo gerado a partir desse momento.

JMS Bridge

Uma ponte JMS é um canal direto de comunicação criado entre duas filas JMS que é gerenciada pelo servidor JMS. Isso quer dizer que se tivermos uma fila A1 no servidor G1 e uma fila B1 no servidor G2 fazendo uma ponte, todas as mensagens escritas em A1 são recebidas em B1 automaticamente. Com isso podemos sempre tratar as filas como sendo locais e não precisamos fazer conexões externas em nossa aplicação, pois o servidor JMS é quem cuida da replicação.

Apesar de outros detalhes, acho que essas são as coisas mais importantes que devemos saber antes de iniciar a parte divertida, ou seja, a programação.

Objetivo

Comunicação entre duas aplicações Java rodando em AppServers distintos (Glassfish). Cada aplicação deve ter uma fila de leitura, onde recebe mensagens de uma aplicação remota, e também deve ter uma fila de escrita, na qual ela vai escrever mensagens para que uma aplicação remota receba.

Problema

Antes de conhecer as pontes do JMS, (JMS Bridge) eu pensava que cada aplicação deveria conectar-se no outro AppServer remoto para poder consumir e produzir os dados na suas respectivas filas. Depois um certo tempo batendo a cabeça tentando fazer essa solução sem obter sucesso, eu abri uma pergunta no GUJI, então apareceu essa solução rápida, limpa e certeira!


Solução

A solução foi usar o próprio servidor JMS embutido no Glassfish para fazer essa ponte, de modo que da perspectiva da aplicação, só existam filas locais. Com isso, a responsabilidade de replicação das filas fica com o AppServer, diminuindo consideravelmente a complexidade do problema.

Desenvolvendo a solução

Primeiramente devemos ter um ambiente de testes, com maquinas virtuais, maquinas real ou duas instâncias de um glassfish rodando na mesma maquina.

Ambiente de teste para esse post
- Duas maquinas. Uma com Windows 7 64 bits e um MAC OS 10.8.2
- Java7
- Glassfish 3.1.2.2 (build 5)

Vamos dar nomes aos bois. Vamos chamar um servidor de  Master e o outro de Slave. No servidor Master, vamos rodar uma aplicação chamada JMSMaster e no servidor Slave vamos rodar uma aplicação chamada JMSSlave.

Vamos acessar o console do servidor Master, http://localhost:4848. Vá em Resources > JMS Resources > Connection Factories. Vá em novo, e coloque os dados abaixo:

Pool Name: jms/ReplicationFactory
Resource Type: javax.jms.TopicConnectionFactory
Descriprion: Replication Factory
Status: enabled

Agora vá em Destination Resouce. Vá em novo e crie duas filas com os dados abaixo:

JNDI Name: jms/ReplicationProducer
Physical Destination Name: ReplicationProducer
Resource Type: javax.jms.Topic
Description: Replication Producer
Status: enabled

JNDI Name: jms/ReplicationConsumer
Physical Destination Name: ReplicationConsumer
Resource Type: javax.jms.Topic
Description: Replication Consumer
Status: enabled

Certo, o que precisamos saber aqui é que:
Pool Name e JNDI Name é o nome do recurso usado para acessa-lo dentro da nossa aplicação. 
Physical Destination Name é o nome usado para configurar a ponte JMS

Agora vamos acessar o console do servidor Slave. Faça o mesmo processo com os dados abaixo:

Connection Factory
Pool Name: jms/ReplicationFactory
Resource Type: javax.jms.TopicConnectionFactory
Descriprion: Replication Factory
Status: enabled

Topic

JNDI Name: jms/ReplicationProducer
Physical Destination Name: ReplicationConsumer
Resource Type: javax.jms.Topic
Description: Replication Producer
Status: enabled

JNDI Name: jms/ReplicationConsumer
Physical Destination Name: ReplicationProducer
Resource Type: javax.jms.Topic
Description: Replication Consumer
Status: enabled

Note que tudo é igual, exceto de que o  Physical Destination Name foi invertido:

Quando escrevermos em ReplicationProducer no Slave, a mensagem vai ser replicada para ReplicationConsumer  no Master.

Quando o Master escrever no ReplicationProducer a mensagem será replicada para ReplicationConsumer no Slave.

Parece meio complexo lendo agora, mas é bem simples.

Nesse momento já temos nossos dois servidor configurados para fazer um JMS Bridge, com isso é hora de programar!

JMSMaster 

Vamos criar um EJB com as seguintes classes:



@Stateless
public class JMSProducerBean implements Serializable {

    @Resource(name = "jms/ReplicationFactory")
    private ConnectionFactory cmf;
    @Resource(name = "jms/ReplicationProducer")
    private Destination destination;
    private DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");

    public String send() throws JMSException {
        TopicConnection connection = null;
        String date = dateFormat.format(new Date());
        Logger.getGlobal().log(Level.SEVERE, "#> {0} send new replication..", date);
        try {
            connection = ((TopicConnection) cmf.createConnection());
            TopicSession topicSession =
                    connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
            TopicPublisher topicPublisher = topicSession.createPublisher((Topic) destination);
            TextMessage message = topicSession.createTextMessage("Master Node: " + date + " - new replication");
            topicPublisher.publish(message);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

        return null;
    }
}

@MessageDriven(mappedName = "jms/ReplicationConsumer")
public class JMSConsumer implements MessageListener {

    @Override
    public void onMessage(Message message) {

        try {
            TextMessage textMessage = (TextMessage) message;
            Logger.getGlobal().log(Level.SEVERE, "#> receive: {0}", textMessage.getText());
        } catch (Exception e) {
            Logger.getGlobal().log(Level.SEVERE, "#error process receiver.. {0}", e.getMessage());
        }
    }
}

@Stateless
public class TimerSessionBean {

    @EJB
    private JMSProducerBean productorBean;

    @Schedule(persistent = false, second = "*/10", hour = "*", minute = "*", dayOfMonth = "*", year = "*")
    public void myTimer() {

        try {
            productorBean.send();
        } catch (Exception e) {
            Logger.getGlobal().log(Level.SEVERE, "### error send message: {0}", e.getMessage());
        }
    }
}

JMSSlave

@Stateless
public class JMSProducerBean implements Serializable {

    @Resource(name = "jms/ReplicationFactory")
    private ConnectionFactory cmf;
    @Resource(name = "jms/ReplicationProducer")
    private Destination destination;
    private DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");

    public String send() throws JMSException {
        TopicConnection connection = null;
        String date = dateFormat.format(new Date());
        Logger.getGlobal().log(Level.SEVERE, "#> {0} send new replication..", date);
        try {
            connection = ((TopicConnection) cmf.createConnection());
            TopicSession topicSession =
                    connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
            TopicPublisher topicPublisher = topicSession.createPublisher((Topic) destination);
            TextMessage message = topicSession.createTextMessage("Slave Node: " + date + " - new replication");
            topicPublisher.publish(message);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

        return null;
    }
}

@MessageDriven(mappedName = "jms/ReplicationConsumer")
public class JMSConsumer implements MessageListener {

    @Override
    public void onMessage(Message message) {

        try {
            TextMessage textMessage = (TextMessage) message;
            Logger.getGlobal().log(Level.SEVERE, "#> receive: {0}", textMessage.getText());
        } catch (Exception e) {
            Logger.getGlobal().log(Level.SEVERE, "#error process receiver.. {0}", e.getMessage());
        }
    }
}

@Stateless
public class TimerSessionBean {

    @EJB
    private JMSProducerBean productorBean;

    @Schedule(persistent = false, second = "*/10", hour = "*", minute = "*", dayOfMonth = "*", year = "*")
    public void myTimer() {

        try {
            productorBean.send();
        } catch (Exception e) {
            Logger.getGlobal().log(Level.SEVERE, "### error send message: {0}", e.getMessage());
        }
    }
}

Se você analisar os códigos, vai ver que a única mudança do JMSMaster para o JMSSlave, são as mensagens enviadas. Agora você pode fazer e deploy da aplicação nos seus respectivos servidores e ver a magica acontecer nos logs de cada um. Faças os testes com mensagens duráveis e não duráveis deixando um ou os dois servidores off-line de vez em quando, reiniciando, sem rede, esse tipo de coisa.

Mais uma vez volto a dizer que sou novo nessa tecnologia, e ainda não coloquei essa lógica em produção, pois preciso ainda ver a questão de persistência segura, desempenho e tudo mais. Então, se em algum momento escrevi alguma besteira, fiquem a vontade para me corrigir.

Espero que esse post possa ajudar alguém que esteja procurando uma solução parecida. 

Até a próxima! abrasss


segunda-feira, 1 de abril de 2013

Carona Verde

Salve galera,

Nessa postagem vou divulgar um aplicativo muito interessante criado pela Mobile Mind Empresa de Tecnologia. O aplicativo Carona Verde é gratuito e desenvolvido dentro da plataforma do Facebook. Seu objetivo é fornecer um lugar comum onde pessoas possam oferecer e/ou procurar caronas. Os criadores do aplicativo identificaram uma grande demanda na oferta e procura de caronas em grupos do Facebook, e para ajudar nessa tarefa, o aplicativo Carona Verde foi criado. Seu grande diferencial se comparado aos grupos do facebook, é a possibilidade de pesquisa por origem ou destino, a também a possibilidade de ser notificado caso uma carona para seu destino seja ofertada. O aplicativo está em pleno desenvolvimento, com novidades e correções sendo realizadas quase que diariamente. 

O Carona Verde não tem o objetivo de salvar o mundo, e sim facilitar a vida dos caroneiros de plantão e até contribuir para um trânsito menos caótico, que já seria um baita benefício.

Esse aplicativo é gratuito e sempre será, mas para que ele "sobreviva" em meio à "selva de bits", ele deve ser usado pelo maior número de pessoas possível, com isso, sua manutenção será justificada. Então ajude a disseminar a ideia, curtindo a página ou compartilhando no seu perfil em alguma rede social.

 liks
Aplicativo no facebook https://apps.facebook.com/caronaverde/
Como usar http://www.4minds.com.br/artigo/show/33

E é isso ai pessoal, compartilhem a ideia! abrassss

segunda-feira, 4 de março de 2013

Java + too many open files

Salve galera,

Nesse poste uma dica bem rápida e útil. Ao tentar fazer o deploy de uma aplicação em um container Java jetty, tomcat, o seguinte erro é apresentado:



java.io.FileNotFoundException:  (Too many open files)

 Para resolver o problema, executamos:


 
ulimit -n 65535
echo 65535 > /proc/sys/fs/file-max


E no arquivo   /etc/sysctl.conf alterar ou adicionar o valor:


 
fs.file-max=65535

Depois disso não é mais pra acontecer o erro! Até a próxima.


Mais informações em http://lj4newbies.blogspot.com.br/2007/04/too-many-open-files.html

sábado, 23 de fevereiro de 2013

GitWeb + Dav

Salve galera,

Não vou fazer um tutorial sobre como instalar o Gitweb com o módulo Dav no Apache, até por que tem um monte de bons tutorias por ai, como esse:

http://ballen.co.uk/2012/07/23/installing-a-git-server-using-apache-webdav-on-ubuntu-server-12-04/

Mas o lance, é que nunca funciona como queremos ou imaginamos =/ Então, aqui vai algumas dicas:


O usuário dono da pasta onde estão armazonados os repositórios deve ser www-data:

chown www-data.www-data /var/www/repository -R

Outra coisa que não consegui, foi criar um repositório fora do diretório do Apache /var/www, mesmo dando todas as permissões do mundo pro diretório. Então, recomendo que esse repositório fique dentro dos "domínios" do Apache, e que você controle a segurança da pasta através das diretivas do Apache.

Segue aqui os arquivos de configuração prontos:

/etc/gitweb.conf

 /etc/apache2/conf.d/git


 /etc/apache2/conf.d/gitweb


Um detalhe interessante na primeira imagem, o arquivo /etc/gitweb.conf é para configuração


@git_base_url_list = ('http://mydomain.com/git');

É a configuração que define a url que vai ser apresentada na pagina do repositório. Ou seja, para um repositório chamado system, ele vai mostrar:

URL http://mydomain.com/git/system

Para gear o arquivos de senhas do apache, o comando é:


sudo htpasswd  -c /etc/apache2/pass/gitpass user

Lembrando que o parametro -c é de create new file, e deve ser adicionado só na criação do primeiro usuário.

Acho são esses detalhes que sempre esqueço quando tenho que configurar um novo servidor de repositório, o que não é muito comum no meu dia-a-dia.

Até a próxima!