アクションでツイスト-Pythonのmemcache

前文


週末に関連して、私はTwisted pythonフレームワークを使用してMemcacheサーバーの実装に少し時間を費やしました。 その結果、パフォーマンスは2倍遅くなりましたが、これはあまり重要ではないと考えています。また、元のプロトコルのいくつかの拡張機能を実装する能力もあります。 パフォーマンスをさらに向上させる最適化も可能です。
プロトコルは完全には実装されていません-まだ作業する時間がありますが、標準のset / getは完全に機能し、すぐに使用できます。

手段


キャッシュを保存するには、dictベースクラスを使用します。 ご想像のとおり、Pythonでのdictの実装は高速です。この基本タイプはpythonで積極的に使用されているため、詳細な最適化が必要です。 したがって、キャッシュをメモリに保存するための構造が自動的に作成されます。 memcacheプロトコルを実装して、他のプログラムへのdictアクセスを提供します。

サーバーを実装するには、Twistedを使用します。 現在、PythonのノンブロッキングIOには多くのバリエーションがありますが、Twistedはすでに古典的であり、そのような問題を簡単に解決するのに十分なツールを備えています。



ネットワークプロトコルの実装


プロトコルはどのように実装されますか? もちろん、まず、プロトコルの説明を見つける必要があります。 ここで見つけました-code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt

プロトコルを読んだ後、クライアントから1行または2行を受け取ることが明らかになり、最初の行をスペースで安全に要素に分割できます。 2行目は、データをサーバーに送信するコマンドで使用されます-set、add、replaceなど。 あなたがより詳細に記事を掘り下げたいなら、私はあなた自身に説明を読むためにあなたを送ります、彼の翻訳をここに置く目的はありませんでした。

この知識を武器に、Twistedがこの問題を解決するために提供できるものを調べ、すぐにLineOnlyReceiverを見つけます。これは、文字列を交換するプロトコル、つまり必要なものだけで機能するTwistedベース配信のプロトコルです。 Twistedには既にmemcacheプロトコルの実装がありますが、クライアント部分のみであり、サーバーを作成します。

