技術の犬小屋

Webプログラミングを餌に生きる犬のメモ帳

Posts in the Linux/UNIX category

Puppetを使い始めて半年経つので,そろそろ使い方をブログにまとめておく。
 

VagrantとPuppet

Vagrantについての説明を省きたいので,Vagrantの概要や使い方についてはこちらの記事をご閲覧いただきたい。
今回は,VagrantとPuppetを組み合わせて使用する。
 

Puppetとは

Puppetはオープンソースの構成管理ツールである。Rubyで書かれており2.7.0まではGPL,それ以降はAPLライセンスとなっている。
PuppetはUnix系やWindowsの設定を管理するようデザインされている。ユーザーはシステムリソース及び状態をPuppetによる表現もしくはRubyのDSLで表現する。この情報はmanifestと呼ばれるファイルに保存される。
 

仮想マシン上でPuppetを使ってみる

Vagrantで仮想マシンを構築する際には,予めRubyとPuppetがインストールされているBoxを使用する。その理由は,PuppetはPuppet自身を用意(構成管理)することが出来ない為だ。
 

ちなみに…

ちなみに,手動でPuppetをインストールするには以下のようなコマンドを使用する。

gem install puppet --no-rdoc --no-ri

 

manifestを準備する

manifestとは

manifestとは,サーバのあるべき状態を記述することができる設定ファイルである。
Puppetでは,サーバー設定を記述したファイルを「manifest(マニフェスト)」と呼び,設定すべき項目を「リソース」と呼ぶ。扱えるリソースの種類(リソースタイプ)にはユーザーの管理を行うための「user」やサービスの管理を行うための「service」,ソフトウェアパッケージの管理を行う「package」など様々なものが用意されており,これらリソースに対し設定すべき項目とその値をmanifestに記述していく。
 

manifestを書いてみる

以下のような内容のファイルを作成し,ファイル名をhello_world.ppとして保存する。

notice("Hello, World!")

 

manifestをどこに設置するか

先ほど作成したhello_world.ppを,Vagrantfileと同じディレクトリに設置する。
このようにすることで,仮想マシン内にマウントされた共有フォルダ上でhello_world.ppを扱うことが出来るようになる。
vagrant sshコマンドで仮想マシンにログインし,/vagrantディレクトリに移動すると,hello_world.ppが存在することが確認出来るはずだ。
 

manifestを適用する

manifestを適用するには,仮想マシン内の/vagrantディレクトリに移動し,以下のコマンドを実行する。

puppet apply hello_world.pp

 
Hello, World!と出力されれば,適用が成功している。
 

実際にpuppetでパッケージをインストールしてみる

実際にzshのパッケージをインストールする際の設定を書いてみる。
先ほどのhello_world.ppと同じ階層に,以下の内容でzsh.ppというファイルを作成する。

package { 'zsh':
  ensure => installed,
}

 
今度は,ログの表示だけではなく,システムへの変更も行うので,仮想マシン内の/vagrantディレクトリに移動し,sudo権限でmanifestを適用する。

sudo puppet apply zsh.pp

 
Finished catalog runと出力されれば,適用が成功している。
 
zshがインストールされていることを,以下のコマンドで確認してみると良いだろう。

which zsh

 

モジュールを使ってみる

Puppetが標準で扱うことが出来るリソース型はType Referenceで確認できるが,「モジュール」と呼ばれる機能拡張をインストールすることで,これ以外のリソースを扱うことも可能になる。モジュールはPuppetの開発元であるPuppet Labsだけでなく,サードパーティからも公開されており,Puppet Forgeというサイトにまとめられている。
 
試しに,以下のコマンドでmysqlモジュールをインストールしてみる。

puppet module install puppetlabs/mysql

 
モジュールをインストールすることで,そのモジュールで提供される機能がマニフェスト内で利用可能になる。例えば,mysqlモジュールを使ってMySQLクライアントをインストールするには,以下をmanifestに書き加えるだけでよい。

class { “mysql”: }

 

VagrantからPuppetを操ってプロビジョニングする

Vagrantfileにmanifestのパスやモジュールのパスを書いておくことで,VagrantからPuppetを利用してプロビジョニング(構成管理)を行うことが出来るようになる。
以下をVagrantfileに記述する。

