今更ながら「大規模サービス技術者入門」を読み返したんだす。

AhoCorasickのこと今までアホコラシックて読んでたけど、
正しくはエイホコラシック(Wikipedia調べ)だそうなんだす。

ソースコード -> pyahocorasick

failure linkを作るところを理解するのに時間がかかった。
トライ木を構築した後、根から順繰りに次ぎのノードを見てって、最長接尾辞を決定するところ。
再帰でつくってみた。

最終的にこんな感じでテスト。

参考

Aho Corasick 法 – naoyaのはてなダイアリ

 

buildout を使って Flask + Google App Engine な環境を構築してみたメモ。

buildout とは、Pythonベースの環境構築ツール。
日本語による記事が少なくて非常にとっつきにくいですが、慣れると簡単便利です。

こちらが今回書いた物。一応、Linux(CentOS5), MacOSX10.6で動作確認済み。
flask-gae-template

セットアップしてみる

以下のコマンドを入力する。
(gitがない場合、代わりに上記サイトのDownloadsからダウンロード&解凍すればOK)

$ git clone https://github.com/utahta/flask-gae-template.git
$ cd flask-gae-template
$ curl -LO http://svn.zope.org/*checkout*/zc.buildout/trunk/bootstrap/bootstrap.py
$ python bootstrap.py -d
$ ./bin/buildout

するといろいろディレクトリが作成されたり、GoogleAppEngineのSDKやFlaskがダウンロードされたりする。
いろいろ作成された中のsrcという名のディレクトリが、作業場。

とこれだけでセットアップは完了。

残るは動作確認。

次のコマンドを打って、

$ ./bin/dev_appserver src/

ウェブサーバを起動し、http://localhost:8080にアクセスしてエラーがなければ成功です。

ちなみに

作業場の名前(src)が気に食わなかったら、buildout.cfgのこの部分を好きな名前に書き換えて、もう1度./bin/buildoutすると良い感じです。

GoogleAppEngineLauncher等、既にインストール済みのヤツを使いたかったら、buildout.cfgのこの行をコメントアウトなり削除なりして、パスを指定してあげると良い感じです。
たとえば、/usr/local/google_appengineにインストールしたやつを使いたかったら、

sdk-directory = /usr/local/google_appengine

とします。

参考

Flask で始める Google AppEngine アプリ開発 – TIM Labs
Google App Engine の開発をbuildoutで行う — Python Hack-a-thon 4: ハンズオン v1.0 documentation

 

Google App Engine 上で Flask を動かす用のスケルトンプログラムを作った。

git 使い専用。

buildout 以外のアプローチ。(buildout ややこしいんですもん)

git clone して copy コマンドを叩くと、プロジェクトの雛形がつくられる。

使い方

$ git clone https://utahta@github.com/utahta/flask-gae-skeleton.git
$ cd flask-gae-skeleton
$ git submodule update --init
$ python copy_skeleton.py -t /path/to/project_name

/path/to/project_name下に雛形がコピられる。

自分の環境は Mac & GoogleAppEngineLauncher.app なので、
この後、GUIから/path/to/project_nameを設定して開始ボタンを押したりする。

スケルトンプログラム自体は元々、blossom / flask-gae-skeleton というものがあって、
これをフォークしてシンプルにしただけ。

こーゆーアプローチもありなんじゃないかということで。

 

Python + FlaskでMVC的なサンプルをつくったメモ。

MVC的な記事まとめ

ひとまずMVCについて言及されている記事をまとめ。
MVCはWikipediaが詳しい。
以下は調べた中でもわりと個人的にしっくりきた記事たち。

Web Applicationを綺麗に設計するためのMVACという考え方 – Dive into the Tech World!
Re: @kazuho: handlersocket plugin や mycached を使えば memcached は不要か、それとも使うべきケースがあるか。考察せよ [10点] – blog.nomadscafe.jp
Life is beautiful: Ruby on Railsの「えせMVC」の弊害
the { buckblogs :here }: Skinny Controller, Fat Model

共通して言われている気がするのが下記2点。守れば比較的キレイに設計できるかも。

  • コントローラに処理を書きすぎない
  • モデル = ORM としない

これはやっとけみたいなことが多いわりに絶対的なお約束がないMVC。
これはやっとけを全部守ってたらカオスになりそうなのでこの2点。

ModelでDBI, ORM, …という各機能をラップしてあげるイメージが一番しっくりきた。
これでごちゃごちゃしたソースコードが少しはスッキリしてくれるとありがたい。

