« 2010年7月 | メイン | 2010年9月 »

2010年8月26日

アジャイルな開発をチームでやってみた(2010年版)
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは murahashi です。
アジャイルな開発をチームでやってみている(2010年)のですが、いざやってみると結構ハマリどころがありました。やってみたことを共有しておこうと思います。

かたちから入ろう

acts_as_agile_armor_600_457.jpg
朝会
アジャイルな開発と言えば朝会なので、朝会から始めました。
開始時刻をメンバーで決めて、それぞれが昨日やったこと、今日やること、おしらせ、困っていること、を共有しました。
さらに、朝会前に社内wikiにメモ書き程度の項目を書いておきます。これにあらかじめ目を通すことで、一番の課題に時間を集中することができました。

acts_as_agile_anti_pattern_50_67.jpg
アンチパターン・決めた時刻を守らない
11時から朝会始めようと決めたのに、11時過ぎに汗だくで飛び込んできて「遅れてすみません」「wiki書いてません」「wiki読んでません」というのは、チームの空気を悪くするだけでなく、単純に全員の時間を無駄にしてしまいます。
時刻を守るか、守れるルールに変えましょう。ごめんなさい。

できることからやろう
組み込めそうなものからとりいれてみました。
具体的には、『達人プログラマー』で言う三本柱(テスト・自動化・早期デプロイ)、朝会、継続的な統合(CI)、テスト駆動開発(TDD)、テスト用データベースの独立、分散VCSを使う、などです。
CI, git, TDDは一気に導入しました。テスト用データベースの独立は必要になってから取り入れました。

acts_as_agile_anti_pattern_50_67.jpg
アンチパターン・あれもこれも
一個一個見ればそれはあったほうがいいんだろうなと思いますが、きりがありません。多すぎて身構えてしまうし、開発が乗ってくるまでにどれだけかかるんだよと。
かといって完全マスターしないと増やさないポリシーも意味がありません。要はバランスです。
今思えばgitの導入がかなり説明不足でした。その時はsubversionをフル活用した社内ライブラリとの接合をとるのでいっぱいいっぱいでした。

チームでやろう
今までもタスクが個人レベルまで分解されたあとは、アジャイルというかXPというか"ぼくのかんがえたTDD"でやってみてました。これが結構できるようになったなーと思ってチームでやるようになったのですが、ひとりxpとチームxpのあいだの一歩は思ったより大きかったです。
逆説的ですが、ひとりxpが完璧にできるようになるのを待つ必要はないと思います。最終的にチームでやらない意味がわからないので、できることからさっさとチームで始めるのが良いと思います。

acts_as_agile_anti_pattern_50_67.jpg
アンチパターン・チームで決めた方針は変えない
合わなかったら、そして合理的な理由があればさっさと方針を変えましょう。
はじめのルールのままテストがどんどん遅くなっていっているのでどうにかしたいです。

テスト駆動で開発しよう
テストコードを先に書く事で、テスト可能な設計になります。むしろテスト可能な設計でしか書けません。テストコードが存在することで、テストコードの範囲内で正しく動くっぽいことが確認できます。テストコードがなければ、正しく動くっぽいことの確認すらできません。
また、テストを壊してしまったら、CIサーバのhudsonが容赦なく "Build faild in Hudson" なるメールをガッシュガッシュ投げつけてくるので、想定の範囲内でデグレードを防ぐことができます。

acts_as_agile_anti_pattern_50_67.jpg
アンチパターン・テストを書かなければ失敗しない
テストを書かなければ失敗しないのですが、それでは意味がありません。また、通らなくなったテストをコメントアウトしたり、消したりしてテストを通してはいけません。
さらに「いけません」とチームに言ってるのに「他の作業するところで影響でるから」と実装だけ修正したりすると、「なんであいつだけ」となるので以後気をつけます。
ただし"たまに落ちるテスト"はどうしていいかいまだによくわかりません。

イディオムに従う
symfonyのイディオムはjobeetなので、jobeetに従っておくと共通認識をつくりやすいです。php, symfony, lime, jobeetのイディオムから外れるときは明確に意図を持って外れるようにしましょう。
「正しいunit testでは」みたいな話ばかりをしていても仕方がないので。しましたけど。

そんなこんなやってみた構成

  • php5.3, symfony1.4, mysql5.1, memcache, flashlite1.1
  • テスティングフレームワーク lime
  • BTS trac
  • VCS git
  • CI hudson
  • デプロイ capistrano
だいたいそんなかんじ

やってみてのハマりどころ

アジャイルな開発のスイートスポットは新規開発?
不慣れなチームだったので別にスイートスポットではありませんでした。
ケータイ向けアプリのflash
flashが重要な役割を占めるけど、limeでテスト駆動開発...そこは割り切って開発することにしました。
デプロイするcapistranoのレシピは早々に書いて動かしてたのですが、flashとphpが出来上がってきたところでがっちゃんこになりました。そして、がっちゃんこしたら結局わりきった接合面でなんでか動きません。TDDでmodelを厚く作ったのに...
だいたい原因は、symfony にも不慣れな人ばかりだったので、そんなに厚くないcontroller部分の値の受け渡し部分なことが多かったです。
ケータイ向けopensocialに特有の部分
テスト用擬似ブラウザsfBrowserのclickとかredirectとか(NDAかもしれないので省略されました)

acts_as_agile_stone_wall_600_569.JPG
fixtureの硬直化がDBのschemaの硬直化を呼ぶ
fixtureをyamlで書いてそれを毎回DBにロードしてるので、スキーマ変更するたびにyamlの編集が必要になりました。テスト書き換えるだけでもしんどいとおもうひとばかりなのに、yamlをえんえん編集してるのはもっとひどくてかなりダメージを受けてました。赤→緑→リファクタリングのサイクルで脳汁が出る人ばかりではありません。自然と、 DBのschemaが硬直してしまいました。
factory_girlはやくきてくれー。
全体的に
やりたいやりたい言ってる私がコーチになるどころかそんなに役に立ててないのが割と原因の一つです。でも私もわかんないよ。手探り手探りです。

あわせてよみたい

これから

問題にぶつかったなかで、今一番多い解決策は"とりあえずわりきって前に進む"であることがおおいので、もうちょっとましな解決tipsのエントリを挙げられるといいなあと思います。
以上デース

2010年8月25日

リビドーに赴くままlibmemcachedをPHPから使ってみる
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

Keita です。

浴衣姿の女性は人類の宝だと思います。

さて、phpのpeclのmemcache'd'ってご存知でしょうか。 長い歴史をもつpecl memcache(名前近過ぎだ)はその拡張の中でmemcachedプロトコルの実装をしていますが、pecl memcachedはlibmemcahcedにその実装を依存を任せています。

利用するときの違いとしては
memcache:

  • インストール時に他のライブラリに依存しない
  • 長いこと使われてるライブラリなので安定性はある
memcached
  • memcacheよりも利用できる機能が多い
  • 今後もプロトコルの拡張があったときにlibmemcachedに追従するだけなので対応が早いことが予測される
という点が挙げられます。(まだまだ一杯ありますが・・)

memcacheよりも利用できる機能が多いというのはかなり大きく、具体的に言うと、set時に比較して他のプロセスが更新されてないかどうかを確認してから更新するという処理をアトミックにできるMemcached::casは、揮発性の高い本家のmemcached(デーモンの方)はともかく、最近の永続的なストレージとして、memcachedプロトコル互換をうたうKVSを使う時には是非とも使いたい機能だと思います。

追記:
kamipoさんより、はてブのご指摘でPECL::memcacheも3.0.0(alpha)より、casをサポートしているというご指摘いただきました。
よくよく見てみると確かにCasに限らずappendやバイナリプロトコルのサポートなどいままでほしかった機能が実装されています。(どんだけやる気なんだと作者に対して強く尊敬の念をいだきました)安定性やなどかんがみると、PECL::memcacheは非常に有力な候補となると個人的には思いますのでこれは別途調査してみたいとおもいますし皆様も選択の際はぜひ頭の隅にいれていただければ幸いです。

前置きが長くなりましたが、libmemcachedとpecl memcachedの構築に本来は簡単なのになんだかとっても、手間取ったので記録を残したいと思います。

前提としては、PHP5.3.3の環境でRPMを作成する環境は整っているという前提です。 とりあえずrpmmacrosをの設定
cd ~/
echo "%_topdir `pwd`/rpmbuild" > ~/.rpmmacros
mkdir -p ~/rpmbuild/{SRPMS,RPMS/x86_64,SOURCES,SPECS,BUILD}
libmemcachedのrpmを作成
tar zxvf libmemcached-0.39.tar.gz 
cd libmemcached-0.39
./configure
make rpm
インストール
$ cd ~/rpmbuild/RPMS/x86_64/
$ rpm -ivh libmemcached-devel-0.39-1.x86_64.rpm libmemcached-0.39-1.x86_64.rpm 
これで、limemcachedがインストールされました。 そのあとpeclのRPMを作るのですが、会社のRPMだとラボブログに書くちょっと癖があったりするので、ここでは、Les RPM de Remi - PackagesからSRPMを拝借することにします。 libmemcachedも含めてここにRPMが公開されてるのでそちらをつかってさくっと入れてしまうのが楽だとは思いますが、RPM作るのに手間取ったのでその手順を書くというのが本記事の主旨なのでとりあえずこのまま進めます。
# PHPはすでに入っていて、phpizeが使える前提で・・・
$ wget http://rpms.famillecollet.com/SRPMS/php-pecl-memcached-1.0.2-1.remi.src.rpm
$ rpm -i php-pecl-memcached-1.0.2-1.remi.src.rpm
$ cd ~/rpmbuild/SPECS
$ rpmbuild -ba php-pecl-memcached.spec
$ cd ~/rpmbuild/RPMS/x86_64/
$ rpm -ivh php-pecl-memcached-1.0.2-1.x86_64.rpm
ちなみに何を手間取ったかというと、libmemcached-0.39で、pecl-memcached-1.0.1を動かそうとしていたからずーっと動かなかったりしていたのですが。 Change Logにしっかり書いてありました。 それに気がついた時いろいろ雄たけびあげました。 何はともあれ、これで安心してCasが使えます。動作としてはこんな感じのイメージです。
<?php
$memcached = new memcached();
$memcached->addServer('localhost', 11211);