config.vm.provision :puppet do |puppet|
  puppet.manifests_path = 'puppet/manifests'
  puppet.manifest_file  = 'default.pp'
  puppet.module_path    = 'puppet/modules'
end

 
仮想マシン起動中にVagrantからプロビジョニングするには,以下のコマンドを実行する。

vagrant provision

 
また,以下のコマンドでは仮想マシンの起動時にVagrantからプロビジョニングをすることも出来る。

vagrant up --provision

 
 
以上
 
 
参考
Puppet (ソフトウェア) – Wikipedia
Rubyist Magazine – 入門 Puppet ダイジェスト
Puppetを使ったLinuxシステムの設定自動管理 – さくらのナレッジ

Vagrantを使い始めて半年経つので,そろそろ使い方をブログにまとめておく。
 

Vagrantとは

仮想環境の雛形を作成し,どこでも簡単に同じ環境を再現できるようにするソフトウェア。
コンピュータ上にVirtualBoxで仮想マシンを起動し,その上でLinuxやWindows,Mac OS Xなどを動作させることができる。Vagrantでは仮想マシンやOSの設定をテキストファイルで与えることで特定の状態に初期化することができ,この設定ファイルをコピーすることでどこでも簡単に同じ仮想環境を再現することができる。
 

Vagrantのセットアップ

上記の通り,VagrantはVirtualBoxと連携して動作するので,両方をインストールする必要がある。
今回はHomebrewとhomebrew-caskを使ってVirtualBoxとVagrantをインストールする。
Homebrewとhomebrew-caskの使い方については,説明を省きたいので,こちらの記事をご閲覧いただきたい。
 

brew cask install virtualbox
brew cask install vagrant

 

Boxとは

BoxとはVagrant用に調整されたOSが格納されたファイルである。以下のページから様々なBoxをダウンロードすることが出来る。
 
A list of base boxes for Vagrant – Vagrantbox.es
 

Boxを追加する

仮想環境を構築するにはOSが必要なので,以下のコマンドでVagrantにBoxを追加する。

vagrant box add precise64 http://files.vagrantup.com/precise64.box

 

Boxが追加されたことを確認する

VagrantにBoxが追加されたことを確認するには,以下のコマンドを実行する。

vagrant box list

 
precise64と表示されればBoxの追加が成功している。precise64というのは,先ほどBoxに付けた名前である。
 

Vagrantの初期設定を行う

任意のディレクトリを作成し,仮想マシンを初期化・セットアップする。

mkdir test #任意のディレクトリを作成
cd test
vagrant init precise64

 
vagrant initを行うと,Vagrantfileというファイルが生成される。このファイルには,ベースとしたイメージファイルや,作成された仮想マシンのIPなどが記録されている。
 

Vagrantの使い方

仮想マシンを起動する。

vagrant up

 
仮想マシンをシャットダウンする。

vagrant halt

 
Vagrantfileの再読み込みする。

vagrant reload

 
仮想マシンの状態を確認する。

vagrant status

 
仮想マシンを除去する。

vagrant destroy

 
仮想マシンにssh接続する。

vagrant ssh

 
ChefやPuppet,Ansibleなどと連携して仮想マシンに対してプロビジョニングを行う。

vagrant provision

 
Vagrantを起動しつつ,プロビジョニングする。

vagrant up --provision

 
 
以上
 
 
参考
Vagrantとは – 意味/解説/説明/定義 : IT用語辞典
Vagrant セットアップ (Mac) – Qiita
Vagrantの使い方 ~仮想マシンを簡単に作成しよう。 – (゚∀゚)o彡 sasata299’s blog

tmuxの使い方

tmuxを使い始めたので,導入方法や使い方をメモしておく。
今回は最低限必要な概念や操作だけを覚えた。
 

tmuxとは

tmuxは端末を多重化することができるソフトウェアである。1つのスクリーンから複数の端末を作成,アクセス,制御することを可能にする。
tmuxを使っていれば,例えば,何かのビルド中やDBを設定している最中にsshが切れても安全である。もし使っていない場合,sshが切れるとプロセスがkillされ,最悪の場合,処理が中途半端な状態になり,環境を壊してしまう恐れがある。
 

tmuxのインストール

今回はhomebrewを使用してインストールを行った。homebrewがインストールされた状態で,以下のコマンドをターミナルから入力する。

