Simplificando o desenvolvimento de software em nuvem escalável com Apache Thrift

Apache Thrift é uma estrutura que permite desenvolvimento escalável entre linguagens, resultando em comunicação inequívoca entre componentes em ambientes de rede. Neste artigo, apresentamos as ideias em torno do Thrift (uma definição de interface para chamada de procedimento remoto com ligações para várias linguagens) e demonstramos o Thrift como aplicativo de cliente e de servidor para várias linguagens.

M. Tim Jones, Independent author, Consultor

Photo of M.Tim JonesM. Tim Jones é arquiteto de firmware embarcado e autor de Artificial Intelligence: A Systems Approach, GNU/Linux Application Programming (na segunda edição), AI Application Programming (na segunda edição) e BSD Sockets Programming from a Multilanguage Perspective. Seu histórico em engenharia vai do desenvolvimento de kernels para espaçonaves geossíncronas a arquitetura de sistemas embarcados e desenvolvimento de protocolos de rede. Tim é arquiteto de plataforma na Intel e autor morando em Longmont, Colorado, EUA.



28/Fev/2014

Desenvolver aplicativos em ambientes de nuvem tende a resultar em um conjunto de problemas semelhante ao do desenvolvimento de sistemas distribuídos, envolvendo comunicação entre nós, protocolos e serviços expostos. Esse desenvolvimento pode ficar ainda mais complicado com a presença de várias linguagens de programação, cada uma com seu sistema de tipos, o que exige tradução de tipos entre clientes e servidores para garantir que todas as entidades no grupo falem os mesmos formatos. Mesmo quando um sistema é desenvolvido na mesma linguagem de programação, as interações exigem que os objetos sejam comunicados de maneira eficiente e inequívoca. Adicione uma ou mais linguagens e a complexidade do sistema e o escopo do trabalho aumentam drasticamente.

Um grande problema no desenvolvimento de sistemas distribuídos é a comunicação entre os elementos de software. Uma simplificação desse tipo de serviço permite que o software abstraia as interações distribuídas sob chamadas de procedimento. Uma das primeiras implementações do que é chamado de chamada de procedimento remoto (RPCs) foi apresentada pela Xerox em 1981 em um protocolo chamado Courier. O protocolo fornecia primitivas para permitir que aplicativos escritos na linguagem de programação Mesa comunicassem-se em uma rede, mas era preciso intervenção manual para serializar e desserializar as chamadas. Esse conceito foi muito aplicado desde então em vários aplicativos. Outros exemplos de RPCs incluem a Common Object Request Broker Architecture, o pacote Google Protocol Buffers (protobufs), Apache Avro e muitos outros.

Em 2007, o Facebook lançou no domínio de software livre uma nova implementação de RPC com suporte para várias linguagens, que agora se tornou um projeto Apache chamado de Thrift. O Thrift oferece suporte para várias linguagens de programação (em níveis variados), incluindo Python, Ruby, Smalltalk, Haskell, Erlang, Java™, Node.js, PHP, Go, D e C/C++.

Origem do Apache Thrift

Antes de tornar-se um projeto de software livre do Apache, Thrift era uma biblioteca de software e conjunto de ferramentas de geração de código usados internamente no Facebook. Seu objetivo era permitir comunicação eficiente entre linguagens de programação usando uma ponte de software transparente de alto desempenho.

Thrift é uma estrutura para desenvolvimento de serviços escaláveis entre linguagens que fornece não apenas suporte a geração de código para várias linguagens, mas também uma pilha de software que simplifica o desenvolvimento de serviços relacionados à rede. Vamos explorar algumas das ideias subjacentes no Thrift e criar uma demonstração de sua capacidade.


Apache Thrift como língua franca

Apache Thrift implementa uma pilha de software que simplifica o desenvolvimento de aplicativos de comunicação em várias linguagens. A Figura 1 contém uma ilustração da pilha de software. Na parte inferior da pilha está a interface física (que poderia ser uma interface de rede ou algo simples como um arquivo). Essa interface influencia os níveis mais altos da pilha.

Figura 1. A pilha de software Thrift

Thrift oferece várias camadas de transporte e protocolo que definem como os dados são movidos e quais formatos eles assumem (usando um Processador, que contém os fluxos de entrada e saída). Por exemplo, TBinaryProtocol define um protocolo binário eficiente para comunicação que pode ser usado sobre transporte TSocket para comunicação entre terminais de rede. Também é possível usar TJSONProtocol para comunicação, usando o formato JavaScript Object Notation (JSON) com TMemoryTransport para comunicação de memória compartilhada.

