作者:汤姆·普雷斯顿-沃纳 ([email protected])
Ernie 是一个 BERT-RPC 服务器实现,它使用 Erlang 服务器接受传入连接,然后将请求委托给可以用任何语言编写的自定义模块(当前仅包括 Ruby 和 Erlang 支持)。
用 Ruby 或任何非 Erlang 语言编写的模块称为“外部”模块,您必须指定应生成每个模块的工作进程数量。针对这些模块的请求在工作人员之间进行平衡。用 Erlang 编写的模块称为“本机”模块,并在 Erlang 服务器的运行时内运行。由于这些是作为轻量级进程产生的,因此与外部模块相比,不需要平衡,并且通信开销要少得多。
Ernie 支持多个异构模块。例如,您可以让一个运行 10 个工作进程的外部 Ruby 模块和一个同时运行的本机 Erlang 模块。 Ernie 跟踪将请求发送到正确的模块。使用一种称为“影子”的技术,您可以使用本机代码有选择地优化某些外部模块函数,Ernie 将负责选择正确的函数。
请参阅 bert-rpc.org 上的完整 BERT-RPC 规范。
Ernie 目前支持以下 BERT-RPC 功能:
call
请求cast
要求Ernie 是为 GitHub 开发的,目前在生产环境中每天为数百万个 RPC 请求提供服务。稳定性和性能堪称典范。
Ernie 遵循语义版本控制来进行发布版本控制。
第 1 步:安装 Erlang(R13B 或更高版本)。
http://www.erlang.org/download.html
第 2 步:安装厄尼:
$ [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 配置文件被写成一系列带点的 Erlang 术语。每个术语都是指定模块选项的二元组列表。
本机模块的形式是:
[{module, Module},
{type, native},
{codepaths, CodePaths}].
其中 Module 是与模块名称相对应的原子,CodePaths 是表示应添加到运行时代码路径的文件路径的字符串列表。这些路径将添加到代码路径之前,并且必须包含本机模块的目录和任何依赖项的目录。
外部模块的形式为:
[{module, Module},
{type, external},
{command, Command},
{count, Count}].
其中 Module 是与模块名称相对应的原子,Command 是一个字符串,指定要启动工作程序而要执行的命令,而 Count 是要生成的工作程序数量。
如果您指定同名的本机模块和外部模块(并按该顺序),Ernie 将检查本机模块以查看它是否导出了所请求的函数,如果有则使用该函数。如果没有,那么它将依靠外部模块。这可用于有选择地优化模块中的某些功能,而无需对客户端代码进行任何修改。
在某些情况下,根据参数的性质有条件地隐藏外部模块中的函数可能会很好。例如,您可能希望当 X 小于 10 时将math:fib(X)
的请求路由到外部模块,但当 X 等于 10 或更大时由本机模块处理。这可以通过在本机模块中实现函数math:fib_pred(X)
来完成。请注意附加到普通函数名称的_pred
(pred 是 predicate 的缩写)。如果存在这样的函数,Ernie 将使用请求的参数调用它,如果返回值为true
则将使用本机模块。如果返回值为false
则将使用外部模块。
以下示例配置文件告知 Ernie 有两个模块。第一项标识驻留在“/path/to/app/ebin”目录下的 nat.beam 文件中的本机模块“nat”。第二项指定一个外部模块“ext”,该模块将有 2 个工作程序以命令“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}].
如果您请求写入访问日志(使用 -a 或 --access-log 选项),则所有请求都将记录到该文件中。每个请求都打印在一行上。日志行的元素如下(右侧有注释):
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
日志行是在请求完成时写入的,因此它们可能会在连接时间方面出现混乱。为了便于日志轮换,如果当前日志文件被移动或删除,Ernie 将创建一个新的访问日志文件。
本机处理程序被编写为普通的 Erlang 模块。导出的函数将可供 BERT-RPC 客户端使用。
-module(nat).
-export([add/2]).
add(A, B) ->
A + B.
-> {call, nat, add, [1, 2]}
<- {reply, 3}
这个 gem 中包含一个名为ernie
的库,它可以让您轻松地用 Ruby 编写 Ernie 处理程序。您所要做的就是编写一个标准 Ruby 模块并将其公开给 Ernie,该模块的功能将可供 BERT-RPC 客户端使用。
使用 Ruby 模块和 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}
您可以通过将以下行添加到处理程序来将日志记录发送到文件:
logfile('/var/log/ernie.log')
loglevel(Logger::INFO)
这会将启动信息、请求和错误消息记录到日志中。选择 Logger::DEBUG 将包含响应(请小心,这样做可能会生成非常大的日志文件)。
通常,Ruby Ernie 处理程序将在文件加载后激活。您可以通过设置禁用此行为:
Ernie.auto_start = false
Ernie 为传入连接维护高优先级和低优先级队列。如果高优先级队列中有任何连接,它们将始终首先被处理。如果高优先级队列为空,则将从低优先级队列处理连接。默认情况下,连接进入高优先级队列。要选择队列,必须在呼叫之前发送以下形式的信息 BERP。
-- {info, priority, Priority}
其中Priority
是high
原子或low
原子。选择低优先级队列的示例序列如下所示。
-> {info, priority, low}
-> {call, nat, add, [1, 2]}
<- {reply, 3}
您可以使用 BERTRPC gem 从 Ruby 进行 BERT-RPC 调用:
require 'bertrpc'
svc = BERTRPC::Service.new('localhost', 8000)
svc.call.ext.add(1, 2)
# => 3
如果你想破解 Ernie,请从在 GitHub 上分叉我的存储库开始:
http://github.com/mojombo/ernie
要获取所有依赖项,请先安装 gem。要从源代码运行 ernie,您必须首先构建 Erlang 代码:
rake ebuild
将更改合并回核心的最佳方法如下:
rake
确保一切仍然通过版权所有 (c) 2009 汤姆·普雷斯顿-沃纳。有关详细信息,请参阅许可证。