brew install tmux

 

tmuxの使い方

tmuxの基本的な使い方は以下となる。
 
基本的に,tmuxコマンドでtmuxを起動しておくだけで端末を多重化することができる。

tmux

 
アタッチを行うことで前回デタッチした状態から作業を再開することが出来る。

tmux
tmux a
tmux attach

 
デタッチを行うことで作業を中断することが出来る。また,sshが切れた際には自動的にデタッチが行われる。

tmux detach

 
新しくtmux内でタブを増やすことも出来る。

tmux new-window

 
tmux内でのタブの移動

tmux next-window
tmux previous-window

 
tmuxセッションの確認

tmux ls

 
tmuxセッションの終了
該当するtmuxセッションにてexitコマンドを実行することで,tmuxセッションを終了することが出来るが,全てのセッションを終了したい場合は以下のコマンドで行うことが出来る。

tmux kill-server

 

tmuxのショートカットキー

tmuxでは,上記で挙げた操作にショートカットキーが用意されているので,こちらを覚えると作業が捗る。
 

キー 動作
Ctrl-b + d セッションのデタッチ
Ctrl-b + c 新しくtmux内でタブを増やす
Ctrl-b + w tmux内のタブを選択移動
Ctrl-b + n tmux内のタブを順移動
Ctrl-b + p tmux内のタブを逆順移動

 
 
以上
 
 
参考
@paselaからの指導と助言
tmuxを使い始めたので基本的な機能の使い方とかを整理してみた – 完熟トマト
デザイナーのための tmux 超入門
Macにtmuxを導入する – Qiita

Redisの使い方を書籍で勉強したので,まとめておく。
 

Redisとは

メモリ上にKey-Valueストア(KVS)を構築することができるソフトウェアの一つ。
KVSは任意の保存したいデータ(値:value)に対し,対応する一意の標識(キー:key)を設定し,これらをペアで保存するデータベースの一種で,Redisはコンピュータのメインメモリ上にKVSを構築し,外部のプログラムからデータの保存と読み出しができる。
保存する値(value)として様々なデータ構造を利用することができ,文字列,バイナリデータ,リスト,集合(セット),ハッシュなどを保存することができる。
 

Redisの5種類のデータ構造
型名 格納されている情報 データ構造の読み書き機能
STRING 文字列,整数,浮動小数点数 文字列全体,文字列の一部の操作,整数や浮動小数点数のインクリメント/デクリメント
LIST 文字列の連結リスト 両端の要素のプッシュ,ポップ,オフセットによるトリミング,個々/複数の要素の読み出し,値による要素の探索,削除
SET 一意な文字列の順序のないコレクション 個々の要素の追加,フェッチ,削除,メンバーかどうかのチェック,積集合,和集合,差集合,ランダムな要素のフェッチ
HASH 順序のないキーと値のハッシュテーブル 個々の要素の追加,フェッチ,削除,ハッシュ全体のフェッチ
ZSET 浮動小数点数のスコア順に並べられた文字列メンバーからスコアへのマッピング 個々の要素の追加,フェッチ,削除,スコアの範囲,メンバーの値に基づく要素のフェッチ

 

Redisの特徴と用途

Redisは,メモリ上で動作するキー・バリュー型の揮発性データベースである。揮発性と聞くと,Redis自身が停止したり,Redisが動作しているハードの電源が切れたりすると,Redisに保存されているデータは消失してしまうように思われるが,Redisはデータをディスクに書き込み,永続化させる機能も持ち合わせているのでそれを回避できる。特徴としては,メモリ上にデータを格納するので,非常に高速にデータの書き込み・読み込みを行うことができる。キャッシュ用のデータベースとしても優れているが,単体でもデータストアとして利用可能なように設計されており,100を超える様々なコマンドを備えている。
 

レプリケーションによる負荷分散

Redisには,他のコンピュータに複製を作る機能があり,更新が可能な1台の「マスター」と,その複製である読み出し専用の複数台の「スレーブ」という構成にすることで大規模化を図ることができる。この負荷分散の方法を「レプリケーション」と呼ぶ。
 

データの永続化

Redisには,データを永続化(ディスクへの書き込み)を行う仕組みとして,SnapshotとAppend Only Fileの2つの方法が用意されている。
 

