Encantando Python: Computação de Distribuição com RPyC

Uma opção nativa de Python para controle contínuode muitas máquinas

RPyC é uma biblioteca consistente para integração de processos Python em muitas máquinas/processos. Este artigo verifica as vantagens e desvantagens que RPyC possui em relação a outras estruturas distribuídas Python, como XML-RPC e Pyro. Alguns exemplos simples do uso de RPyC estão incluídos para fornecer uma sensação da biblioteca.

David Mertz, Freelance writer, Gnosis Software, Inc.

David Mertz 的照片Para David Mertz, o mundo todo é um teste; ele devota sua carreira a fornecer instruções de testes marginais. Para saber mais sobre sua carreira, consulte sua página pessoal na Web. Ele escreve as colunas Charming Python e XML Matters do developerWorks desde 2000. Consulte seu livro, Text Processing in Python. É possível entrar em contato com David em mertz@gnosis.cx.



31/Mar/2009

Em 2002, escrevi uma série de artigos sobre "computação de distribuição" (consulte Recursos). Naquela época, RPyC não existia, mas cobri a ferramenta Pyro de Python e a biblioteca xmlrpclib. Surpreendentemente pouco mudou nos últimos sete anos. Pyro ainda está por aí e mantido ativamente e xmlrpclib ainda existe em Python, assim como SimpleXMLRPCServer (apesar de terem sido refatorados como xmlrpc.client e xmlrpc.server em Python 3).

O espaço no qual RPyC ingressa é em grande parte aquele já ocupado por Pyro e XML-RPC. RPyC não inclui realmente nada fundamentalmente novo nessas ferramentas há tempos no mercado, mas há alguns recursos interessantes no design de RPyC.

RPyC 3.0+, em síntese, possui dois modos: um "modo clássico", que já estava disponível antes de sua versão 3, e um "modo de serviços", que foi introduzido na versão 3. O modo clássico não possui nenhuma estrutura de segurança (que nem sempre é uma coisa ruim) e simplesmente apresenta máquinas remotas como se fossem recursos locais. O mais recente modo de serviços isola uma coleta de interfaces publicadas que um servidor suporta e é seguro de forma semelhante já que proíbe tudo que não seja explicitamente permitido. O modo clássico é essencialmente idêntico ao Pyro (sem a estrutura de segurança opcional do Pyro); o modo de serviço é essencialmente RPC (por exemplo, XML_RPC), exceto por alguns detalhes de convenções de chamada e implementação.

Plano de Fundo sobre Computação de Distribuição

No paradigma de computação pessoal independente, a estação de trabalho de um usuário contém diversos recursos que são usados para executar um aplicativo: armazenamento em disco para programas e dados; uma CPU; memória volátil; um monitor de exibição de vídeo; um teclado e um dispositivo ponteiro; possivelmente, dispositivos de E/S periféricos, como impressoras, scanners, sistemas de som, modems, entradas de jogos, etc. Computadores pessoais também possuem recursos de rede, mas, por convenção, uma placa de rede tem sido em geral simplesmente outro tipo de dispositivo de E/S.

A "computação de distribuição" é uma frase de impacto relacionada ao fornecimento de mais relacionamentos diferentes entre recursos de computação e computadores reais. Antigamente, falávamos de "cliente/servidor" e da "arquitetura N-Tier" para descrever relacionamentos hierárquicos entre computadores. No entanto, diferentes recursos podem entrar em muitos tipos diferentes de relacionamentos —alguns hierárquicos, outros organizados em treliças, anéis e diversas outras topologias. A ênfase se deslocou para gráficos, distanciando-se de árvores. Alguns dos muitos exemplo possíveis:

  • SANs (storage-area networks) centralizam recursos de disco persistentes para um grande número de computadores.
  • Na direção oposta, os protocolos ponto a ponto (P2P), como Gnutella e Freenet, descentralizam armazenamento de dados e sua recuperação.
  • O X Window System e VNC (Virtual Network Computing da AT&T) permitem que dispositivos de exibição e de entrada se conectem a máquinas fisicamente remotas.
  • Protocolos como Linux Beowulf permitem que muitas CPUs compartilhem o processamento de uma computação complexa, enquanto que projetos como SETI@Home (Search for Extraterrestrial Intelligence da NASA), GIMPS (Great Internet Mersenne Prime Search) e diversos "desafios" criptográficos façam o mesmo com necessidade muito menor de coordenação.
  • Ajax é outro meio, embora especificamente em um cliente de navegador da Web, de utilização de recursos de muitas origens.

