Python:PySyncObjを使用した分散システムの構築

クラスがあると想像してください:

class MyCounter(object): def __init__(self): self.__counter = 0 def incCounter(self): self.__counter += 1 def getCounter(self): return self.__counter
      
      





そして、あなたはそれを配布させたいのです。 SyncObjから継承し(同期するサーバーのリストを渡す)、クラスの内部状態を変更するすべてのメソッドで@replicatedデコレーターをマークします。

 class MyCounter(SyncObj): def __init__(self): super(MyCounter, self).__init__('serverA:4321', ['serverB:4321', 'serverC:4321']) self.__counter = 0 @replicated def incCounter(self): self.__counter += 1 def getCounter(self): return self.__counter
      
      





PySyncObjは、サーバー間のフォールトトレランス(フォールトトレランス(サーバーの半分以上が動作している限りすべて動作します))、および(必要な場合)ディスクへのコンテンツの非同期ダンプを自動的に提供します。

PySyncObjに基づいて、たとえば、分散ミューテックス、分散データベース、課金システムなどのさまざまな分散システムを構築できます。 信頼性とフォールトトレランスが最初に来るすべてのもの。



概要



PySyncObjはレプリケーションにRaftアルゴリズムを使用します。 Raftは、分散システム用の単純なコンセンサス構築アルゴリズムです。 Raftは、 Paxosアルゴリズムのより単純な代替品として開発されました。 つまり、いかだアルゴリズムは次のように機能します。 すべてのノードの中から、一定期間後に残りのノードをpingするリーダーが選択されます。 各ノードは、リーダーからのpingを待機するランダムな期間を選択します。 待機時間が終了しても、リーダーからのpingが届かない場合、ノードはリーダーが落ちたと判断し、自分がリーダーになったというメッセージを他のノードに送信します。 一連の状況が成功すると、これはすべて終了します(他のノードは同意します)。 また、2つのノードが同時にリーダーになりたい場合は、リーダーの選択手順が繰り返されます(ただし、待機時間の他のランダムな値が使用されます)。 視覚化を見るか、 科学記事を読むことで、リーダーの選択についてさらに学ぶことができます。



リーダーが特定されると、彼は分散ジャーナルを管理する責任があります。 システムの状態を変更するすべてのアクションは、分散ジャーナルに書き込まれます。 ほとんどのノードがレコードの受信を確認した場合にのみ、アクションがシステムに適用されます-これにより一貫性が保証されます。 分散ログのエントリ数が無期限に増えないように、ログ圧縮と呼ばれる操作が定期的に発生します。 現在のログは破棄され、代わりに現在のシステムのシリアル化された状態が保存され始めます。



コンテンツが失われないようにするため(たとえば、すべてのサーバーがオフになっている場合)、定期的にディスクに保存する必要があります。 データ量は非常に大きくなる可能性があるため、コンテンツは非同期に保存されます。 データを同時に処理し、同時にディスクに保存できるようにするために、PySyncObjはプロセスのフォークを通じてCopyOnWriteを使用します。 分岐後、親プロセスと子プロセスはメモリを共有します。 データのコピーは、このデータを上書きしようとした場合にのみ、オペレーティングシステムによって実行されます。



PySyncObjは完全にPython(Python 2およびPython 3でサポート)で実装されており、外部ライブラリを使用しません。 ネットワークは、プラットフォームに応じて、選択またはポーリングを使用して実行されます。





そして今、いくつかの例。



キーバリューストレージ


 class KVStorage(SyncObj): def __init__(self, selfAddress, partnerAddrs, dumpFile): conf = SyncObjConf( fullDumpFile=dumpFile, ) super(KVStorage, self).__init__(selfAddress, partnerAddrs, conf) self.__data = {} @replicated def set(self, key, value): self.__data[key] = value @replicated def pop(self, key): self.__data.pop(key, None) def get(self, key): return self.__data.get(key, None)
      
      





一般に、すべてはカウンターと同じです。 定期的にデータをディスクに保存するには、SyncObjConfを作成し、fullDumpFileをそこに転送します。



コールバック


PySyncObjはコールバックをサポートしています-いくつかの値を返すメソッドを作成でき、それらは自動的にコールバックにスローされます:

 class Counter(SyncObj): def __init__(self): super(Counter, self).__init__('localhost:1234', ['localhost:1235', 'localhost:1236']) self.__counter = 0 @replicated def incCounter(self): self.__counter += 1 return self.__counter def onAdd(res, err): print 'OnAdd: counter = %d:' % res counter = Counter() counter.incCounter(callback=onAdd)
      
      







分散ロック


例はもう少し複雑です-分散ロック。 githubのすべてのコードを見ることができます。ここでは、その作業の主な側面を簡単に説明します。



インターフェースから始めましょう。 ロックは次の操作をサポートしています。



ロックの最初の可能な実装は、キーと値のリポジトリに似ています。 lockAキーに何かがある場合、ロックが取得されます。それ以外の場合はロックが解除され、自分でロックを取得できます。 しかし、それほど単純ではありません。



まず、上記の例のkvリポジトリを変更せずに単に使用すると、要素の存在を確認する操作(ロックが取得されるかどうか)と要素を書き込む(ロックを取得する)操作はアトミックではありません(つまり、他の誰かのロックを上書きできます) ) したがって、ロックの確認と取得は、複製されたクラス(この場合はtryAcquireLock)内で実装される単一の操作である必要があります。



第二に、ロックを取得したクライアントの1つが落ちた場合、ロックは永久に(またはクライアントが持ち上げて解放するまで)ハングします。 ほとんどの場合、これは望ましくない動作です。 したがって、タイムアウトが発生すると、ロックが解除されたと見なされます。 また、ロックの取得を確認する操作(pingと呼びましょう)を追加する必要があります。これは、タイムアウト/ 4間隔で呼び出され、取得したロックの寿命を延ばします。



3番目の機能-複製されたクラスは、すべてのサーバーで同じ動作を提供する必要があります。 これは、内部で異なるデータを使用しないことを意味します。 たとえば、サーバー上のプロセスのリスト、ランダムな値、または時間。 したがって、時間を引き続き使用する場合は、時間を使用するクラスのすべてのメソッドにパラメーターとして渡す必要があります。



これを念頭に置いて、結果の実装は、複製オブジェクトであるLockImplとそのラッパーであるLockの2つのクラスで構成されます。 Lockの内部では、LockImplのすべての操作に現在の時刻が自動的に追加され、取得されたロックを確認するために定期的にpingが実行されます。 結果のロックは最小限の例にすぎず、必要な機能を考慮して変更できます。 たとえば、ロックの取得と解放について通知するコールバックを追加します。



おわりに



WOT BlitzプロジェクトでPySyncObjを使用して、異なる地域のサーバー間でデータを同期します。 たとえば、 IS-3 Defenderイベント中の残りの戦車のカウンター。 PySyncObjは、分散システムの既存のストレージメカニズムの優れた代替手段です。 主な類似物は、 Apache Zookeeperなどのさまざまな分散データベースです。 対照的に、PySyncObjはデータベースではありません。 これは低レベルのツールであり、複雑な状態マシンを複製できます。 さらに、外部サーバーを必要とせず、Pythonアプリケーションに簡単に統合できます。 現在のバージョンの欠点の中には、潜在的に最高のパフォーマンスではない可能性があります(現在は完全にpythonコードであり、c ++拡張として書き直そうとする計画があります)およびサーバー/クライアント部分への分離の欠如-時々、多くのクライアントノード(多くの場合、接続された/切断)と常に少数のサーバーのみを実行しています。



参照資料






All Articles