PythonでThriftを通してHibari
HibariというErlangによる分散KVSがあるようです
http://hibari.github.com/hibari-doc/hibari-app-developer-guide.ja.html
流行りのEventualConsistencyではなく、高い一貫性を守りつつスケールするという代物です。
インストールする
http://hibari.github.com/hibari-doc/hibari-app-developer-guide.ja.html
に従っていけばできます。Repoというソフトウェアに依存しています。
比較的新しいErlang環境を要求するところも注意が必要です。
逆に、repoさえあれば
$ repo init -u git://github.com/hibari/manifests.git -m hibari-default.xml
$ repo sync
$ make
$ make package
でほぼ完了します。
Repoの入手場所のサーバが落ちていて途方に暮れていたら[twitter:@tatsuya6502]さんが緊急回避的にgistを教えてくださいました。
Repoから参照しているandroid.git.kernel.orgが使えなくて途方に暮れていたらまたしても[twitter:@tatsuya6502]さんに助けて頂けました。至りつくせりです。本当にありがとうございます。Thriftを使う
インストールしたHibariの内部に
hibari/lib/gdss_ubf_proto-0.1.7/priv/thrift/hibari.thrift
というファイルがあるので好きな場所に持ってきます。持ってきた場所で
$ thrift --gen py hibari.thrift
と打つとgen-pyディレクトリが作成され、中にimportできる物が一式揃います。便利ですね。
$ ls hibari.thrift gen-py/ $ ls gen-py __init__.py hibari/
早速使ってみます。
import thrift from thrift.transport import TSocket from thrift.transport import TTransport from thrift.protocol import TBinaryProtocol from sys import path path.append('gen-py') from hibari import Hibari # Clientを使う from hibari import ttypes # 命令に使うオブジェクト群 # ソケットを作成 socket = TSocket.TSocket('localhost',7600) socket.setTimeout(None) transport = TTransport.TBufferedTransport(socket) protocol = TBinaryProtocol.TBinaryProtocol(transport) client = Hibari.Client(protocol) socket.open() # 使ってみる request = ttypes.Add(b"tab1", b"fookey", b"Hello Hibari!") response = client.Add(request) #=> None or Exception print response
$ python hibari_thrift.py HibariResponse(value=None, key=None, timestamp=135) $ python hibari_thrift.py Traceback (most recent call last): File "app_main.py", line 53, in run_toplevel File "samp.py", line 21, inresponse = client.Add(request) File "gen-py/hibari/Hibari.py", line 200, in Add return self.recv_Add() File "gen-py/hibari/Hibari.py", line 223, in recv_Add raise result.ouch HibariException: HibariException(timestamp=None, what=101, why='Key Exist')
Add命令は「キーが存在していない場合に限り成功する」ので二度目に失敗するのは仕様通りの挙動です。(何故boolでなく例外で返してるか分かりませんが)
確かに動いてるのですが、自分好みにラップしたのが以下です。
thrift依存っぽいところをクラス内に押し込んで、python-memcacheっぽいインタフェースを提供しつつ、boolで返せそうな所はboolを使うようにして、valueには好きなデータを使いたいのでpickleによりシリアライズするよう。
import thrift from thrift.transport import TSocket from thrift.transport import TTransport from thrift.protocol import TBinaryProtocol import serializer from sys import path path.append('gen-py') from hibari import Hibari from hibari import ttypes class HibariClient(object): def __init__(self, host, port): self.socket = TSocket.TSocket(host, port) self.socket.setTimeout(None) self.transport = TTransport.TBufferedTransport(self.socket) self.protocol = TBinaryProtocol.TBinaryProtocol(self.transport) self.client = Hibari.Client(self.protocol) self.socket.open() self.timestamp_cache = {} def __del__(self): self.socket.close() def add(self, table, key, value): value = serializer.serialize(value) try: self.client.Add(ttypes.Add(table, key, value)) return True except Exception, e: return False def get(self, table, key): try: value = self.client.Get(ttypes.Get(table,key)).value except Exception, e: return None return serializer.deserialize(value) def gets(self, table, key): try: result = self.client.Get(ttypes.Get(table,key)) except Exception, e: return None self.timestamp_cache[(table,key)] = result.timestamp return serializer.deserialize(result.value) def set(self, table, key, value): value = serializer.serialize(value) self.client.Set(ttypes.Set(table, key, value)) def replace(self, table, key, value): value = serializer.serialize(value) try: self.client.Replace(ttypes.Replace(table, key, value)) except Exception, e: return False return True def delete(self, table, key): try: self.client.Delete(ttypes.Delete(table,key,False)) except Exception, e: return False return True def cas(self, table, key, value): """ it does'nt work!!! because of thrift-hibari doesn't implement """ if (table,key) not in self.timestamp_cache: raise Exception("you must 'gets' key before cas") try: self.client.Replace(ttypes.Replace(table, key, value)) # TODO except: pass
pickleも自分好みにラップしてます。Pythonがまだちょっと身体に馴染まないのです。
https://gist.github.com/1239225#file_serializer.py
ラップしたものの使い方はこんな感じです。
client = HibariClient('localhost', 7600) # 保存 client.set('tab1','key3',3) # 存在する場合に限り置換 result = client.replace('tab1','key3',4) print 'replace:',result # 獲得 result = client.get('tab1','key3') print 'get:',result # 削除(二度目は失敗するのでFalseが帰る result = client.delete('tab1','key3') print 'delete:',result result = client.delete('tab1','key3') print 'delete:',result # 削除済みデータをGet。もちろん失敗する result = client.get('tab1','key3') print 'get:',result
実行するとこんな感じ
$ python HibariClientTest.py replace: True get: 4 delete: True delete: False get: None
上手くいってる感じですが、今の実装だとサーバ障害などによる例外を握りつぶしてしまうのでもう少し調整が必要そうですが今日はここまでで。