Hatena::Diary

スコトプリゴニエフスク通信 このページをアンテナに追加 RSSフィード

2009-01-08

[]Python Google Chart

PythonGoogle Chartを扱うためのライブラリ。なかなか便利そう。

例えば、QRコードを生成するならば、

# -*- coding: utf-8 -*-
from pygooglechart import QRChart

# 250 * 250のQRコードを生成
chart = QRChart(250, 250)

# データを追加
chart.add_data("http://gu3.jp/")

# URLを取得
# 'http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=http%3A//gu3.jp/'
url = chart.get_url()

# ファイルとして保存
chart.download("/path/to/gu3_jp.png")

[][]intを含むURL

intと言っても、主に日付に使う整数。

Djangoで、

from django.conf.urls.defaults import *

pattern = patterns(
  'myapp.views',
  url(r'^entries/(?P<year>\d+)/(?P<month>\d+)/(?P<day>\d+)/', 'blog_archive'),
)

のようなURLマッピングを書くと、下記のようなURLはすべて同一に扱われてしまう。

/entries/2009/1/1/
/entries/2009/01/01/
/entries/2009/000001/000001/

これは気分悪い。ということで、

from django.conf.urls.defaults import *

pattern = patterns(
  'myapp.views',
  url(r'^entries/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/', 'blog_archive'),
)

のように書き直すと、今度は、

from django.core.urlresolvers import reverse
url = reverse("blog_argchive", args=[2009, 1, 1])

というURL解決ができなくなってしまう。

from django.core.urlresolvers import reverse
url = reverse("blog_argchive", args=['%04d' % 2009, '%02d' % 1, '%02d' % 1])

これならば通るのだが、これはキモい。

Werkzeugならば、

>>> from werkzueg.routing import Map, Rule
>>> m = Map([
... Rule('/entries/<int(fixed_digits=4):year>/<int(fixed_digits=2):month>/<int(fixed_digits=2):day>/',
... endpoint='blog_archive')
... ])
>>> a = m.bind("example.com")
>>> a.match("/entries/2009/01/01")
('blog_archive', {'month': 1, 'day': 1, 'year': 2009})

逆側のURL解決も期待した通りに行ってくれる。

>>> a.build('blog_archive', dict(year=2009, month=1, day=1))
'/entries/2009/01/01/'

ただ、Werkzeugでは"/entires/yyyymmdd"のようなURLを構成する際に、

from werkzueg.routing import Map, Rule
m = Map([
  Rule('/entries/<int(fixed_digits=4):year><int(fixed_digits=2):month><int(fixed_digits=2):day>',
       endpoint='blog_archive')
])

というマッピング定義では「パス→マッチするルール」の方向のマッピングが上手くいかなかったのが気になった。

結局のところ、年毎・月毎・日毎に列挙できるリソースならば、

/entries/2009/
/entries/2009/01/
/entries/2009/01/01/

のように構成し、/entires/2009/1/1/のようなURLに対してはHTTP 301を返すのが適切だろうか。「存在しない日付に対してHTTP404を返し、期待するフォーマットではない日付に対しては301を返す」ようなデコレータというものが書けそうだし、実際にありそうだけれども、寡聞にして知らぬ。

2009-01-06

[]サーバの状態の確認

Rabbit MQサーバ管理者ガイド・サーバの状態の項を読みながら確認する。

キューの状態を確認

list_queuesでキューの状態を確認。bindされたキューがない状態でコマンドを実行すると、

$ sudo /usr/sbin/rabbitmqctl list_queues
Listing queues ...
...done.

ひとつだけ消費者プロセスを立ち上げた後、

$ sudo /usr/sbin/rabbitmqctl list_queues
Listing queues ...
amq.gen-/8acTXLFJUCupyu1BDmqbQ==        0
...done.

同じ、Exchange Nameでもう一つ消費者プロセスを起動。