Python + FlaskでMVC的なアプリ

上記記事を参考にしながらPython+FlaskでつくったMVC的構造のサンプルアプリをペタリ。

https://github.com/utahta/Flask-MVC-Pattern

projects/create で何かしらを登録し、projects/ で登録した何かしらを表示する至極簡素な機能付き。

以下、ソースコード(抜粋)。

コントローラ(myapp/controller/projects.py)でルーティング&入力値チェック。

from flask import Module, render_template, redirect, url_for, request
from flaskext.wtf import Form, TextField, validators
from myapp.model.projects import ProjectsModel
 
projects = Module(__name__)
 
@projects.route('/')
def index():
    model = ProjectsModel()
    # リスト表示
    return render_template('projects/index.html', model=model)
 
class CreateForm(Form):
    """
    /projects/create 用のフォームクラス
    入力値チェックも担う
    """
    title = TextField(u'タイトル', [validators.Length(min=1, max=25)])
    description = TextField(u'説明', [validators.Length(min=1, max=100)])
 
@projects.route('/create', methods=("GET", "POST"))
def create():
    model = ProjectsModel()
    form = CreateForm(request.form)
    if request.method == 'POST' and form.validate():
        # チェック通ったらデータを保存し、/projects へリダイレクト
        model.save(form.title.data, form.description.data)
        return redirect(url_for('projects.index'))
    # 作成画面を表示
    return render_template('projects/create.html', form=form)

Flask-WTFというFlaskの拡張機能を使ってフォームの値をチェック。
class CreateForm(Form) の部分。
モデル側に定義しようか一瞬迷うも、/createでしか使わないことを考えコントローラ側に定義。
プログラムがもっと大きくなったらまた考える。

モデル(myapp/model/projects.py)でデータ取得&保存。

from myapp import db
from myapp.db.orm.projects import Projects
 
class ProjectsModel(object):
    def get_entries(self):
        return db.session.query(Projects).all()
 
    def save(self, title, description):
        db.session.add(Projects(title, description))
        db.session.commit()

ORMオブジェクトのProjectsをProjectsModelが操作。ラップップ。
ORMはSQLAlchemyを使用。
仕組みが単純なのでやってることも単純。
プログラムがもっと大きくなっても、データの一貫性保証とかもろもろここで処理する。

設計はデザイン

デザインはセンス。センスは経験。
ということで、以前つくったウェブアプリをFlaskで書き直して経験値ためる。
ついでにGAEに移植できたらいいかしらん。

3月 042011
 

Flask 基礎を勉強中。

環境構築

OS:CentOS 5.4

Python-2.6.6 インストール。

$ pythonbrew install 2.6.6
$ pythonbrew switch 2.6.6

Flask インストール。

$ pip install Flask

Hello Flask

$ vi main.py
from flask import Flask
app = Flask(__name__)
 
@app.route('/')
def hello():
    return 'Hello Flask.'
 
if __name__ == '__main__':
    app.run()
$ python main.py
 * Running on http://127.0.0.1:5000/

ルーティング&変数ルール

from flask import Flask
app = Flask(__name__)
 
@app.route('/user/<username>')
def show_username(username):
    return 'User. name:%s' % username
 
@app.route('/post/<int:post_id>')
def show_post(post_id):
    return 'Post. post_id:%s' % post_id
 
if __name__ == '__main__':
    app.run()

リダイレクト

from flask import Flask, redirect, url_for
app = Flask(__name__)
 
@app.route('/')
def hello():
    return 'Hello Flask.'
 
@app.route('/redirect')
def redirect_hello():
    return redirect(url_for('hello'))
 
if __name__ == '__main__':
    app.run()

リクエスト

from flask import Flask, request
app = Flask(__name__)
 
@app.route('/')
def index():
    return 'Get. name:%s' % request.values.get('name', '')
 
if __name__ == '__main__':
    app.run()

スタティックファイル&テンプレート

ディレクトリ構成
/
main.py
|-- static
|      |-- style.css
|-- templates
       |-- index.html

main.py

from flask import Flask, render_template
app = Flask(__name__)
 
@app.route('/')
@app.route('/<name>')
def index(name=None):
    return render_template('index.html', name=name)
 
if __name__ == '__main__':
    app.run()

style.css

h1{
    font-size: 12px;
    font-weight: normal;
}

index.html

<!doctype html>
<head>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css" />
<title>Hello from Flask</title>
</head>
<body>
{% if name %}
  <h1>Hello {{ name }}!</h1>
{% else %}
  <h1>Hello World!</h1>
{% endif %}
</body>
</html>