Os protocolos e programas que distribuem o que eram basicamente recursos de hardware de aplicativos antiquados de PC formam somente parte da imagem de computação distribuída. Em um nível mais abstrato, coisas muito mais interessantes podem ser distribuídas: dados, informações, lógica do programa, "objetos" e, por fim, responsabilidades. DBMSs são um meio tradicional de centralizar dados e estruturar sua recuperação. Na outra direção, NNTP, e posteriormente P2P, descentralizou de forma radical o armazenamento de informações. Outras tecnologias, como mecanismos de procura, reestruturam e recentralizam coletas de informações. A lógica do programa descreve as regras reais de computação proibida (diversos tipos de RMI e RPC distribuem isso); protocolos de comissão de objeto, como DCOM, CORBA e SOAP, remodelam a noção de lógica em uma estrutura OOP. É claro que até mesmo DBMSs antiquados com acionadores, limitadores e normalizações sempre carregaram um certo grau de lógica do programa com eles. Todos esses recursos abstratos são, em algum ponto, armazenados em discos e fitas, representados na memória e enviados como fluxos de bits através de redes.

No final, o que é compartilhado entre computadores distribuídos são conjuntos de responsabilidades. Um computador "promete" a outro que sob determinadas circunstâncias enviará alguns bits que atendem determinas especificações por um canal. Essas promessas ou "contratos" raramente se referem, em primeiro lugar, a configurações específicas de hardware, mas são quase sempre sobre satisfazer requisitos funcionais dos destinatários.


Por que RPyC É Útil

Antes de descrever o que RPyC faz, permita-me sugerir três categorias de recursos/responsabilidades que podem ser distribuídas pelo RPyC:

  1. Recursos de computação (hardware). Alguns computadores têm CPUs mais rápidas do que outros e alguns, de forma semelhante, têm mais ciclos livres nessas CPUs quando consideradas as prioridades de processo e cargas de aplicativo. De forma semelhante, alguns computadores têm mais memória do que outros ou mais espaço em disco (importante, por exemplo, em alguns cálculos científicos de grande escala). Em alguns casos, periféricos especializados podem ser conectados a uma máquina em vez de a outra.
  2. Recursos informativos. Alguns computadores podem ter acesso privilegiado a determinados dados. Esse privilégio pode ser de diversos tipos. Por um lado, uma máquina específica pode ser a fonte de origem real dos dados; por exemplo, porque está conectada a algum tipo de coletor de dados automatizado, como um instrumento científico, ou porque é um terminal no qual usuários inserem dados (como uma caixa registradora, um balcão de check-in, um site de observação, etc.) . Por outro lado, um banco de dados pode ser local para um computador privilegiado ou, pelo menos, para um grupo limitado ao qual a máquina ou conta pertence. Computadores não privilegiados podem, todavia, ter alguma razão para ter acesso a determinados dados agregados ou filtrados derivados do banco de dados.
  3. Conhecimento da lógica de negócios. Em qualquer organização—ou entre organizações —determinadas partes (indivíduos, departamentos, etc.) têm a capacidade e responsabilidade de decidir sobre as regras de decisão em determinados domínios. Por exemplo, o departamento de folha de pagamento pode determinar (e, às vezes, modificar) a lógica de negócios referente a faltas por problema de saúde e a bônus. Ou Jane, a administradora de banco de dados, pode ter a responsabilidade de determinar a maneira mais eficiente de extrair esse dado de tabelas relacionais complexas.

RPyC permite distribuir todos esses recursos.