スナップショット(Snapshot)

スナップショットでは,設定が有効な場合,Redisは定期的にデータベースの内容をディスクに出力する。Redisを再起動すると,このファイルからデータが読み込まれ復元される。一定回数の更新・一定間隔でディスクにファイルを出力する。ファイルの出力タイミングは設定ファイル,CONFIGコマンドで変更可能である。出力は非同期で行われるため,プロセスがクラッシュした場合には前回のスナップショット以降のデータが失われる可能性があるので,多少のデータロスは許容できる必要がある。手動でスナップショットを保存する場合は,SAVEコマンドまたはBGSAVEコマンドで行うことができる。出力されたファイルの形式はバイナリとなる。
 

スナップショットの設定項目(/etc/redis.conf)
設定項目 内容 書式
save データベースをディスクに保存するタイミングを設定することができる。与えられた秒数経過するか,指定された回数分,書き込み命令を受け付けるとデータベースを保存する。 save seconds changes
rdbcompression .rdb形式のデータベースにダンプするときに,圧縮するかどうかを設定することができる。デフォルトでは’yes’になっており,常に圧縮するようになっている。もし保存時にCPUパワーを節約したい場合は’no’を設定する。 rdbcompression yes
dbfilename DBをダンプするときのファイル名を指定する。 dbfilename dump.rdb
dir 作業ディレクトリを設定します。DBは,このディレクトリ内に,dbfilename設定ディレクティブで設定された名前で書き出される。追記専用ファイルモードもこのディレクトリ内に作成される。 dir ./

 

追記専用ファイル(Append Only File)

Append Only Fileは,書き込み操作を全てログファイルに記録する。スナップショットでのデータロスの可能性を許容できない場合にこれを使う。スナップショットと併用可能で,ドキュメントではAppend Only Fileの単体運用は推奨されていない。スナップショットと併用した場合,再起動時にはAppend Only Fileの方が使用される。ファイル出力タイミングは設定で変更可能で,デフォルトでは1秒毎となっている。出力されるファイルの形式はテキストで,ファイルサイズはどんどん肥大化していくので,定期的に再構成する必要がある。バージョン2.2までは手動でBGREWRITEAOFコマンドを実行し,再構成する必要があったが,バージョン2.4からは自動的に再構成を実行できるようになった。
 

Append Only Fileの設定項目(/etc/redis.conf)
設定項目 内容 書式
appendonly もしデータが大切で,データを失いたくないのであれば,これを有効にすべきである。このモードが設定されると,Redisはappendonly.aofに書き込み操作を受け取るたびにすべて記録していく。このファイルは起動時に全データセットをメモリ内に構築していくときに読み込まれる。 appendonly yes
appendfilename 追記専用ファイルの名前を設定する。デフォルトはappendonly.aofである。 appendfilename appendonly.aof
appendfsync オペレーティングシステムに対して,出力バッファにデータが貯まるのを待つのではなく,データをディスクに書き出すように指示することができる。OSによっては実際にデータをディスクに書き出したり,なるべく速く書き出すようにしたりする。Redisは次の3つのモードをサポートしている。no:fsyncしない。データの書き出しはOSに任せるので,高速に動作する。always:追記専用ログに書き込むたびにfsyncを行う。低速だが安全に動作する。everysec:最後のfsyncから1秒経過するとfsyncを行う。上記の2つの中間に位置する。 appendfsync everysec
no-appendfsync-on-rewrite Append Only Fileのfsyncポリシーがalwaysかeverysecに設定されており,バックグラウンドのセーブ用のプロセス(バックグラウンドのセーブか,Append Only Fileのログのバックグラウンド書き込み)がディスクに対して大量のI/Oを発生していたとすると,Linuxの設定によっては,fsync()呼び出し時にRedisが長時間ブロックしてしまう可能性がある。現在では修正方法がないため,別スレッドのfsync呼び出しは,同期的な書き込みの呼び出しまでブロックさせてしまう。このオプションを使うと, BGSAVEやBGREWRITEAOFが実行中は,メインのスレッドではfsync()呼び出しが行われないようになり,この問題を回避することができる。これはつまり,他の子スレッドが保存している間は,appendfsync noneが設定されているのと同じ動作をするようになるため,利用者の視点で説明するとすれば,クラッシュした場合に,最悪のシナリオを想定すると,最大で30秒のログが失われる可能性があるということだ。もし遅延時間の問題を持っているのであれば,yesを設定する。そうでない場合には,安全性の観点からnoを選択して,この問題は放置するようにする。 no-appendfsync-on-rewrite no

 