セッション

from flask import Flask, session, url_for, redirect
app = Flask(__name__)
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
 
@app.route('/')
def index():
    if 'username' in session:
        return 'Logged in. <a href="%s">Logout</a>' % url_for('logout')
    return '<a href="%s">Login</a>' % url_for('login')
 
@app.route('/login')
def login():
    session['username'] = 'user'
    return redirect(url_for('index'))
 
@app.route('/logout')
def logout():
    session.pop('username', None)
    return redirect(url_for('index'))
 
if __name__ == '__main__':
    app.run(debug=True)

参考

Welcome to Flask — Flask 0.7dev documentation

 

この1ヶ月、土日の休みを使いTUBELONGERってWebアプリをつくってました。
言語はPython。
Pythonは日本語の取り扱いが若干面倒だったりするけど、書いてて楽なので好き。
以下、PythonでWebアプリをつくるにあたって調べたり分かったりしたことのメモ。

構成

OS:さくらVPS CentOS 5.5
サーバ:Nginx + Gunicorn
データベース:MySQL

当初はGoogle App Engineで開発を進めてたけど、データストアの使い方を調べるのにいちいち時間をとられたり、Cron的な動作に制限があったりといろいろ面倒くさかったので、途中からroot権限のあるサーバでいちから構築する方向に切り替えました。
やっぱりroot権限は自由度が半端じゃないです。
GAEがダメってわけではないです。
むしろ僕の知識が足りなすぎて学習コストが尋常じゃなかったのでまた今度にしました。

Webフレームワークを選ぶ

Pythonには、山のようにフレームワークが存在します。
当初は、Pylonsでつくろうと意気込んでいたのだけど、やればやるほど学ばなければいけないことが増えていくように感じたので、紆余曲折ありつつ途中からTornadoに切り替えました。
他にRubyでいうSinatraに近いFlaskというフレームワークがあってそれも良さそうでした。

Tornadoを選んだ理由は、GAEのwebappに非常に似ていた(当初webappで作成してたアプリの移植が簡単そうだった)こと、ドキュメントが完全に日本語訳されていたことが挙げられます。ただドキュメントがわりと簡素なので、詳細を知りたければソースコードを読まざるを得ないかも。面倒だったけれど、ソースコードはシンプルで読みやすく、勉強になりました。

ちなみにTornadoは、WSGI(WSGIについてはリンク先のWikipediaを参照)で運用してます。TornadoをWSGIで運用することは公に推奨されていないようだけど、いくつか非同期の機能が使えなくなるだけで元々その機能を使う予定がない場合は問題ないだろうというのと、問題があるとしたら速度的なところだろうと思って、FlaskとTornado(WSGI)両方のベンチマークをとって比較したけれど大した差は見られなかったので、まぁいいかという判断です。

Tornado について少々の雑感

Tornadoは、FriendFeedで使われていたフレームワークが元になっていて、シングルスレッド・ノンブロッキングで動作します。
通常シングルスレッドで動作する為、1リクエストの処理が遅いと全体の性能が大きく劣化します。例えばTornadoサーバが1つ動いている場合、1リクエストに1秒かかると全体の性能も1秒に1リクエストしかこなせない程度に劣化します。シングルスレッドなので当たり前ですね。故にTornadoサーバにはHTTP非同期処理の仕組みが備わっていて、負荷の高いであろう処理は別のWEBサーバで行わせて、そのWEBサーバに大してHTTP非同期処理して性能劣化を防いだりするそうな。
面倒だったので、僕はWSGI+gunicorn:geventを選択しました。

他にもTemplate機能や他便利機能が揃ってます。
CSRF対策機能とかMySQLdbのラッパとか便利です。
総合して、Pylonsほど高機能ではないが、web.pyほどシンプルでもない、中間的な立ち位置のフレームワークに感じました。

Gunicorn

サーバは、Gunicornを選びました。
Gunicornは、WSGIを備えたサーバプログラムで、RubyのUnicornを元につくられたプロジェクト。
この上で、Tornadoを動作させています。
理由は、一番使い勝手が良さそうだったから。(ついったー上でNginx+Gunicornがアツイみたいな発言も見かけたのも大きかった)
他にも似たところで、Apache + mod_wsgiuwsgiといったものがあるようです。
ちなみにGunicornは、ワーカーというものをプラグインで選ぶことが出来ます。
今回は、geventというコルーチンベースのものを選択。
内部でlibeventを使っていて、比較的パフォーマンスが良いらしい。
これとNginxを組み合わせて運用してます。

