Por Tom Preston-Werner ([email protected])
Ernie es una implementación de servidor BERT-RPC que utiliza un servidor Erlang para aceptar conexiones entrantes y luego delega la solicitud a módulos personalizados que puede escribir en cualquier idioma (actualmente solo se incluye soporte para Ruby y Erlang).
Los módulos escritos en Ruby o en cualquier lenguaje que no sea Erlang se conocen como módulos "externos" y debe especificar cuántos trabajadores de cada módulo se deben generar. Las solicitudes contra estos módulos se equilibran entre los trabajadores. Los módulos escritos en Erlang se conocen como módulos "nativos" y se ejecutan dentro del tiempo de ejecución del servidor Erlang. Dado que estos se generan como procesos livianos, no es necesario ningún equilibrio y hay mucha menos sobrecarga de comunicación en comparación con los módulos externos.
Ernie admite múltiples módulos heterogéneos. Por ejemplo, puede tener un módulo Ruby externo ejecutando 10 trabajadores y un módulo Erlang nativo ejecutándose simultáneamente. Ernie realiza un seguimiento del envío de solicitudes al módulo adecuado. Utilizando una técnica llamada "sombreado", puede optimizar selectivamente ciertas funciones del módulo externo con código nativo y Ernie se encargará de seleccionar la función correcta.
Consulte la especificación BERT-RPC completa en bert-rpc.org.
Ernie actualmente admite las siguientes funciones de BERT-RPC:
call
cast
Ernie fue desarrollado para GitHub y actualmente se encuentra en uso de producción atendiendo millones de solicitudes RPC todos los días. La estabilidad y el rendimiento han sido ejemplares.
Ernie sigue el control de versiones semántico para el control de versiones.
Paso 1: instale Erlang (R13B o superior).
http://www.erlang.org/download.html
Paso 2: Instale 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'
Los archivos de configuración de Ernie están escritos como una serie de términos de Erlang con puntos. Cada término es una lista de 2 tuplas que especifican opciones para un módulo.
El formulario para módulos nativos es:
[{module, Module},
{type, native},
{codepaths, CodePaths}].
Donde Módulo es un átomo correspondiente al nombre del módulo y CodePaths es una lista de cadenas que representan las rutas de archivo que deben agregarse a la ruta del código del tiempo de ejecución. Estas rutas se antepondrán a la ruta del código y deben incluir el directorio del módulo nativo y los directorios de cualquier dependencia.
El formulario para módulos externos es:
[{module, Module},
{type, external},
{command, Command},
{count, Count}].
Donde Módulo es un átomo correspondiente al nombre del módulo, Comando es una cadena que especifica el comando que se ejecutará para iniciar un trabajador y Recuento es el número de trabajadores que se generarán.
Si especifica un módulo nativo y un módulo externo con el mismo nombre (y en ese orden), Ernie inspeccionará el módulo nativo para ver si tiene la función solicitada exportada y la usará si la tiene. Si no es así, recurrirá al módulo externo. Esto se puede utilizar para optimizar selectivamente ciertas funciones en un módulo sin realizar modificaciones en el código de su cliente.
En algunas circunstancias, puede ser bueno seguir condicionalmente una función en un módulo externo según la naturaleza de los argumentos. Por ejemplo, es posible que desee que las solicitudes de math:fib(X)
se enruten al módulo externo cuando X sea menor que 10, pero que sean manejadas por el módulo nativo cuando X sea 10 o mayor. Esto se puede lograr implementando una función math:fib_pred(X)
en el módulo nativo. Observe el _pred
agregado al nombre de la función normal (pred es la abreviatura de predicado). Si una función como esta está presente, Ernie la llamará con los argumentos solicitados y si el valor de retorno es true
se utilizará el módulo nativo. Si el valor de retorno es false
se utilizará el módulo externo.
El siguiente archivo de configuración de ejemplo informa a Ernie de dos módulos. El primer término identifica un módulo nativo 'nat' que reside en el archivo nat.beam en el directorio '/path/to/app/ebin'. El segundo término especifica un módulo externo 'ext' que tendrá 2 trabajadores iniciados con el 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}].
Si ha solicitado que se escriba un registro de acceso (usando la opción -a o --access-log), todas las solicitudes se registrarán en ese archivo. Cada solicitud se imprime en una sola línea. Los elementos de la línea de registro son los siguientes (con comentarios en el lado derecho):
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
Las líneas de registro se escriben cuando se completa la solicitud, por lo que pueden aparecer desordenadas con respecto al tiempo de conexión. Para facilitar la rotación de registros, Ernie creará un nuevo archivo de registro de acceso si el archivo de registro actual se mueve o elimina.
Los controladores nativos están escritos como módulos Erlang normales. Las funciones exportadas estarán disponibles para los clientes BERT-RPC.
-module(nat).
-export([add/2]).
add(A, B) ->
A + B.
-> {call, nat, add, [1, 2]}
<- {reply, 3}
En esta joya se incluye una biblioteca llamada ernie
que facilita la escritura de controladores Ernie en Ruby. Todo lo que tiene que hacer es escribir un módulo Ruby estándar y exponerlo a Ernie y las funciones de ese módulo estarán disponibles para los clientes BERT-RPC.
Usando un módulo Ruby y 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}
Puede enviar el registro a un archivo agregando estas líneas a su controlador:
logfile('/var/log/ernie.log')
loglevel(Logger::INFO)
Esto registrará información de inicio, solicitudes y mensajes de error en el registro. Al elegir Logger::DEBUG se incluirá la respuesta (tenga cuidado, hacer esto puede generar archivos de registro muy grandes).
Normalmente, los controladores de Ruby Ernie se activarán después de que se haya cargado el archivo. Puede desactivar este comportamiento configurando:
Ernie.auto_start = false
Ernie mantiene colas de prioridad alta y baja para conexiones entrantes. Si hay conexiones en la cola de prioridad alta, siempre se procesarán primero. Si la cola de prioridad alta está vacía, las conexiones se procesarán desde la cola de prioridad baja. De forma predeterminada, las conexiones van a la cola de alta prioridad. Para seleccionar una cola, se debe enviar una información BERP del siguiente formato antes de la llamada.
-- {info, priority, Priority}
Donde Priority
es el átomo high
o low
. Una secuencia de ejemplo en la que se selecciona la cola de baja prioridad sería similar a la siguiente.
-> {info, priority, low}
-> {call, nat, add, [1, 2]}
<- {reply, 3}
Puedes realizar llamadas BERT-RPC desde Ruby con la gema BERTRPC:
require 'bertrpc'
svc = BERTRPC::Service.new('localhost', 8000)
svc.call.ext.add(1, 2)
# => 3
Si desea piratear a Ernie, comience bifurcando mi repositorio en GitHub:
http://github.com/mojombo/ernie
Para obtener todas las dependencias, primero instale la gema. Para ejecutar ernie desde el código fuente, primero debes compilar el código Erlang:
rake ebuild
La mejor manera de volver a fusionar los cambios en el núcleo es la siguiente:
rake
Copyright (c) 2009 Tom Preston-Werner. Consulte LICENCIA para obtener más detalles.