Redisの使い方

今回はredis-cliからの使用方法と,PHP言語からの使用方法の2つを説明する。redis-cliとは,Redisをインストールしたときに付属するクライアントプログラムである。ちなみにRedisには,redis-serverというサーバプログラムも付属している。
 
初めに,yumコマンドやapt-getなどのコマンドを使って,Redisをインストールし,Redisを起動する。

#Redisがインストールされているか確認する
which redis
sudo find / -name redis

#インストールされていなければ,Redisをインストールする
sudo yum --enablerepo=epel install redis

#Redisを起動する
sudo redis-server /etc/redis.conf

#Redisが起動したことを確認する
ps aux | grep redis

#Redisが自動起動するように設定する
sudo chkconfig redis on

 
ちなみにRedisを終了させたいときは,Redisをインストールしたときに付属するredis-cliというRedisのクライアントプログラムからshutdownコマンドを実行することで終了させることができる。
 

redis-cliからRedisを使ってみる

redis-cliからRedisを使う方法を説明する。
 
redis-cliからRedisに接続し,データの書き込み・読み込みを行う。

#Redisに接続する Redisはデフォルトでは,6379番ポートをリッスンしている
redis-cli
 
#データの保存
#set key value
set wanko bow-wow
+OK
 
#データの読み出し
#get key
get wanko
"bow-wow"

#データの削除
#del key
del wanko
(integer) 1

#キーに有効期限を設定したい場合はsetexを利用する
#setex key expires value
setex wanwan 60 wow
+OK

get wanwan
wow

#60秒後にはデータが消えている
get wanwan
(nil)

 
単純な文字列型データだけではなく,リスト型のデータを操作することもできる

#dogというリストを作成し,先頭にfooというデータを追加
#lpush key value
lpush dog wan
:1

#dataというリストの先頭にbarというデータを追加
lpush dog wow
:2

#dataというリストの先頭(0)から末尾(-1)までを取得
#lrange key start end
lrange dog 0 -1
1) "wow"
2) "wan"

#dogというキーに紐付くデータを削除
del dog
(integer) 1

 

Redisで利用できるコマンドの抜粋
データ型 コマンド名 コマンド内容 引数
文字列型 SET keyにvalueを保存する key, value
文字列型 SETEX keyにvalueをexpires付きで保存する key, expires, value
文字列型 GET keyに対応する値を取得する key
リスト型 LPUSH keyに対応するリストの先頭にvalueを加える key, value
リスト型 RPUSH keyに対応するリストの末尾にvalueを加える key, value
リスト型 LLEN keyに対応するリストの要素数を返す key
リスト型 LRANGE keyに対応するリストの指定したindex範囲(start..end)の要素を返す key, start, end
リスト型 LTRIM keyに対応するリストを指定したindex範囲(start..end)にトリムする key, start, end
リスト型 LINDEX keyに対応するリストのindexにある要素を返す key, index
セット型 SADD keyに指定されたmemberを保存する key, member
セット型 SCARD keyに対応する要素の数を返す key
セット型 SMEMBERS keyに対応するセット内のすべてのメンバを返す key
セット型 SUNION 指定されたkey1, key2, …の和集合を返す key1, key2, …
セット型 SINTER 指定されたkey1, key2, …の積集合(共通メンバ)を返す key1, key2, …
ソート済みセット型 ZADD keyに対して,指定されたmemberをscoreに保存する key, score, member
ソート済みセット型 ZRANGE keyに対応する要素のうち,指定したindex範囲(start..end)の要素を返す key, start, end
ソート済みセット型 ZRANGEBYSCORE keyに対応する要素のうち,指定したスコア範囲(min..max)の要素を返す key, min, max
ハッシュ型 HSET keyに対応するハッシュの指定されたfieldにvalueを保存する key, field, value
ハッシュ型 HGET keyに対応するハッシュの指定されたfieldに対応する値を返す key, field
ハッシュ型 HKEYS glob形式のパターンを使って,存在するfieldの一覧を返す glob形式のfieldパターン
ハッシュ型 HEXISTS keyに対応するハッシュ内に指定したfieldがあれば1を,無ければ0を返す key, field
ハッシュ型 HDEL keyに対応するハッシュ内のfieldを削除する key, field
型によらず DEL keyに対応するデータを削除する key
型によらず KEYS glob形式のパターンを使って,存在するkeyの一覧を返す glob形式のパターン 例)user:*
型によらず EXISTS 指定されたkeyが存在すれば”1″を,無ければ”0″を返す key
型によらず RENAME keyをold_keyからnew_keyにリネームする old_key, new_key
型によらず TYPE 指定されたkeyに対応するデータの型を返す key

 