Modo Clássico do RPyC

O modo clássico do RPyC, em essência, simplesmente permite que todos os recursos de uma instalação remota de Python sejam executados em um sistema Python local. A segurança aqui basicamente se resume à mesma coisa que fornecer uma conta de shell a quem conectar. Se a segurança estiver em questão, a biblioteca de criptografia tlslite pode criptografar conexões e exigir logins para conexões. Ou seja, é possível criar facilmente o equivalente a ssh em vez de o equivalente a telnet. É claro que a vantagem aqui é que essas conexões podem ser controladas por scripts Python e permitem uma interação mais robusta entre recursos locais e remotos do que uma linguagem como Expect.

Para ativar um servidor em uma máquina remota, simplesmente execute classic_server.py que é fornecido com RPyC. Se quiser uma conexão segura, inclua a opção --vdb . Para customizar uma porta, use --port ou verifique --help para obter opções adicionais no servidor. É possível, é claro, ativar seu servidor como parte da inicialização geral do sistema ou em tarefas cron, para assegurar que estejam em execução em uma determinada máquina. Assim que alguns servidores estiverem em execução, é possível se conectar a eles a partir de quantos clientes quiser. Observe, no entanto, que os servidores não são realmente tão especiais; a mesma máquina e processo pode utilizar muitos servidores e agir como servidor para muitos clientes ao mesmo tempo (incluindo duas máquinas "atendendo" simetricamente uma a outra).

Uma sessão de shell (quando alguns servidores forem ativados) mostra isto:

