[elixir!#0070]不死のネットワーク,細数BeamクラスタとBitcoinの類似点


ljzn(下)のコラムをよく読む友达は、beam仮想マシンとbitcoinネットワークの2つの技術が普段一番好きであることを知っているかもしれません.その原因は,両者とも永生的なネットワーククラスタの構築を追求している可能性がある.ターゲットが似ている場合は、実装方法が似ているに違いありません.distributed erlangとbitcoin networkがどれだけ似ているかを調べてみましょう.
フルコネクションネットワーク
Bitcoin
Bitcoinの鉱夫ノード間は高度につながっており,これはビットコインの掘削機構によって決定される.新しいブロックヘッダには、前のブロックを含むhashが必要です.言い換えれば、鉱夫はネットワークに新しいブロックが現れるかどうかを常に観察し、最新のブロックの後ろについて掘削しなければなりません.そうでなければ、鉱夫が掘った塊は孤立した塊になり、奨励金が得られない可能性が高い.1秒ごとに新しいブロックが観察され、鉱夫の計算力は1秒も浪費された.また、鉱夫も自分が掘ったブロックを他のすべての鉱夫に自発的に送信します.1秒ごとに送信されるため、自分のブロックが孤塊になる可能性が1分増加します.最良の戦略は、他のすべての鉱夫のノードに接続することです.
Beam
分散Erlangのノード間はフルコネクションである.私たちは小さなテストをすることができます.
  • 2つの端末で2つのerlang nodeを起動する:
  • $ iex --sname bob@localhost
    $ iex --sname carl@localhost
  • carlを使用してbobに接続:
  • iex(carl@localhost)2> Node.connect :bob@localhost
    true
    iex(carl@localhost)3> Node.list
    [:bob@localhost]
  • carl:
  • をbobで見ることができます.
    iex(bob@localhost)4> Node.list
    [:carl@localhost, :alice@localhost]
  • 新しい端末でaliceを起動しbob:
  • に接続
    zhanglinjie@MacBook-Pro-2 ~ % iex --sname alice@localhost
    Erlang/OTP 22 [erts-10.7.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
    
    Interactive Elixir (1.10.3) - press Ctrl+C to exit (type h() ENTER for help)
    iex(alice@localhost)1> Node.connect :bob@localhost
    true
  • carlでaliceが表示されます.分散erlangのノードは、新しく追加されたノードを他のノードと接続するようにアクティブに誘導するからです.
  • iex(carl@localhost)4> Node.list
    [:bob@localhost, :alice@localhost]

    グローバルロック
    Bitcoin
    Bitcoinはブロックを使用して帳簿全体の状態を更新し、ブロックチェーン全体に1つのロックしかないと言える--ブロック.例えば、現在のブロックの高さは60001であり、すべての鉱夫は60002ブロックの所有権を競争しているが、勝った鉱夫は60002ブロックを使用して帳簿状態を更新することができ、その後、すべての鉱夫は60003ブロックの競争に入る.
    競合が発生した場合、Bitcoinは鉱夫Aと鉱夫Bが同時に高さ60002のブロックを爆発させ、最初にネットワークに公開するなどの「ゲーム」モデルを採用している.鉱夫Cはまず鉱夫Aのブロックaを受け入れ、ブロックaを受け入れ、その上で60003ブロックを掘り始めた.次に、短い時間で、鉱夫Cはまた鉱夫Bのブロックbを受け取った.このとき、鉱夫Cはすぐにそれを捨てることはできない.aとbのブロックはすべてネットで認められた60002ブロックになる可能性があるため、同時に、aとbのブロックはすべて孤立ブロックになる可能性がある.したがって、最良の戦略は、2つのブロックを候補として保存するとともに、どのブロックが全網で受け入れられる可能性が高いかを判断し、そのブロックを掘り下げることである.
    では、大量の計算力を占めている鉱夫がブロック独占を実現する可能性がありますか?(分散ロックの文脈から見ると,ロックの所有権の引き渡しを拒否する).実際にはこのような行為は経済的ではありません.さっきの状況で、時間T 0がブロックaとbを爆発させたと仮定します.時間T 1で鉱夫がブロックbの後に新しいブロックbを爆発させたと仮定すると、鉱夫Aがブロックaで選挙に勝つのは難しいです.鉱夫がブロックaを9分掘ってブロックを出さなかったからといって、次の分以内にaを掘り出す可能性が増えるわけではない.道理も9回コインを投げたからといって反対で、10回目の正面の確率は増加しない.このとき、鉱夫Aがaで掘り続けると、その相手は網全体の他の鉱夫であり、b’で鉱山を掘り始めたからだ.
    この時、鉱夫Aにとってgood endingは新しいa'を掘り起こし、a'とb'が公平に競争し続ける(高度は同じ).bad endingは他の鉱夫が時間T 2でb''を掘り、ブロックaは2つのブロックに遅れ、他の鉱夫に受け入れられる可能性が低下し続ける.good endingとbad endingの発生確率比は鉱夫Aとネットワーク中の残りの鉱夫の計算力比に等しい.good endingの収益は0(競争を続けるため)、bad endingの収益は-(A T1 T2 )*(b'' )である.このように算出する「b′を見た後もa上で掘削を継続する」という収益の期待は常に負である.
    Beam
    分散erlangではグローバルロックの機能が提供されている.
  • aliceでグローバルロックを取得し、ロックされたidは:lock1であり、メタグループの2番目の要素はリクエスト者のid
  • である.
    iex(alice@localhost)12> :global.set_lock {:lock1, self()}                 
    true
  • carlでこのロックを取得しようとすると、保留されていることがわかります.この場合はタイミングで再試行取得:
  • iex(carl@localhost)6> :global.set_lock {:lock1, self()}
    
    
  • aliceでこのロックを解放:
  • iex(alice@localhost)13> :global.del_lock {:lock1, self()}                 
    true
  • 数秒後、carl取得成功:
  • iex(carl@localhost)6> :global.set_lock {:lock1, self()}
    true

    内部実装から見ると,分散erlangのグローバルロックは「縮小」戦略を採用している.コアのコードは次のとおりです.
    check_replies([{_Node, false=Reply} | _T], Id, Replies) ->
        TrueReplyNodes = [N || {N, true} 

    ここでの論理は、ノードAが分散ロックLを取得したいと考えており、他のノードにロックLを占有するように伝えている.他のノードがメッセージを受け取った後、自分のローカル状態をチェックすると、ロックLが占有されていないことに気づいたら、ロック状態を更新し、trueに返信し、falseに返信します.ノードAはすべての返信がtrueの場合にロックの占有を完了し、1つのノードがfalseに戻ると、ノードAはすぐに「縮退」し、trueに戻ったすべてのノードにメッセージを送り、急いでさっきの記録を削除させます.
    したがって、競合が発生した場合、双方が萎縮し、占有プロセスに競合が存在しないまで再試行する可能性があります.
    グローバル登録名
    Bitcoin
    Bitcoinは乱数を用いて各ユーザが一意のアドレスを持つことを保証し,ユーザは256 bitsの秘密鍵を用いて公開鍵(さらにアドレスに変換)を生成し,重複する可能性は微々たるものである.欠点も明らかで、住所は人間が記憶しにくい長さであり、第三者の登録名管理サービス、例えば取引所、財布などが管理を助ける必要がある.
    現在、Bitcoinには「読み取り可能な登録名」->「アドレス」のマッピングを管理するオリジナルの手段はありませんが、ビットコインスクリプトで実現できる可能性があります.これは依然として最先端の技術であり、しばらくは表にありません.
    Beam
    erlangでは、各プロセスに一意のpidを持たせるために、自己増加カウンタを使用します.リモートノードのプロセスでは、pidにノードの情報が追加され、グローバルに一意になります.また、erlangはグローバルな登録名管理メカニズムを提供しています.
  • carlでshellプロセスを登録:god
  • iex(carl@localhost)10> :global.register_name :god, self()    
    :yes
  • bobで登録を試みる:god、戻る:no、登録に失敗したことを示し、すでに占有されているため
  • iex(bob@localhost)17> :global.register_name :god, self()
    :no
  • carlでログアウト:god
  • iex(carl@localhost)14> :global.unregister_name :god
    :ok
  • 現在bobに登録可能:god
  • iex(bob@localhost)18> :global.register_name :god, self()
    :yes

    内部実装は,上述したグローバルロックを用いた.登録名情報を変更するときは、まずロックを取得します.ここではいくつかの最適化を行い,まず1つのノード(The Boss)でロックを取得しようとしたが,成功した後に他のすべてのノードでロックを取得しようとした.
        %% Use the  same convention (a boss) as lock_nodes_safely. Optimization.
        case set_lock_on_nodes(Id, [Boss]) of
            true ->
                case lock_on_known_nodes(Id, Known, Nodes) of
                    true ->
                        Nodes;
                    false -> 
                        del_lock(Id, [Boss]),
                        random_sleep(Times),
                        set_lock_known(Id, Times+1)
                end;
            false ->
                random_sleep(Times),
                set_lock_known(Id, Times+1)
        end.

    小結
    この2つの異なる技術をざっと比較して、興味深い共通点を発見しました.
  • フルコネクションネットワーク(yes)
  • グローバルロック(yes)
  • グローバル登録名(maybe)
  • この話題に興味があるなら、「いいね」ボタンを強くたたいてみましょう.