Von Tom Preston-Werner ([email protected])
Ernie ist eine BERT-RPC-Serverimplementierung, die einen Erlang-Server verwendet, um eingehende Verbindungen zu akzeptieren, und die Anfrage dann an benutzerdefinierte Module delegiert, die Sie in jeder Sprache schreiben können (derzeit ist nur Ruby- und Erlang-Unterstützung enthalten).
Module, die in Ruby oder einer anderen Nicht-Erlang-Sprache geschrieben sind, werden als „externe“ Module bezeichnet und Sie müssen angeben, wie viele Worker jedes Moduls erzeugt werden sollen. Anfragen gegen diese Module werden zwischen den Arbeitern ausgeglichen. In Erlang geschriebene Module werden als „native“ Module bezeichnet und innerhalb der Laufzeit des Erlang-Servers ausgeführt. Da diese als leichtgewichtige Prozesse erstellt werden, ist im Vergleich zu externen Modulen kein Ausgleich erforderlich und der Kommunikationsaufwand ist deutlich geringer.
Ernie unterstützt mehrere heterogene Module. Sie können beispielsweise ein externes Ruby-Modul mit 10 Workern und ein natives Erlang-Modul gleichzeitig ausführen lassen. Ernie verfolgt das Senden von Anfragen an das richtige Modul. Mit einer Technik namens „Shadowing“ können Sie bestimmte externe Modulfunktionen gezielt mit nativem Code optimieren und Ernie kümmert sich um die Auswahl der richtigen Funktion.
Die vollständige BERT-RPC-Spezifikation finden Sie unter bert-rpc.org.
Ernie unterstützt derzeit die folgenden BERT-RPC-Funktionen:
call
cast
Ernie wurde für GitHub entwickelt und ist derzeit im Produktionseinsatz und verarbeitet täglich Millionen von RPC-Anfragen. Die Stabilität und Leistung waren vorbildlich.
Ernie folgt bei der Release-Versionierung der semantischen Versionierung.
Schritt 1: Installieren Sie Erlang (R13B oder höher).
http://www.erlang.org/download.html
Schritt 2: Ernie installieren:
$ [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'
Ernie-Konfigurationsdateien werden als eine Reihe von gepunkteten Erlang-Begriffen geschrieben. Jeder Begriff ist eine Liste von 2-Tupeln, die Optionen für ein Modul angeben.
Das Formular für native Module lautet:
[{module, Module},
{type, native},
{codepaths, CodePaths}].
Dabei ist Module ein Atom, das dem Modulnamen entspricht, und CodePaths ist eine Liste von Zeichenfolgen, die die Dateipfade darstellen, die dem Codepfad der Laufzeit hinzugefügt werden sollen. Diese Pfade werden dem Codepfad vorangestellt und müssen das Verzeichnis des nativen Moduls und die Verzeichnisse etwaiger Abhängigkeiten enthalten.
Das Formular für externe Module lautet:
[{module, Module},
{type, external},
{command, Command},
{count, Count}].
Dabei ist „Module“ ein Atom, das dem Modulnamen entspricht, „Command“ eine Zeichenfolge, die den Befehl angibt, der ausgeführt werden soll, um einen Worker zu starten, und „Count“ die Anzahl der Worker, die erzeugt werden sollen.
Wenn Sie ein natives Modul und ein externes Modul mit demselben Namen (und in dieser Reihenfolge) angeben, überprüft Ernie das native Modul, um festzustellen, ob die angeforderte Funktion exportiert wurde, und verwendet diese, wenn dies der Fall ist. Ist dies nicht der Fall, wird auf das externe Modul zurückgegriffen. Dies kann verwendet werden, um bestimmte Funktionen in einem Modul gezielt zu optimieren, ohne dass Änderungen an Ihrem Client-Code erforderlich sind.
Unter bestimmten Umständen kann es sinnvoll sein, eine Funktion in einem externen Modul basierend auf der Art der Argumente bedingt zu beschatten. Beispielsweise möchten Sie möglicherweise, dass Anforderungen für math:fib(X)
an das externe Modul weitergeleitet werden, wenn X kleiner als 10 ist, aber vom nativen Modul verarbeitet werden sollen, wenn X 10 oder größer ist. Dies kann durch die Implementierung einer Funktion math:fib_pred(X)
im nativen Modul erreicht werden. Beachten Sie das an den normalen Funktionsnamen angehängte _pred
(pred ist die Abkürzung für „predicate“). Wenn eine solche Funktion vorhanden ist, ruft Ernie sie mit den angeforderten Argumenten auf und wenn der Rückgabewert true
ist, wird das native Modul verwendet. Wenn der Rückgabewert false
ist, wird das externe Modul verwendet.
Die folgende Beispielkonfigurationsdatei informiert Ernie über zwei Module. Der erste Begriff identifiziert ein natives Modul „nat“, das sich in der Datei „nat.beam“ im Verzeichnis „/path/to/app/ebin“ befindet. Der zweite Begriff gibt ein externes Modul „ext“ an, bei dem zwei Worker mit dem Befehl „ruby /path/to/app/ernie/ext.rb“ gestartet werden.
[{module, nat},
{type, native},
{codepaths, ["/path/to/app/ebin"]}].
[{module, ext},
{type, external},
{command, "ruby /path/to/app/ernie/ext.rb"},
{count, 2}].
Wenn Sie das Schreiben eines Zugriffsprotokolls angefordert haben (mit der Option -a oder --access-log), werden alle Anforderungen in dieser Datei protokolliert. Jede Anfrage wird in einer einzelnen Zeile gedruckt. Die Elemente der Protokollzeile sind wie folgt (mit Kommentaren auf der rechten Seite):
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
Protokollzeilen werden geschrieben, wenn die Anfrage abgeschlossen ist, sodass sie hinsichtlich der Verbindungszeit möglicherweise nicht in der richtigen Reihenfolge erscheinen. Um die Protokollrotation zu erleichtern, erstellt Ernie eine neue Zugriffsprotokolldatei, wenn die aktuelle Protokolldatei verschoben oder gelöscht wird.
Native Handler werden als normale Erlang-Module geschrieben. Die exportierten Funktionen werden für BERT-RPC-Clients verfügbar.
-module(nat).
-export([add/2]).
add(A, B) ->
A + B.
-> {call, nat, add, [1, 2]}
<- {reply, 3}
In diesem Juwel ist eine Bibliothek namens ernie
enthalten, die es einfach macht, Ernie-Handler in Ruby zu schreiben. Sie müssen lediglich ein Standard-Ruby-Modul schreiben und es Ernie zur Verfügung stellen. Die Funktionen dieses Moduls werden dann für BERT-RPC-Clients verfügbar.
Verwendung eines Ruby-Moduls und 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}
Sie können die Protokollierung an eine Datei senden lassen, indem Sie die folgenden Zeilen zu Ihrem Handler hinzufügen:
logfile('/var/log/ernie.log')
loglevel(Logger::INFO)
Dadurch werden Startinformationen, Anfragen und Fehlermeldungen im Protokoll protokolliert. Wenn Sie „Logger::DEBUG“ wählen, wird die Antwort einbezogen (Vorsicht, dies kann zu sehr großen Protokolldateien führen).
Normalerweise werden Ruby-Ernie-Handler aktiv, nachdem die Datei geladen wurde. Sie können dieses Verhalten deaktivieren, indem Sie Folgendes festlegen:
Ernie.auto_start = false
Ernie unterhält Warteschlangen mit hoher und niedriger Priorität für eingehende Verbindungen. Wenn Verbindungen in der Warteschlange mit hoher Priorität vorhanden sind, werden diese immer zuerst verarbeitet. Wenn die Warteschlange mit hoher Priorität leer ist, werden Verbindungen aus der Warteschlange mit niedriger Priorität verarbeitet. Standardmäßig werden Verbindungen in die Warteschlange „Hohe Priorität“ gestellt. Um eine Warteschlange auszuwählen, muss vor dem Anruf ein Info-BERP der folgenden Form gesendet werden.
-- {info, priority, Priority}
Wobei Priority
entweder das high
oder low
Atom ist. Eine Beispielsequenz, bei der die Warteschlange mit niedriger Priorität ausgewählt wird, würde wie folgt aussehen.
-> {info, priority, low}
-> {call, nat, add, [1, 2]}
<- {reply, 3}
Sie können BERT-RPC-Aufrufe von Ruby aus mit dem BERTRPC-Gem durchführen:
require 'bertrpc'
svc = BERTRPC::Service.new('localhost', 8000)
svc.call.ext.add(1, 2)
# => 3
Wenn Sie Ernie hacken möchten, beginnen Sie mit dem Forken meines Repos auf GitHub:
http://github.com/mojombo/ernie
Um alle Abhängigkeiten zu erhalten, installieren Sie zuerst das Gem. Um Ernie aus dem Quellcode auszuführen, müssen Sie zunächst den Erlang-Code erstellen:
rake ebuild
Der beste Weg, Ihre Änderungen wieder in den Kern zusammenzuführen, ist wie folgt:
rake
ausführenCopyright (c) 2009 Tom Preston-Werner. Einzelheiten finden Sie unter LIZENZ.