pythonでイベント駆動サーバー

趣味で非同期サーバーが作りたくて土日に色々調べてたんですが一旦まとめてみます。
node.jsなんか使えば苦労せずに出来るのでしょうが僕はpythonが好きなのでできればpythonでやりかったということです。

予めお断りさせていただくと間違ってる情報が含まれてる可能性があるのでお気をつけ下さい。

◯指針
pythonで書きたい
・できることなら慣れてるし自分の中で一番使いやすいdjangoで書きたい。
wsgiで動かしたい。
・なんとなくapacheは使いたくない。
python-amazon-product-apiを使いたい。(bitbucketで公開されてます。)


まず最初にdjangoで書きたいについてなんですが、本当は一番最初tornadoを検討していました。
評判良くて以前は結構話題になっているみたいでしたので。

しかし非同期周りのコードが専用の物になっててサードのライブラリ使おうとするとブロッキングして思った通りの動きをしてくれません。
今回のamazonのライブラリ使うところがそれです。
tornadoで動かそうとするとリクエスト周りを自分で書かないといけないので今回は見送りました。

ではこの問題を解決するには?
調べた限りではgeventのmonkey_patchしかなさそうでした。

uwsgiとgunicornがデフォでgeventに対応してます。
どちらもオプションでコントロールできます。
uwsgiなら

gevent-monkey-patch = true
gevent = 100

みたいな感じで動くし

gunicornでも

$ gunicorn wsgi:application --worker-class gevent -k gunicorn.workers.ggevent.GeventWorker

で起動すれば動きます。

ただしdjangoを動かそうとすると話が変わってきます。

uwsgiは何回かアクセスするとdb周りでDatabaseErrorが発生します。
thread.get_ident()が変わるせいです。
ファイルで言うとdjango/db/backends/__init__.py内のvalidate_thread_sharingで例外が出ます。

しかしgunicornだとうまく動きます。なんでかっていうとソースを見ればわかるのですが本来だと↑のvalidate_thread_sharingで引っかかるところをpatchあてて通るようにしているからです。
この当て方で大丈夫なのか疑問なところではありますが。
どうにも無理矢理な感じするのですがdjango側のソースをしっかり見れていないので何とも言えません。

そこは今後の課題にするとして、手間をかけずに動くのはgunicornなのでその方向で行きたいと思います。

とは言えuwsgiでも似たような感じで対応できるのでどっちでも良いような気がします。結局は好きな方使えば良いんじゃないでしょうか。

最後に言いたいのはサードパーティのライブラリがそのまま使えるようになるmonkey_patchは強力です。

CentOS6のパーティション拡張

CentOS6.4のディスクを拡張したときのメモ。
環境はMacBookProでVMware Fusion5を使用しています。
CentOSパーティションはLVMで設定してます。
/だけに全部割り当てている感じです。

以下の手順は自己責任でお願いします。

まず始めに物理的なディスクを拡張します。

VMwareのメニューから仮想マシン->ハードディスク(IDE)->ハードディスク(IDE)設定を選択。

適当に増やしたい分だけ増やします。注意書きにある通りシャットダウンしておかないと無理です。

増設したら起動します。

rootでログインしたらfdiskコマンドでちゃんと増えているか確認。

[root@vmware ~]# fdisk -l /dev/sda

Disk /dev/sda: 53.7 GB, 53687091200 bytes
255 heads, 63 sectors/track, 6527 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x0003b5d8

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *           1          64      512000   83  Linux
Partition 1 does not end on cylinder boundary.
/dev/sda2              64        3917    30944256   8e  Linux LVM

容量だけ53.7GBに増えてます。
実際に使える領域は増えていません。

Linux LVMのIdは後で指定しますので要確認。

パーティションを切り直します。

僕がやった時はデータは消えませんでしたが環境によっては消えるかもしれませんのでバックアップは取りましょう。

[root@vmware ~]# fdisk /dev/sda

Command (m for help):

入ったらまず既存のパーティションを一旦消します。

Command (m for help): d
Partition number (1-4): 2

Command (m for help): p

Disk /dev/sda: 53.7 GB, 53687091200 bytes
255 heads, 63 sectors/track, 6527 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x0003b5d8

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *           1          64      512000   83  Linux
Partition 1 does not end on cylinder boundary.

消えたので作り直します。

Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
p
Partition number (1-4): 2
First cylinder (64-6527, default 64): 
Using default value 64
Last cylinder, +cylinders or +size{K,M,G} (64-6527, default 6527): 
Using default value 6527

Command (m for help): p

Disk /dev/sda: 53.7 GB, 53687091200 bytes
255 heads, 63 sectors/track, 6527 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x0003b5d8

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *           1          64      512000   83  Linux
Partition 1 does not end on cylinder boundary.
/dev/sda2              64        6527    51915103+  83  Linux

LVMなので変更します。

Command (m for help): t
Partition number (1-4): 2
Hex code (type L to list codes): 8e
Changed system type of partition 2 to 8e (Linux LVM)

