quinta-feira, 27 de outubro de 2016

Introdução ao Brutos MVC


Modelo-Vista-Controlador (MVC) é um modelo de arquitetura de software que separa a representação da informação da interação do usuário com ela. O modelo consiste de dados de aplicação, regras de negócio, lógica e funções.
A exibição pode ser qualquer representação de saída de dados. Múltiplas visões dos dados são possíveis. O controlador mediante os dados de entrada, os converte em comandos para o modelo.
O pacote org.brandao.brutos é a base do Brutos Framework. Nele contém a ApplicationContext que é a raiz de toda aplicação. Ele fornece um mecanismo avançado de configuração e todos os recursos necessários para a implementação de uma aplicação no modelo MVC em contextos específicos, tal como WebApplicationContext para aplicações WEB.
No Brutos Framework, um controlador pode ser uma entidade de fachada pertencente ao modelo, pois nele apenas irá conter um mapeamento que o liga a seu respectivo controlador. Então, pode se dizer que, ele é conceitualmente um controlador, mas de fato é uma entidade pertencente ao modelo. Também é possível trabalhar do modo tradicional, onde é delegado aos controladores criados o processamento da solicitação.

1- Obtendo o Brutos Framework.


1.1- Obtendo o pacote.


Os pacotes de liberação estão hospedados no sistema de arquivos da SourceForge em formato ZIP. Cada pacote contém jars, exemplos, código fonte, entre outros. O download pode ser feito a partir da url http://sourceforge.net/projects/brutos/files/brutos/.

1.2- Repositório de artefatos Maven.


São produzidos uma série de artefatos sob o groupId org.brandao.

brutos-core
O artefato principal, necessário para construir aplicações usando o Brutos APIs nativo.

brutos-annotation
Artefato opcional que permite a construção de aplicações usando anotações.
Este artefato depende do brutos-core.

brutos-web
Artefato opcional que permite a construção de aplicações web.
Este artefato depende do brutos-core.

O repositório oficial do Brutos Framework é http://www.brutosframework.com.br/maven/2.

2- Ciclo de vida.


O framework possui um ciclo de vida bem definido que permite a manipulação das solicitações e submissões de formulários.
O seu entendimento é extremamente importante para se evitar problemas ao desenvolver uma aplicação. Ele é dividido em seis etapas:

  • obter o modelo; 
  • processar interceptores; 
  • processar validações e atualizar valores do modelo; 
  • invocar aplicação; 
  • processar vista; 
  • tratar exceções. 

2.1- Obter o modelo


Obter o modelo é a primeira etapa do ciclo de vida de uma solicitação. Nessa etapa é recebida a solicitação examinada e extraída a identificação. Essa identificação é usada para localizar a classe do modelo associado a um controlador. Se não existir um controlador associado à identificação, a solicitação será finalizada.
Também é gerado o manipulador da solicitação que permite o acesso, na próxima etapa, à instância do modelo, ao contexto, a ação que irá ser executada, os parâmetros da ação, a identificação da solicitação, ao resultado que a execução da ação por ventura venha a gerar.

2.2- Processar interceptores.


Processar interceptores é a segunda etapa no ciclo de vida de uma solicitação. Nessa etapa o manipulador da solicitação já foi gerado, então ocorre o processamento da pilha de interceptores, que pode ser customizada.
Ainda nessa etapa os parâmetros da ação não foram iniciados; fazendo o primeiro acesso os dados serão automaticamente carregados, validados e estarão prontos para serem usados.

2.3- Validar e atualizar os valores do modelo.


Validar e atualizar os valores do modelo é a terceira etapa do ciclo de vida de uma solicitação. É nessa etapa que ocorre a validação e a atualização do modelo.
A validação dos parâmetros de uma ação pode ocorrer durante a interceptação. Isso ocorre se na interceptação os parâmetros forem acessados.
Se nesse processo forem encontrados valores inválidos, o fluxo de execução será direcionado para a etapa “Tratar Exceção”.

2.4- Invocar aplicação.


Invocar aplicação é a quarta etapa do ciclo de vida de uma solicitação. Nessa etapa é invocada a aplicação. Seu resultado pode alterar o fluxo lógico de execução. Todos os erros mapeados, ocorridos ao invocar a aplicação, serão interceptados e estarão disponíveis na vista.
Depois de invocada a aplicação, todos os valores do modelo serão disponibilizados na vista, incluindo o resultado da ação. 

2.5- Processar vista.


Processar vista é a quinta etapa do ciclo de vida de uma solicitação. Nessa etapa a vista é processada junto com os valores do modelo e o resultado da ação.

2.6- Tratar exceções.


O tratamento de uma exceção ocorrerá se existir algum problema no processamento da solicitação ou na invocação da aplicação. Somente as exceções configuradas serão interceptadas e disponibilizadas na vista.

3- Visão Geral.


Uma aplicação tem como base o uso da interface org.brandao.brutos.ApplicationContext. Ela representa uma aplicação MVC baseada no framework e é responsável por montar, configurar, instanciar os componentes da aplicação. Além de ser responsável por controlar todo o ciclo de vida de uma solicitação.
As instruções de como montar, configurar, instanciar os componentes são obtidas por meio de metadados de configuração. Eles podem ser representados por XML, anotações ou código Java.
Em aplicações standalone, aconselhamos a criação de uma instância da AnnotationApplicationContext em conjunto com o uso de um scanner de classes. Assim não é necessário o uso de XML na configuração. Já em aplicações WEB é necessário apenas algumas linhas no web.xml.

3.1- Metadados de configuração.


Para que a aplicação consiga construir, configurar, instanciar os componentes é necessário fornecer os metadados de configuração. Eles indicam como a aplicação deve montar os componentes. Eles podem ser fornecidos no formato XML, anotações ou código Java.
A configuração consiste na definição de um ou mais componentes, a baseada em XML são representadas pelos elementos <controller/>, <interceptor/> e <bean/>, a baseada em anotação são representadas pelas @Controller, @Intercepts e @Bean.
Os metadados de configurações baseados em XML podem ser distribuídos em múltiplos arquivos, permitindo uma melhor organização. Os arquivos XML podem conter um ou mais controladores, seu agrupamento é feito com o elemento <importer/>.