Lista 1. Informações do Sistema e de Python
>>> import sys,os
>>> os.uname()        # Algumas informações sobre a máquina local
('Darwin', 'Mary-Anns-Laptop.local', '10.0.0d1',
'Darwin Kernel Version 10.0.0d1: Tue Jun  3 23:40:01 PDT 2008;
root:xnu-1292.3~1/RELEASE_I386', 'i386')
>>> sys.version_info  # Algumas informações sobre a versão local de Python
(2, 6, 1, 'final', 0)
>>> os.getcwd()
'/Users/davidmertz'

Agora, vamos importar RPyC e conectar a alguns servidores. Antes de fazer isso, servidores locais foram ativados a partir de duas versões diferentes de Python e uma em uma máquina remota.

Lista 2. Importando RPyC e Conectando a Servidores
>>> import rpyc
>>> conn26 = rpyc.classic.connect('localhost')
>>> conn26.modules.os.uname()
('Darwin', 'Mary-Anns-Laptop.local', '10.0.0d1',
'Darwin Kernel Version 10.0.0d1: Tue Jun  3 23:40:01 PDT 2008;
root:xnu-1292.3~1/RELEASE_I386', 'i386')
>>> conn26.modules.sys.version_info
(2, 6, 1, 'final', 0)
>>> conn26.modules.os.getcwd()
'/Users/davidmertz/Downloads/rpyc-3.0.3/rpyc/servers'
>>> conn25 = rpyc.classic.connect('localhost',port=18813)
>>> conn25.modules.os.uname()
('Darwin', 'Mary-Anns-Laptop.local', '10.0.0d1',
'Darwin Kernel Version 10.0.0d1: Tue Jun  3 23:40:01 PDT 2008;
root:xnu-1292.3~1/RELEASE_I386', 'i386')
>>> conn25.modules.sys.version_info
(2, 5, 1, 'final', 0)
>>> conn25.modules.os.getcwd()
'/Users/davidmertz/Downloads/rpyc-3.0.3/rpyc/servers'
>>> connGlarp = rpyc.classic.connect("71.218.122.169")
>>> connGlarp.modules.os.uname()
('FreeBSD', 'antediluvian.glarp.com', '6.1-RELEASE',
'FreeBSD 6.1-RELEASE #0: Fri Jul 18 00:01:34 MDT 2008;
root@antediluvian.glarp.com:/usr/src/sys/i386/compile/ANTEDILUVIAN',
'i386')
>>> connGlarp.modules.sys.version_info
(2, 5, 2, 'final', 0)
>>> connGlarp.modules.os.getcwd()
'/home/dmertz/tmp/rpyc-3.0.3/rpyc/servers'

É possível ver que temos conexões para duas máquinas diferentes com diferentes versões de Python. As funções e os atributos acessados são arbitrários, mas o ponto importante é que podemos chamar quaisquer funções ou classes disponíveis nessas máquinas. Portanto, por exemplo, se eu souber que a máquina antediluvian.glarp.com tem um módulo Payroll de Python instalado na mesma que tem a função get_salary() no mesmo, posso chamar:

Lista 3. Chamando get_salary()
>>> connGlarp.modules.Payroll.get_salary(last='Mertz',first='David')

Em antediluvian, pode haver um banco de dados local instalado ou pode até mesmo fazer suas próprias conexões com outros recursos. O que é retornado por minha chamada de função, no entanto, é simplesmente os mesmos dados que seriam retornados se a função tivesse sido executada localmente em antediluvian.

Colocando Código na Máquina Remota

Executar funções do módulo padrão em uma máquina remota é um belo truque, mas o que frequentemente queremos fazer de forma mais útil é executar nosso próprio código remotamente. Há diversas maneiras de se fazer isso no modo clássico de RPyC. A maneira mais direta é, possivelmente, simplesmente abrir um shell Python na máquina através da conexão que estabelecemos. Por exemplo:

Lista 4. Abrindo um Shell Python em uma Máquina Remota
>>> conn = rpyc.classic.connect('linux-server.example.com')
>>> rpyc.classic.interact(conn)
Python 2.5.2 (r252:60911, Oct  5 2008, 19:24:49)
[GCC 4.3.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> #... executar quaisquer comando em máquina remota

A partir desse shell remoto, é possível definir quaisquer funções ou classes desejadas, importar qualquer código externo ou fazer qualquer coisa possível a partir de um shell Python local.

Há também uma função RPyC chamada deliver() que parece enviar um objeto local para um servidor remoto, onde, presumivelmente, pode executar no contexto local. Infelizmente, não pude fazer com que essa função agisse conforme esperava (tenho certeza que simplesmente tenho alguma sintaxe errada, mas a documentação é vaga). Como um kludge, é possível executar diretamente (ou eval()) no servidor remoto. Por exemplo:

Lista 5. Executando Código em uma Máquina Remota
>>> # Defina uma função (ou classe, etc.) como código de origem real
>>> hello_txt = """
...  def hello():
...     import os
...     print "Hello from", os.getcwd()
...  """
>>> exec hello_txt            # Execute a definição de função localmente
>>> hello()
Hello from /tmp/clientdir
>>> conn.execute(hello_txt)   # Execute a definição de função remotamente
>>> remote_hello = conn.namespace['hello']
>>> remote_hello()            # Exibe no servidor remoto
>>> with rpyc.classic.redirected_stdio(conn): # Redirecione para o cliente local
...     conn.namespace['hello']()
...
Hello from /tmp/serverdir

O que fizemos é compilar uma função no espaço de nome remoto e executá-la como se fosse uma função local. No entanto, precisamos também captar essa saída do console remoto se queremos realmente ver a saída a partir de print. Se hello() tivesse, em vez disso, retornado um valor, no entanto, ainda assim seria retornado ao contexto local como nos exemplos anteriores.

Monkey Patching

O que fizemos com o contexto with acima foi basicamente um tipo de "monkey patching". Ou seja, usamos temporariamente um STDIO local no local de um STDIO remoto. RPyC permite que isso seja feito em geral com recursos do sistema. O código pode ser executado (ou seja, usar CPU e memória) em uma máquina remota, mas ainda assim usar algum recurso chave na máquina remota. Essa pode ser uma chamada de função cara do ponto de vista computacional se o servidor tiver mais recursos de CPU, mas, frequentemente, é algo como um soquete onde a máquina remota possui diferentes domínios de acesso. Por exemplo:

Lista 6. Monkey Patching de uma Conexão de Soquete
import myserver
c = rpyc.classic.connect('machine-outside-firewall')
myserver.socket = c.modules.socket
# ...execute myserver, que agora abrirá soquete fora do firewall

Recursos Assíncronos

Caso utilizar um recurso em uma máquina remota consuma muito tempo, o programa local que está usando esse recurso pode parecer paralisar enquanto a ação remota está sendo concluída. No entanto, existe essa necessidade se essas chamadas forem feitas de forma assíncrona. Um objeto remoto pode relatar se possui um resultado pronto e é possível executar outras ações locais nesse tempo (ou, possivelmente, ações que utilizam outros servidores remotos).

Lista 7. Uma Chamada Assíncrona
>>> conn.modules.time.sleep(15)     # Isso levará algum tempo
>>> # Deixe o servidor realizar a espera
>>> asleep = rpyc.async(conn.modules.time.sleep)
>>> asleep
async(<built-in function sleep>)
>>> resource = asleep(15)
>>> resource.ready
False
>>> # Faça alguma outra coisa por um tempo
>>> resource.ready
True
>>> print resource.value
None
>>> resource
<AsyncResult object (ready) at 0x0102e960>

Em nosso exemplo resource.value não é interessante. No entanto, se o método remoto que tornamos assíncrono tiver retornado um valor, esse valor estaria disponível assim que resource.ready tornar-se True.


Modo de Serviço de RPyC

Gravarei relativamente pouco sobre o modo de serviço RPyC mais novo. O modo clássico é realmente o sistema mais geral, apesar de ser construído como um serviço de estilo novo em apenas algumas linhas de código. Um serviço sob RPyC é realmente pouco diferente de XML-RPC (ou algo-RPC). O modo clássico é apenas um serviço que expõe tudo no sistema remoto, mas é possível construir serviços que expõem apenas algumas coisas em um pequeno número de linhas de código. Por exemplo:

Lista 8. Usando o Modo de Serviço para Expor Alguns Métodos
import rpyc
class DoStuffService(rpyc.Service):
   def on_connect(self):
       "Do some things when a connection is made"
   def on_disconnect(self):
       "Do some things AFTER a connection is dropped"
   def exposed_func1(self, *args, **kws):
       "Do something useful and maybe return a value"
   def exposed_func2(self, *args, **kws):
       "Like func1, but do something different"

if __name__ == '__main__':
   rpyc.utils.server.ThreadedServer(DoStuffService).start()

A partir de um cliente, esse serviço é exatamente como o servidor de modo clássico, exceto tudo que expõe é os métodos com prefixo exposed_ (menos o prefixo). Tentar acessar outros métodos (como módulos integrados) falhará. Portanto, um cliente pode ter a seguinte aparência:

Lista 9. Chamando Métodos Expostos pelo Serviço
>>> import rpyc
>>> conn = rpyc.connect('dostuff.example.com')
>>> myval = conn.root.func1()   # 'root' de conexão especial
>>> local_computation(myval)

Conclusão

Há mais em RPyC do que mencionei. Por exemplo, como Pyro, RPyC fornece um "Registro" que permite denominar serviços e acessá-los por nome, em vez de por nome de domínio ou endereço IP. Isso é direto e a documentação de RPyC explica isso.

Como indiquei neste artigo—e como a própria documentação de RPyC informa explicitamente —RPyC é de muitas maneiras "mais um pacote RPC". Por exemplo, o serviço que construímos resumidamente acima é praticamente idêntico ao mesmo código que gravaríamos usando SimpleXMLRPCServer. A única diferença é o protocolo wire usado para pedidos e resultados. Todavia, apesar de ter encontrado alguns pequenos problemas técnicos, RPyC é bem construído e muito simples de colocar em execução. É possível construir facilmente uma distribuição robusta de recursos e responsabilidades usando simplesmente algumas linhas de código RPyC.

Recursos

Aprender

Obter produtos e tecnologias

Discutir

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=Linux, Software livre
ArticleID=394410
ArticleTitle=Encantando Python: Computação de Distribuição com RPyC
publish-date=03312009