PHPからRedisを使ってみる

PHPからRedisを使う方法を説明する。PHPからRedisに接続するためのモジュールは,主にpredisとphpredisの2つがある。この2つの大きな違いは,predisはphpで普通に実装が行われているのに対し,phpredisはPHPのエクステンション(C言語)で実装されていることである。これらがやっていることが,ほとんど同じ内容なのであれば,C言語で作られたモジュールの方が動作が速いはずなので,今回はphpredisを使ってPHPからRedisを操作してみる。

#Cコンパイラが必要なのでgccをインストールする
sudo yum install gcc

#Gitでソースを取得する
git clone git://github.com/nicolasff/phpredis.git

cd phpredis

#phpizeとは,エクステンションを追加する際に,
#phpを最初からコンパイルし直す必要なく,追加するためのコマンド
phpize

#コンパイルする
./configure
make
make install

 
インストールが完了したら,エクステンションとして読み込ませるために,php.iniにextension=redis.soを追記する。
 
読み込まれたモジュールにRedisが追加されているかを確認する。

php -m | grep redis

 
これでphpredisの導入が完了した。
続いて,PHPでRedisの基本操作を行ってみる。

<?php
$redis = new Redis();
$redis->connect("127.0.0.1",6379);

//set(key, value)
$redis->set("dog","baw-baw");

//get(key)
$res = $redis->get("dog");
echo $res;

 
シェルにbaw-bawと出力されれば,コードが上手く実行できている。
上記のコードは動作確認済みなので,そのままコピペして試すことができる。
 
 
以上
 
 
参考
書籍 Redis入門 インメモリKVSによる高速データ管理
書籍 NoSQLデータベース ファーストガイド
書籍 NOSQLの基礎知識 ビッグデータを活かすデータベース技術
ec2にyumでredisをinstall – Qiita
Redisとは 【 Remote dictionary server 】 – 意味/解説/説明/定義 : IT用語辞典
redisドキュメント日本語訳 redis 2.0.3 documentation
PhpRedisでphpからredisを使う – 恥知らずのウェブエンジニア
phpredisのインストール – it-boyの日記

memcachedについて,書籍を読んで勉強したので使用方法をまとめておく。
 

memcachedとは

memcachedは,分散メモリキャッシュシステムを構築することができるソフトウェアの一つ。データベースの読み出し結果などをメモリに保存し、次に同じデータが参照されたときにメモリから即座に返すことができる。複数のコンピュータにまたがる巨大な分散キャッシュシステムを構築でき、台数を増やすことで性能を向上させることができるため、大規模なWebサービスなどでよく用いられる。
 

memcachedの特徴と用途

memcachedは,メモリ上で動作するキー・バリュー型の揮発性データベースである。揮発性なので,memcached自身が停止したり,memcachedが動作しているハードの電源が切れたりすると,memcachedに保存されているデータは消失してしまう。そのため,消えてしまっては困るようなデータを保存することはできない。memcachedはメモリ上にデータを格納するので,非常に高速にデータの書き込み・読み込みを行うことができる。
以上に挙げた特徴から分かるように,memcachedはキャッシュを行うような場面において,その真価を発揮する。memcachedの具体的な用途としては,MySQLやPostgreSQLのようなRDBと組み合わせて,キャッシュ用のデータベースとして使用することで,RDBにかかる負荷を抑えることができる。
 

コンシステント・ハッシングによる負荷分散