Command (m for help): p

Disk /dev/sda: 53.7 GB, 53687091200 bytes
255 heads, 63 sectors/track, 6527 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x0003b5d8

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *           1          64      512000   83  Linux
Partition 1 does not end on cylinder boundary.
/dev/sda2              64        6527    51915103+  8e  Linux LVM

これで広がりました。
確認して大丈夫だったら保存して抜けます。
そしてマシンを再起動します。

この時点ではまだ増えていません。
dfで見ても元のままです。

次にだらだらと以下の手順を行います。

[root@vmware ~]# pvresize /dev/sda2
  Physical volume "/dev/sda2" changed
  1 physical volume(s) resized / 0 physical volume(s) not resized

[root@vmware ~]# lvextend -L +20G /dev/mapper/vg_vmcentos-lv_root
  Extending logical volume lv_root to 45.57 GiB
  Logical volume lv_root successfully resized

[root@vmware ~]# resize2fs /dev/mapper/vg_vmcentos-lv_root
resize2fs 1.41.12 (17-May-2010)
Filesystem at /dev/mapper/vg_vmcentos-lv_root is mounted on /; on-line resizing required
old desc_blocks = 2, new_desc_blocks = 3
Performing an on-line resize of /dev/mapper/vg_vmcentos-lv_root to 11945984 (4k) blocks.
The filesystem on /dev/mapper/vg_vmcentos-lv_root is now 11945984 blocks long.

作業完了後dfで確認すると26GBから45GBに増えました。

思ったより簡単でした。

Xcodeを5にしたらhgsubversionが動かなくなった

お久しぶりです。

最近OSをMavericksにしたのでXcodeも5にしたのですよ。
そしたらhgsubversionが動かなくなっちゃって。

とりあえず動くように持ってったのでメモ。

まずCommand Line Toolsをインストールします。
Xcode5からPreferencesのDownloadからインストールできなくなってるみたいなのでApple Developerのページからダウンロードしてきます。

インストールしたらsubverpyとhgsubversionをpipで入れ直します。
この作業は必要なのかわかりませんけど僕の場合なんとか動かそうと、とにかくもう色々やってたので結果的に入れ直しました。

インストール時のコマンドはこちらを参考にさせて頂きました。
http://javelindev.jp/blog/tech/entry/1
(バージョン違うのでパスは変わります。)

で、これで大丈夫かなーと思ったらそうでもなく、

abort: no compatible bindings available:

Subversion 1.5.0 or later required, but no bindings were found
Subvertpy 0.7.4 or later required, but not found

Please install either Subvertpy or the Subversion Python SWIG bindings!

とか出てしまう。

検索したらDYLD_LIBRARY_PATHを設定すると良いみたいだったので

export DYLD_LIBRARY_PATH=/Applications/Xcode.app/Contents/Developer/usr/lib/

として解決しました。

Django標準のjson serializerをカスタマイズする

Djangoは標準でjsonシリアライズする関数があるんですが、

from django.core import serializers

# recordsはModel.objects.all()とかの結果
data = serializers.serialize("json", records, ensure_ascii=False)
# これのことです。

標準の機能だとpkとmodelとfieldsがキーになったjsonオブジェクトになってます。

でもpkとmodelが邪魔なのでfieldsの値だけを抽出したいんです。

それが今回のテーマです。

元々親切に簡単に拡張出来るようになっているのでそれを利用します。

(今回はDjango1.5を使ってます。)

まずjson.Serializerを継承したクラスを作ります。

# serializer.py

# -*- coding: utf-8 -*-
from django.core.serializers.json import Serializer as JSONSerializer


class Serializer(JSONSerializer):
    def get_dump_object(self, obj):
        return self._current

    def start_serialization(self):
        super(Serializer, self).start_serialization()
        self.json_kwargs["ensure_ascii"] = False

次にこのクラスを読み込む為にsettings.pyを編集します。

良い名前が思いつかないのでplainjsonとでもしておきます。

# settings.py

SERIALIZATION_MODULES = {
    "plainjson": "myproject.serializer"
}

実際の呼び出しは

from django.core import serializers

data = serializers.serialize("plainjson", records)

これでpkとmodelが削られたjsonシリアライズされます。

ちなみにstart_serializationをオーバーライドしてるのは、まぁ日本語はそのまんま出したいだろうって意図です。ちょっとだけコード量が減るってだけです。

これまた久々の更新(ライブラリの紹介)

忙しいってのを言い訳にしてはいけないと思うんですがネタも特になくて更新してませんでした。

以前から公開してるzz.jsというスマホ向けHTML5ライブラリなんですが、
だいぶまともになったので改めて紹介してみます。

https://bitbucket.org/ta2xeo/zz.js/wiki/Home

いわゆる昨今よく見るスマホ用ゲームライブラリです。
元々勉強で書いてたんですが今は仕事でも使ってます。