<?xml version="1.0" encoding="UTF-8"?>
<controllers  xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
              xmlns='http://www.brutosframework.com.br/schema/controllers'
              xsi:schemaLocation='
              http://www.brutosframework.com.br/schema/controllers 
              http://www.brutosframework.com.br/schema/controllers/brutos-controllers-1.1.xsd'>


    <import resource="classpath:..."/>
    <import resource="..."/>


    <controller id="..." view="..." class="...">
        <!-- actions -->
    </controller>
    
    <controller id="..." view="..." class="...">
        <!-- actions -->
    </controller>


    <!-- mais controladores -->    
</controllers>

3.2- Iniciando uma aplicação.


Iniciar uma aplicação é extremamente fácil. Basta passar para o construtor da ApplicationContext o local dos recursos que podem estar no sistema de arquivos, no classpath ou ser representado por um grupo de classes.

Class[] clazz = ... /* classes da aplicação*/;
AnnotationApplicationContext appContext = 
    new AnnotationApplicationContext(clazz);
appContext.flush();

ClassPathXMLApplicationContext appContext =
    new ClassPathXMLApplicationContext(
        new String[]{"controllers1.xml", "controllers2.xml"});
appContext.flush();

4- Aplicações WEB.


Para o desenvolvimento de uma aplicação web, no mínimo são necessários os artefatos brutos-core e brutos-web e algumas ações para configurá-los. Deve-se incluir os jars no classpath ou pom.xml, se usado o maven. Criar o arquivo de configuração brutos-config.xml na pasta /WEB-INF, o local e o nome do arquivo podem ser configurados. É permitida a criação de vários arquivos de configuração, podendo seguir a arquitetura da aplicação. Registrar o ContextLoadListener, e DispatcherServlet, ou o BrutosRequestFilter no web.xml.

Nota
Se estiver sendo usado um container que suporta a especificação Servlet 3.0, o registro do ContextLoadListener, e DispatcherServlet, ou BrutosRequestFilter não serão necessários. Eles serão automaticamente registrados.

4.1- Iniciando o ApplicationContext.


Em uma aplicação web, o ApplicationContext é instanciado pelo ContextLoader ou programaticamente usando uma de suas implementações. É possível definir qual implementação será instanciada usando a variável context_class. Se não informada, a implementação org.brandao.brutos.web.XMLWebApplicationContext será instanciada.
A configuração do contexto é definida pela variável contextConfig. Se não informada, será assumido /WEB-INF/brutos-config.xml.

Nota
É permitido informar mais de um arquivo de configuração usando, como separador,  uma vírgula.

<context-param>
    <param-name>contextConfig</param-name>
    <param-value>
        /WEB-INF/contexts/applicationContext1.xml,
        /WEB-INF/contexts/applicationContext2.xml
     </param-value>
</context-param>


<listener>
    <listener-class>org.brandao.brutos.web.ContextLoaderListener</listener-class>
</listener>

4.2- DispatcherServlet


O framework foi, como muitos outros web MVC frameworks, projetado em torno de um servlet que despacha as requisições aos controladores. O DispatcherServlet é um servlet real que herda as características da classe HttpServlet, e como tal, deve ser declarado no web.xml.

<servlet>
    <servlet-name>BrutosServlet</servlet-name>
    <servlet-class>org.brandao.brutos.web.DispatcherServlet</servlet-class>
</servlet>


<servlet-mapping>
    <servlet-name>BrutosServlet</servlet-name>
    <url-pattern>*.jbrs</url-pattern>
</servlet-mapping>

4.3- Filter


O filtro é um recurso da especificação Servlet 2.3, que possibilita a interceptação de uma solicitação antes de atingir um recurso. O framework o usa para interceptar as solicitações e direcioná-las aos controladores. Se uma solicitação não estiver associada a um controlador, ela irá continuar com o seu fluxo normal. Por exemplo, se o URI /img.jpg estiver associado a uma ação, essa será executada, mas se não estiver, o servidor tentará enviar o arquivo img.jpg ao solicitante.

<filter>
        <filter-name>Brutos Framework Filter</filter-name>
        <filter-class>org.brandao.brutos.web.http.BrutosRequestFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>Brutos Framework Filter</filter-name>
        <url-pattern>*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>

4.4- Upload e downlaod de arquivos.


O upload de arquivo é nativamente suportado pelo framework e permite trabalhar de forma transparente. Sem a necessidade do uso direto da interface ServletHttpRequest ou de outros frameworks. Também não necessitando o uso direto das classes do framework.

public class FileController{


    private String repositoryPath;


    public FileController( String repositoryPath ){
        this.repositoryPath = repositoryPath;
    }


    public void uploadFile( File file ){
        ...
    }


    public File download( String fileName ){
        File file = new File( repositoryPath + "/" + fileName );
        return file;
    }


}

4.5- Upload de arquivos


Um campo do tipo file pode ser mapeado para os tipos Java: UploadedFile, java.io.File, byte[] ou Byte[]. Pode ser usado em um bean nas propriedades ou nos argumentos de um construtor e em um controlador nas propriedades ou nos parâmetros de uma ação. Se em uma solicitação existir mais de um campo do tipo file com mesmo nome, eles serão mapeados para uma lista ou um array de arquivos. Com a classe UploadedFile, é possível obter além do arquivo que foi enviado na solicitação o seu tipo e seu nome.

public interface UploadedFile {


    File getFile();
    String getContentType();
    String getFileName();


 }

  • getFile: obtém o arquivo.
  • getContentType: obtém o tipo do conteúdo do arquivo.
  • getFileName: obtém o nome do arquivo.