$key = 'hoge';
$value1 = 'fuga';
$value2 = 'mote';

$memcached->set($key, $value1);
$cas = null;
$resultValue = $memcached->get($key, null, $cas);
assert($resultValue === $value1); //fugaが返される

$success = $memcached->cas($cas, $key, $value2);
$resultValue = $memcached->get($key, null, $cas);

assert($success === true); //更新は成功
assert($resultValue === $value2); //更新されmoteが返される


$memcached->set($key, $value1);
$resultValue = $memcached->get($key, null, $cas);

$memcached->set($key, $value1); // 取得したあとに誰かがもう一度セットしたとする

//更新しようとする
$success = $memcached->cas($cas, $key, $value2);

assert($success === false); //更新失敗
assert($resultValue === $value1); //更新されずhogeが返される
そんなわけで、どうでもいいですが、僕は、秋祭り、一緒に浴衣でいってくれる女子を募集中です。 記事ともどもご参考になれば幸いです。

2010年8月19日

線画のゴミ除去方法
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

初めまして。ウノウでデザイナーをしているyamamotoです。

最近は色々と便利な本が出ているので、当たり前の技術になっていますが
知っているとチョビット便利なフォトショップ上でのゴミの取り方をご紹介します。

画像データはフォトショップ上にて直接描いてしまったサンプルですが
実際は紙に描いたものをスキャニングしたものだと仮定して下さい。

スキャニングし線を抽出するとどうしてもゴミがついてきてしまいます。
そこで便利なゴミの取り方のご紹介です。


1:線画部分だけを抽出した状態

ラボブログ001.jpg


レイヤー部分をダブルクリックしレイヤースタイルを表示します
※フォトショップ上レイヤータブからもレイヤースタイルを選択できます。

この時、境界線を選択し、塗りつぶしのタイプをカラーにし
カラーをわかりやすい色にします。ここでは赤を選択しています。

描画モードを通常にし位置を外側にします。
※大抵はカラー以外はデフォルト設定で大丈夫だと思います。

自分の見やすいサイズを選択しOKを押します。

2:レイヤースタイルを表示した状態

ラボブログ002.jpg

上記の行程を行うと線画に赤い線がつきます
また、キャラクター以外のヌキの部分のゴミにも同様に
赤い線がつくのでわかりやすいと思います。

後は消しゴムツールでゴミの部分を消してあげると奇麗になりますが
取りきれない部分もありますのである程度は自分で確認する必要もあります。


3:境界線が選ばれている状態

ラボブログ003.jpg


ラボブログ004.jpg

この後は設定したレイヤースタイルをそのままドラッグして
ゴミ箱に捨てれば完成です。







3:線画完成!

らぼぶろ.jpg

あとはリビドーのままに塗ってください!
おつかれさまでした!





PNG画像書き出しの「あとちょっと軽くできたら...」を可能にするファイル作成TIPS
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは。ウノウでデザイナーをしているChristelです。

銀色の全身スーツを着、透明チューブの中を走る車で移動する21世紀で暮らす私たち。
夢のクラウド化が囁かれる昨今ですが、未だ携帯電話関連の画像作成には、スカイツリーよりも高い容量の壁があります。
「Flashの3階層メニューで文字も画像で100kb以内でよろしく」とか
「あれとこれとそのファイル合わせて一回で表示するのは20kb以内でね」など、
関係者の皆さんは、1バイトに泣き、1バイトに笑う日々を送っておられることでしょう。

今日はそんな皆さんに
「ビットマップデータを透過PNGで書き出すとき、クオリティを下げずにサイズをちょっとだけ減らせる画像作成のコツ」
をご紹介します。

ニッチ過ぎて申し訳ないです。


1)まずPhotoshopで画像を作成

miyakami_1.jpg
↑ 一世を風靡した感のあるWEB2.0風。画像の痛さには目を瞑ってください。


2)そのまま背景透過のPNG24で書き出します

miyakami_2.jpg
↑背景透過、マットなし、のPNG24です

15.3kbのPNG画像ができました
miyakami_3.jpg

...と、ここまではフツー。


3)それではここでFireworksを立ち上げましょう

miyakami_4.jpg
私たちの未来を明るく照らしだすFireworks先生。ゴールドカラー(きいろとも言う)がまばゆい。


4)先ほどのPSDファイルを読み込み、そのまま背景透過のPNG32で書き出します

miyakami_5.jpg
背景透過、マットなし、のPNG32です


5)ではいま作成したファイルサイズを見てみましょう

miyakami_6.jpg
14.7kbのPNGファイルができました


...なんということでしょう~!!

Photoshop書き出し15.3kb
Fireworks書き出し14.7kb

Photoshopで作成したファイルより軽い♪

二つのファイルを比べてみてもほとんど遜色ありません。

miyakami_7.jpg

ありがとう、ありがとうFireworks先生!!


...端的に申し上げると「Fireworksを使え」という事ですが、意外と皆さんご存知ないようです。
大量のPNGを使用するFlashや携帯サイト等では差が歴然です。
小さな差異を笑ってはいけません。
また、GIFの場合もFireworksを使うと若干軽くなる場合が多いです。



★☆ ↓↓おまけ↓↓ ☆★

◆◆試してみよう~...その1◆◆

ちなみにFireworksによるJpeg画像の書き出しではどうでしょうか。
同じ写真をPhotoshopとFireworksで同じ設定で書き出してみました。


miyakami_8.jpg
元の写真。みかんゼリーおいしい。


画質80、最適化チェック、マットなしのJpeg書き出しです
miyakami_9.jpg

プロパティを見てみると

左:Photoshop書き出し19.2kb
右:Fireworks書き出し10.6kb


Fireworks書き出しの方が俄然軽いですが、やはり画像が荒くなっています。
しかしながら目を覆うほどの劣化ではないので、臨機応変に使い分けて頂ければ良いでしょう。



◆◆試してみよう~...その2◆◆

では更に同じ写真をPNGで書き出したらどうでしょうか。

Photoshop 背景透過、マットなし、のPNG24
Fireworks 背景透過、マットなし、のPNG32
で書き出し。

miyakami_10.jpg

左:Photoshop書き出しのファイル
右:Fireworks書き出しのファイル


とってもきれいでどちらも遜色ありません。

さてファイルのサイズはというと... 

Photoshop書き出し75.2kb
Fireworks書き出し84.2kb

と非常に残念な結果に...。


書き出しは用途を考えて正しく行いましょう~!


では、素敵な毎日を。。。



2010年8月13日

MySQLのチューニングのためのデータの集め方
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

いつの間にか会社で古株になったyamaokaです。

webアプリケーションのバックエンドにMySQLを使っている場合、 クエリ(SQL)のチューニングをする必要がありますよね。 皆さんはチューニングの計画をどのように立てていますか。

もちろん、既に明らかに重いことが想定されているページがあれば、 その処理で使われているクエリを中心にEXPLAINなどを使って解析していけばいいと思います。

でもそうではなく、全体的にクエリの見直しやチューニングを行いたい場合は 実際に実行されているクエリを確認していくという作業が必要です。 そこで使うことができる3つの方法について書きたいと思います。

遅いクエリを記録する

MySQLにはスロークエリログといって、 実行に時間がかかったクエリを記録する機能が最初から付いています。 /etc/my.cnfに次のように設定を書けば実行時間が1秒を超えたクエリが出力されるようになります。

slow_query_log = 1
slow_query_log_file = /path/to/mysql-slow.log
long_query_time = 1
オンラインでset globalを使って変更する場合は次のようにします。
set global slow_query_log = 1;
set global slow_query_log_file = '/path/to/mysql-slow.log';
set global long_query_time = 1;

出力されたログファイルをMySQLに付属しているmysqldumpslowというツールで解析すると便利です。次のように実行すれば、平均の実行時間が長い順にソートして表示してくれます。

mysqldumpslow -s at /path/to/mysql-slow.log
クエリのパラメータは数値はN、文字列はSに置換して表示してくれるので 同じクエリをまとめてチェックすることができます。

実行回数の多いクエリを記録する

実行時間は短いけれど多くの回数実行されるクエリというのもあります。 これらは通常のスローログには出てこないのですが、 実は負荷の大部分を占めている、ということもありえます。 キャッシュなど別の方法を考えることで アプリケーションの負荷を減らすことができるかもしれません。

従来MySQLではlong_query_timeに1秒以上の値しか設定できませんでしたが、 MySQL 5.1から1秒未満の値も設定できるようになりました。 つまり、次のように/etc/my.cnfで「0」を設定すれば 全てのクエリが記録できることになります。

slow_query_log = 1
slow_query_log_file = /path/to/mysql-slow.log
long_query_time = 0

もちろん、全てのクエリの記録は負荷も大きくかかることを 理解してから設定を行うようにしてください。 また、ログの容量も大きくなるので、ディスクの空き容量にも注意が必要です。 オンラインでset globalを使って一時的に値を0に変更し、すぐに元の値(1など)に戻すのがオススメです。

set global slow_query_log = 1;
set global slow_query_log_file = '/path/to/mysql-slow.log';
set global long_query_time = 0;
# 後で set global long_query_time = 1; で元に戻す

出力されたログファイルをmysqldumpslowで次のように解析することで、 実行回数の多い順に表示することが可能です。

mysqldumpslow -s c /path/to/mysql-slow.log

mysqldumpslowは他にもいろいろ機能を持っているので、「--help」を付けて実行して 一度オプションを確認してみるといいかもしれません。

追記: MySQLでは/etc/my.cnfに次のような設定をすることで全てのクエリを記録することが可能です。 最初から全ての記録を保存したい場合はこちらの方法もいいかもしれません。

log = /path/to/mysql-query.log

インデックスを使っていないクエリを記録する

多くの場合、インデックスを使用しないクエリは遅いです。 インデックスの設計は計画的に行う必要があると思いますが、 今現在インデックスを使用していないクエリはどれなのか知りたい場合があると思います。 次のように/etc/my.cnfに記述することでインデックスを使用していないクエリを スロークエリログに記録することができるようになります。