ActionScript3のクラスをパクっててAS3を触ったことがある人にとっては理解しやすいです。
まぁこの手のライブラリは山ほどあるんですけど。
車輪の〜とか言わない。

最近は一つのcanvasタグに描画してるのも増えて来ましたが、
これは一つ一つのオブジェクトがdivコンテナを持ってるので個別にcssが指定出来ます。

超手抜きですが以下サンプル

http://ta2xeo.bitbucket.org/zz.js/sample/

いろんな端末で動作検証しててやっと目立ったバグもなく動くようになりました。
Androidの一部の端末でcanvasがうまくクリアされない問題とか。
Androidのバグが酷いんですよマジで。

これ系の有名なやつでCreateJSがあると思いますがあれは海外製ですし日本の端末でちゃんと検証されてないと思います。
これは日本人ですし検証してる端末も某3大キャリアの物なのでその辺はだいぶ安心出来ると思います。(ドヤァ
とは言え、あっちはユーザー多いしすぐパッチも当たると思いますが。

ちなみにパフォーマンスはそれほど優れているとは言えません。
端末の問題とも言えますがF-11Dとかカックカクです。
一部のこういう変なクセのある機種に関してはぶっちゃけどうにもなりません。ホントに。

実際のところ大体の機種(二年前とかの機種)はそれなりの描画でFPS20以上は出ます。
ソシャゲの演出なら恐らくFPS12とかで作ってるでしょうから問題ないレベルだと思います。

ちなみにこのライブラリの特徴として各Stage毎にFPSが設定できるので
「表示物の少ないStageはFPS24を設定」「重そうなアニメーションにはFPS16を設定」
なんてことも出来ます。複雑なアニメーションでどうしてもFPSを落とさざるを得ないなんて時はこういったやり方で対処出来るかもしれません。
仕事ではそのテクニックを使ってます。

今後の展開ですがサンプルコードをちゃんと書くのと、あとはUI周り。今はタッチ拾うくらいしか機能として用意してないのでコントローラー的な物を作りたいなー。

後悔してるのはイベントリスナーのthisが自身のオブジェクトで固定になっているところ。ここは自分で指定出来るようにするべきだった。

まー、ここを変更すると動かなくなるので変更するにしてもメジャーバージョンアップ時くらいしかなさそう。

忙しくて更新サボっとりました。

久しぶりの書き込みになります。

現在進行形の仕事が忙しくて更新サボってました。
今後も数週間はまともに更新できないことでしょうw

話は変わりますが、最近Pyramidに興味を持ってて少し触ってます。
他に触ったことのあるPythonフレームワークDjangoがほとんどでFlaskとかは少しだけ。

Pyramidはまだ少ししか触ってませんが楽しいです。
個人的な感想を言うとDjangoに比べると難しい気がします。
Djangoはやりたいことの機能は大体入っててすぐ使える状態になってるイメージです。

Pyramidが難しく感じるのはSQLAlchemyのせいかもしれません。
自分の実力不足なんですけど高機能過ぎて使いこなせない感じw

url dispatch周りとかはデコレータ使えたりで使いやすいと思います。
ただincludemeってのだけは許せないw
普通に指定させれば良いのにって思う。(もちろん指定はできます。)

あとテンプレートエンジンはchameleonがデフォルトですが
Django使ってた人はjinja2に切り替えた方が無難でしょう。
xmlベースはレイアウト崩れない利点あったりするんですが
それはプラグイン書けば済む話だし慣れないせいもあってか単純に読み辛いです。

Djangoにあったadminサイトは無いので必要なら自作するしかなさそうです。
FormAlchemyってのがあるそうなんですが至らない点が結構あるみたいです。


まとめるとDjangoは一番使われてるだけあって使いやすいです。
ドキュメントも日本語化されてますし情報も多く転がってます。
普通のWEBアプリ作るならDjangoを選択して問題が出ることはまず無いでしょう。
ソシャゲにもオススメです。僕もDjangoで作ってましたし他の会社もDjangoで作ってるところ結構あると思います。

小さいものならBottleとかでも良いんじゃないでしょうか。

Pyramidはネットの情報を見る限りではベンチマークが優秀なのでパフォーマンス重視の選択ならアリだと思います。
機能的にも全く問題無いでしょうし拡張性も高いです。
公式にも書いてありますが小規模から大規模までまかなえるのでその辺りの融通がきくところはメリットだと思います。

というわけで僕はPyramid+jinja2でもう少し触ってみようかと思います。

デバッグ機能作りました。

自分用としてzz.jsにデバッグ機能を用意しました。
細かいバグとかはありますが使えるレベルにはなっているので公開してあります。

機能としては、

  • FPSをシームレスに変更可能。
  • オブジェクトのツリー表示。
  • 各オブジェクトのプロパティを変更可能。
  • MCの再生停止機能。
  • キーボードによるオブジェクトの移動。

JSとcssを読み込むだけで有効になります。
なのでデバッグの為にわざわざ何かコードを書く必要はありません。
一番のウリです。

頑張ればオーサリングツール作れそうだなー。