public class Person{

    private String name;
    private File photo;
    private List images;

    ...
}

@Controller("/person")
public class PersonController{


    @Action("/save")
    public void save(@Basic(bean="person") Person person){
       ...
    }


}

No exemplo acima, temos a entidade Person e o controlador PersonController. Como na entidade existem associações do tipo File, o formulário da solicitação tem que ser codificado com o formato multipart/form-data.
Se List for um tipo padrão, não será feito um mapeamento e a solicitação terá um outro formato.

public class Person{

    private String name;

    private File photo;

    @Type(ListType.class)
    private List images; //or File[]

    ...
}

4.6- Download de arquivos.


O download de um arquivo ou fluxo de bytes é definido pelos tipos Java java.io.File, Download, byte[] ou Byte[]. Para que os dados sejam enviados ao solicitante, basta que o tipo do resultado de uma ação seja um dos informados anteriormente. Com a interface Download, é possível definir o tipo dos dados que serão enviados. Além de outras informações que podem ser enviadas pelo cabeçalho.

public interface Download {

    String getContentType();
    Map getHeader();
    long getContentLength();
    void write( OutputStream out ) throws IOException;

 }

  • getContentType: obtém o tipo do conteúdo.
  • getHeader: obtém o cabeçalho.
  • getContentLength: obtém o tamanho do fluxo de byte.
  • write: direciona o fluxo de byte ao solicitante.

@Controller("/download")
public class DownloadController {

    @Action("/file/{fileName}")
    public Download getFile(@Basic(bean="fileName")String name){
        return new FileDownload(new File("/mnt/files" + name));
    }

}

No exemplo acima, temos o controlador DownloadController que possui uma ação que retorna um objeto do tipo Download. Essa ação abre e envia ao solicitante o arquivo /mnt/files/<fileName>. Um exemplo de solicitação seria o URI /download/file/photo.png, onde o parâmetro name do método getFile seria  photo.png.

5- Modelos de URI.


Um modelo de URI é um texto que representa um URI contendo um ou mais nomes de variáveis. Quando se define valores a essas variáveis, o modelo torna-se um URI.
As variáveis do modelo de URI são tratadas de forma transparente. Sendo processadas e injetadas na solicitação. Assim é possível disponibilizá-las em um bean nas propriedades ou nos argumentos de um construtor e em um controlador nas propriedades ou nos parâmetros de uma ação.

public void showProfile(Integer userId) {
    ...
}

No exemplo acima, o parâmetro do método showProfile, userId, faz parte do URI. Um exemplo de URI seria /profile/10, onde 10 seria o parâmetro userId do método showProfile.

5.1- Modelos de URI com expressão regular.


Em alguns casos, existe a necessidade de refinar a definição de uma variável no modelo de URI. Para esses casos, pode ser usado expressões regulares. A sintaxe da variável, nesse caso, ficaria {varName:regex}, onde varName é o nome da variável e regex é a expressão regular. 

@Action("/profile/{userId:\d+}")
public void showProfile(Integer userId){
...
}

No exemplo acima, a variável userId somente permite números. Caso o URI solicitado não seja compatível com a expressão regular /profile/\d+, será retornado o erro 404.

6 - O controlador.


Um controlador é responsável por receber os dados de entrada e determinar qual objeto do modelo e vista serão utilizados. Também é responsável por converter, validar e filtrar os dados de entrada.
O framework pode, ou não, se comportar como um front-controller. Nos dois casos, somente é necessário fazer respectivamente o mapeamento do controlador ou modelo. Esse mapeamento é nada mais, que informar como os dados de uma solicitação serão disponibilizados nas propriedades do controlador ou modelo e nos parâmetros de uma ação; e como seu resultado será exibido na vista. Pode causar confusão em um primeiro momento quando o framework não se comporta como um front-controller, pois a classe do modelo mapeada é conceitualmente um controlador. O que o framework faz é gerar um controlador a partir do mapeamento especificado na classe.
O modelo usado pelo controlador será sempre a classe mapeada e seus métodos serão invocados quando suas respectivas ações forem executadas.

6.1 - Especificando um controlador.


Um controlador pode ser especificado com o uso do elemento <controller/>, a anotação @Controller ou usando suas convenções de configuração.

Toda classe com o sufixo Controller será considerado um controlador. Exemplo:

public class TestController{
}

No exemplo acima, o controlador Test está associado ao path /test e sua vista é /WEB-INF/views/testcontroller/index.jsp.

Também existe a opções de usar anotações. O exemplo anterior com anotações ficaria:

@Controller
public class Test{
}

Não é preciso informar o path na anotação. O path e vista são os mesmos do exemplo anterior, mas é possível informar path e vista.

@Controller(value="/test-path")
@View("test/home")
public class Test{
}

Agora o path do controlador é /test-path e a vista /WEB-INF/views/test/home.jsp.
Ainda se preferir é possível fazer a configuração usando XML. Ela tem que ser informada no arquivo brutos-config.xml.
O exemplo anterior usando xml ficaria:

<controller id="/test-path" view="test/home" class="Test"/>

7 - Ações.


Um controlador pode possuir uma ou mais ações. Elas podem ser representadas por métodos. Os métodos podem ter, ou não, parâmetros. Um parâmetro pode ser um objeto ou um tipo primitivo. Se for um objeto, pode-se criar um mapeamento para definir como os dados de uma solicitação serão disponibilizados nele. Se o método tiver resultado, será processado e disponibilizado na vista. 
As exceções lançadas dentro do método podem ser interceptadas e alterar o fluxo lógico da aplicação.

7.1 - Especificando uma ação.


Uma ação pode ser especificado com o uso do elemento <action/>, a anotação @Action ou usando suas convenções de configuração.

Todo método com o sufixo Action será considerado uma ação. Exemplo:

@Controller("/test-path")
public class TestController{

    public void indexAction(){
        ...
    }
}