slow_query_log = 1
slow_query_log_file = /path/to/mysql-slow.log
long_query_time = 5
log_queries_not_using_indexes = 1
オンラインでset globalを使って設定する場合は次のようにします。
set global slow_query_log = 1;
set global slow_query_log_file = '/path/to/mysql-slow.log';
set global long_query_time = 5;
set global log_queries_not_using_indexes = 1;

出力されたログファイルは今までと同じように mysqldumpslowを使って解析していくことになると思います。

終わりに

最近は、開発するときにフレームワークに付属のORマッパーを使ったりして 実際に発行されるクエリを意識しないことが多くなっていると思います。 もちろんそれはメリットだと思うのですが、実際に実行されるのはクエリ(SQL)である以上、 完全に意識しないで済むということはありません。

実際に発行されているクエリを眺めつつ、 少しでもパフォーマンスのよいwebアプリケーションを作っていけたらいいと思います。

追記: オプションの変数名がMySQL 5.1基準のものになっていなかったのでMySQL 5.1をベースに修正しました。

2010年8月12日

JavaライブラリでOAuth認証
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

みなさん、こんにちは。 7月にウノウに入社しました細川です。

私は、これまでいろいろなオープンソースの恩恵にあずかってきましたが、こちらから貢献をしたことは、ほとんどありませんでした。この記事がお役に立てれば幸いです。

ウノウ入社前にはJavaを主に使っていましたので、今回は、JavaのOAuthライブラリを使う方法について書いてみたいと思います。

OAuthとは

OAuthは、セキュアな認証手段として多く使われてきています。 twitterもweb APIの認証手段として採用しています。 OAuthを使った認証を行うことで、ユーザーはアカウントやパスワードを知られることなく、第三者サービスにAPIの使用を許可することができます。また、その認証は第三者サービスに関係なく取り消すことができます。

OAuth Community http://oauth.net/

OAuthコミュニティが各種プラットフォーム向けのライブラリを公開していますので、今回は、Javaのライブラリをビルドして使用します。

Code - OAuth http://oauth.net/code/

Repository http://oauth.googlecode.com/svn/code/

OAuth Library をビルドする

Eclipseを使ったビルドを紹介します。

新規プロジェクトを生成し、ショートカットメニューから「インポート」を選択します

import_project_from_svn.JPG

SVNからプロジェクトを選択」を選択し、次へ

check_out_from_svn.JPG
ロケーションにhttp://oauth.googlecode.com/svn/code/ を指定します。

select_check_out_folder.JPG
フォルダ「java」を選択します。

select_check_out_option.JPG
チェックアウトオプションは上画像のように選択してください。

チェックアウトできましたら、次にプロジェクトの設定を行います。
build_path_source.JPGのサムネール画像
Default output folderに「oauth/bin」を設定します。
また、Sourceタブから上画像のようにフォルダをパスに設定します。

build_path_libraries.JPG
LibrariesタブからSVNからチェックアウトしたlibフォルダ以下にあるライブラリをビルドパスに追加します。
これで、ライブラリがビルドされるはずです。

このままですと、classファイルがばらばらになっている状態ですので、Fat Jar Eclipse Plug-Inを使って、Jarファイルにアーカイブしましょう。インストールはこちらから。

Fat-Jarは、実行形式のJar ファイルを簡単に作れたり、参照する外部JarファイルライブラリもJar内に配置できたりする優れものですが、今回は、単純なアーカイブを作成します。

Fat-Jarをインストールしたら、プロジェクト上でショートカットメニューを開き、「Build Fat Jar」を選択します。

configure_fat_jar.JPG
Jarアーカイブの名前を設定して「Finish」で作成されます。

OAuthライブラリを使う
今回は、Twitterを例にとって、サーブレットから認証、APIアクセスを行うこととします。

Twitterのアカウントをもっている方なら、Twitterアプリケーション からアプリケーションを登録することができますので、実際に試したい場合には登録してください。
登録が完了すると、アプリケーションがOAuth認証に必要とする2つのキーと認証のためにアクセスする3つのURLが手に入ります。

これらの提供された値とOAuthライブラリの主に4つのクラスを用いて認証を行い、Twitter APIにアクセスしてみましょう。

4つのクラス
net.oauth.client.OAuthClient
通信を行うクラス
実際に使用されるHTTP通信の実装をラップします。

net.oauth.OAuthServiceProvider
プロバイダを定義しているクラス
プロバイダから提供されるURLをラップします。

net.oauth.OAuthConsumer
コンシューマ(第三者サービス)を定義しているクラス
コンシューマ・キー、コンシューマ・シークレット、コールバックURLなど、コンシューマに結びつく値を扱います。

net.oauth.OAuthAccessor
アクセスを定義しているクラス
アクセス・トークン、リクエスト・トークン、トークン・シークレットなど、個々の認証に関わる値を扱います。

認証URLを生成する
OAuth認証において認証はTwitter(プロバイダ)が行い、その結果がリダイレクトによりサイト(コンシューマ)に通知されます。
ですから、まず、認証先へのURLを生成し、それをユーザーにクリックしてもらうか、リダイレクトして認証先に移動させる必要があります。以下に、リダイレクトにより認証先に飛ばすサーブレットのdoGetメソッドを示します。
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	
	String destUrl = request.getParameter("dest");

	OAuthClient client = new OAuthClient( new URLConnectionClient());
	OAuthServiceProvider provider = new OAuthServiceProvider( REQUEST_TOKEN_URL, AUTHORIZE_URL, ACCESS_TOKEN_URL);
	OAuthConsumer consumer = new OAuthConsumer( CALL_BACK_URL,
			CONSUMER_KEY, COMSUMER_SECRET, provider);
	OAuthAccessor accessor = new OAuthAccessor( consumer);
		
	String redirectTo = null;
	try{
		try {
 			//get request token first from Twitter.com
 			HashMap params = new HashMap();
			params.put( "oauth_callback", 
					OAuth.addParameters(accessor.consumer.callbackURL,
							"dest", destUrl));
			//get request token first from Twitter.com
			client.getRequestToken(accessor, null, params.entrySet());
		} catch (OAuthException e) {
			throw new OperationFailedException( "It failed to authenticate Twitter account", e);
		} catch (URISyntaxException e) {
			throw new OperationFailedException( "It failed to authenticate Twitter account", e);
		}
			
		//build redirect path to twitter authentication page
		redirectTo = OAuth.addParameters(
				accessor.consumer.serviceProvider.userAuthorizationURL,//auth URL(twitter.com)
				"oauth_token", accessor.requestToken//
				);
	} catch (IOException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	}

	response.sendRedirect( redirectTo);
}
最初の部分ですが、ここでは、OAuthClientをもっとも単純なURLConnectionクラスを使うように初期化しています。
OAuthClient client = new OAuthClient( new URLConnectionClient());
Jakarta Commons HttpClientのv3やv4を使用することもできます。その場合にはそれぞれnet.oauth.client.HttpClient3、net.oauth.client.HttpClient4クラスを使って初期化します。しかし、Google App Engine for Java環境では、スレッドが使えない関係で、URLConnectionClientしか動作しません。 

次に、OAuthライブラリの各クラスをTwitter(プロバイダ)から提供されたパラメータを使って初期化しています。
	OAuthServiceProvider provider = new OAuthServiceProvider( REQUEST_TOKEN_URL, AUTHORIZE_URL, ACCESS_TOKEN_URL);
	OAuthConsumer consumer = new OAuthConsumer( CALL_BACK_URL,
			CONSUMER_KEY, COMSUMER_SECRET, provider);
	OAuthAccessor accessor = new OAuthAccessor( consumer);
アプリケーション登録で手に入れたパラメータを使って各クラスを初期化しています。

次のパートでは、パラメータを積んでTwitter(プロバイダ)と通信を行い、リクエストトークンを受け取ります。
 			//get request token first from Twitter.com
 			HashMap params = new HashMap();
			params.put( "oauth_callback", 
					OAuth.addParameters(accessor.consumer.callbackURL,
							"dest", destUrl));
			//get request token first from Twitter.com
			client.getRequestToken(accessor, null, params.entrySet());
リクエストトークンは認証をリクエストする際に必要になります。 
パラメータ"oauth_callback"は、リダイレクトによるコールバックが呼び出すURLを指定します。アプリケーション登録でコールバックURLは登録しているのですが、ここではそのURLにパラメータ"dest"を追加するために使っています。"oauth_callback"を指定しない場合、アプリケーション登録で登録したURLにリダイレクトされます。

 次のパートでは、getRequestTokenでTwitter(プロバイダ)から取得されたリクエストトークンを取り出し、認証先URLを生成しています。
		//build redirect path to twitter authentication page
		redirectTo = OAuth.addParameters(
				accessor.consumer.serviceProvider.userAuthorizationURL,//auth URL(twitter.com)
				"oauth_token", accessor.requestToken//
				);
うまくいけば、ユーザーは以下のようなTwitterのページに誘導されます。Twitterと連携するWebアプリケーションを使っている方なら、見たことがあるのではないでしょうか。
twitter_auth_confirmstion.JPG
ユーザーが「許可する」もしくは「拒否する」をクリックすると、"oauth_callback"で指定したURLにリダイレクトされます。
次にコールバック先での処理を見てみましょう。