$ sudo /usr/sbin/rabbitmqctl list_queues
Listing queues ...
amq.gen-1CdTJbQGZKiwFrKwkE3ZIA==        0
amq.gen-VWcx8dSsq8bcq47z3MHuig==        0
...done.

ここまでやって気づいたが、二つプロセスを立ち上げなくても、Exchangeを2つ以上宣言して、一つのChannelを使いまわせばいいのか。

ch.exchange_declare('ex1', 'fanout', auto_delete=True)

qname1, _, _ = ch.queue_declare()
ch.queue_bind(qname1, 'ex1')
ch.basic_consume(qname1, callback=callback)

ch.exchange_declare('ex2', 'fanout', auto_delete=True)

qname2, _, _ = ch.queue_declare()
ch.queue_bind(qname2, 'ex2')
ch.basic_consume(qname2, callback=callback2)

while ch.callbacks:
    ch.wait()
$ sudo /usr/sbin/rabbitmqctl list_queues name messages consumers
Listing queues ...
amq.gen-PlAM5g32mLf5pulTnN4MUw==        0       1
amq.gen-6A1WFoeuR+wRC7H0kXnK/g==        0       1
...done.

Exchangeを列挙する

"list_exchanges"でExchangeを列挙する。RabbitMQはAMPQ 0-8を実装しているので、Exchangeタイプはdirect, fanout, topicのどれか。(AMPQ 0-10にはこの3つの他にheaderタイプがある。)

$ sudo /usr/sbin/rabbitmqctl list_exchanges name type
Listing exchanges ...
amq.rabbitmq.log        topic
amq.topic       topic
amq.direct      direct
amq.fanout      fanout
        direct
...done.

2009-01-05

[][]だいぶAMQPの概要が分かってきた

RabbitMQで遊び、関連ライブラリを調べているうちにだいぶAMQPの概要が分かってきた。現時点で、参考になった資料を列挙しておく。

導入・基本概念

py-amqplibのサイトから辿っていくうちにみつけたスライド。最初は読み流してしまったけど、後で見返すとAMQP, RabbitMQ, AMQPクライアント・ライブラリについて非常によくまとまっている。

クライアント・ライブラリ

実際にコードを書いて試してみるにあたっては、次の二つのページが役にたった。

前者はオフィシャルのJavaクライアント・ライブラリのドキュメント。ステップ・バイ・ステップで書いてあるので、各モデルの概念を理解するには役に立つ。Javaの例だけれども、.NET, Python, Ruby, Erlang, AS3等のAMQPライブラリはどれも同じような構成になっているので、適時読み替えればOK。

後者はRabbitMQのcontributorで、AS3のAMQPライブラリ作者であるBen Hood氏によるErlangのAMQPクライアント・ライブラリのイントロダクション。同氏によるAMQPに関するエントリはどれも至高!!

AMQPの仕様

入門記事や実装コードを読んでいると、「実際のところ、Connection, Channel, Queue, Exchange, Messageとは何だろう?」という疑問が湧いてくる。この疑問に対しては、やはりAMQPのSpecificationを読むしかないと思う。0-10のSpecificationで300ページ弱あるので、一気に読むのはなかなか難しいと思うが、序盤の"2.1 Introduction to The AMQP Model"だけでも目を通すと、プロトコルの仕様や実装に対する見通しが広がるのではないかと思う。

Pythonプログラマならばpy-amqplibのdocstringを一通り目を通すのもよい。何がMUSTで、何がSHOULDなのか簡潔にまとまっている。

AMQPの仕様に対する見通しが立った上でも、その前でもいいと思うが、上述のBen Hood氏による下記の記事は、AMQPの仕様の背景を理解する上で非常に優れたエントリで必読だと思う。

スレッド安全性に関して

Message, Queue, Exchange, bindといった比較的高次のレイヤーの概念はどの言語でも同じだと思うのだが、低次のレイヤーのConnectionとかChannelを、各言語のライブラリでどう扱えばよいのか悩むところがある(主にスレッド安全性)。最終的には、各ライブラリのドキュメントやソースコードにあたらなくてはいけないのかもしれないが、下記の記事は参考になるだろう。