memcachedは,コンシステント・ハッシング(Consistent Hashing)と呼ばれるアルゴリズムによってデータ分割・割り当て(負荷分散)を行う。
このアルゴリズムでは,分散型システムを構成する各ノード(memcached)が,論理的にリング(輪)を形作るように配置される。
以下がコンシステント・ハッシングの図である。
 
コンシステント・ハッシングの図
 
図では,4つのノード(memcached)を均等に分散配置している。データのキーの値はハッシュ関数によって求められ,ハッシュ関数を使った演算は,不規則な発番(整理番号の付与)を行う方法として広く利用されている。このようにキーの値として,ハッシュ値を得ることによってキーの値が特定の値に偏ることなく,広く均等に拡散するようになる。
そして,その整理番号に従って,リングの各スペースにキーを割り当てる。キーが割り当てられたスペースを時計回りで進み,そこで最初に配置されているノードにデータを書き込む。コンシステント・ハッシングはこのようなルールのアルゴリズムである。
 

CAS操作によるアトミック性の実現

memcachedにおいて,アトミック性(原子性)を実現するにはCAS操作を行う必要がある。アトミック性とは,トランザクションに含まれるタスクが全て実行されるか、あるいは全く実行されないことを保証する性質をいう。このような性質は,MySQLやPostgreSQLのようなRDBでは標準的に対応しているが,memcachedにおいては,CAS操作と呼ばれる処理を行わなければ,対応することができない。
アトミック性(原子性)が保障されない場合,どのようなことが起こり得るか,簡単な例を示す。
以下の例では,「プロセス1」と「プロセス2」の2つのプロセスが1つのカウンタに対して数値を足している。
 
プロセス1 現在のカウンタの値を取得する …… カウンタの値:0
プロセス1 カウンタの値を1増やす ……………… カウンタの値:1
プロセス2 現在のカウンタの値を取得する …… カウンタの値:0
プロセス1 カウンタの値を保存する …………… カウンタの値:1
プロセス2 カウンタの値を1増やす …………… カウンタの値:1
プロセス2 カウンタの値を保存する …………… カウンタの値:1
 
上記の例では,本来はカウンタの値が2になることを期待しているが,最終的にカウンタの値は1になってしまっている。CAS操作を行わない場合はこのようなことが起こり得る。
 

memcachedの使い方

今回はtelnetからの使用方法と,PHP言語からの使用方法の2つを説明する。
初めに,yumコマンドやapt-getなどのコマンドを使って,memcachedをインストールし,memcachedを起動する。

#memcachedがインストールされているか確認する
which memcached
sudo find / -name memcached

#インストールされていなければ,memcachedをインストールする
sudo yum install -y memcached

#memcachedのリッスンするポートの変更やメモリの割り当てなどを変更することもできる
sudo vi /etc/sysconfig/memcached

#memcachedを起動する
sudo service memcached start

#memcachedが起動したことを確認する
ps aux | grep memcached

#memcachedが自動起動するように設定する
sudo chkconfig memcached on

 

telnetからmemcachedを使ってみる

telnetからmemcachedを使う方法を説明する。まずは,yumやapt-getなどのコマンドを用いてtelnetのインストールを行う。

#telnetがインストールされているかを確認する
which telnet
sudo find / -name telnet

#インストールされていなければ,telnetをインストールする
sudo yum install -y telnet

 
telnetからmemcachedに接続し,データの書き込み・読み込みを行う。

#telnetでmemcachedに接続する memcachedはデフォルトでは,11211番ポートをリッスンしている
telnet localhost 11211

#データの保存
#set key,flag,expires,byte[改行]
#value
set foo 0 100 3
bar

#データの読み出し
get foo

 
いろいろ操作を行ってみる。

#データの保存
set counter 0 0 1
1

#加算(increment)
#+1
incr counter 1

#減算(decrement)
#-1
decr counter 1

#データの保存
set test 0 0 4
test

#データの先頭へ追加
prepend test 0 0 3
bow

#データの末尾へ追加
append test 0 0 5
wanko

#データの削除
delete test

#memcached上のすべてのデータを削除
flush_all

 

名前 説明 注意点
flag データを圧縮するかどうかの指定。0:非圧縮,1:圧縮
expires データをいつまで保持するかをUNIXタイムスタンプもしくは現在からの秒数で指定する 現在からの秒数は30日(60*60*24*30=2592000)を超えることはできない。0を指定すると有効期限無し(半永久的に保持)になる。
byte valueとして保存するデータのバイト数を指定する

 