コールバックでの処理
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	
	String destUrl = request.getParameter( "dest");
	
	String requestToken = request.getParameter(OAuth.OAUTH_TOKEN);
	if( requestToken == null){
		//rejected by USER
		response.sendRedirect( destUrl);
		return;
	}
	
	String verifire = request.getParameter(OAuth.OAUTH_VERIFIER);
	if( verifire == null){
		//rejected by USER
		response.sendRedirect( destUrl);
		return;
	}
	
	OAuthClient client = new OAuthClient( new URLConnectionClient());
	OAuthServiceProvider provider = new OAuthServiceProvider( REQUEST_TOKEN_URL, AUTHORIZE_URL, ACCESS_TOKEN_URL);
	OAuthConsumer consumer = new OAuthConsumer( CALL_BACK_URL,
			CONSUMER_KEY, COMSUMER_SECRET, provider);
	OAuthAccessor accessor = new OAuthAccessor( consumer);
	
	accessor.requestToken = requestToken;
	
	try {
		HashMap params = new HashMap();
		params.put( OAuth.OAUTH_VERIFIER, verifire);
		//get access token and secret from twitter.com
		client.getAccessToken(accessor, null, params.entrySet());
	} catch (OAuthException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	} catch (URISyntaxException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	}
	
	
	//Retrieve user's information
	OAuthMessage oMessage = null;
	try {
		oMessage = accessor.newRequestMessage("GET", "http://api.twitter.com/1/account/verify_credentials.json", null);
	} catch (OAuthException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	} catch (URISyntaxException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	}

	OAuthResponseMessage rMessage = client.access(oMessage, ParameterStyle.AUTHORIZATION_HEADER);
	int status = rMessage.getHttpResponse().getStatusCode();
	if( status == HttpResponseMessage.STATUS_OK){
		String jsonStr = rMessage.readBodyAsString();
		JSONObject jObj = JSONObject.fromObject(jsonStr);
		String userId = jObj.optString("id");
		accessor.setProperty( "id", userId);
	}else{
		throw new RuntimeException( "It failed to authenticate Twitter account STATUS CODE:"+status);
	}
	
	//Store access token and secret to Cookie
	TwitterAPIUtil.storeTokenToCookie(accessor, response);
	
	response.sendRedirect( destUrl);
}  	
まず、認証によりTwitter(プロバイダ)から付け加えられたパラメータを取得しています。
	String requestToken = request.getParameter(OAuth.OAUTH_TOKEN);
	if( requestToken == null){
		//rejected by USER
		response.sendRedirect( destUrl);
		return;
	}
	
	String verifire = request.getParameter(OAuth.OAUTH_VERIFIER);
	if( verifire == null){
		//rejected by USER
		response.sendRedirect( destUrl);
		return;
	}
これらがセットされていなかった場合、ユーザーが認証を拒否したと考えられますので、あらかじめコールバックURLに追加したパラメータに積んでおいた飛び先URLにリダイレクトさせます。

先の処理と同様にOAuthのクラスを初期化後、認証により手に入ったリクエストトークン、ベリファイアを使ってTwitter(プロバイダ)からアクセストークンとトークンシークレットを取得します。
	accessor.requestToken = requestToken;
	
	try {
		HashMap params = new HashMap();
		params.put( OAuth.OAUTH_VERIFIER, verifire);
		//get access token and secret from twitter.com
		client.getAccessToken(accessor, null, params.entrySet());
	} catch (OAuthException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	} catch (URISyntaxException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	}
アクセストークンとトークンシークレットはAPIをコールするために必要になります。 うまく取得できた場合、すでにOAuthAccessorオブジェクトにアクセストークンとトークンシークレットは格納されていますので、Twitter APIを呼び出すことができます。
	//Retrieve user's information
	OAuthMessage oMessage = null;
	try {
		oMessage = accessor.newRequestMessage("GET", "http://api.twitter.com/1/account/verify_credentials.json", null);
	} catch (OAuthException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	} catch (URISyntaxException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	}

	OAuthResponseMessage rMessage = client.access(oMessage, ParameterStyle.AUTHORIZATION_HEADER);
	int status = rMessage.getHttpResponse().getStatusCode();
	if( status == HttpResponseMessage.STATUS_OK){
		String jsonStr = rMessage.readBodyAsString();
		JSONObject jObj = JSONObject.fromObject(jsonStr);
		String userId = jObj.optString("id");
		accessor.setProperty( "id", userId);
	}else{
		throw new RuntimeException( "It failed to authenticate Twitter account STATUS CODE:"+status);
	}
ここでは、認証済みユーザー自身のユーザー情報を返す account/verify_credentials APIを呼び出しています。レスポンスのJSONを扱うのには、Json-lib ライブラリを使用しています。

最後に認証情報をクッキーに保存しています。
	//Store access token and secret to Cookie
	TwitterAPIUtil.storeTokenToCookie(accessor, response);
このメソッドの中身は以下のようになっています。
public static void storeTokenToCookie( OAuthAccessor accessor, HttpServletResponse response, int maxAge){
	Cookie cookie = new Cookie( accessor.consumer.consumerKey+"_requesttoken", accessor.requestToken);
	cookie.setPath( "/" );
	cookie.setMaxAge( maxAge);
	response.addCookie( cookie);
	
	cookie = new Cookie( accessor.consumer.consumerKey+"_accesstoken", accessor.accessToken);
	cookie.setPath( "/" );
	cookie.setMaxAge( maxAge);
	response.addCookie( cookie);

	cookie = new Cookie( accessor.consumer.consumerKey+"_secret", accessor.tokenSecret);
	cookie.setPath( "/" );
	cookie.setMaxAge( maxAge);
	response.addCookie( cookie);
	
	String userId = (String)accessor.getProperty("id");
	if( userId == null){
		userId = "";
	}
	cookie = new Cookie( accessor.consumer.consumerKey+"_id", userId);
	cookie.setPath( "/" );
	cookie.setMaxAge( maxAge);
	response.addCookie( cookie);
}
認証情報は、もちろんデータベースなどのストレージに保存することも可能です。しかし、これらの認証情報は、Twitter(プロバイダ)のサイトでユーザーが一方的に無効にできることには留意しておく必要があるでしょう。

別の場所で認証情報を使ってAPIを呼び出すには、以下のようにします。
	.........
	OAuthClient client = new OAuthClient( new URLConnectionClient());
	OAuthServiceProvider provider = new OAuthServiceProvider( REQUEST_TOKEN_URL, AUTHORIZE_URL, ACCESS_TOKEN_URL);
	OAuthConsumer consumer = new OAuthConsumer( CALL_BACK_URL,
			CONSUMER_KEY, COMSUMER_SECRET, provider);
	OAuthAccessor accessor = new OAuthAccessor( consumer);
	
	//try to retrieve token
	Cookie[] cookies = request.getCookies();
	for( int i = 0; i < cookies.length; ++i){
		Cookie cookie = cookies[i];
		if( cookie.getName().equals( accessor.consumer.consumerKey+"_accesstoken")){
			accessor.accessToken = cookie.getValue();
		}else if( cookie.getName().equals( accessor.consumer.consumerKey+"_requesttoken")){
			accessor.requestToken = cookie.getValue();
		}else if( cookie.getName().equals( accessor.consumer.consumerKey+"_secret")){
			accessor.tokenSecret = cookie.getValue();
		}else if( cookie.getName().equals( accessor.consumer.consumerKey+"_id")){
			accessor.setProperty( "id", cookie.getValue());
		}
	}
	.........
非常に駆け足になってしまいましたが、どのような印象を受けましたか?ライブラリを使用することで、かなりOAuthへの敷居が下がったと感じた方もおられるのではないでしょうか?

この記事がちょっとでもだれかのお役に立てば幸いです。


今からはじめるCassandra入門
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

emori01.gif

こんにちわ、7月に入社したばかりの@emorinsです。

題名の通りですが分散データベース『Apache Cassandra』を紹介したいと思います。

少し前はHadoop(とHBase)と比較されることの多かったCassandraですが、最近はHadoopの人気に押されつつあるようにも感じます。
しかし、CassandraとHadoopは特徴が異なり、よく言われるのがCassandraはリアルタイム処理に向き、一貫性のかわりに可用性を重視し、またHadoopとは違って単一障害点もありません。

今日はそんなHadoopとは違った魅力のある分散データベース『Apache Cassandra』をはじめてみましょう。

目次

  • Cassandraとは
  • アーキテクチャ
    • Cassandraの特徴
    • コンシステンシレベル
    • データモデル
    • MemtableとSSTable
  • セットアップ
  • storage-conf.xmlの設定
    • Keyspace
    • ColumnFamily
    • MemtableThroughputInMB
    • MemtableFlushAfterMinutes
  • Cassandra-CLIの使い方
  • ThriftAPIによるクライアントからの操作
    • Thriftとは
    • Thriftのインストール
  • PHPによるクライアントアプリケーションを作る
    • データを取得する簡単なプログラム
    • ThriftAPI
  • 最後に
    • 本エントリで取り上げなかったこと
    • 参考

Cassandraとは

emori02.gif

そもそもCassandraとは何でしょうか。

長い間データベースと言えばRDBMS(MySQL、PostgreSQL、Oracle Databaseなど)と、それを操作するSQL言語を使用するのが主流でした。しかし、大規模データ処理を必要としたGoogleやAmazonは、リレーショナルデータベースより(ペタバイトクラスの膨大なデータ処理に対して)高速で可用性のある、スケールアウトしやすいデータベースを必要としました。そこで生まれたのがGoogleのBigtableと、AmazonのDynamoです。

Cassandraは、そんなBigtableやDynamoを参考にFacebookが開発したNoSQLデータベースです。現在はApacheソフトウェア財団に寄贈され、オープンソースとして開発が進められています。

アーキテクチャ

Cassandraの特徴

Cassandraは、CAP定理のうちAP(可用性:Availabilityと分割耐性:Partition Tolerance)を重視しています。また一貫性:Consistencyについては、どのレベルの整合性にするか調整可能です(後述)。
Cassandraの主な特徴として などが言えます。
特に可用性の高さとSPOFが無いのが特筆すべき点です。

コンシステンシレベル

Cassandraは可用性の代わりに一貫性を犠牲にしていますが、遅延とのトレードオフで一貫性のレベル設定ができます。
  • W=書き込みを保証するレプリケート数
  • R=読み込むレプリケート数
  • N=レプリケートしているノード数(storage-conf.xml内の<ReplicationFactor>)
  • Q=QUORUM (Q = N / 2 + 1)