Nginx

Gunicornをバックエンド、Nginxをフロントエンドとして使ってます。
構築する環境が、さくらVPSということでメモリ容量が少々不安だった為、Apacheよりメモリ消費の少なそうなNginxを選んでみた。
Gunicornと連携する設定は、ここを参考にしました。(というかほぼ丸コピペ)

Supervisord

ファイルに何らかの変更を加えた後、その修正を反映するためにGunicornの再起動が必要だったりするのだけど、いちいちPIDを調べてKILLしたりするのが面倒だったので、Supervisordというプロセス管理ツールを導入してみました。
(Gunicornの設定ファイルにこれを追加することで自動再読み込みさせられるのだけど、本番環境でやるのは気が引けた)

Supervisordを使うと、以下のように簡単に稼働状況を確認したり再起動できたりして、なにかと楽できます。
これは、導入して正解でした。

$ supervisorctl
supervisor> status gunicorn 
gunicorn                         RUNNING    pid 17275, uptime 3 days, 9:21:22
supervisor> restart gunicorn 
gunicorn: stopped
gunicorn: started

まとめ的な雑感

以上、ざっくりとつかってみたフレームワークやそれについての雑感をつらつらと書いてみた。
何かの参考になれば幸いです。
設定ファイルの中身だとかインストールの手順だとかは省いてしまったけど、いずれ隙をみつけてメモりたい。

 

mod_wsgi を入れようとしたら、糞ハマったのでメモり。

環境:
CentOS5
httpd-2.2.17
mod_wsgi-3.3
Python-2.7.1
/home/user 以下にインストール

httpd, mod_wsgi, python は、ソースからコンパイルしたものを使いました。

とりあえず、/home/user ディレクトリに実行権限を付け忘れていたことが原因。

すごくざんねんです。

子プロセスが起動しない

具体的なエラーは下記の通り。

$ ps aux | grep httpd
root     27203  0.0  0.1   5816  3252 ?        SNs  12:46   0:00 /path/to/httpd-2.2.17/bin/httpd -k start
daemon   27210  0.0  0.0      0     0 ?        ZN   12:46   0:00 [httpd] <defunct>
daemon   27211  0.0  0.0      0     0 ?        ZN   12:46   0:00 [httpd] <defunct>

なぜか子プロセスがゾンビ化。

Apache のログを見ると、mod_wsgi の初期化部分でコケてた。

[Sun Jan 16 12:45:10 2011] [info] mod_wsgi (pid=27185): Python home /path/to/Python-2.7.1.
[Sun Jan 16 12:45:10 2011] [info] mod_wsgi (pid=27185): Initializing Python.
ImportError: No module named site

ちなみに Apache のログレベルはinfo。

mod_wsgiのソースを眺めたところ、mod_wsgi.cで呼び出されているPy_Initialize()内でコケてた。

解決策

/home/userディレクトリに実行権限をつけたった。

たぶんpythonを/home/user下にインストールしてたため、daemonユーザだとリード権限がなくて、siteというモジュールを読みにいけなかったのだろうと推測。

最終的にPython-2.6.6をenable-sharedつきでコンパイルしたものを使い、httpdの立ち上げに成功。。。
そして、ウェブページにアクセスしForbiddenという文字列を見て、実行権限の問題に気づきました。
まったくアホですね。

問題の切り分けが下手だと徹夜するという教訓でした。

参考

Multiple Python Versions
Lack Of Python Shared Library

 

先日 Python で実装してみた VBCode の続き。

ここからダウンロードしたはてなのデータを実際に圧縮してみた。

準備

ダウンロードしたやつの中に入っている eid_tags.txt を使う。

$ curl -LO http://image.gihyo.co.jp/assets/files/book/2010/978-4-7741-4307-1/hugedatabook_samplecode.zip
$ unzip hugedatabook_samplecode.zip
$ du -h hugedatabook_samplecode/hgdata_example/06/eid_tags.txt
172M	hugedatabook_samplecode/hgdata_example/06/eid_tags.txt

大きさは、だいたい172MBぐらいらしい。

フォーマットは、「はてなタグ<タブ>ID,ID,ID…」的なかんじ。
こいつを適切に読みつつ、IDの部分にVBCodeを適用してやります。
はてなタグは文字列。IDは整数(4byte)。
IDは、小さい順にソートされているので、前後の差分を取ってそいつを圧縮してやると効率がよいとのこと。
本の中では、ギャップと表現されてました。
例で表すと以下のとおり。