PHPからmemcachedを使ってみる

PHPからmemcachedを使う方法を説明する。今回はApacheとPHPが既にインストールされているという前提で話を進める。
 
まずは,PECLのmemcachedライブラリをインストールする。

#PHPからmemcachedを使うためのライブラリをインストールする
sudo yum install -y php-pecl-memcached

 
続いて,memcachedにデータを書き込むPHPプログラムと,memcachedからデータを読み込むPHPプログラムを作成する。
 

<?php
//memcachedにデータを書き込むPHPプログラム
$m = new Memcached();
$m->addServer('localhost', 11211);
$m->set('key', 'wanko', 60); // キー,バリュー,有効期限(秒)

 

<?php
//memcachedからデータを読み込むプログラム
$m = new Memcached();
$m->addServer('localhost', 11211);
if ($val = $m->get('key')) {
    echo $val;
}

 
上記のプログラムでは,keyというキーに対して,wankoというバリューを格納し,1分間だけmemcachedが情報を保持している。
 

CAS操作の実践

CAS操作では,getsとcasという2つのコマンドを使ってデータの不整合を発生させないようにする。getsはデータを読み出すコマンド,casはデータを保存するコマンドである。まず,getsコマンドを使うとデータだけでなく,cas idと呼ばれる値も取得できる。cas idはそのデータ識別する一意の値であり,データが更新されると必ず変更される。そして,データの保存時にはcasコマンドに対して保存するデータと一緒にcas idを渡す。このとき,casコマンドに渡されたcas idと,現在のデータを表すcas idが一致したときのみ保存が行われる。cas idが一致しない場合には,getsコマンドで取得してからデータが変更されているということであり,データの保存は失敗する。このように,保存時にそのデータが別のプロセスによって変更されていないかを確認することで,データの不整合が発生しないようにしている。
 

telnetからCAS操作を行ってみる

telnetでCAS操作を行うと,以下のようになる。

#データの保存
#set key,flag,expires,byte[改行]
#value
set hoge 0 1500 10 
1111111111
STORED

#数値の1111111111とcas id(6)を取得できる
gets foo
VALUE hoge 0 10 6
1111111111
END

#cas idを渡して,新しい値に書き換える
cas foo 0 1500 10 6
2222222222
STORED

 
上記では,4~6行目にてデータの格納を行い,9~12行目でデータの取得とcas idの取得を行っている。更に15~17行目でkey, flag, expires, byte, cas idを渡し,値を書き換えている。
 

PHPからCAS操作を行ってみる

PHPでCAS操作を行うと,以下のようになる。

<?php
$m = new Memcached();
$m->addServer('localhost', 11211);

//memcachedからデータを取得
//キー,コールバック関数名,CASトークンを格納する変数
$data = $m->get('key', null, $cas);

//get()で,キーが見つからなかった場合は新たにキーとデータを追加する
if ($m->getResultCode() == Memcached::RES_NOTFOUND) {

    //キー,バリュー,有効期限(秒)
    $m->add('key', 'value', 60);

//get()で,キーが見つかった場合は,CASトークンを使ってデータを更新する
} else { 

    //CASトークン,キー,バリュー,有効期限(秒)
    $m->cas($cas, 'key', 'value', 60);

}

 
これはこちらのページにあったコードを,自分なりに読みやすく書き換えたものである。動作確認済みなので,このままコピペして試すことができる。Memcached::get()はコールバック関数の実行が行えたりと,少し変わった使い方もできるので,こちらのページを参考にして使い方を確認してみてほしい。
 
 
以上
 
 
参考
書籍 NoSQLデータベース ファーストガイド
書籍 NOSQLの基礎知識 ビッグデータを活かすデータベース技術
memcachedとは 【 memory cache daemon 】 – 意味/解説/説明/定義 : IT用語辞典
ACID (コンピュータ科学) – Wikipedia
[CentOS] memcachedをインストールして、PHPから使用する | HAPPY*TRAP
PHP: Memcached::cas – Manual
PHP: Memcached::get – Manual
PHP: Memcached::add – Manual
PHP: Read-through キャッシュコールバック – Manual