No nível superior estão tipos de servidor que podem ser implementados com o auxílio de Thrift. Essa configuração pode incluir um servidor de encadeamento único para depuração (TSimpleServer), um servidor HTTP que pode fornecer URLs semelhantes a Representational State Transfer (THttpServer), ou um servidor de vários processos que bifurca um processo para cada solicitação recebida (TForkingServer). Thrift é escrito não apenas para simplificar a comunicação entre várias abordagens (protocolos e transportes), mas também para simplificar o desenvolvimento de servidor usando vários estilos de servidor.

Não está mostrado na Figura 1 um sistema de tipo que permite comunicação entre linguagens e para o qual o Thrift oferece suporte. Esse sistema oferece suporte para tipos como byte, short, int double, string e tipos mais avançados, como contêineres (listas, mapas) e estruturas. Esse sistema de tipos genérico é a base comum para comunicação de dados.


Instalando Thrift

É possível instalar a partir da fonte, um processo relativamente simples, ou através de um gerenciador de pacotes, como apt ou yum. Para instalar a partir da fonte, basta fazer download do tarball em formato gzip, configurar e desenvolver com make. Depois que Thrift é desenvolvido, é possível instalar facilmente usando o destino de instalação de make. Dependendo da linguagem escolhida, podem ser necessários mais pacotes (por exemplo, clientes e servidores em C++ exigem o pacote Boost).

Se você estiver executando uma versão recente de uma distribuição Linux®, pode ser possível instalar o compilador Thrift através do gerenciador de pacotes. Por exemplo, no Ubuntu, você pode tentar o seguinte:

$ sudo apt-get install thrift-compiler

Consulte o recurso de instalação para detalhes.


RPC, sistemas de tipos e definição de interface

Pela pilha de software mostrada na Figura 1, a solução parece ser bem complicada. Embora haja alguma complicação no Thrift (como em qualquer esquema de RPC para várias linguagens), a realidade ao utilizar o Thrift é muito mais simples.

A primeira etapa para usar o Thrift para desenvolver um aplicativo RPC é definir o arquivo de definição de interface (veja a Figura 2). Esse arquivo permite definir os tipos e serviços que serão expostos. Esse arquivo independe da linguagem e usa os tipos e as definições do Thrift. Usando esse arquivo, o compilador do Thrift gera arquivos de origem (para o cliente e o servidor) na linguagem escolhida.

Figura 2. Usando o compilador do Thrift para geração de origem

Vamos ver um exemplo simples que ilustra esse conceito e, em seguida, passar para um exemplo funcional. O uso geral do compilador do Thrift é:

$ thrift --gen <language> >.thrift file>

Considere um aplicativo simples, um serviço aritmético em rede. Esse serviço oferece serviços matemáticos básicos para clientes remotos através de uma interface de programação de aplicativo simples. Para definir esse serviço, eu crio um arquivo Thrift como mostra a Listagem 1. Esse arquivo contém espaços de nome para os vários usuários de linguagem (py representa Python e rb representa Ruby). Em seguida, defino o serviço que quero expor. Usando a definição do Thrift, eu uso a palavra-chave de serviço e um nome para representar o serviço (neste caso, MyMath). Defina seus serviços de maneira independente de linguagens. Este serviço consiste em quatro funções expostas, e cada uma toma um conjunto de valores de entrada e retorna um resultado.

Lista 1. Arquivo .thrift de exemplo que define a interface (proj.thrift)
# proj.thrift

namespace py demoserver
namespace rb demoserver

/* All operands are 32-bit integers called a Value */
typedef i32 Value
typedef i32 Result

/* Math service exposes an some math function */
service MyMath
{
  Result add( 1: Value op1, 2: Value op2 ),
  Result mul( 1: Value op1, 2: Value op2 ),
  Result min( 1: Value op1, 2: Value op2 ),
  Result max( 1: Value op1, 2: Value op2 )
}

Para compilar essa definição de interface em código, eu defino minha linguagem de programação desejada (neste caso, Python) para o Thrift da seguinte maneira:

$ thrift --gen py proj.thrift

Esse processo cria um subdiretório chamado gen-py que contém alguns arquivos que definem os tipos, as constantes e o código. O conteúdo completo desse subdiretório de código gerado é mostrado abaixo. O interessante é o código gerado para os tipos (ttypes.py) e a pilha de software para o serviço (MyMath.py). Observe também MyMath-remote, sobre o qual eu falo mais em breve, como uma maneira de testar o novo serviço.