R + W > N
の条件を満たすとき、一貫性があると言えます。
設定できるレベルは以下の4つです。
  • Zero:保証なし
  • One:W=1,R=1
    • [書き込み]1つのノードの書き込みを保証
    • [読み込み]最初に読み込んだノードから返す
  • Quorum:w=Q,R=Q
    • [書き込み](N+1)/2数のノードに書き込まれたことを保証
    • [読み込み](N+1)/2数のノードから読み込み、最新を返す
  • All:w=N,R=N
    • [書き込み]N個のノードに書き込まれたことを保証する
    • [読み込み]N個ののノードから読み込み、最新を返す

データモデル

基本はKey/Value型ですが、階層的な構造を持つ4次元or5次元のデータモデルになっています。単純なKVSよりも高機能かつ表現力豊かなデータをモデリングできます。
それぞれのデータ構造をRDBMSと照らし合わせながら見ていきましょう。
emori03.gif
キースペース
RDBMSでいうデータベースに当たるものです。Cassandraデータモデルの一次元目で、カラムファミリのコンテナになります。一般に1アプリケーションに1キースペースとされます。
カラムファミリ
RDBMSでいうテーブルに当たるものです。カラムのコンテナです。カラムファミリは後述するstorage-conf.xmlで設定して作成します。複数の行(Row)から構成されます。
行(Row)
キーと複数のカラムを持つ。RDBMSでいえば、行はレコード、キーは主キーのイメージです。
カラム
Cassandraでの最小のデータ構造。名前(name)、値(value)、タイムスタンプ(timestamp)を持ちます。
スーパーカラム
値(value)にソート済みの複数のカラムのリストを持つ。スーパーカラムは必ずしも必要ではありません。
分かりにくいと思うので、以下のCassandra-CLIでのデータ挿入の命令文を、少し無理にSQL文に置き換えてみましょう(Cassandra-CLIの使い方については後述します)。
  • Cassandra-CLIでのデータ挿入
    set Keyspace1.Standard2['jsmith']['job']='developer'
  • SQLによるINSERT文(※syntax error)
    INSERT INTO Keyspace1.Standard2 (job) VALUES('developer') WHERE id='jsmith';
この場合、Keyspace1がキースペース、Standard2がカラムファミリ、['jsmith']が行(キーは'jsmith')、そして['job']がカラムで'job'が名前(name)です。

MemtableとSSTable

ハードウェア上のデータの流れはどうなっているでしょうか。
まずCassandraに書き込み操作がされるとCommitLogに書き込まれます。その後、カラムファミリ毎にMemtableというメモリ空間に対して書き込まれます。Memtableが一杯になると、ディスク上にSSTableという形式で保存(フラッシュ)されます。

MemtableではSSTableにフラッシュする際に使用するキーを設定してソートしておくので、MySQLでのランダムアクセスとは違い、HDDにシーケンシャルに書き込みむので、高速です。

ここまで話を聞くと、MemtableからSSTableにフラッシュする回数を極力減らすため、Memtableの容量を設定したくなるかと思いますが、もちろん可能です。後述するstorage-conf.xmlという設定ファイルで、Memtableに使うメモリ容量や保存時間などが指定できます。

セットアップ

今回使用した環境は以下です。
  • FreeBSD 8.1
  • JDK 1.6
  • Cassandra 0.6.3
  • PHP 5.3.2
まずはCassandraを動かすために必要なJava環境を作るため、JDK 1.6をインストール。
# wget http://www.FreeBSDFoundation.org/cgi-bin/download?download=diablo-caffe-freebsd7-i386-1.6.0_07-b02.tar.bz2
# make install clean
Cassandraのインストール。
# cd /usr/ports/databases/cassandra
# make install clean
続いて初期設定。取りあえずサンプルで用意されているlog4j.properties.sampleとstorage-conf.xml.sampleをリネームして作成。設定ファイルの説明は後述します。
# cd /usr/local/share/cassandra
# cp conf/log4j.properties.sample conf/log4j.properties
# cp conf/storage-conf.xml.sample conf/storage-conf.xml
準備完了なので、起動します。これでシングルノードではありますがCassandraクラスタのできあがりです。
# /usr/local/share/cassandra/bin/cassandra
起動オプション
  • -f:フォアグラウンドで起動
  • -h:使用できるオプションを表示
  • -p :PIDをに出力。

storage-conf.xmlの設定

Cassandraの最も重要な設定ファイルがstorage-conf.xmlです。このファイル内で、クラスタの各設定や、カラムファミリの作成、Memtableの容量など設定できます。

全ては紹介できませんが、いくつか重要な設定箇所をピックアップします。詳しい情報や設定例はstorage-conf.xml.sampleを見てください。

Keyspace

キースペースを作成します。Name属性を指定して、カラムファミリはこのKeyspaceタグ内に記述します。
今回の設定例:

<Keyspace Name="Keyspace1">
...
</Keyspace>

ColumnFamily

カラムファミリの設定です。下記のような属性を指定できます。
  • Name:カラムファミリ名
  • ColumnType:カラムタイプ。デフォルトは"Standard"。スーパーカラムを持ちたい場合は"Super"にする。
  • CompareWith:カラムのソート方法
  • KeysCached:キーをキャッシュする個数(%をつけると割合)
今回の設定例:
<ColumnFamily Name="Standard2" CompareWith="UTF8Type" KeysCached="100%"/>

MemtableThroughputInMB

Memtableのデータ容量

MemtableFlushAfterMinutes

フラッシュまでの最大時間。この時間を超えると強制的にフラッシュする。

その他詳しい設定例などは以下をご参考ください。
StorageConfiguration - Cassandra Wiki

Cassandra-CLIの使い方

データモデルの項でチラっと出てきましたが、Cassandra-CLIというクライアントツールを使って、Cassandraのデータを操作してみましょう。

Cassandraを起動した状態で、Cassandra-CLIを立ち上げます。

/usr/local/share/cassandra/bin/cassandra-cli --host localhost --port 9160
すると
Connected to: "Test Cluster" on localhost/9160
Welcome to cassandra CLI.

Type 'help' or '?' for help. Type 'quit' or 'exit' to quit.
cassandra>


と、Cassandra-CLIのコンソールに入ります。

では、手始めにデータを書き込んでみましょう。今回はKeyspace1のStandard2カラムファミリを利用します。Keyspace1と Standard2自体はstorage-conf.xml.sampleに初めから定義されているキースペース・カラムファミリですので、特別なことをしなくてもそのまま利用できるはずです。

cassandra> set Keyspace1.Standard2['unoh']['age']='25'

set文で、Standard2カラムファミリに'unoh'キーを追加した上で、'age'という名前のカラムに25という値を挿入しました。

実際にちゃんとデータの追加ができているかget文で確認してみます。

cassandra> get Keyspace1.Standard2['unoh']            
=> (column=age, value=25, timestamp=1281559772819000)
Returned 1 results.
Standard2カラムファミリの'unoh'キーにあるカラムが取得できました。カラム名、カラム値、タイムスタンプがきちんと挿入されているのが確認できます。
さらに追加してみましょう。
cassandra> set Keyspace1.Standard2['unoh']['phone']='03-5766-3911'
Value inserted.
cassandra> set Keyspace1.Standard2['unoh']['web']='www.unoh.net'  
Value inserted.
で、再度取り出してみる。
cassandra> get Keyspace1.Standard2['unoh']                        
=> (column=web, value=www.unoh.net, timestamp=1281560104883000)
=> (column=phone, value=03-5766-3911, timestamp=1281560057868000)
=> (column=age, value=25, timestamp=1281559772819000)
Returned 3 results.
完璧ですね。
cassandra> help 
とすれば、実行可能なコマンド一覧がでますので、各自試してみてください。Cassandra-CLIはこれくらいにして次に進みます。


ThriftAPIによるクライアントからの操作


Cassandraをクライアントプログラムから操作するときには、RPCフレームワークである『Thrift』が利用できます。

Thriftとは

Thriftは、同じくFacebookが開発した言語間サービス開発のためのRPCフレームワークです。C++、C#、Java、OCaml、Perl、Python、PHP、Rubyなどで書かれたプログラム間を通信可能にしてくれます。

CassandraはJavaで書かれているわけですが、Thriftを使うことで、異なる言語からCassandraを操作することができるようになります。CassandraにはThiftインターフェースがあらかじめ用意されているので、Thrift(とCassandra)がサポートしている言語ならどの言語のクライアントコードからでも操作可能です。今回はタイトル通りPHPから利用してみましょう。

※CassandraのAPI/RPC/Thriftポートは9160番です。

Thriftのインストール

Thriftの大まかな使い方の流れは、
  1. Thriftインターフェース(.thrift)の作成
  2. そのインターフェースに沿った各言語のスケルトンを生成
  3. スケルトンを元にサーバー・クライアントのコーディング
という流れになります。Thriftインターフェース(.thrift)は、Cassandraのインストール先ディレクトリ下のinterfaceディレクトリに"cassandra.thrift"ファイルを利用するので、1の作業は必要ありません。また、2,3のサーバー(Cassandra)側のコーディングも必要ありません。
では、Thriftをインストールします。
# cd /usr/ports/devel/thrift/
# make install clean
インストールが完了したら、まずはThriftプロジェクト用のディレクトリを適当な場所に作成しましょう。
# mkdir ~/thrift
次に、Thriftプロトコルが実装されたPHPコードをThriftパッケージ内からコピーしてきます。このコードはPHPからThriftを扱うために必要な全プロジェクトで共通して使うライブラリです。
# wget http://ftp.riken.jp/net/apache/incubator/thrift/0.2.0-incubating/thrift-0.2.0-incubating.tar.gz
# tar zxvf thrift-0.2.0-incubating.tar.gz
# cp -R thrift-0.2.0/lib/php/src ~/thrift
次に"cassandra.thrift"を使ってCassandra用Thriftクライントを生成します。サーバーごとのThriftクライントはsrcディレクトリ内のpackagesディレクトリに分けて入れておきましょう。
# cd thrift
# mkdir src/packages
# cp /usr/local/share/cassandra/interface/cassandra.thrift ~/thrift/cassandra.thrift
# thrift --gen php cassandra.thrift
# mv -R gen-php/cassandra src/packages
これでCassandraをThriftから扱うための準備が整いました。ディレクトリ構成を以下にまとめます。
/thrift
  ├ /src・・・Thriftプロトコルの実装ファイル
   ├ Thrift.php
      ├ autoload.php
      ├ /ext
      ├ /packages・・・各サーバごとのThriftクライアント(gen-phpとして生成されたコード)
           └ /cassandra・・・cassandra用Thriftクライアントが入ったディレクトリ
                ├ Cassandra.php
                ├ cassandra_constants.php
                └ cassandra_types.php
      ├ /protocol
      └ /transport
  └ /unoh・・・プロジェクトディレクトリ
      └sample.php・・・クライアントアプリケーション(ここで具体的にCassandraを操作するコードを書きます)