整数列:1, 5, 10, 12, 16
差分 :1, 4, 5, 2, 4

実行してみた

— 圧縮 —

$ time ./chap6_vbencode.py eid_tags.txt > eid_tags.txt.vb 
real	1m37.259s
user	1m35.743s
sys	0m0.917s
$ du -h  eid_tags.vb
 40M	eid_tags.txt.vb

— 解凍 —

$ time ./chap6_vbdecode.py eid_tags.txt.vb > eid_tags2.txt
real	0m29.463s
user	0m27.269s
sys	0m0.941s
$ diff eid_tags2.txt eid_tags.txt

最初は圧縮するまで2分半もかかりましたが、もろもろ最適化したら1分半になりました。

でもサンプルに付属していた Perl スクリプトだと1分かからないぐらい。

この速度差はなんだろ。。。

おもむろにいろいろ測ったところ、「数値を文字列化している部分」と「pack関数周り」が怪しげだった。
その部分だけ実行するスクリプトを回してみたところ、性能に約5倍ほど差がでた。

$ cat a.py
#!/usr/bin/env python
from struct import pack
for i in xrange(19997779):
    bytes = [1, 10, 100]
    pack('%dB' % len(bytes), *bytes)
$ time ./a.py
real	0m45.475s
user	0m45.293s
sys	0m0.075s
---------------------------------------------
$ cat a.pl
#!/usr/bin/env perl
for($i=0; $i<19997779; $i++){
    @bytes = (1, 10, 100);
    pack('C*', @bytes);
}
$ time ./a.pl
real	0m9.943s
user	0m9.930s
sys	0m0.009s

もうすこし絞りたいと、数値を文字列化している部分を直値に直したら性能差は約2倍まで縮まった。

$ cat a.py
#!/usr/bin/env python
from struct import pack
for i in xrange(19997779):
    bytes = [1, 10, 100]
    pack('3B', *bytes)
$ time ./a.py
real	0m17.557s
user	0m17.472s
sys	0m0.023s

しかし、直値での実装方法は不明。
packはさらに奥が深そうなので放置。
この問題は迷宮入りです。

圧縮後ファイルサイズは、約40MBになった。

172MB → 40MBなので、圧縮率23%ぐらい。凄い。

圧縮ファイルを解凍して元ファイルと差がないことを確認し、おしまいにしました。

雑感

Variable Byte Code はアルゴリズムさえ知っていれば、実装は意外と簡単だった。

作成したソースコードはgistに貼り付けた。

 

よる年の瀬の最中、Web開発者のための大規模サービス技術入門という本を読んでる。

ソーシャルゲームが流行っている昨今、大規模サービスの運用論や方法論について、少しは学んでおくか的なノリで読んでる。

こいつは、株式会社はてなで行われたインターン実習が元になってて、負荷対策の基本等が分かりやすく実習形式で解説されてるオシャレな本だ。
OSのページキャッシュの仕組みとかデータの分散とか検索アルゴリズム等、もろもろ興味深いのだ。

今回は、整数データをコンパクトに持つ為の圧縮技術VBCodeをPythonで実装してみたメモり。(本の中ではPerl)

はてなのインターン生に混じった気分で、疑似コードを参考に実装してたら、なんだかんだ1時間ぐらいかかった。

もっとさくっと組めたらいいのに。

つっかかった点は以下2点。
・Pythonにおけるバイトコードの取り扱い(pack, unpack)
・128というマジックコード
おもうさま翻弄された。

特に後者の128ってマジックコード。
2進数であらわすと10000000で、VBCodeのキモになる値。
足したり割ったり眺めたりしてようやく理解したけど、擬似コードがn << 7のようにC言語ライクに書いてくれてたら、もう少し早く理解できたかもな!

できたソースコードはgistに貼り付けた。

 

GREEエンジニアブログのグリーの大規模分散ストレージ戦略(nanofs) Vol.2を見ていて、Apache の Request Counting ってなんぞやと思ったので、調べたメモ。

Request Counting アルゴリズムとは

Apache の mod_proxy_balancer モジュールに組み込まれているロードバランサのスケジューラアルゴリズムの1つ。

やってきたリクエストが、各ワーカーにちゃんと分担されるようにというか詳細は、こちら

リンク先に擬似コードが示されていたので、試しにいくつかの言語で実装してみた。