1 クラス MemcacheProtocol (LineOnlyReceiver):
2 "" "
3 プロトコルの基礎を実装します-クライアントからメッセージを受信します
4 および結果を返します。
5      「」「
6
7 def lineReceived (self、line):
8 debug(repr(line))
self.instructionの' parameters ' ない 場合9
10パラメーター= line.split( '   '
11 debug( " 新しいコマンドを取得しました " +パラメーター[0])
12 self.instruction [ ' parameters ' ] =パラメーター
13
14 #データが期待されない場合、実行用
15 Cache.oneline_commandsのパラメーター[0]の場合
16 self.process()
17 その他
18 #実行用の2行コマンドの受信データ
19 debug( " データを取得しました " +行)
20 self.instruction [ ' data ' ] =行
21 self.process()
22
23 def プロセス (自己):
24 #Cache.callはジェネレーターを返します
Cache.callの行(self.instruction)の場合 25
26 #そして、それが生成するすべてを別々の行に送信します
27 debug( " 行を送信 " +行)
28 self.sendLine(ライン)
29 #キャプテン、さらなる指示の準備ができました!
30 self.instruction = {}
31
32 def connectionMade (自己):
33 debug( " Connected! "
34 self.instruction = {}


コードからわかるように、キャッシュは実際の作業に使用されます。 これは、シングルトンであり、本質的には@classmethodデコレータによってメソッドがラップされるクラスにすぎません。 Cache.callの呼び出しは、文字列を返すジェネレーターを返す必要があり、文字列は次にプロトコル実装がクライアントに返します。

クライアントからのリクエストを解析する


最初の行はスペースで区切られたコマンドとパラメーターであるため、stringメソッドsplitを使用し、出力はリストです。 次に、チームがデータの操作を開始する前に、コンポーネントに分解する必要があります。 クラスに使用するのは、パラメーターにアクセスしてドットでポイントする可能性が高いためです。 以下のコードは、プロトコルの説明を読む必要があり、ガイダンス行の怠pairなペアのために:
                                                                                                                        
データ記録コマンド:                                                                                                       
 <コマンド名> <キー> <フラグ> <exptime> <バイト> [noreply] \ r \ n                                                                 
 cas <key> <flags> <exptime> <bytes> <cas unqiue> [noreply] \ r \ n                                                               

データの受信:
 get <key> * \ r \ n   
 <key>を取得* \ r \ n  
 <key>を削除\ r \ n 

まあなど。

実装の解析:

1 クラスの 命令 (オブジェクト):
2 def __init__ (self、i):
3 p = i [ ' パラメータ ' ]
4 self.cmd = p.pop(0)
5
6 #noreplyを確認する
7 [p [-1] == ' noreply 'の場合
8 self.reply = False
9 #捨てる
10 p.pop(-1)
11
12 self.reply = True
13
14 Cache.storage_commandsのself.cmdの場合
15 #CASの場合、別のパラメーターがあります(つまり、特別な場合)
16 self.cmd == " cas "の場合
17 self.unique = p.pop(-1)
18
19 #これですべてのパラメーターが一意になりましたが、プロトコルを拡張したいので、
20 #すべてがdict(zip())ほど単純ではないため
21 self.bytes = p.pop(-1)
22 self.exptime = p.pop(-1)
23 self.flags = p.pop(-1)
24 self.keys = p
25 self.data = i.get( ' data ' 、なし)
26
27 #取得および取得
28 elif self.cmd in [ " get "" gets "" getn "" delete " ]:
29 self.keys = p
30
31 def __str__ (自己):
32 return str(self .__ dict__)


キャッシュストレージを実装して操作する


私はすぐにプロトコルを拡張しました。つまり、埋め込みデータを扱うことができます。 キャッシュはツリーとして再設計され、標準に従って1つのキーを指定するすべての操作は、スペースで区切られたキーのリストを示すことができます。 ただし、これは簡単に取り除くことができますが、作業の意味は完全に不明確になります。

Entryクラスは、子Entryインスタンスを持つ辞書(dict型の子)を含むストレージユニットとして実装されます。 さらに、階層の最上位は、Entryクラスのインスタンスでもあります。

ここで、キャッシュシングルトンのフラグメントを示します。

1 クラス キャッシュ (オブジェクト):
2 #const
3 storage_commands = [ " set "" add "" replace "" append "" prepend "" cas " ]
4 oneline_commands = [ get gets getn delete incr decr stats ]
5
6 #キャッシュストレージ
7データ=エントリ(0,0,0)
8
9 #キャッシュ操作
10 @ classmethod
11 def 呼び出し (cls、命令):
12 i =指示(指示)
13デバッグ(i)
14コマンド= getattr(cls、i.cmd)
15 returnコマンド(i)
16
17 @ classmethod
18 def セット (cls、i):
19 セット、ネストされたキーのサポート
20親= cls.data.get_child(i.keys [:-1])
親の場合21
22 parent.set_child(i.keys [-1]、エントリ(i.data、i.flags、i.exptime))
23 利回り 保存済み
24
25 NOT_STORED 」を 生成します


エントリコードとその他すべてをここで確認します-github.com/Deepwalker/tx-cache/blob/master/mck.py

修正


コメントで正しく指摘されているように、シングルトンを使用してCacheを格納することはやや不当であるため、githubにはこの迷惑な欠陥を修正する改訂版があります。 ありがとう、 drJonnie

言い訳


この記事に何を期待しますか? 多くの賢い人々が私のコードを見て、アカデミック教育の欠如の欠点に鼻を突くでしょう。 おそらく、この記事は、潜在的な欠点を抱えている人にとって役立つでしょう。プログラムにネットワークプロトコルを実装する方法をここで説明します。 誰かがmemcache拡張にこのコードを使用するかもしれません。

Source: https://habr.com/ru/post/J78114/


All Articles