PHPによるクライアントアプリケーションを作る

では、いよいよPHPでCassandraを使ったアプリケーションを書いていきましょう。

データを取得する簡単なプログラム

・~/thrift/unoh/sample.php
 <?php
 $GLOBALS['THRIFT_ROOT'] = '../src';
 require_once $GLOBALS['THRIFT_ROOT'].'/packages/cassandra/Cassandra.php';
 require_once $GLOBALS['THRIFT_ROOT'].'/packages/cassandra/cassandra_types.php';
 
 require_once $GLOBALS['THRIFT_ROOT'].'/transport/TSocket.php';
 require_once $GLOBALS['THRIFT_ROOT'].'/protocol/TBinaryProtocol.php';
 require_once $GLOBALS['THRIFT_ROOT'].'/transport/TFramedTransport.php';
 require_once $GLOBALS['THRIFT_ROOT'].'/transport/TBufferedTransport.php';
 
 try {
   // Thriftの接続用インスタンスの作成                 
   $socket = new TSocket('localhost', 9160);
   $transport = new TBufferedTransport($socket, 1024, 1024);
   $protocol = new TBinaryProtocolAccelerated($transport);
 
   //Cassandraクライアントの作成                        
   $client = new CassandraClient($protocol);
 
   //接続開始                                            
   $transport->open();
 
   // ColumnFamilyの指定                                
   $columnParent = new cassandra_ColumnParent();
   $columnParent->column_family = "Standard2";
 
   //スライスの指定。切り出すColumnの範囲を指定できる。
   $predicate = new cassandra_SlicePredicate();
   $sliceRange = new cassandra_SliceRange();
   //例えば、'age'から'phone'までのColumnを切り出す(Columnは昇順でソートされる)
   $sliceRange->start = "age";
   $sliceRange->finish = "phone";
   $predicate->slice_range = $sliceRange;
 
   //クエリを投げる
   $result = $client->get_slice('Keyspace1', 'unoh', $columnParent,$predicate, cassandra_ConsistencyLevel::ONE);
   //中身を出力してみる
   var_dump($result);
 
   //接続終了
   $transport->close();
 }
 catch (TException $tx) {
   print 'TException: '.$tx->why. ' Error: '.$tx->getMessage() . "\n";
 }
出力結果
# php ~/thrift/unoh/sample.php
array(2) {
  [0]=>
  object(cassandra_ColumnOrSuperColumn)#9 (2) {
    ["column"]=>
    object(cassandra_Column)#10 (3) {
      ["name"]=>
      string(3) "age"
      ["value"]=>
      string(2) "25"
      ["timestamp"]=>
      float(1.281559772819E+15)
    }
    ["super_column"]=>
    NULL
  }
  [1]=>
  object(cassandra_ColumnOrSuperColumn)#11 (2) {
    ["column"]=>
   object(cassandra_Column)#12 (3) {
      ["name"]=>
      string(5) "phone"
      ["value"]=>
      string(12) "03-5766-3911"
      ["timestamp"]=>
      float(1.281560057868E+15)
    }
    ["super_column"]=>
    NULL
  }
}
Cassandraからデータを取り出すだけの簡単なコードですが、Cassandraとうまく連携できているのが確認できました。

ThriftAPI

先程のコード中でnew cassandra_SliceRange()を使ってスライスを作成したり、$client->get_slice()でクエリを投げて結果を受け取ったりしていました。これらはThriftAPIで利用できます。
利用可能なThriftAPI一覧は以下のページを参照してください。
API - Cassandra Wiki

最後に

本エントリで取り上げなかったこと

かけ足になりましたが、Cassandraの概要とシングルノードによる基本的な操作をご紹介しました。マルチノードクラスタ、ノードの追加・削除方法、チューニング、モニタリング、実用性など、また次の機会に突っ込めたらと思います。

参考

2010年8月11日

レディの嗜みについて
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加


はじめまして
6月にウノウ株式会社改め Zynga Japan※謎 に入社致しました mayutanです。

社内全体 MTG で flickr のプレミアムユーザであることを dis られたので
弊社のサービスでありますフォト蔵の API を使って皆さん(誰?)に許していただこうと思います。

Android 端末を見たことも触ったこともなかったので、後学のためにアプリを作ってみることにしました。
タグ的な何かを選択したらそのイメージに合った写真を一覧表示するアプリを作ってみましょう。

Aandroid アプリの開発環境はこちらを参考に整えました http://developer.android.com/sdk/installing.html

ここから直感的にアプリをデザインしてみましょう!

res ディレクトリの layout/main.xml を開いてエディター下部にあります、「レイアウト」というタブをクリックしますと・・・!

なんということでしょう!!!!!!!!!!!!!!!

なんかデザインできそうなふいんき(なぜかへんかんできない)です。

黒い画面の上で、右クリック→追加を選択すると、ダイアログが出てくるので、
無難に ListView を追加します。

「VB みたいにぐいぐいーとかそれそれーとかできるんだろうな♪」
とか思ったらできねー・・・

今回は時間がないので、仕方ないのでコードで書きます。
コードはこちらお暇な方はどうぞ→Photozoroid.java

アンドロイドはまだまだ直感的には開発できないですね。がんばれアンドロイド

さて、layout/main.xml を見てみますと、ListView を追加したことにより、新しい記述も追加されていますね。
ここにある android:id="@+id/ListView01" というのが、このリストの ID です。

実際には ListView にどうやって表示させたいオブジェクトを渡すのかといいますと、setAdapter メソッドを使います。
今回は表示するリストは固定ですので、中から指定しちゃいます///

onCreate は必ず呼ばれるメソッドで必ず実装しなければいけません。
setContentView で、引数に渡した画面が呼ばれます。
ここまでくればピン!ときますね。 main.xml に記述されているやつらです。

リストを表示させるならこれ!
ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1);
adapter.add("girl");
adapter.add("food");
・・・
ListView listView = (ListView) findViewById(R.id.ListView01);
listView.setAdapter(adapter);

そして実行すると・・・!なんということでしょう・・・!

6.jpg

じゃーん。リストが出ましたね。

次はこれに onclick イベントとか実装しちゃいましょう!
それでは onClick イベントはどう実装すればいいのでしょうか。
API を見ると、どうやら AdapterView クラスが持っているこの関数を実装してやればいいらしい。

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
・・・

ここで引数にしているのは AdapterView クラスが持っているインナークラスのメソッドで、
外から呼ぶにはこうしてやります。
中見てないので適当に予想しますが、おそらく View クラスはたくさんのインターフェースを持っているのでしょう。たぶん。

次に、OnItemClickListener#onItemClick で実際にクリックされた場合の処理を実装します。

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View view, int position, long id) {
ListView listView = (ListView) parent;
String searchKeyWord = (String) listView.getItemAtPosition(position);
}
});

そして、これをキーワードにして API より取得したデータから画像を別画面に表示してみましょう。
遷移画面用の XML ファイルを res/layout に作成します。
そして、AndroidManifest.xml にこの View を登録。

次画面に遷移するために Intent クラスを使うようですね。そして、渡したいオブジェクトを putExtra してやります。
渡すデータは Serializable である必要があります。

次画面でデータを受け取り、API でキーワードから検索します。
コードはこちら→PhotozoViewer.java
このとき urlconn.connect で、"java.net.SocketException: Permission denied" が発生すると思います。
Manifest.xml に
uses-permission android:name="android.permission.INTERNET"
を追加して、自分の Android アプリからの通信を許可してあげましょう。。


さていよいよ画像の表示です。こちらを参考に作りました。
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/Grid2.html
ほとんどサンプルのままですが、ここでは URL から読み込んだ画像 stream を Bitmap に変換して表示してみました。


last.jpg

じゃーん!

getView はいつ呼ばれるの?と思うかもしれませんが、これは View が表示されるたびに呼ばれます。

こんな感じで簡単に Web サービスと連携させたクライアントアプリケーションが作れてしまう Android には
とても可能性を感じますね!これからも色々いじってみます。

!!!まったく関係ありませんが the clovers もよろしくお願いいたします!!!

DBの選択について
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

皆様、はじめまして。

7月入社のトクヤマと申します。
以後、お見知りおき頂ければ幸いです。

私は以前、別の会社の仕事でPostgreSQL,MySQL,Oracle,SQLSeverを同時に運用していたことがありました。
また現在はMySQLとPostgreSQLを運用しています。

今回はそれぞれのDBを、どのようなWEBサイトやオープン系の開発を行う時に選ぶべきなのかという事をお話したいと思います。
なおOracle、SQLServerは現在はWEBサイトやオープン系の開発では使われなくなっていますのでPostgreSQLとMySQLに絞りたいと思います。

「定説」として以前はPostreSQLは多機能で遅い、MySQLは機能は少ないが検索が速いと言われていました。
しかし、それぞれバージョンアップを繰り返すうちに現在は、どちらもお互いの特徴をカバーしてきています。

それではどの面をみて選択するのかということですが、それぞれのデメリットを検討して決めてるべきではないかと思います。
私が個人的にベンチマークや運用感を見て感じたデメリットや適用例は下記のようなものがあると考えています。

デメリット:
●PostgreSQL
・チューニング次第でかなり性能が変わる。また細かいチューニングを行わないと遅い。
・レプリケーション機能はサードパーティのアプリケーションで実現できるが設定やデータ不整合対策がMySQLより手間がかかる。(*1)
・単純検索ではMySQLの方がまだ速い。(チューニングを行うとMySQLよりも早くなる場合もあります)
*1...PostgreSQL9.0でようやくレプリケーション機能が本体に実装されますがMySQLと比較すると少し不便さを感じます。