lbfactor が重み、lbstatus が優先度みたいなものっぽい。

Python

#!/usr/bin/env python
 
class Worker(object):
    def __init__(self, name, factor):
        self.name = name
        self.lbfactor = factor
        self.lbstatus = 0
        self.debug_count = 0
 
def request_counting(workers):
    """Request Counting Algorithm"""
    candidate = None
    total_factor = 0
 
    for worker in workers:
        worker.lbstatus += worker.lbfactor
        total_factor += worker.lbfactor
        if not candidate or worker.lbstatus > candidate.lbstatus:
            candidate = worker
 
    candidate.lbstatus -= total_factor
    return candidate
 
if __name__ == "__main__":
    workers = [Worker('A', 70), 
               Worker('B', 30)]
 
    for i in xrange(10):
        worker = request_counting(workers)
        print "(%s) %s" % (worker.name, ", ".join(["%3d" % w.lbstatus for w in workers]))
        worker.debug_count += 1
 
    for worker in workers:
        print "%s:%3d" % (worker.name, worker.debug_count)

Perl

#!/usr/bin/env perl
use strict;
use warnings;
 
{
    package Worker;
 
    sub new{
        my $pkg = shift;
        my %hash = (
            name => shift,
            lbfactor => shift,
            lbstatus => 0,
            debug_count => 0,
        );
        return bless \%hash, $pkg;
    }
}
 
package main;
 
sub request_counting{
    my $candidate = undef;
    my $total_factor = 0;
    my $workers = shift;
 
    foreach my $worker (@$workers){
        $worker->{lbstatus} += $worker->{lbfactor};
        $total_factor += $worker->{lbfactor};
        if(!defined($candidate) or $worker->{lbstatus} > $candidate->{lbstatus}){
            $candidate = $worker;
        }
    }
 
    $candidate->{lbstatus} -= $total_factor;
    return $candidate;
}
 
my @workers = (Worker->new('A', 70),
               Worker->new('B', 30));
 
for(my $i = 0; $i < 10; $i++){
    my $worker = request_counting(\@workers);
 
    my $debug_str = "";
    foreach my $tmp (@workers){
        $debug_str .= ($debug_str eq "") ? "" : ", ";
        $debug_str .= sprintf("%3d", $tmp->{lbstatus});
    }
    printf("(%s) %s\n", $worker->{name}, $debug_str);
    $worker->{debug_count}++;
}
 
foreach my $worker (@workers){
    printf("%s:%3d\n", $worker->{name}, $worker->{debug_count});
}

Ruby

#!/usr/bin/env ruby
 
class Worker
    def initialize(name, lbfactor)
        @name = name
        @lbfactor = lbfactor
        @lbstatus = 0
        @debug_count = 0
    end
 
    def get_name()
        return @name
    end
 
    def get_lbfactor()
        return @lbfactor
    end
 
    def add_lbstatus(lbstatus)
        @lbstatus += lbstatus
    end
    def sub_lbstatus(lbstatus)
        @lbstatus -= lbstatus
    end
    def get_lbstatus()
        return @lbstatus
    end
 
    def incl_debug_count()
        @debug_count += 1
    end
    def get_debug_count()
        return @debug_count
    end
end
 
def request_counting(workers)
    candidate = nil
    total_factor = 0
 
    for worker in workers:
        worker.add_lbstatus(worker.get_lbfactor)
        total_factor += worker.get_lbfactor
        if not candidate or worker.get_lbstatus > candidate.get_lbstatus:
            candidate = worker
        end
    end
 
    candidate.sub_lbstatus(total_factor)
    return candidate
end
 
workers = [Worker.new('A', 70),
           Worker.new('B', 30)]
 
for i in 1..10
    worker = request_counting(workers)
 
    debug_str = ""
    for tmp in workers
        debug_str += (debug_str == "") ? "" : ", "
        debug_str += sprintf("%3d", tmp.get_lbstatus)
    end
    printf("(%s) %s\n", worker.get_name, debug_str)
    worker.incl_debug_count
end
 
for worker in workers
    printf("%s:%3d\n", worker.get_name, worker.get_debug_count)
end

結果

(A) -30,  30
(B)  40, -40
(A)  10, -10
(A) -20,  20
(A) -50,  50
(B)  20, -20
(A) -10,  10
(A) -40,  40
(B)  30, -30
(A)   0,   0
A:  7
B:  3

ちゃんと重みに基づいて分散された。

いつか、バランシング処理を書く機会があったら、使ってみようかな。