No exemplo acima, a ação index está associada ao path /test-path/index e sua vista é /WEB-INF/views/testcontroller/indexaction/index.jsp.

Também existe a opções de usar anotações. O exemplo anterior com anotações ficaria:

@Controller("/test-path")
public class TestController{

    @Action
    public void index(){
        ...
    }
}

Não é preciso informar o path na anotação. O path e vista são os mesmos do exemplo anterior, mas é possível informar path e vista.

@Controller("/test-path")
public class TestController{

    @Action("/action")
    @View("test/index")
    public void index(){
        ...
}
}

Agora o path da ação é /test-path/action e a vista /WEB-INF/views/test/index.jsp.
Ainda se preferir é possível fazer a configuração usando XML. Ela tem que ser informada no arquivo brutos-config.xml.
O exemplo anterior usando xml ficaria:

<controller id="/test-path" class="TestController">
    <action id="/action" view="test/index" executor="index"/>
</controller>

Uma ação não precisa obrigatoriamente ter um método. Sem um método a ação ficaria:

@Controller("/test-path")
@Action("/action", view=@View("test/index"))
public class TestController{

}

8 - Recebendo parâmetros.


Um parâmetro pode ser recebido por meio de uma propriedade do controlador ou ação. Pode ser um objeto ou tipo primitivo. Se for um objeto, pode-se criar um mapeamento para definir como os parâmetros de uma requisição serão disponibilizados em suas propriedades.

@Controller("/test-path")
public class TestController{

    private String name;

    @Action("/action", view=@View("test/index"))
    public void index(@Basic(bean="id") Integer id){
        ...
    }

}

No exemplo acima, a ação action recebe o parâmetro id e a propriedade name o parâmetro name. Se uma requisição for /test-path/action?name=jose&id=1, a propriedade name teria o valor jose e o parâmetro id do método index 1.
É possível usar a anotações @Bean na propriedade name. Um exemplo seria:

@Controller("/test-path")
public class TestController{

    @Basic(bean="pName")
    private String name;

    @Action("/action", view=@View("test/index"))
    public void index(@Basic(bean="id") Integer id){
        ...
    }

}

Nesse caso o nome do parâmetro name passou a ser pName. A requisição anterior, agora teria que ser /test-path/action?pName=jose&id=1.

9 - Trabalhando com formulários.


O recebimento de parâmetros oriundos de um formulário segue o mesmo princípio de um parâmetro simples, mas para esse caso, pode-se agrupar os parâmetros em um pojo (Plain Old Java Object).
Agrupando os parâmetros do exemplo anterior, o controlador ficaria:

@Controller("/test-path")
public class TestController{

    @Action("/action", view=@View("test/index"))
    public void index(@Basic(bean="data") Form form){
        ...
    }

}

O pojo Form:

public class Form{

    private Integer id;

    private String name;

    //gets e sets

}

Na vista, o formulário teria o formato:

<form action="/test-path/action">
    ID: <input type="text" name="data.id"><br>
    Name: <input type="text" name="data.name">
</form>

Existe a opção de receber os parâmetros diretamente no campo.

public class Form{

    public Integer id;

    public String name;

}

Também os parâmetros podem ser recebidos pelo construtor.

public class Form{

    @Transient
    private Integer id;

    @Transient
    private String name;

    public Form(){
    }

    @Constructor
    public Form(@Basic(bean="id") Integer id, @Basic(bean="name") String name){
    this.id = id;
    this.name = name;
    }

//gets e sets

}

Em um mesmo pojo é possível mesclar escopos.

public class Form{

    private Integer id;

    private String name;

    @Basic(scope=ScopeType.Session)
    private String userName;

    //gets e sets

}

Conclusão


O Brutos framework é uma poderosa ferramenta de fácil aprendizado. Ele permite criar n soluções para um mesmo problema.

quinta-feira, 20 de outubro de 2016

BRCache vs. Memcached Benchmark



