AJAJA: memcachedを使えるようにしてみよう
大よそ、C言語でのAJAJAのモジュールの書き方がわかってきたので、例題としてmemcachedをAJAJAで使えるようにしてみました。(何でこのタイミングでmemcachedのバインディングを書いたのか?と聞かれると困ります(汗。単に興味があったからやってみただけですw。) memcachedとは、簡単に言うとメモリーキャッシュサーバのことで、LiveJournalやmixiや はてななどの大規模システムで利用されているツールです。
memcachedのC言語のバイディングAPIを提供しているlibmemcacheを使って、memcachedの機能の一部を利用できるようにしました。昨日はじめてmemcachedをインストールして触りだしたので、機能的にはまだまだしょぼいですが、一応動きます。
クライアントでの利用例
現段階で、以下のようなコードが動作します。keyとvalueのペアをmemcachedにキャッシュして、keyをもとにキャッシュしたvalueを単に取り出しているだけです。APIは、CPANモジュールのCache::Memcached::XSを少し参考にしましたが、まだ程遠いのが現状です。現状では、keyとvalueにはStringが利用できます。オブジェクトをmemcachedにキャッシュするのは、どうも難しそうな気配がします。
% vim memcached_test.js use('System'); use('Memcached'); memd = new Memcached({server:"127.0.0.1", port: "11211"}); memd.set("hello", "world"); memd.set("good", "bye!"); System.puts("hello, " + memd.get("hello") + "\n"); System.puts("good " + memd.get("good") + "\n");
memcachedをlocalhostの11211ポートで走らせておいて、ssjsで実行させると、期待通りの結果が得られます。
% memcached -d -p 11211 % ssjs memcached_test.js hello, world good bye!
モジュールのソースコード
モジュールのソースコードは以下の通りです。AJAJAに搭載されているSQLiteのバインディングのコード(SQLite/SQLite.c)をかなり参考にして書きました。LLな言語になれていると、C言語でのコーディングはやりにくく感じますが、そのぶん細かい制御ができるので、それはそれで楽しいですね。結構C言語にはまりつつあります。。
/* * memcached for AJAJA * */ #include <string.h> #include "jsapi.h" #include "jsprf.h" #include "jscntxt.h" #include "jsinterp.h" #include <memcache.h> #ifdef DEBUG #define DBG(msg...) \ fprintf(stderr, msg) #else #define DBG(msg...) #endif /* prototype */ static JSClass mc_class; static JSBool memcached_constructor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSString *str_server, *str_port; jsval jsval_server, jsval_port; JSObject *obj_argv; char *server, *port; struct memcache *mc; if (!JSVAL_IS_OBJECT(argv[0])) goto error; obj_argv = JSVAL_TO_OBJECT(argv[0]); if (!JS_GetProperty(cx, obj_argv, "server", &jsval_server)) goto error; if (!JS_GetProperty(cx, obj_argv, "port", &jsval_port)) goto error; if ((str_server = JS_ValueToString(cx, jsval_server)) == NULL) goto error; if ((str_port = JS_ValueToString(cx, jsval_port)) == NULL) goto error; server = JS_GetStringBytes(str_server); port = JS_GetStringBytes(str_port); DBG("memcached_constructor(): server = %s, port = %s\n", server, port); /* libmemcache */ mc = mc_new(); mc_server_add(mc, server, port); if (!JS_SetPrivate(cx, obj, mc)) goto error; return JS_TRUE; error: *rval = JSVAL_VOID; return JS_FALSE; } static void memcached_finalize(JSContext *cx, JSObject *obj) { struct memcache *mc; mc = JS_GetInstancePrivate(cx, obj, &mc_class, NULL); DBG("memcached_finalize()\n"); mc_free(mc); } static JSBool memcached_set(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSString *str; char *key, *value; struct memcache *mc; mc = JS_GetInstancePrivate(cx, obj, &mc_class, NULL); /* get key */ if ((str = JS_ValueToString(cx, argv[0])) == NULL) return JS_FALSE; key = JS_GetStringBytes(str); /* get value */ if ((str = JS_ValueToString(cx, argv[1])) == NULL) return JS_FALSE; value = JS_GetStringBytes(str); DBG("memcached_set(): key = %s, value = %s\n", key, value); mc_add(mc, key, strlen(key), value, strlen(value), 10, 0); return JS_TRUE; } static JSBool memcached_get(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSString *str; char *key, *value; struct memcache *mc; mc = JS_GetInstancePrivate(cx, obj, &mc_class, NULL); /* get key */ if ((str = JS_ValueToString(cx, argv[0])) == NULL) return JS_FALSE; key = JS_GetStringBytes(str); value = (char *)mc_aget(mc, key, strlen(key)); *rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, value)); DBG("memcached_get(): key = %s, value = %s\n", key, value); return JS_TRUE; } static JSClass mc_class = { "Memcached", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, memcached_finalize }; static struct JSPropertySpec mc_props[] = { /* {name, tinyid, flags, getter, setter}, */ {0} }; static struct JSFunctionSpec mc_funcs[] = { /* {name, call, nargs, flags, extra}, */ {"set", memcached_set, 2, 0, 0}, {"get", memcached_get, 1, 0, 0}, {0} }; int JS_InitModule_Memcached(JSContext *cx, JSObject *obj) { JS_InitClass(cx, obj, NULL, &mc_class, memcached_constructor, 1, mc_props, mc_funcs, NULL, NULL); return 1; }
モジュールのビルドとインストール
以下の手順でモジュールのビルドとインストールができます。libmemcacheを予めインストールしておく必要があります。ajaja-build/と同じ階層でMemcached-0.01.tar.gzを展開すれば、うまくmakeできるはずです。libmemcache.aが/usr/local/lib/以下にあると仮定しているので、環境によっては、これが原因でmakeできないかもしれません。その時は、Makefileにある「LDFLAGS」のパスを調整して下さい。
% cd ajaja % ls ajaja-build/ % wget http://vaio.redirectme.net/lib/ajaja/Memcached-0.01.tar.gz % tar zxvf Memcached-0.01.tar.gz % cd Memcached-0.01 % make % sudo make install