周辺技術

HTTP経由でAMQPのメッセージングを行うためのバインディング。まだ試していないのでよく分からないのだが、AMQP Erlangクライアントを使って、YawsやMochiWeb等のHTTP経由でメッセージ・リレーを行うという選択をとらなかったのはなぜだろう?(JSON-RPCの関係?)

ローカルネットワークでのAMQPメッセージをリモートのブローカーにリレーするShovelというアプリケーションの紹介。これ自体は個人的には「ふーん」って感じだが、Erlangを使うとRabbitMQのプラグイン的なものが書けるのか??というのが気になるし、よく分からないところ。

Nanite is a new way of thinking about building cloud ready web applications. Having

a scalable message queueing back-end with all the discovery and dynamic load based

dispatch that Nanite has is a very scalable way to construct web application back-ends.

http://github.com/ezmobius/nanite/tree/master

ということなのだが、こういうものを何と表現するのか良く分からない。分散プログラミング?概要を読んでいて、agentとmapperの概念や、agentの生存確認の部分とかが興味深く試してみたかったのだが、手順通りに進めていって、git cloneした後でつまってしまったので、とりあえず断念した・・・(やっぱりRubyはよく分からん。)

[]ユーザ・バーチャルホストの追加

サーバ管理者ガイドをみながら、ざっとコマンドを確認。

ユーザの列挙

"list_users"でユーザを列挙する。初期状態ではguestだけのはず。

$ sudo /usr/sbin/rabbitmqctl list_users
Listing users ...
guest
...done.

ユーザを追加する

"add_user <username> <password>"でユーザを追加。

$ sudo /usr/sbin/rabbitmqctl add_user scott tiger
Creating user "scott" ...
...done.

ユーザを削除する

"delete_user"でユーザを削除。

$ sudo /usr/sbin/rabbitmqctl delete_user guest
Deleting user "guest" ...
...done.

パスワードを変更

"change_password <username> <new_password>"でパスワードを変更。

sudo /usr/sbin/rabbitmqctl change_password scott tonythetiger
Changing password for user "scott" ...
...done.

バーチャルホストを確認する

ユーザを追加すればブローカーに接続できるのかと思ったら、バーチャルホストとユーザの対応付けを行わないといけないらしい。既存のバーチャルホストを列挙するには、"list_vhosts"。初期状態では"/"というバーチャルホストだけが登録されていた。

sudo /usr/sbin/rabbitmqctl list_vhosts
Listing vhosts ...
/
...done.

バーチャルホストとユーザの対応付け

"map_user_vhost"で先ほど作成したscottをバーチャルホスト/に対応付ける。

$ sudo /usr/sbin/rabbitmqctl map_user_vhost scott /
Mapping user "scott" to vhost "/" ...
...done.

バーチャルホストとユーザの対応付けを確認

あるユーザのバーチャルホストを列挙するには、"list_user_vhosts <username>"

$ sudo /usr/sbin/rabbitmqctl list_user_vhosts scott
Listing vhosts for user "scott" ...
/
...done.

逆に、あるバーチャルホストのユーザを列挙するには、"list_vhost_users <vhost>"

sudo /usr/sbin/rabbitmqctl list_vhost_users /
Listing users for vhosts "/" ...
scott
...done.

2009-01-04

[]R12B-5でeunit-2.0/includeがインストールされない

RabbitMQのErlangクライアントをビルドするにはeunitが必要、eunitはErlang最新版のR12B-5ならばバンドルされているということで、R12B-5でRPMを作り直した。

が、/usr/lib/erlang/lib/eunit-2.0/includeがインストールされていない。おかしいなと思ってググッたらすぐに回答が見つかった。リリース後にeunitのMakefileに不備が見つかり、再リリースのアナウンスをせずに、配布物を変更したということらしい。えー。

MD5チェックサムを確認すると確かに違った。torrentでダウンロードしたのがまずかったのか。改めて、HTTPでダウンロードし直し、RPMを作り直す。