●MySQL
・単純検索は速いがJOINやORDER BY、GROUP BY、または一部サブクエリーを使用した複雑な検索では遅くなる。
・データ量が多くなると性能の劣化が比較的大きい。
・PostgreSQLと比較して同時接続負荷に弱い。


適用例:
●PostgreSQL
・SNSなどのコミュニケーションサイト
・更新や検索が複雑に絡みあるゲーム系ソーシャルアプリ
・その他:データ量が多くなる、同時アクセス数が多くなる。また各テーブルが複雑に連携しあうようなスキーマ設計を行う場合。

●MySQL
・占いや着うたサイトなどの会員やコンテンツデータ、履歴管理程度を行うサイト
・検索や課金系のサイト
・その他:サイトの開発期間が短いなどの場合

設定の難しさはありますが設計や運用のし易さで言えばPostgreSQLに分があると思います。ネックはレプリケーション機能の弱さです。
しかしMySQLのレプリケーション機能が絶対に不整合を起こさないかと言えばそんなことはありません。
設計次第でどちらも不整合を減らし無くすこともできます。

またMySQLは(あくまでも比較として)同時接続負荷の弱さが一つとしてありますがこれはレプリケーションの取り扱いの簡易さから簡単なDBの追加で対応が可能です。
ネックといえば今後の先行きが多少不明な点でしょうか。MySQLの権利がOracle社に移ったことはご存じかとは思いますがOracleとの兼ね合いでMySQLが今後の開発がどうなるのか、まだ方針が明確にはなっていません。
(Oracle社はMySQLへの投資を続けるとは言っていますが...)

以上、勝手なことを申し上げてきましたが、これらはあくまでも目安となります。
現在はmemcacheなどメモリキャッシュの使用も前提としてWEBサイトは設計されますし、
サーバーインフラやDBスキーマの設計次第の部分もありますから、
一概に特定のWEBサイト開発では特定のDBでしか開発・運用できないと言うことはありません。

結論として、あまりまとめきらずに恐縮ですが、
それぞれのDBに特徴や弱点があるとはいえ「派閥」というべきか、どちらか一方のDBだけを押す声が多いなか
一つのDBだけで考えるのではなく複数ある中から柔軟に使用するDBを選択していきたいものです。

※もちろん、同じ会社のなかでシステム毎にDB違うのも考え物ですが...以前、私はそれで苦労しました。。。

仮想マシンとLVM2で簡単サーバ構築
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

はじめまして、7月に入社いたしましたsatoshiと申します。

会社でも自宅でもPCをMacにして、Linuxと同じ操作感で使用できて快適な今日この頃ですが、皆様いかがお過ごしでしょうか?

今回、KVM(Kernel-based Virtual Machine)とLVM(Logical Volume Manager)を使用して、サーバ構築すると便利なケースを紹介したいと思います。

新しくサーバを構築する場合、OSインストールイメージをDVDに焼いて、DVDをドライブに挿入して、手順の通り選択して...となると思います。 (中には、Cobblerを使用しているという先進的な方もいらっしゃるかもしれませんが)

仮想マシンでは新たにサーバ環境を構築する場合、既存の仮想ディスクをコピーして使い回せるのでインストールの手間を簡略化できます。 さらに、一歩進んでLVMで作成したスナップショットLVを使用すると消費するディスク領域を大幅に節約できます。

今すぐにデモ環境を用意したいとか、Webサーバは共通のものを使いたいが、アプリケーションがPHPとPythonって環境が異なるとか OSをアップグレードしたらどうなるかチェックしたいとか...等々いろいろ利用ケースを考えてみました。

前提環境

作成する仮想マシンは、ベース環境となる仮想マシンを必要としますのであらかじめ作成しておく必要があります。 さらに、スナップショットLVを利用した仮想マシン使用時は、元の仮想マシン(ベース環境)を停止しておきます。 また、インストール時のパラメータを変更すれば、XenやほかのLinuxディストリビューションでも流用可能だと思います。

物理マシン(ホスト環境)

物理マシン(サーバ機)にインストールした環境をホスト環境と呼ぶことにします。

KVMを仮想マシンに使用する場合は、ほぼ強制的にCPU仮想化支援機能が有効化されていることが必要です。

CPU仮想化支援機能
IntelVT-x OR AMD-V
OS
CentOS5.5
仮想マシンソフトウェア
KVM
仮想マシン管理ライブラリ
Libvirt
ボリューム管理
LVM2

仮想マシン(ベース環境,クローン環境)

複製元となる環境をベース環境と呼ぶことにします。

複製元からデータをコピーして作成した環境をクローン環境と呼ぶことにします。

OS
CentOS5.5
仮想ディスク
LVM2のLV(Logical Volume)

仮想マシン(オンデマンド環境)

ベース環境のLVから作成したスナップショットLVを利用した環境をオンデマンド環境を呼ぶことにします。

OS
CentOS5.5
仮想ディスク
LVM2のスナップショットLV

手順

事前準備

まずは、ベース環境の仮想マシンを作成します。 diskオプションに指定しているpoolは、VG(Volume Group)です。このように指定するとVGからLVが自動的に作成されます。 (LV名は、nameオプションで指定した名称+.img)

ベース環境作成:

# VGをlibvirtで管理
$ sudo virsh pool-create-as ServiceVG00 --type=lvm2 --target=/dev/ServiceVG00
# ベース環境の仮想マシンを作成
$ sudo virt-install \
--location http://ftp.riken.jp/Linux/centos/5/os/x86_64/ \
--accelerate \
--name basevm \
--ram 1024 \
--vcpus 2 \
--noacpi \
--os-variant rhel5.4 \
--network bridge:br0 \
--extra-args console=ttyS0,115200n8 \
--disk pool=ServiceVG00,bus=virtio,size=48,cache=writethrough \
--noreboot \
--keymap=ja

#インストーラが起動するので、OSのインストールを行います。
インストールを開始しています...
(...)
Guest installation complete... you can restart your domain
by running 'virsh start basevm'

インストールが完了したら、ホストOSにて仮想マシンの時刻設定を行います。

仮想マシンの時刻設定:

# 仮想マシンの時刻設定変更
$ sudo virsh edit basevm
<domain type='kvm'>
  <name>basevm</name>
  <uuid>bd55f202-cfd4-3d8b-ef23-d9e4d37c4711</uuid>

(...)
  </features>
  <clock offset='localtime'/>     # <= 'utc'を'localtime'に変更
  <on_poweroff>destroy</on_poweroff>
(...)
</domain>

後は、いつも通りOSの初期設定を行います。 (時刻設定、ネットワーク設定、パッケージインストール・更新等)

ベース環境初期設定:

# 仮想マシンの起動
$ sudo virsh start basevm
# ゲストOSにログインして初期設定
$ sudo virsh console basevm
connect to domain basevm
Escape character is ^]

(...)
CentOS release 5.5 (Final)
Kernel 2.6.18-194.8.1.el5 on an x86_64

basevm login:~root
Password:
Last login: Sun Aug  1 19:34:34 2010 from 192.168.100.8
[root@basevm ~]#

ケース1: 開発環境を複製する

個人ごとの開発環境を複製して、個別に環境をカスタマイズしたいという場合、ベース環境に必要なパッケージをインストールし、そのベース環境のLVを元にスナップショットLVを作成し、オンデマンド仮想マシンを作成すれば簡単に作成できます。 また、スナップショットLVに割り当てるディスク容量はデータが変更される分のみ見積もればよいので、ディスク容量を大幅に節約できます。

手順

  1. ベース環境のゲストOSで必要なパッケージをインストール

  2. 仮想マシン(ベース環境)をシャットダウン

  3. 必要な分だけ以下の操作を繰り返す

  4. ベース環境用LVのスナップショットLVを作成

    スナップショットLVの作成:

    # ベース環境LVのパスを確認
    $ sudo virsh dumpxml basevm | grep -A5 '<disk'
        <disk type='block' device='disk'>
    
          <driver name='qemu' cache='writethrough'/>
          <source dev='/dev/ServiceVG00/basevm.img'/> #<= LVのパス
          <target dev='vda' bus='virtio'/>
        </disk>
        <interface type='bridge'>
    # スナップショットLVの作成(ベース環境に対してディスク容量を1/48で設定)
    $ sudo lvcreate -s -L 1G -n snapvm.img /dev/ServiceVG00/basevm.img
    
    
  5. 作成したスナップショットLVを使用し、オンデマンド仮想マシンを作成

    OSのインストールはスキップできるので一瞬で仮想マシンの作成は完了します。 diskオプションに指定しているvolはスナップショットLVを指定しますが、指定方法は"Pool名/Volume名"となります。 (Libvirtで管理されているPoolからLVを作成すると自動的にLibvirtの管理下に入ります)

    オンデマンド仮想マシンの作成:

    $ sudo virt-install \
        --import \
        --accelerate \
        --name snapvm \
        --ram 1024 \
        --vcpus 2 \
        --noacpi \
        --os-variant rhel5.4 \
        --network bridge:br0 \
        --extra-args console=ttyS0,115200n8 \
        --disk vol=ServiceVG00/snapvm.img,bus=virtio,cache=writethrough \
        --noreboot \
        --keymap=ja
    
    インストールを開始しています...
    Guest installation complete... you can restart your domain
    by running 'virsh start snapvm'
    
  6. オンデマンド環境の仮想マシンに展開されたOSの各種調整(主にネットワーク設定)

    オンデマンド環境の仮想マシンで動作するOSは、元のベース環境とほぼ同じですが、MACアドレスの書換等ネットワーク周りの設定が自動的に変更されています。 環境依存の部分(ホスト名、ユーザパスワード)の変更を行えば、ベース環境のデータを共有しつつも、それぞれ別の開発環境として使用することが可能になります。

  7. 個別の環境作成

    後は、アプリケーションのデプロイをするだけで専用の開発環境が作成できます。

必要がなくなれば、仮想マシンを停止しスナップショットLVを削除するだけです。


