Por Tom Preston-Werner ([email protected])
Ernie é uma implementação de servidor BERT-RPC que usa um servidor Erlang para aceitar conexões de entrada e, em seguida, delega a solicitação a módulos personalizados que você pode escrever em qualquer linguagem (atualmente apenas o suporte Ruby e Erlang está incluído).
Módulos escritos em Ruby ou em qualquer linguagem não-Erlang são conhecidos como módulos "externos" e você deve especificar quantos trabalhadores de cada módulo devem ser gerados. As solicitações contra esses módulos são equilibradas entre os trabalhadores. Os módulos escritos em Erlang são conhecidos como módulos "nativos" e são executados no tempo de execução do servidor Erlang. Como estes são gerados como processos leves, não há necessidade de balanceamento e muito menos sobrecarga de comunicação quando comparados a módulos externos.
Ernie oferece suporte a vários módulos heterogêneos. Por exemplo, você pode ter um módulo Ruby externo executando 10 trabalhadores e um módulo Erlang nativo executando simultaneamente. Ernie acompanha o envio de solicitações para o módulo apropriado. Usando uma técnica chamada "sombreamento", você pode otimizar seletivamente certas funções de módulos externos com código nativo e Ernie cuidará da seleção da função correta.
Veja a especificação completa do BERT-RPC em bert-rpc.org.
Ernie atualmente oferece suporte aos seguintes recursos BERT-RPC:
call
cast
Ernie foi desenvolvido para GitHub e atualmente está em uso em produção, atendendo milhões de solicitações RPC todos os dias. A estabilidade e o desempenho foram exemplares.
Ernie segue o Versionamento Semântico para versionamento de lançamento.
Passo 1: Instale o Erlang (R13B ou superior).
http://www.erlang.org/download.html
Etapa 2: Instale o Ernie:
$ [sudo] gem install ernie
Usage: ernie [command] [options]
-c, --config CONFIG Config file.
-p, --port PORT Port.
-l, --log-level Log level (0-4).
-a, --access-log LOGFILE Access log file.
-d, --detached Run as a daemon.
-P, --pidfile PIDFILE Location to write pid file.
--name NAME Erlang process name.
--sname SNAME Erlang short process name.
-E, --erlang ERLANG_OPTIONS Options passed to Erlang VM.
Commands:
Start an Ernie server.
reload-handlers Gracefully reload all of the external handlers
and use the new code for all subsequent requests.
stats Print a list of connection and handler statistics.
Examples:
ernie -d -p 9999 -c example.cfg
Start the ernie server in the background on port 9999 using the
example.cfg configuration file.
ernie reload-handlers -p 9999
Reload the handlers for the ernie server currently running on
port 9999.
ernie -c example.cfg -E '-run mymodule'
Start the ernie server with an additional erlang module called
'mymodule'
Os arquivos de configuração Ernie são escritos como uma série de termos Erlang pontilhados. Cada termo é uma lista de 2 tuplas que especificam opções para um módulo.
O formulário para módulos nativos é:
[{module, Module},
{type, native},
{codepaths, CodePaths}].
Onde Module é um átomo correspondente ao nome do módulo e CodePaths é uma lista de strings que representam os caminhos de arquivo que devem ser adicionados ao caminho do código do tempo de execução. Esses caminhos serão anexados ao caminho do código e deverão incluir o diretório do módulo nativo e os diretórios de quaisquer dependências.
O formulário para módulos externos é:
[{module, Module},
{type, external},
{command, Command},
{count, Count}].
Onde Módulo é um átomo correspondente ao nome do módulo, Comando é uma string que especifica o comando a ser executado para iniciar um trabalhador e Contagem é o número de trabalhadores a serem gerados.
Se você especificar um módulo nativo e um módulo externo com o mesmo nome (e nessa ordem), Ernie inspecionará o módulo nativo para ver se ele tem a função solicitada exportada e a usará se tiver. Caso contrário, ele recorrerá ao módulo externo. Isso pode ser usado para otimizar seletivamente certas funções em um módulo sem quaisquer modificações no código do cliente.
Em algumas circunstâncias, pode ser bom ocultar condicionalmente uma função em um módulo externo com base na natureza dos argumentos. Por exemplo, você pode querer que as solicitações de math:fib(X)
sejam roteadas para o módulo externo quando X for menor que 10, mas que sejam tratadas pelo módulo nativo quando X for 10 ou maior. Isso pode ser feito implementando uma função math:fib_pred(X)
no módulo nativo. Observe o _pred
anexado ao nome da função normal (pred é a abreviação de predicado). Se uma função como esta estiver presente, Ernie irá chamá-la com os argumentos solicitados e se o valor de retorno for true
o módulo nativo será usado. Se o valor de retorno for false
o módulo externo será usado.
O exemplo de arquivo de configuração a seguir informa Ernie sobre dois módulos. O primeiro termo identifica um módulo nativo 'nat' que reside no arquivo nat.beam no diretório '/path/to/app/ebin'. O segundo termo especifica um módulo externo 'ext' que terá 2 trabalhadores iniciados com o comando 'ruby /path/to/app/ernie/ext.rb'.
[{module, nat},
{type, native},
{codepaths, ["/path/to/app/ebin"]}].
[{module, ext},
{type, external},
{command, "ruby /path/to/app/ernie/ext.rb"},
{count, 2}].
Se você solicitou que um log de acesso fosse gravado (usando a opção -a ou --access-log), todas as solicitações serão registradas nesse arquivo. Cada solicitação é impressa em uma única linha. Os elementos da linha de log são os seguintes (com comentários no lado direito):
ACC type of message [ ACC | ERR ]
[2010-02-20T11:42:25.259750] time the connection was accepted
0.000053 seconds from connection to processing start
0.000237 seconds from processing start to finish
- delimiter
0 size of high queue at connect time
0 size of low queue at connect time
nat type of handler [ nat | ext ]
high priority [ high | low ]
- delimiter
{call,nat,add,[1,2]} message
As linhas de log são gravadas quando a solicitação é concluída, portanto podem parecer fora de ordem em relação ao tempo de conexão. Para facilitar a rotação do log, Ernie criará um novo arquivo de log de acesso se o arquivo de log atual for movido ou excluído.
Os manipuladores nativos são escritos como módulos Erlang normais. As funções exportadas ficarão disponíveis para clientes BERT-RPC.
-module(nat).
-export([add/2]).
add(A, B) ->
A + B.
-> {call, nat, add, [1, 2]}
<- {reply, 3}
Incluída nesta joia está uma biblioteca chamada ernie
que facilita a escrita de manipuladores Ernie em Ruby. Tudo que você precisa fazer é escrever um módulo Ruby padrão e expô-lo a Ernie e as funções desse módulo ficarão disponíveis para clientes BERT-RPC.
Usando um módulo Ruby e Ernie.expose:
require 'rubygems'
require 'ernie'
module Ext
def add(a, b)
a + b
end
end
Ernie.expose(:ext, Ext)
-> {call, nat, add, [1, 2]}
<- {reply, 3}
Você pode enviar o registro para um arquivo adicionando estas linhas ao seu manipulador:
logfile('/var/log/ernie.log')
loglevel(Logger::INFO)
Isso registrará informações de inicialização, solicitações e mensagens de erro no log. Escolher Logger::DEBUG incluirá a resposta (tenha cuidado, isso pode gerar arquivos de log muito grandes).
Normalmente, os manipuladores Ruby Ernie ficarão ativos após o arquivo ser carregado. Você pode desativar esse comportamento configurando:
Ernie.auto_start = false
Ernie mantém filas de prioridade alta e baixa para conexões de entrada. Se houver alguma conexão na fila de alta prioridade, ela sempre será processada primeiro. Se a fila de alta prioridade estiver vazia, as conexões serão processadas a partir da fila de baixa prioridade. Por padrão, as conexões vão para a fila de alta prioridade. Para selecionar uma fila, um BERP informativo do seguinte formato deve ser enviado antes da chamada.
-- {info, priority, Priority}
Onde Priority
é o átomo high
ou low
. Um exemplo de sequência em que a fila de baixa prioridade está sendo selecionada seria semelhante ao seguinte.
-> {info, priority, low}
-> {call, nat, add, [1, 2]}
<- {reply, 3}
Você pode fazer chamadas BERT-RPC de Ruby com a gem BERTRPC:
require 'bertrpc'
svc = BERTRPC::Service.new('localhost', 8000)
svc.call.ext.add(1, 2)
# => 3
Se você quiser hackear Ernie, comece bifurcando meu repositório no GitHub:
http://github.com/mojombo/ernie
Para obter todas as dependências, instale a gem primeiro. Para executar ernie a partir do código-fonte, você deve primeiro construir o código Erlang:
rake ebuild
A melhor maneira de mesclar suas alterações de volta ao núcleo é a seguinte:
rake
Direitos autorais (c) 2009 Tom Preston-Werner. Consulte LICENÇA para obter detalhes.