Em experimento realizado, o BRCache demonstrou ser mais rápido que o Memcached.
O experimento está disponível no repositório de arquivos do SourceForge (https://sourceforge.net/projects/brcache/files/).

O experimento


No experimento somente foi considerado o armazenamento em memória. A instância de cada servidor foi configurada para consumir no máximo 8GB. Os métodos testados foram o de escrita e leitura. Nenhum cliente foi utilizado. Foram criados métodos que geram as solicitações e processam as respostas.
O experimento foi dividido em duas etapas. A primeira etapa escreve os dados no cache. Cada registro tem um tamanho fixo de 1k. São utilizados grupos de 50 até 300 clientes, com 50 de frequência. Cada cliente faz 50 registros únicos. Cada agrupamento de clientes é executado três vezes e o resultado com menor tempo médio de resposta é considerado. A segunda etapa lê os dados do cache. Cada leitura tem um tamanho fixo de 1k, gerado na primeira etapa. São utilizados grupos de 50 até 300 clientes, com 50 de frequência. Cada cliente lê 50 registros únicos. Cada agrupamento de clientes é executado três vezes e o resultado com menor tempo médio de resposta é considerado.

O hardware


O cliente e o servidor foram executados na mesma máquina com a seguinte configuração:

  • processador Intel(R) Core(TM) i3-2100  (3.10GHz 64bits)
  • 16GB de memória.

O servidor de cache


O experimento foi feito com a versão 1.4.5 64bit do Memcached e a versão 1.0 beta 4 do BRCache sobre Java HotSpot(TM) 64-Bit Server VM (build 20.45-b01, mixed mode).

A configuração de arranque


Para o experimento, o servidor Memcached foi iniciado usando os parâmetros de linha de comando m igual a 8000 e t igual a 8.

memcached -m 8000 -t 8

O servidor BRCache foi iniciado usando os parâmetros de linha de comando server e XX:ParallelGCThreads igual a 8.

java -server -XX:ParallelGCThreads=8 -jar brcache-server-1.0-b4.jar

As configurações adicionais do cache


O Memcached não necessitou de configurações adicionais e o BRCache foi configurado como se segue abaixo:

port=9090
max_connections=1024
swapper_thread=2
memory_access_type=unsafe
timeout_connection=1024
reuse_address=false
data_path=/mnt/brcache
nodes_buffer_size=1024m
nodes_page_size=1k
nodes_swap_factor=0.1
index_buffer_size=512m
index_page_size=1k
index_swap_factor=0.1
data_buffer_size=4000m
data_page_size=8k
data_block_size=1k
data_swap_factor=0.1
write_buffer_size=16k
read_buffer_size=16k
max_size_entry=128m
max_size_key=64
transaction_support=false



O calculo


A quantidade de operações por segundo foi obtida com a formula: ops = (1000000000*i)/t, onde:


  • t: tempo total, em nano segundos, que o agrupamento de clientes demora para executar todas as operações.
  • i: quantidade total de operações executadas pelo agrupamento de clientes.
  • 1000000000: constante que representa um segundo em nano segundos.



Resultado


O método de escrita do BRCache demonstrou ser mais rápido que o Memcached ao passo que a quantidade de clientes aumentava. Demonstrada pela curva ascendente do gráfico abaixo.

Escritas por segundo

O método de leitura do BRCache é tão rápido quanto o do Memcached. Demonstrado pelo entrelaçamento das linhas do gráfico abaixo.
Foram feitos testes usando milissegundo como unidade de tempo. Nesse caso, os resultados demonstraram que o BRCache foi mais rápido em ambos os métodos. Isso sugere que a diferença de performance, no caso do método de leitura, está na base de nano segundos.

Leituras por segundo

Variação nos resultados


Foram feitos inúmeros testes e a performance de ambos os servidores variaram para mais ou menos de 70.000 operações por segundo. Chegando a passar de 200.000 mil operações por segundo. Fazendo uma analise preliminar, essa variação se deve a sensibilidade do experimento e ao modo como os dados trafegam, mesmo o cliente e o servidor estando em uma mesma maquina, Por exemplo, o laço da linha 67, do método de leitura, em ambos clientes variavelmente foi executado mais de uma vez.


Conclusão

Mesmo se tratando de uma versão beta, o BRCache demonstrou ser tão ou mais rápido que a última versão estável do Memcached. Outros experimentos ainda serão feitos.

segunda-feira, 10 de outubro de 2016

Instalando o BRCache Server.


As distribuições binárias e código fonte do servidor estão disponíveis no repositório SourceForge. (http://sourceforge.net/projects/brcache). A pronta disponibilidade do código fonte permite-lhe depurar o servidor, aprender seu funcionamento interno e criar versões personalizadas para seu uso pessoal ou empresarial.

Obtendo os arquivos binários


O mais recente lançamento do BRCache está disponível na página de arquivos do BRCache no SourceForge, http://sourceforge.net/projects/brcache. Você também vai encontrar versões anteriores, bem como beta e candidatos a futuras versões.

Pré-requisitos


Antes de instalar e executar o servidor, verifique o seu sistema para certificar-se de que você tem uma instalação funcional do JDK 1.5+. A maneira mais simples de fazer isso é executar o comando java -version para assegurar que o executável java está acessível e que está utilizando a versão 1.5 ou superior.
Não importa o local de instalação do BRCache em seu sistema. Evite instalar o BRCache em um diretório com nome que contenha espaço, pois pode causar problemas. Não existe nenhuma exigência para o acesso root na execução do BRCache em sistemas UNIX/Linux.

Instalando o pacote binário


Depois de obter o arquivo binário que deseja instalar, use uma ferramenta para descompactar arquivos no formato ZIP para extrair o conteúdo do arquivo brcache-server-yy-xx.zip em um local de sua escolha. O processo de extração criará o diretório brcache-yy-xx.

Estrutura de diretórios


A extração do arquivo ZIP cria o diretório brcache-yy-xx que contém scripts de inicialização do servidor, JARs e arquivos de configuração.


Diretório Descrição
data Local onde os dados serão persistidos após atingir o limite de uso da memória.
Pode ser alterado.
lib O diretório lib é o local onde ficam todas as bibliotecas usadas pelo servidor.

Arquivo de configuração


No diretório raiz da instalação, brcache-yy-xx, contém o arquivo brcache.conf. Nele contém toda a configuração do servidor.

Teste básico de instalação


Depois de ter instalado o BRCache, é aconselhável realizar um teste de inicialização simples para verificar que não há grandes problemas com a sua combinação de Java VM e sistema operacional. Para testar a instalação, vá para o diretório raiz e execute java -jar brcache-server-yy-xx.jar .
Se nenhuma mensagem for exibida, significa que o servidor iniciou normalmente.

sábado, 8 de outubro de 2016

Trabalhando com transações no BRCache com PHP



Resumidamente, uma transação é uma coleção de operações que desempenha uma função lógica única. Ela tem que ter as seguintes propriedades:

  • atomicidade: todas as operações contidas na transação são tratadas como uma única unidade. Todas as operações são feitas ou nada é feito.
  • consistência: uma transação concluída deixa o sistema em um estado interno consistente.
  • isolamento: em um ambiente de múltiplas transações, uma não interfere na outra.
  • durabilidade: os resultados das transações são armazenados permanentemente no sistema.

Níveis de isolamento suportados


Na atual versão do BRCache, somente o nível de isolamento READ COMMITTED é suportado. Ele permite que uma transação leia e manipule os dados já confirmados por outras transações. As alterações dentro de uma transação somente ficarão visíveis para as demais após a sua confirmação. Esse nível de isolamento evita a leitura suja, porém, permite a leitura fantasma e a leitura não repetitiva.

Iniciando uma transação


No BRCache, uma transação é iniciada com o uso do método setAutoCommit() com o parâmetro igual a false e finalizada com o método commit(), rollback() ou setAutoCommit com o parâmetro true.

O método setAutoCommit


O método setAutoCommit() define o modo de confirmação automática. Se o modo de confirmação automática estiver ligado, todas as operações serão tratadas como transações individuais. Caso contrário, as operações serão agrupadas em uma transação que deve ser confirmada com o método commit() ou descartadas com o método rollback. Por padrão, cada nova conexão inicia com o modo de confirmação automática ligada.

//inicia uma transação desligando o modo de confirmação automática.
$con->setAutoCommit(false);
...
//confirma todas as operações ligando o modo de confirmação automática.
$con->setAutoCommit(true);

O método commit


O método commit() confirma todas as operações da transação atual e libera todos os bloqueios detidos pela atual sessão.

//inicia uma transação.
$con->setAutoCommit(false);
...
//confirma todas as operações.
$con->commit();

O método rolback


O método rollback() desfaz todas as operações da transação atual e libera todos os bloqueios detidos pela atual sessão.

try{
    //inicia uma transação.
    $con->setAutoCommit(false);
    ...
    //confirma todas as operações.
    $con->commit();
}
catch(Exception $e){
    //Desfaz todas as operações.
    $con->rollback();
}

Evitando a leitura fantasma e leitura não repetitiva


Para resolver o problema de leitura fantasma e leitura não repetitiva, o BRCache oferece a opção de bloquear um determinado item no momento de sua seleção. A opção é o parâmetro forUpdate do método get.
No exemplo abaixo, depois que o método get() é executado, o item key fica bloqueado até que o método commit() ou rollback seja executado.

try{
    //inicia uma transação.
    $con->setAutoCommit(false);
    //bloqueia o item key.
    $value = $con->get("key", true);
    if($value != null && strcmp($value,"val") == 0){
        //remove o item key se existir e for igual ao valor 'val'
        $con->remove($key);
    }
    //confirma todas as operações.
    $con->commit();
}
catch(Exception $e){
    //Desfaz todas as operações.
    $con->rollback();
}

Exemplo


Para exemplificar uma transação com o BRCache, será usado o clássico exemplo de uma transação bancária.
Basicamente, uma transação bancária é dividida nas operações de débito e crédito. Um determinado valor é debitado de uma conta e creditado em outra.

No exemplo serão criadas as funções init(), printValue(), transaction() e exception().

A função init() irá iniciar a conta #00001 com 1000 e a conta #00002 com 300.

function init($con){
 $con->put("#00001", 1000); 
 $con->put("#00002", 300); 
}

A função printValue() irá imprimir o valor total de cada conta.

function printValue($con){
 
 echo "-----------------\r\n";
 $money = $con->get("#00001");
 echo "#00001";
 echo "total: " . $money . "\r\n";
 
 $money = $con->get("#00002");
 echo "#00002";
 echo "total: " . $money . "\r\n";
}

A função transaction() irá transferir um valor da conta #00001 para a conta #00002. Neste processo, pode ocorrer um erro na função exception(). Justamente no local onde os dados do sistema podem ficar inconsistentes.

function transaction($con, $value){
 
 $originMoney = $con->get("#00001");
 $originMoney = $originMoney - $value;
 $con->put("#00001", $originMoney);
 
 exception();
 
 $destMoney = $con->get("#00002");
 $destMoney = $destMoney + $value;
 $con->put("#00002", $destMoney);
 
}

A função exception() simula um erro. Ela pode ou não lançar uma exceção.

function exception(){
 throw new Exception("some error");
}

Sem o controle transacional, o trecho principal ficaria:

$con = new BRCacheConnection();
init($con);
echo "before transaction\r\n";
printValue($con);
try{
    transaction($con, 300);
}
catch(Exception $e){
    echo "erro: " + $e->getMessage();
    echo "-----------------\r\n";
}
echo "after transaction\r\n";
printValue($con);

A saída do trecho principal em um cenário sem erros seria:

before transaction
-----------------
#00001
total: 1000
#00002
total: 300
after transaction
-----------------
#00001
total: 700
#00002
total: 600

Note que a transação bancária ocorreu normalmente, mas a saída do mesmo trecho em um cenário com um erro seria:

before transaction
-----------------
#00001
total: 1000
#00002
total: 300
erro: some error
-----------------
after transaction
-----------------
#00001
total: 700
#00002
total: 300

Perceba que os dados agora estão inconsistentes. Foi debitado da conta de origem, mas não foi creditado na conta de destino. Para resolver esse problema isolamos a função transaction() dentro de uma transação. O novo trecho principal seria:

$con = new BRCacheConnection();
init($con);
echo "before transaction\r\n";
printValue($con);
$con->setAutoCommit(false);
try{
    transaction($con, 300);
    $con->commit();
}
catch(Exception $e){
    $con->rollback();
    echo "erro: " + $e->getMessage();
    echo "-----------------\r\n";
}
echo "after transaction\r\n";
printValue($con);

A saída do novo trecho em um cenário sem erros seria:

before transaction
-----------------
#00001
total: 1000
#00002
total: 300
after transaction
-----------------
#00001
total: 700
#00002
total: 600

A transação bancária ocorreu normalmente, mas a saída do mesmo trecho em um cenário com um erro seria:

before transaction
-----------------
#00001
total: 1000
#00002
total: 300
erro: some error
-----------------
after transaction
-----------------
#00001
total: 1000
#00002
total: 300

Perceba que mesmo ocorrendo um erro, os dados do sistema permaneceriam consistentes.

Conclusão 


 O uso de transações no BRCache é intuitivo e fácil de usar. Se possuir familiaridade com transações em banco de dados relacional, o entendimento será mais fácil.

quarta-feira, 5 de outubro de 2016

BRCache + PHP



O BRCache é uma ótima opção de cache para aplicações PHP. É rápido, permite o uso de conexões persistentes, suporta armazenamento em memória e disco, tem suporte transacional. Não é necessário o carregamento de nenhum arquivo de configuração. Ele pode ser usado para fazer o cache de páginas, compartilhamento de variáveis globais e manipulação de sessões.

Instalando o cliente BRCache


Para usar o BRCache em uma aplicação PHP, é necessário fazer o download de um cliente. Uma opção é o cliente fornecido pela equipe do BRCache. Depois de feito o download, deve-se descompacta-lo.

ex:
unzip <origem>/brcache-client-yy-xx.zip -d <destino>

A variável <origem> é o local onde o arquivo está e <destino> é o local onde será descompactado. A pasta de destino tem que ser informada em include_path (php.ini).

As operações no cache são feitas com o uso da classe BRCacheConnection. Ela fica localizada no script brandao\brcache\BRCacheConnection.php.

class BRCacheConnection{

    public function close(){...}
 
    public function isClosed(){...}
 
    public function replace($key, $value, $timeToLive = 0, $timeToIdle = 0){...}
 
    public function replaceValue($key, $oldValue, 
             $newValue, $cmp, $timeToLive = 0, $timeToIdle = 0){...}
 
    public function putIfAbsent($key, $value, $timeToLive = 0, $timeToIdle = 0){...}

    public function put($key, $value, $timeToLive = 0, $timeToIdle = 0){...}
 
    public function set($key, $value, $timeToLive = 0, $timeToIdle = 0){...}
 
    public function get($key, $forUpdate = false){...}
 
    public function remove($key){...}

    public function removeValue($key, $value, $cmp){...}
 
    public function setAutoCommit($value){...}
 
    public function isAutoCommit(){...}
 
    public function commit(){...}
 
    public function rollback(){...}
 
    public function getHost(){...}
 
    public function getPort(){...}
 
}

  • close(): fecha a conexão com o servidor.
  • isClosed(): verifica se a conexão está fechada.
  • replace(): substitui o valor associado à chave somente se ele existir.
  • replaceValue(): substitui o valor associado à chave somente se ele for igual a um determinado valor.
  • putIfAbsent(): associa o valor à chave somente se a chave não estiver associada a um valor.
  • put(): associa o valor à chave.
  • set(): associa o valor à chave somente se a chave não estiver associada a um valor.
  • get(): obtém o valor associado à chave bloqueando ou não seu acesso as demais transações.
  • remove(): remove o valor associado à chave.
  • removeValue(): remove o valor associado à chave somente se ele for igual a um determinado valor.
  • setAutoCommit(): define o modo de confirmação automática.
  • isAutoCommit(): obtém o estado atual do modo de confirmação automática.
  • commit(): confirma todas as operações da transação atual e libera todos os bloqueios detidos por essa conexão.
  • rollback(): desfaz todas as operações da transação atual e libera todos os bloqueios detidos por essa conexão.
  • getHost(): obtém o endereço do servidor.
  • getPort(): obtém a porta do servidor.

Adicionando itens


São oferecidos vários métodos para inserir um item no cache. Cada um com sua particularidade.

Método put


O método put associa um valor a uma chave, mesmo que ela exista. Ele retorna true, se já existir um valor associado à chave, ou false, se não existir um valor associado à chave.

$result = $con->put('key', $value);

if($result){
    echo 'replaced';
}
else{
    echo 'stored';
}

Método replace


O método replace substitui o valor associado à chave somente se ele existir. Ele retorna true, se o valor for substituído, ou false, se o valor não for armazenado.

$result = $con->replace('key', $value);

if($result){
    echo 'replaced';
}
else{
    echo 'not stored';
}

Método replaceValue


O método replaceValue substitui o valor associado à chave somente se ele existir e for igual a um determinado valor. Ele retorna true, se o valor for substituído, ou false, se o valor não for armazenado.

$result = $con->replaceValue('key', 
$value,
function($a, $b){
    return strcmp($a,$b) == 0;
});

if($result){
    echo 'replaced';
}
else{
    echo 'not stored';
}

Método putIfAbsent


O método putIfAbsent associa o valor à chave somente se a chave não estiver associada a um valor. Esse método tem uma particularidade. Quando existe um valor associado à chave, o mesmo é retornado, mas será lançada uma exceção se ele expirar no momento em que for recuperado.

try{
    $currentValue = $con->putIfAbsent('key', $value);
    if($currentValue == null){
        echo 'stored';
    }
    else{
        echo 'not stored';
    }
}
catch(CacheException $e){
    if(e.getCode() == 1030){
        //o valor atual expirou
    }
    throw e;
}

Método set


O método set associa o valor à chave somente se a chave não estiver associada a um valor. Ele retorna true, se o valor for associado à chave, ou false, se ele for descartado.

$result = $con->set('key', $value);
if(result){
    echo 'stored';
}
else{
    echo 'not stored';
}

Obtendo itens.


Um valor é obtido com o uso do método get. Ele retorna o valor associado à chave ou null.

$value = $con->get('key');

if($value != null){
    echo 'value exists';
}
else{
    echo 'value not found';
}

Removendo itens.


São oferecidos vários métodos para remover um item do cache.

Método remove.


O método remove apaga o valor associado à chave. Ele retorna true, se o valor for removido, ou false, se ele não existir.

$result = $con->remove('key');

if($result){
    echo 'removed';
}
else{
    echo 'not found';
}

Método removeValue.


O método removeValue remove o valor associado à chave somente se ele existir e for igual a um determinado valor. Ele retorna true, se o valor for removido, ou false, se ele não existir ou for diferente do valor informado.

$result = $con->remove(
'key',
function($a, $b){
    return strcmp($a,$b) == 0;
});

if($result){
    echo 'removed';
}
else{
    echo 'not found';
}

Exemplo


No exemplo a seguir o BRCache é usado para fazer o cache de páginas. Esse exemplo apenas ilustra o uso da classe BRCacheConnection.

<?php
require_once 'brandao/brcache/BRCacheConnection.php';

function getURI(){
  $serverRoot   = str_replace('\\','/', $_SERVER['DOCUMENT_ROOT']);
  $docRoot      = str_replace('\\','/', __DIR__);
  $relativePath = str_replace($serverRoot,'', $docRoot);
  $query        = $_SERVER['QUERY_STRING'];
  $uri          = str_replace($relativePath . '/index.php', '', $_SERVER['REQUEST_URI']);
  $uri          = str_replace('?' . $_SERVER['QUERY_STRING'], '', $uri);
  return $uri;
}

$uri     = getURI();
$con     = new BRCacheConnection();
$content = $con->get($uri);

if($content != null){
  echo $content;
  exit;
}

ob_start();

include $uri;

$content = ob_get_contents();

$con->put($uri, $content, 300000);

ob_end_flush();
?>

No trecho abaixo o URI no formato  /<path>/index.php<uri> é convertido no formato <uri>.
function getURI(){
 $serverRoot   = str_replace('\\','/', $_SERVER['DOCUMENT_ROOT']);
 $docRoot      = str_replace('\\','/', __DIR__);
 $relativePath = str_replace($serverRoot,'', $docRoot);
 $query        = $_SERVER['QUERY_STRING'];
 $uri          = str_replace($relativePath . '/index.php', '', $_SERVER['REQUEST_URI']);
 $uri          = str_replace('?' . $_SERVER['QUERY_STRING'], '', $uri);
 return $uri;
}

No trecho abaixo é feita a conexão com o servidor BRCache. Opcionalmente, pode-se informar o host, porta e se a conexão será persistente.
$con = new BRCacheConnection();

No trecho abaixo ocorre a tentativa de obter o conteúdo da página e colocá-lo em $content.
$content = $con->get($uri);

No trecho abaixo, se $content for diferente de null, o conteúdo da página será enviado ao cliente.
if($content != null){
  echo $content;
  exit;
}

No trecho abaixo é ativado o buffer de saída, incluída a página no buffer e colocada em $content.
ob_start();
include $uri;
$content = ob_get_contents();

No trecho abaixo, o conteúdo da página é enviada ao cache. Ele ficará disponível por 5 minutos.
$con->put($uri, $content, 300000);

No trecho abaixo o buffer de saída é descarregado e desativo.
ob_end_flush();

Instalando o BRCache no Windows.


O BRCache é independente de plataforma. Podendo ser executado em qualquer sistema operacional, desde que tenha uma maquina virtual instalada.
Para o Wrindows, foi disponibilizado um instalador para facilitar sua instalação e configuração.
O download do instalador pode ser feito acessando a página do BRCache no Sourceforge.
Obs: Caso não tenha uma JRE instalada, o download pode ser feito em http://www.java.com/en/download/manual.jsp#win.


Depois de feito o download é necessário executar o arquivo brcache-server-xx-xx.exe.


Na execução da instalação, algumas informações serão pedidas. Na primeira etapa será perguntado o local onde o servidor será instalado. Depois de informado, aperte o botão Next. Se não for um usuário avançado, deixe como está.


Depois de selecionado o local, será perguntado o nome do grupo onde os atalhos serão registrados. Depois de informado, aperte o botão Next. Se não for um usuário avançado, deixe como está.


Depois de selecionado o grupo, será perguntado o tipo de instância do servidor BRCache. Depois de informado, aperte o botão Next. Se não for um usuário avançado, deixe como está.



  • Developer Machine: Escolha esta opção para uma estação de trabalho típico onde o BRCache é apenas para uso pessoal. Supõe-se que muitas outras aplicações desktop estão em execução. O servidor BRCache será configurado para utilizar o mínimo de recursos do sistema.
  • Server Machine: Escolha esta opção para uma máquina servidor onde o servidor BRCache está rodando ao lado de outros aplicativos servidores, como FTP, e-mail e servidores Web. O servidor BRCache será configurado para usar uma porção moderada dos recursos do sistema.
  • Dedicated Server Machine: Escolha esta opção para uma máquina servidor que se destina a executar apenas o servidor BRCache. Supõe-se que não existem outras aplicações em execução. O servidor BRCache será configurado para usar todos os recursos disponíveis do sistema.

Verifique se está tudo correto e aperte o botão Install.


Após o término da instalação, aperte o botão Finish. Nesse momento o servidor já estará em execução.


Para verificar se está tudo funcionando, execute o terminal com o host localhost e porta 1044.


Se o servidor estiver funcionando, o console ficará parado, então aperte enter e uma mensagem de erro irá aparecer. Isso é um sinal que o servidor foi instalado corretamente.





domingo, 21 de agosto de 2016

Named-Lock version 1.0 B2 was released

Named-Lock version 1.0 B2 was released
see more http://namedlock.brandao.org/.

1. Quick Reference.


The named-lock is a utility for acquiring named locks.


1.1 Named factory.


Main class


public class Test {

    public static void main(String[] args) throws InterruptedException{
        NamedLockFactory lockFactory = new NamedLockFactory();
        
        System.out.println("start test");

        Lock lock = lockFactory.getLock("lock_name");
        lock.lock();
        try{
            Task task = new Task(lockFactory);
            task.start();
            Thread.sleep(1000);
            System.out.println("1");
        }
        finally{
            lock.unlock();
        }

        Thread.sleep(1000);
        System.out.println("end test");
        
    }

}

Task class


public class Task extends Thread{

    private NamedLockFactory lockFactory;

    public Task(NamedLockFactory lockFactory){
        this.lockFactory = lockFactory;
    }

    public void run(){

        Lock lock = lockFactory.getLock("lock_name");
        lock.lock();
        try{
            System.out.println("2");
        }
        finally{
            lock.unlock();
        }

    }

}


output:
start test
1
2
end test


1.2 Named lock.



NamedLock namedLock = new NamedLock()
Serializable refLock = namedLock.lock("lock_name");
try{
   // manipulate protected state
}
finally{
  namedLock.unlock(refLock, "lock_name");
}