Tip

スナップショットLVの拡張方法
スナップショットLVは通常のLVと同じく拡張が可能ですので、当初の見積もりよりデータの変更量が大きかった場合、下記のコマンドによりディスク容量の拡張できます。

LVのディスク容量の拡張:

# さらに+1G拡張する
$ sudo lvextend -L +1G /dev/ServiceVG00/snapvm.img

ケース2: オンデマンド環境の構成を実稼働環境として使用

オンデマンド環境のスナップショットLVをバックアップすると、ベース環境のデータとスナップショットに書き込まれたデータが取得できるので、それを新たに作成したLVに書き込みます。 これにより、ベース環境ともオンデマンド環境とも独立したサーバ環境を構築することができます。 オンデマンド環境の場合、大半のデータをベース環境と共存していましたが、この環境の場合は、すべてのデータをコピーしたクローン環境ですので、全く別の環境として構築することが可能です。

手順

  1. 仮想マシンを停止

  2. 新規仮想マシンを稼働させるLVを作成

    作成するLVのサイズはベース環境のLVと同等以上にします。

    LVの作成:

    $ sudo lvcreate -L 48G -n prod01.img ServiceVG00
    
  3. スナップショットLVを利用して仮想イメージを新規LVにコピー

    ベース環境のデータとスナップショットに書き込まれたデータを新たに作成したLVにコピーします。 (ディスク容量が大きければそれだけ時間がかかります。)

    仮想イメージをLVにコピー:

    
    $ sudo dd if=/dev/ServiceVG00/snapvm.img of=/dev/ServiceVG00/prod01.img bs=8192
    
  4. 仮想マシンを起動し、ゲストOSの設定を修正

    手順は、ケース1の4.以降と同様です。

ケース3: 仮想サーバのOSパッケージを実際にインストール・アップグレードした場合の影響が知りたい

yumコマンド等を使用すればいろいろそのパッケージの依存関係とか教えてくれますが、実際にアップグレードしてみないとわからないことも多々あります。 同じ環境の仮想サーバを複数台用意していたとして、そのスペアをアップグレードを試す環境として使うことが多いと思いますが、もし失敗した場合、スペアを破棄しなければならず再インストールということになってしまいます。 実際に試す環境として、スペアから作成したスナップショットLVを使用したオンデマンド仮想マシンを利用することもできます。 これなら失敗したとしてもスペアの環境には影響せず、かつ何度もアップグレードを試すことができます。

手順

  1. 仮想マシンの一時停止(virsh suspend)

    仮想マシンの一時停止:

    $ sudo virsh suspend basevm
    
  2. 仮想マシンLVのスナップショットLVを取得

  3. スナップショットLVを使用して、新規オンデマンド仮想マシンを作成

  4. オンデマンド環境のOSにログインし、実際にパッケージのアップグレード

  5. 問題なかったら、オンデマンド環境を停止し削除

  6. 一時停止状態の既存仮想マシンを再開し、パッケージのアップグレード

    仮想マシンの再開:

    $ sudo virsh resume basevm
    
    

インストール・アップグレードで失敗しても、2-5の手順を何度でもやり直すことができます。

最後に

今回使用したKVMは、Kernel-based...と名前の通りKernelに組み込まれ、標準のOSをそのままに動作させることが可能になっています。 KernelがバージョンアップするごとにKVMも機能強化されていくのでさらに使いやすくなっていくことでしょう。

コマンドラインが扱いづらいという方は、GUIデスクトップベース・Webベースのツールがリリースされていますのでそちらを使えばよいでしょう。

GUIデスクトップクライアント


Note

今回使用したコマンド virt-install も上記と同一プロジェクトの成果物です。


Webアプリケーション

大規模VM管理プラットフォーム

2010年8月 2日

Androidアプリ開発環境を構築する
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

はじめに

4月にウノウ入社しました、takaと申します。 ウノウではまだまだなAndroidですが、敢えてMacでAndroidアプリ開発環境を整えてみようと思います。元々はWindowsユーザでしたがウノウではMacを使って作業していますので、慣れる目的もあります。AndroidアプリはJava、Eclipseを用いた開発が主流だと思いますのでWindowsでもほぼ同様の手順で構築可能なはずです。

JDK6

  • Mac OS Xでは標準で入っていたので特に作業無し。

Eclipse 3.6 Helios

  • http://www.eclipse.org/
  • http://www.eclipse.org/downloads/

Pleiades

  • http://mergedoc.sourceforge.jp/
  • Windowsの場合は「Pleiades All in One」など使用すると各種プラグインも入っていて楽かもしれない。
  • Eclipse を日本語ローカライズするためのプラグイン。
  • Eclipse 3.6 Helios では上手く使えなかったので今回は保留。

Eclipse-ADT

           
  • Android の Eclipse Plugin。
  • Android DDMS 0.9.7.v201005071157-36220
  • Android Development Tools 0.9.7.v201005071157-36220

Eclipse のインストール

  1. Eclipse Helios (3.6) Packages for Mac OS X(Cocoa)
  2. Eclipse IDE for Java Developers の Mac OS X 64 Bitをダウンロード。
  3. Eclipse を起動。
  4. Workspace を適当に設定。デフォルトでOK。
  5. メニューから Help - Install New Software。
  6. Add ボタンを押すと入力ボックスダイアログが表示される。
  7. Name: に適当な名前を入力する。例)Google Android Plugin
  8. Location: に https://dl-ssl.google.com/android/eclipse/
  9. Developer Tools という項目が表示されるのでチェックを入れて Next。
  10. Install Details で以下のインストール項目が表示されるのを確認して Next。
    -Android DDMS
    -Android Development Tools
  11. I accept the term of th license agreements 同意して Finish。
  12. インストール中に Security Warning が出る事があるので OK。
  13. 再起動してプラグインを有効にする必要があるので Restart Now。
  14. Eclipse - Preferences - Java - Compiler - Compiler compliance settings は 1.6。
    -環境にはよっては1.5となっている事があるようで、大量のエラーが出るので要確認。
    -JDK1.5と1.6ではアノテーションの仕様が変わっています。

Android SDKのインストール

  1. http://developer.android.com/sdk/index.html
  2. Mac OS X (intel) android-sdk_r06-mac_86.zip
  3. ダウンロードして任意の場所に展開。
  4. Eclipse - 環境設定 - Android - SDK Location に展開したSDKの場所を指定。
  5. Send usage statistics to Google.はチェックを外して良い。

Android SDK Libraryのインストール

  1. Window - Android SDK and AVD Manager - Available Packages より全てチェック。
    ダウンロードとインストールに時間が掛かるので、例えば、開発ターゲットを絞るのであれば SDK Platform Android 2.1 だけ選択もアリ。ただ、Android1.6で試したい時もあると思うので全部入れる方が楽。
  2. Install Selected より全てインストール。
  3. All Accept して Install。
  4. インストールが完了すると ADB Restart で Yes として再起動。
    Updated ADB to support the USB devices declared in the SDK add-ons.
    'adb kill-server' succeeded.
    'adb start-server' failed.
    と表示されるが気にせず Close。
  5. Eclipse 自体を再起動。

エミュレータ端末の設定

  1. Window - Android SDK and AVD Manager - Virtual Devices より New。
  2. Name: 任意の端末名。例:HT-03aAndroid2.2-HVGAなど判り易い名前。
  3. Target: 任意のPlatform。例:Android 2.2 - API Level 8
    -Googleのサービスを使う場合は Google APIs (Google inc.) - API Level 8 など。
  4. SD Card: 任意の容量。 例:Size: 100 MiB
    -ファイルイメージとしてマウントしたい場合にはFileに指定。
  5. Skin: 各解像度に合わせて設定。Default(HVGA)
  6. Hardware: GPS、タッチスクリーン、トラックボールなどの各種デバイスのサポート指定。
  7. Create ADV で端末作成。
  8. 作成した端末名を選択して Start でエミュレータ端末が起動する。
  9. 起動する際に、Launch Options で表示スケールを設定することもできる。
  10. 作成したエミュレータ端末は詳細が見れるが変更不可能で、変更する場合は作成しなおす。

エミュレータ端末起動後画面

  1. 左側にHOME画面。
  2. 右側にデバイスキー。
  3. マウスやキーボードを使って操作可能。
  4. Window名に リスンポート番号:端末名 と表示。
  5. HOMEからMENU - Setting - Language よりUIを日本語選択可能。

エミュレータ端末でのショートカットキー

画面の縦横切り替え:control+F11
Google検索ボックス:F5
トラックボールモード:F6
MENU/画面ロック解除:F2
受話UP:F3
受話DOWN/画面ロック:F4
戻る:ESC

プログラムの作成

  1. File - New - Project - Android - Android Project
  2. Project name: HelloAndroid
  3. Contents - Create new project in workspace
    -任意適当に設定
  4. Build Target
    -エミュレータ端末の設定と同様にPlatformの選択。Android2.2など。
    -Googleのサービスを使う場合は Google APIs の方を選ぶなど。
  5. Application name: HelloAndroid
  6. Package name: net.unoh.helloandroid
  7. Create Activity: HelloAndroidActivity
  8. Min SDK Version: 8
    -必須Androidバージョンを指定。例だと 8 と指定しないと Warning が出る。
  9. Finish プロジェクトが作成される。
  10. gen にリソースファイルが自動ジェネレートされる
  11. src にソーススケルトン。
  12. エラーとなる場合には Project - Properties - Java Build Path を確認。
  13. Java Build Path - Order and Export の Android 2.2 にチェックし、OK。
  14. assets
  15. res
  16. AndroidManifest.xml
  17. default.properties
  18. ディレクトリとファイルが生成されているのを確認。
  19. Run - Run - Run As - Android Application で OK。
  20. エミュレータ端末の画面に HelloAndroid が表示。
  21. Hello World, HelloAndroidActivity!

最後に

MacでもWindowsと同様に開発環境を構築することができました。メニューの位置が変わっているなど違いはあれどツールとしては同じものを使用しているので開発環境に差は無いでしょう。予めJDK6が入っている分、導入は楽なように思います。 Enjoy! Android!