Par Tom Preston-Werner ([email protected])
Ernie est une implémentation de serveur BERT-RPC qui utilise un serveur Erlang pour accepter les connexions entrantes, puis délègue la demande à des modules personnalisés que vous pouvez écrire dans n'importe quel langage (actuellement, seule la prise en charge de Ruby et Erlang est incluse).
Les modules écrits en Ruby ou dans tout autre langage non Erlang sont appelés modules « externes » et vous devez spécifier combien de travailleurs de chaque module doivent être générés. Les demandes contre ces modules sont équilibrées entre les travailleurs. Les modules écrits en Erlang sont appelés modules « natifs » et s'exécutent dans le runtime du serveur Erlang. Étant donné qu'ils sont générés sous forme de processus légers, aucun équilibrage n'est nécessaire et beaucoup moins de frais de communication par rapport aux modules externes.
Ernie prend en charge plusieurs modules hétérogènes. Par exemple, vous pouvez avoir un module Ruby externe exécutant 10 Workers et un module Erlang natif s'exécutant simultanément. Ernie assure le suivi de l'envoi des requêtes au module approprié. En utilisant une technique appelée « observation », vous pouvez optimiser de manière sélective certaines fonctions de modules externes avec du code natif et Ernie se chargera de sélectionner la fonction correcte.
Consultez la spécification BERT-RPC complète sur bert-rpc.org.
Ernie prend actuellement en charge les fonctionnalités BERT-RPC suivantes :
call
cast
Ernie a été développé pour GitHub et est actuellement utilisé en production et traite des millions de requêtes RPC chaque jour. La stabilité et les performances ont été exemplaires.
Ernie suit Semantic Versioning pour la gestion des versions.
Étape 1 : Installez Erlang (R13B ou supérieur).
http://www.erlang.org/download.html
Étape 2 : Installez 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'
Les fichiers de configuration Ernie sont écrits sous la forme d'une série de termes Erlang en pointillés. Chaque terme est une liste de 2 tuples qui spécifient les options d'un module.
Le formulaire pour les modules natifs est :
[{module, Module},
{type, native},
{codepaths, CodePaths}].
Où Module est un atome correspondant au nom du module et CodePaths est une liste de chaînes représentant les chemins de fichiers qui doivent être ajoutés au chemin de code du runtime. Ces chemins seront ajoutés au chemin du code et doivent inclure le répertoire du module natif et les répertoires de toutes les dépendances.
Le formulaire pour les modules externes est le suivant :
[{module, Module},
{type, external},
{command, Command},
{count, Count}].
Où Module est un atome correspondant au nom du module, Command est une chaîne spécifiant la commande à exécuter afin de démarrer un travailleur et Count est le nombre de travailleurs à générer.
Si vous spécifiez un module natif et un module externe du même nom (et dans cet ordre), Ernie inspectera le module natif pour voir s'il a exporté la fonction demandée et l'utilisera si c'est le cas. Si ce n’est pas le cas, il se rabattra sur le module externe. Cela peut être utilisé pour optimiser sélectivement certaines fonctions d'un module sans aucune modification de votre code client.
Dans certaines circonstances, il peut être intéressant d'observer conditionnellement une fonction dans un module externe en fonction de la nature des arguments. Par exemple, vous souhaiterez peut-être que les requêtes math:fib(X)
soient acheminées vers le module externe lorsque X est inférieur à 10, mais qu'elles soient traitées par le module natif lorsque X est supérieur ou égal à 10. Cela peut être accompli en implémentant une fonction math:fib_pred(X)
dans le module natif. Notez le _pred
ajouté au nom de la fonction normale (pred est l'abréviation de prédicat). Si une fonction comme celle-ci est présente, Ernie l'appellera avec les arguments demandés et si la valeur de retour est true
le module natif sera utilisé. Si la valeur de retour est false
le module externe sera utilisé.
L'exemple de fichier de configuration suivant informe Ernie de deux modules. Le premier terme identifie un module natif « nat » qui réside dans le fichier nat.beam sous le répertoire « /path/to/app/ebin ». Le deuxième terme spécifie un module externe 'ext' qui aura 2 travailleurs démarrés avec la commande '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 vous avez demandé qu'un journal d'accès soit écrit (en utilisant l'option -a ou --access-log), alors toutes les demandes seront enregistrées dans ce fichier. Chaque demande est imprimée sur une seule ligne. Les éléments de la ligne de log sont les suivants (avec des commentaires sur le côté droit) :
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
Les lignes de journal sont écrites une fois la demande terminée, elles peuvent donc apparaître dans le désordre par rapport au temps de connexion. Pour faciliter la rotation des journaux, Ernie créera un nouveau fichier journal d'accès si le fichier journal actuel est déplacé ou supprimé.
Les gestionnaires natifs sont écrits comme des modules Erlang normaux. Les fonctions exportées seront disponibles pour les clients BERT-RPC.
-module(nat).
-export([add/2]).
add(A, B) ->
A + B.
-> {call, nat, add, [1, 2]}
<- {reply, 3}
Ce joyau comprend une bibliothèque appelée ernie
qui facilite l'écriture de gestionnaires Ernie dans Ruby. Tout ce que vous avez à faire est d'écrire un module Ruby standard et de l'exposer à Ernie et les fonctions de ce module deviendront disponibles pour les clients BERT-RPC.
Utilisation d'un module Ruby et d'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}
Vous pouvez envoyer la journalisation dans un fichier en ajoutant ces lignes à votre gestionnaire :
logfile('/var/log/ernie.log')
loglevel(Logger::INFO)
Cela enregistrera les informations de démarrage, les demandes et les messages d'erreur dans le journal. Choisir Logger::DEBUG inclura la réponse (attention, cela peut générer de très gros fichiers journaux).
Normalement, les gestionnaires Ruby Ernie deviendront actifs une fois le fichier chargé. Vous pouvez désactiver ce comportement en définissant :
Ernie.auto_start = false
Ernie maintient des files d'attente de priorité haute et basse pour les connexions entrantes. S'il y a des connexions dans la file d'attente haute priorité, elles seront toujours traitées en premier. Si la file d'attente haute priorité est vide, les connexions seront traitées à partir de la file d'attente basse priorité. Par défaut, les connexions vont dans la file d'attente Haute priorité. Pour sélectionner une file d'attente, un BERP d'information de la forme suivante doit être envoyé avant l'appel.
-- {info, priority, Priority}
Où Priority
est l'atome high
ou low
. Un exemple de séquence dans laquelle la file d'attente à faible priorité est sélectionnée ressemblerait à ce qui suit.
-> {info, priority, low}
-> {call, nat, add, [1, 2]}
<- {reply, 3}
Vous pouvez passer des appels BERT-RPC depuis Ruby avec la gem BERTRPC :
require 'bertrpc'
svc = BERTRPC::Service.new('localhost', 8000)
svc.call.ext.add(1, 2)
# => 3
Si vous souhaitez pirater Ernie, commencez par créer mon dépôt sur GitHub :
http://github.com/mojombo/ernie
Pour obtenir toutes les dépendances, installez d'abord la gemme. Pour exécuter ernie depuis les sources, vous devez d'abord construire le code Erlang :
rake ebuild
La meilleure façon de fusionner vos modifications dans le noyau est la suivante :
rake
Copyright (c) 2009 Tom Preston-Werner. Voir LICENCE pour plus de détails.