$ ls gen-py/*
gen-py/__init__.py

gen-py/demoserver:
constants.py  __init__.py  __init__.pyc  MyMath.py  MyMath.pyc  MyMath-remote  
ttypes.py  ttypes.pyc
$

Para demonstrar totalmente o Thrift e apreciar o que ele fornece, é preciso mais de uma linguagem. Considere um cliente escrito em Ruby. Para oferecer suporte a um cliente Ruby, é necessário gerar um conjunto de código Thrift para Ruby usando o compilador Thrift:

$ thrift --gen rb proj.thrift

Se você observar o recém-criado subdiretório gen-rb, descobrirá os arquivos para uma implementação do Ruby. Cada linguagem oferece uma implementação diferente da pilha de software Thrift e, como você pode ver abaixo, os resultados de arquivo são diferentes, embora forneçam a mesma implementação de tipos, constantes e serviço.

$ ls gen-rb/
my_math.rb  proj_constants.rb  proj_types.rb
$

Com o trabalho do Thrift concluído no arquivo de definição de interface de serviço, estou agora pronto para implementar um servidor que usa a implementação de serviço em Python.


Desenvolvendo um servidor Thrift

Com base na definição de serviço na Listagem 1, eu implemento esses serviços no Python. Como mostra a Listagem 2, meu servidor Python é bem simples. Primeiro eu exponho os módulos Python, anexando o caminho ao código gerado pelo Thrift, e em seguida importo os módulos necessários que usarei. Observe que esses módulos são a pilha de software, como mostrado na Figura 1. Eu defino a classe derivada da implementação matemática com base na classe desenvolvida no Thrift (demoserver.MyMath.Iface). Essa classe define a implementação dos serviços, organizados de forma simples em Python.

A parte final da Listagem 2 é a parte principal do script, que monta os componentes da pilha Thrift. Eu crio o processador, inicializando com a classe MathImpl. Também crio o protocolo e os elementos de transporte da pilha e monto a pilha dentro de TThreadedServer. Uma chamada final para o método serve() da instância de servidor permite processar os resultados encadeados.

Lista 2. Servidor Python Thrift (server.py)
#!/usr/bin/python

import sys

sys.path.append('./gen.py')

from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer

import demoserver.MyMath

class MathImpl( demoserver.MyMath.Iface ):
	def add( self, op1, op2 ):
		return op1 + op2
	def mul( self, op1, op2 ):
		return op1 * op2
	def max( self, op1, op2 ):
		return max([op1, op2])
	def min( self, op1, op2 ):
		return min([op1, op2])

if __name__ == '__main__':

	processor = demoserver.MyMath.Processor( MathImpl() )
	transport = TSocket.TServerSocket( port = 18181 )
	tbfactory = TTransport.TBufferedTransportFactory()
	pbfactory = TBinaryProtocol.TBinaryProtocolFactory()

	server = TServer.TThreadedServer( processor, transport, tbfactory, pbfactory )

	print 'Starting the Math Server...'

	server.serve();

Lembre-se do utilitário MyMath-remote desenvolvido para o serviço Python. O Thrift gerou esse arquivo para permitir o teste do servidor Python. Para usar esse aplicativo de teste, eu preciso primeiro expor os arquivos gerados para o caminho do Python:

$ export PYTHONPATH=$PYTHONPATH:./gen-py

Com isso definido, agora posso testar meu servidor usando esse utilitário da linha de comando. A Listagem 3 apresenta um uso simples desse aplicativo, e examina as informações de ajuda que o utilitário expõe. O utilitário espera uma definição do host (onde está o servidor e o número da sua porta), a função a ser chamada e um conjunto de argumentos. Como meu servidor é executado no mesmo nó que o cliente, uso localhost e o número da porta definida junto com a função e argumentos de teste desejados.

Lista 3. Usando o aplicativo de teste fornecido
$ ./gen-py/demoserver/MyMath-remote --help

Usage: ./gen-py/demoserver/MyMath-remote [-h host[:port]] 
		[-u url] [-f[ramed]] function [arg1 [arg2...]]

Functions:
  Result add(Value op1, Value op2)
  Result mul(Value op1, Value op2)
  Result min(Value op1, Value op2)
  Result max(Value op1, Value op2)

$ ./gen-py/demoserver/MyMath-remote -h localhost:18181 max 5 7
7
$ ./gen-py/demoserver/MyMath-remote -h localhost:18181 mul 9 8
72
$

O aplicativo de teste gerado automaticamente é uma boa maneira de testar o servidor antes que o cliente esteja pronto. Falando em cliente, vamos agora ver um cliente correspondente do Thrift implementado em Ruby.


Desenvolvendo um cliente do Thrift

Para verificar se o servidor está funcionando, eu desenvolvo em seguida um cliente usando Thrift e a linguagem Ruby. Esse projeto ilustra as diferenças ao usar Thrift com várias linguagens (Python e Thrift neste caso) e oferece um bom ambiente de teste para várias linguagens.

A Listagem 4 contém o código-fonte completo de um cliente Thrift simples em Ruby. Começo definindo o caminho de carregamento do código Ruby gerado pelo Thrift, para tornar os módulos do Thrift visíveis. Semelhante ao servidor Python, o cliente Ruby constrói sua pilha de software a partir do código gerado nos módulos Ruby. Crio um soquete (inicializado com o nó e a porta do servidor) e conecto à camada de transporte. Esse soquete é inicializado na instância de protocolo, que é inicializada com os serviços.

Após criar a conexão com o servidor (através do método open para a instância transport), posso fazer chamadas para as funções de serviço que são serializadas no servidor ocultamente. Quando isso é concluído, eu fecho o transporte, o que libera o encadeamento no servidor.

Lista 4. Cliente Thrift em Ruby (client.rb)
# Make thrift-generated code visible
$:.push('./gen-rb')

require 'thrift'
require 'my_math'

begin

	# Build up the Thrift stack
	transport = Thrift::BufferedTransport.new(Thrift::Socket.new('localhost', 18181))
	protocol = Thrift::BinaryProtocol.new(transport)
	client = Demoserver::MyMath::Client.new(protocol)

	transport.open()

	# Try an add operation
	result = client.add( 1, 5 )
	puts result.inspect

	# Try a max operation
	result = client.max( 9, 7 )
	puts result.inspect

	transport.close()

end

A execução do cliente simples contra o servidor Python em execução resulta na saída mostrada na Listagem 5. É pouco código para as abstrações de linguagem que foram apresentadas. O código gerado pelo Thrift mostra os serviços da pilha sendo usados (MyMath.py, my_math.rb), o que, para um serviço simples como este, é considerável.

Lista 5. Testando o cliente Ruby
$ ruby client.rb 
6
9
$

Como demonstrado, Thrift facilita o desenvolvimento de aplicativos de cliente e de servidor que expõem serviços. Thrift facilita ainda mais o desenvolvimento de aplicativos em várias linguagens através de um formato de definição de interface unificado.


Protegendo a comunicação de Thrift

Para aplicativos de produção ou aplicativos da web, segurança é essencial. Thrift oferece suporte à comunicação segura através da camada de transporte usando a classe TSSLTransportFactory. Essa implementação de transporte agrupa as implementações de TSocket e TServerSocket com recursos de Secure Socket Layer (SSL). Em conjunto com um par de chaves RSA, é possível comunicar-se de forma segura entre um cliente e um servidor Thrift.


Indo além

Embora o Thrift seja uma ótima maneira de unir ambientes de vários aplicativos e suas comunicações, não é para os fracos. Documentação escassa e desatualizada e suportes diferentes em cada linguagem fazem com que seja às vezes uma experiência frustrante. A documentação do Thrift não tem exemplos de muitas das linguagens suportadas, o que exige inspeção detalhada do código gerado para entender como ele funciona.

Mas, indo além da imaturidade do Thrift, o utilitário automatiza muito do código detalhado para comunicação, serialização e desenvolvimento de serviços. Esse trabalho de bastidores permite que você se concentre nos aplicativos e desenvolva rapidamente aplicativos distribuídos em várias linguagens.

Recursos

Aprender

  • O website do Apache Thrift é a fonte principal de informações sobre o Thrift. É possível obter o mais recente release do Thrift e saber como usar o Thrift a partir de sua documentação e tutoriais.
  • Instalando Thrift é um procedimento relativamente indolor. Se você encontrar problemas não tiver certeza sobre os requisitos para usar o Thrift, confira esta lista de requisitos de desenvolvimento.
  • Thrift: Scalable Cross-Language Services Implementation de Mark Slee, Aditya Agarwal e Marc Kwiatkowski do Facebook é uma ótima introdução ao Thrift, sua motivação, alguns dos sacrifícios interessantes feitos no design e detalhes da implementação.
  • Meet the Extensible Messaging and Presence Protocol (XMPP) (M. Tim Jones, developerWorks, setembro de 2009) apresenta o XMPP e sua aplicação como um protocolo de mensagem instantânea. XMPP usa XML nos bastidores para gerenciar serialização e comunicação entre clientes e servidores. Embora não tenha requisitos idênticos ao Thrift, XMPP é outro exemplo de comunicação entre aplicativos com suporte para várias linguagens.
  • Nos recursos para desenvolvedores de nuvem do developerWorks, descubra e compartilhe o conhecimento e a experiência dos desenvolvedores de aplicativos e serviços que estão desenvolvendo os seus projetos de implementação de nuvem.
  • Siga as developerWorks no Twitter. Você pode seguir este autor no Twitter em M. Tim Jones.
  • Acompanhe as demos do developerWorks que vão de instalação de produto e demonstrações de configuração para iniciantes a funcionalidade avançada para desenvolvedores experientes.

Discutir

  • Participe da comunidade do developerWorks . Conecte-se com outros usuários do developerWorks e explore os blogs, fóruns, grupos e wikis feitos por desenvolvedores.

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre, Cloud computing
ArticleID=964415
ArticleTitle=Simplificando o desenvolvimento de software em nuvem escalável com Apache Thrift
publish-date=02282014