githubが高速化に成功した様です。
How We Made GitHub Fast - GitHub
Now that things have settled down from the move to Rackspace, I wanted to take some time to go over the architectural changes that we’ve made in order to bring you a speedier, more scalable GitHub.
...
For our data serialization and RPC protocol we are using BERT and BERT-RPC.
http://github.com/blog/530-how-we-made-github-fast
データのシリアライズおよびRPC(リモートプロシージャコール)としてBERT-RPCを選んだ様です。
BERT and BERT-RPC 1.0 Specification
BERT and BERT-RPC are an attempt to specify a flexible binary serialization and RPC protocol that are compatible with the philosophies of dynamic languages such as Ruby, Python, PERL, JavaScript, Erlang, Lua, etc. BERT aims to be as simple as possible while maintaining support for the advanced data types we have come to know and love. BERT-RPC is designed to work seamlessly within a dynamic/agile development workflow. The BERT-RPC philosophy is to eliminate extraneous type checking, IDL specification, and code generation. This frees the developer to actually get things done.
http://bert-rpc.org/
BERT-RPCはBERT(Binary ERlang Term)という名前の通り、ERlangのterm_to_binary/1で生成されるフレキシブルなバイナリデータ交換の仕組みで、これをRPCに利用した物がBERT-RPCです。
BERT
BERT (Binary ERlang Term) is a flexible binary data interchange format based on (and compatible with) Erlang's binary serialization format (as used by erlang:term_to_binary/1).
http://bert-rpc.org/
erlangで使える、integer,float,atom,tupple,bytelist,list,binaryをシリアライズして転送する事が出来ます。仕様書はerlangの拡張仕様ページに掲載されています。
External Term Format
The external term format is mainly used in the distribution mechanism of Erlang.
http://erlang.org/doc/apps/erts/erl_ext_dist.html
詳しくは
githubのブログエントリでも説明されています。
最近ではMessagePackやProtocolBuffer、Thriftなどバイナリフォーマットを使ったRPCが流行ですが、このErlangのバイナリフォーマットは無駄の無いシリアライズ形式になっているかと思います。
実際には型識別と、それに後続するデータで構成され、型によってはデータ部の前にデータ長が付加されます。
Erlangのシリアライズでは、Erlangのprotocolバージョン131から開始されますが、その前に全体長を付与した物がRPCで使われています。RPCの本体はtuppleと、その中のATOMで構成されています。
実装を見たい方は以下のrubyによる実装を見る事が出来ます。
mojombo's bert at master - GitHub
BERT (Binary ERlang Term) serialization library for Ruby.
http://github.com/mojombo/bert
mojombo's bertrpc at master - GitHub
BERTRPC is a Ruby BERT-RPC client library.
http://github.com/mojombo/bertrpc
またerlangによるシリアライズ実装、およびBERT-RPCを使ったRPCサーバの実装は以下から参照出来ます。
mojombo's erlectricity at master - GitHub
Erlectricity exposes Ruby to Erlang and vice versa
http://github.com/mojombo/erlectricity
mojombo's ernie at master - GitHub
Ernie is an Erlang/Ruby BERT-RPC Server.
http://github.com/mojombo/ernie
例えば上記githubブログへのリンク先で説明されている様に
require 'ernie'
mod(:calc) do
fun(:add) do |a, b|
a + b
end
end
というサーバのコードに対しては
require 'bertrpc'
svc = BERTRPC::Service.new('localhost', 9999)
svc.call.calc.add(1, 2)
というクライアントのコードが書けるのですが、内部ではErlangでシリアライズされ、先頭にデータ長が付与された以下のイメージでデータ送信が行われます。
{call,calc,add,[1,2]}
そしてバイト列としては以下の様になります。
0000000: 0000 0021 8368 0464 0004 6361 6c6c 6400 ...!.h.d..calld.
0000010: 0463 616c 6364 0003 6164 646c 0000 0002 .calcd..addl....
0000020: 6101 6102 6a a.a.j
先頭4バイトにデータ長(0x21:33バイト)、バージョン(0x83:131)、tupple(0x68)、tuppleアイテム数(4)、atom ext(0x64)...と続きます。
今日はこのBERT-RPCで動く足し算サーバををC言語から呼び出すサンプルを書いてみました。
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
int
main(int argc, char *argv[]) {
int sock;
struct sockaddr_in serv_addr;
short nport = htons(9999);
struct hostent *host_ent;
char *host = (char*)strdup("127.0.0.1");
char *ptr;
FILE *sockfp;
ptr = strchr(host, ':');
if (ptr) {
*ptr++ = 0;
if (atoi(ptr) == 0) {
struct servent *serv_ent;
serv_ent = getservbyname(ptr, "tcp");
if (serv_ent)
nport = serv_ent->s_port;
} else {
nport = htons(atoi(ptr));
}
}
ptr = host;
if ((host_ent = gethostbyname(ptr)) == NULL) {
sock = -1;
return -1;
}
memset((char *)&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
memcpy(&serv_addr.sin_addr, host_ent->h_addr, host_ent->h_length);
serv_addr.sin_port = nport;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
return;
}
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
close(sock);
sock = -1;
return;
}
char n1;
short n2;
int n4;
n4 = htonl(33);
write(sock, &n4, sizeof(n4));
n1 = 131;
write(sock, &n1, sizeof(n1));
n1 = 104;
write(sock, &n1, sizeof(n1));
n1 = 4;
write(sock, &n1, sizeof(n1));
n1 = 100;
write(sock, &n1, sizeof(n1));
n2 = htons(4);
write(sock, &n2, sizeof(n2));
write(sock, "call", 4);
n1 = 100;
write(sock, &n1, sizeof(n1));
n2 = htons(4);
write(sock, &n2, sizeof(n2));
write(sock, "calc", 4);
n1 = 100;
write(sock, &n1, sizeof(n1));
n2 = htons(3);
write(sock, &n2, sizeof(n2));
write(sock, "add", 3);
n1 = 108;
write(sock, &n1, sizeof(n1));
n4 = htonl(2);
write(sock, &n4, sizeof(n4));
n1 = 97;
write(sock, &n1, sizeof(n1));
n1 = 1;
write(sock, &n1, sizeof(n1));
n1 = 97;
write(sock, &n1, sizeof(n1));
n1 = 2;
write(sock, &n1, sizeof(n1));
n1 = 106;
write(sock, &n1, 1);
char buf[256];
ptr = buf;
int x = 0, n, l;
l = recv(sock, buf, sizeof(buf), 0);
printf("recv size %d\n", l);
for (n = 0; n < l; n++) {
x = (x + 1) % 80;
printf("%02X ", (unsigned char)buf[n]);
if (x == 0) putchar('\n');
}
putchar('\n');
n4 = (unsigned int) (
(*(ptr+0) << 24) +
(*(ptr+1) << 16) +
(*(ptr+2) << 8) +
(*(ptr+3) << 0));
printf("total length = %d\n", n4);
ptr+=4;
n1 = *ptr;
printf("version = %d\n", (unsigned char)n1);
ptr++;
n1 = *ptr;
if (n1 == 104) printf("tupple extension\n");
ptr++;
n1 = *ptr;
printf("number of tupple items %d\n", n1);
ptr++;
n1 = *ptr;
if (n1 == 100) printf("atom extension\n");
ptr++;
n2 = (unsigned short) (
(*(ptr+0) << 8) +
(*(ptr+1) << 0));
printf("atom length = %d\n", n2);
ptr+=2;
char atom[200] = {0};
memcpy(atom, ptr, n2);
printf("atom name = %s\n", atom);
ptr+=n2;
n1 = *ptr;
if (n1 == 97)printf("small int\n");
ptr++;
n1 = *ptr;
printf("int %d\n", n1);
close(sock);
sock = -1;
return 0;
}
分かりやすい様にコメントに種別を書いていますが、実用するには構造化したり抽象化する必要があります。ただし言語によっては型が見合わない場合もあるので、擬似するコードを書かなければならない可能性もあります。まだrubyとjavascriptにしか実装がない様なので、どなたかチャレンジしてみてはいかがでしょうか。