$ rpm -ql erlang | grep eunit.hrl
/usr/lib/erlang/lib/eunit-2.0/include/eunit.hrl

とにかく、これでちゃんとErlang/RabbitMQを使い倒す準備が整ったぞ!


・・・Fedora 10のErlangのspecファイルで、sedを使ってconfigureファイル自体を書き換えSSL_DYNAMIC_ONLYをyesに強制している部分は、configureオプションの--enable-dynamic-ssl-libで同じことが出来そうな気がするのだが、確信が持てない。Fedora Developmentでどうなっているか気になるところであるが、今は考えないことにする。

[][]RabbitMQを試してみる

RabbitMQはErlang製のMQ。

メッセージングならばActiveMQでいい気もするが、僕自身がJavaよりもErlangへのシンパシーが強いのと、ActiveMQをPythonで使うにあったってStompライブラリがたくさんありすぎて、そのわりにどれもイマイチ感がぬぐえないので、RabbitMQとAMQPの組み合わせを試してみる。

CentOSにインストールする

まずErlangが必要。R12B-5がリリースされて久しいらしいが、僕はFedora 10のSRPMをrebuildしてR12B-4をインストールした。ビルドするにあたってこちらで紹介されているパッチが役に立った。

RPMErlangを入れておけば、RabbitMQが配布しているRPMを使えば簡単にインストールできるので楽。

py-amqplibをインストールする

PythonからRabbitMQ(AMQP)を使うには、py-amqplibを使う。

$ sudo easy_install amqplib

もしくは、

$ sudo pip install amqplib

メッセージ送受信の例

ほとんど、py-amqplibのサンプルコードの写経だけど、メッセージ受信側はこんな感じ。

from __future__ import with_statement
from contextlib import closing
import amqplib.client_0_8 as amqp

SERVER = dict(host='localhost',
              userid='guest',
              password='guest',
              ssl=False)

def callback(msg):
    for key, val in msg.properties.items():
        print '%s: %s' % (key, str(val))
    for key, val in msg.delivery_info.items():
        print '> %s: %s' % (key, str(val))

    print ''
    print msg.body
    print '-------'
    msg.channel.basic_ack(msg.delivery_tag)

with closing(amqp.Connection(**SERVER)) as conn:
    with closing(conn.channel()) as ch:
        ch.access_request("/data", active=True, read=True)

        ch.exchange_declare('myfan', 'fanout', auto_delete=True)

        qname, _, _ = ch.queue_declare()
        ch.queue_bind(qname, 'myfan')
        ch.basic_consume(qname, callback=callback)

        while ch.callbacks:
            ch.wait()

メッセージ送信側。

from __future__ import with_statement
from contextlib import closing
import amqplib.client_0_8 as amqp

SERVER = dict(host='localhost',
              userid='guest',
              password='guest',
              ssl=False)

with closing(amqp.Connection(**SERVER)) as conn:
    with closing(conn.channel()) as ch:
        ch.access_request("/data", active=True, write=True)

        ch.exchange_declare('myfan', 'fanout', auto_delete=True)

        msg = amqp.Message("Hello World",
                           content_type="text/plain",
                           application_headers={})

        ch.basic_publish(msg, 'myfan')

細かい点はまだよく分からないが、AMQPのSpecificationを読む価値はありそうだという感じはする。py-amqplibのコードもきれいに書かれている。

2009-01-03

[]稼動中のサーバーのバイナリを新しいバイナリに置き換える

nginxのソースコードを読んでいて、USR2シグナルを送出すると稼動中のnginxサーバーのバイナリを新しいバイナリに置き換えられることを知った(思い出した)。

そういえばNginx Wikiにそんなこと書いてあったなと思い、探してみたらやっぱりあった。我ながら、ソースコードを読んでから、ドキュメントに書いてある機能に気づくってことが多いな。

まだ日本語のページがないようだったので、せっかくの機会なので追記しておいた。