Cloud HaskellでSky Beesを書く



こんにちは、Habr!



ITMOのGoToフォールスクールの終わりからわずか11577635秒しかかかりませんでした。 Distributed Systems Weekは、Cloud Haskellでの分散システムのプロトタイピングから始まりました。 私たちは精力的に始めたため、博士号なしで既存のドキュメントを理解することは難しいことをすぐに発見し、トレーニングマニュアルを作成することにしました。



カットの下で、p2pクラウドhaskellの紹介、PCのプロトタイピング、やる気、そして「なぜ」のわずかに機能的なスタック。






あなたがそう分散した何かをしたいとします (%% sCoin) 既存のシステムではカバーされていません(YARNはすべての質問に対する答えではありません)。 手作業ですべてを始めると、特に目標が最終製品または美しい場合は、多重化接続や暗号化から、NATやピアルーティングの突破まで(人類史上初めてではない)本当に多くの問題をすばやく発見できます。 作業プロトタイプ。



そのような問題の声明から適用されたプログラマは、すぐに「ライブラリ」という単語を思いつきます。 そして本当に。 たとえば、標準のNATパンチメカニズム(STUN、TURN、ICE)も一般的に知られているKademliaから発見とルーティングのピースを取得できます。など



しかし、それでも多くの時間と専門知識が必要になります。 投資家には忍耐がないかもしれません。



ここでは、より経験豊富な同僚が「フレームワークが必要です!」という考えを思いつきます。 そして本当に。 分散システムおよびアプリケーションのプロトタイピング用。



そして誰かが言うことさえあります: 、それはlibp2pです! そして彼は正しいでしょう。 部分的に。



libp2pは、トランスポート、その多重化と暗号化、検出、ピアルーティング、NATブレークダウン、接続アップグレードなどの問題を解決します。 -一般的に、分散アプリケーションの多くのネットワークおよび暗号化のニーズ。 On GoとJS。



これは素晴らしいフレームワークですが、いくつかの問題があります。 これがGoとJSです。 さらに、レプリケーションのフレームワークに何かがあると便利です。






チュートリアルの断片的な性質(一部はまったく機能しませんでした)により、Cloud Haskellを使用しないように説得されました


http://www.scs.stanford.edu/14sp-cs240h/projects/joshi.pdf 、言い換え



私たちのプロジェクトは、Haskellでブロックチェーン(申し訳ありませんが、イノベーション)を作成するという野心から始まりました。したがって、libp2pはありませんでした。 私たちはネットワーク(トランスポート、ディスカバリー、シリアル化)を作るものを探し始めました。 Cloud Haskellが見つかりました。 ドキュメントが複雑であることがわかりました。 私の紹介を書くことにしました。 だから:



Cloud HaskellでSky Beesを書く



この例では、ミツバチのシステムを作成します。ハイブ(マシンのクラスター)とミツバチ(ノード)があります。 ミツバチは花を探すためにスカウトに送られ、おいしい花の座標とともに巣に戻ります。他のすべての蜂はこれらの座標を知っている必要があります。



プログラムを複数のコンピューターで実行する必要はまったくありません。プログラムを並行して実行するラップトップだけです。



完全なコードはリポジトリにあります



Cloud Haskellは、ノードが共通のリソーススペース(RAMなど)を共有しないため、ノード間でメッセージを交換するという原則に基づいて動作します(そのようなモデルはメッセージパッシングと呼ばれます )。 アクターモデルは、 アクターが他のアクターにメッセージを送信し、 メールボックスでメッセージを受信する場合のメッセージパッシングモデルのプライベートな例です。これが、Cloud Haskellでのメッセージパッシングの様子です。






1.最初に、ミツバチが代表するデータのタイプを定義します: src / Types.hs



type Flower = (Int, Int) --   type Flowers = GSet Flower -- Grow-Only Set 
      
      







2.ノードの実装を開始しましょう将来、コマンドラインから実行します: app / Main.hs



 main = do --   [port, bootstrapPort] <- getArgs -- (1)     bootstrap      let hostName = "127.0.0.1" -- IP  P2P.bootstrap --       : hostName port --   (\port -> (hostName, port)) initRemoteTable -- (2)  remote table [P2P.makeNodeId (hostName ++ ":" ++ bootstrapPort)] --    bootstrap  spawnNode --    ,     
      
      





  1. 最初に、ノードは何らかの方法でお互いについて学習する必要があります。つまり、 ピア発見を行います。 Cloud Haskellにはすぐに使用できるソリューションがあります。ノードを初期化するときに、少なくとも1つの他のブートストラップノードを指定するだけで十分です。ノードは、 Peer Exchangeノードをブートストラップで作成します-知っているノード(別名、 ピア )のアドレスを交換します。
  2. リモートテーブルは、ピアがシリアル化をサポートしている場合、ピアがhaskellタイプを交換できるようにするものです。つまり、ネットワークを介して送信し、Haskellオブジェクトに復元できる形式で表すことができます。 型はclass (Binary a, Typeable) => Serializable a



    実装する場合、直列化をサポートしますclass (Binary a, Typeable) => Serializable a



    Serializable



    Binary



    Typeable



    実装をTypeable



    で発明する必要はありません-haskellはこれをあなたのために行います(魔法の自動導出メカニズムを使用):



     {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} --  ,    Binary data Example = Example deriving (Typeable, Generic) instance Binary Example
          
          



    次に、簡潔にするためにinstance Binary



    deriving ...



    instance Binary



    およびプラグマを省略しinstance Binary





3.次に、ノードを起動するためのロジックを記述します。



 spawnNode :: Process () -- (1)     spawnNode = do liftIO $ threadDelay 3000000 --  bootstrap     let flowers = S.initial :: Flowers --  GSet     self <- getSelfPid -- (3)   Pid  REPL     repl <- spawnLocal $ runRepl self -- (2)  REPL    register "bees" self --        "bees" spawnLocal $ forever $ do -- (3)  : send self Tick --          liftIO $ threadDelay $ 10^6 --  0.1   ,     runNode (NodeConfig repl) flowers -- (5)  
      
      





  1. Cloud Haskellでは、主な機能単位はProcess



    (OSプロセスと混同しないでください)。 それらは軽量の緑のスレッドに基づいており、他のプロセスにメッセージを送信できます(特定のプロセスに送信するsend



    機能またはすべての使い慣れたノードに送信するP2P.nsendPeers



    )、 メールボックスでメッセージを受信します( expect



    またはreceive*



    機能)、他のプロセスを開始します(たとえば、ローカルでspawnLocal



    を使用するなど)
  2. REPLを別のスレッドに実装する必要があります。そうしないと、メインスレッド(ノード)がブロックされるため、REPLとノードの両方で変更できるようにGSetのスレッドセーフインターフェイスを作成する必要があります。 システムはアクターに基づいているため、セットを変更するメッセージを送信し、メインスレッドでメッセージを処理する無限のサイクルで順次処理します。
  3. REPLを個別のクラウドハスケルプロセスとして(つまり、緑色のスレッドとして)実行し、メインプロセスのPid(一意のプロセス識別子)を渡して、REPLがユーザーが入力したコマンドをメッセージの形式で送信する場所を認識できるようにします。 次に、PID REPL(spawnLocalが返す)を取得して、コマンドへの応答を送信します。 REPLコードはこちらです。
  4. 花の複製はどのように機能しますか?

    • 各ノードはその状態を定期的にすべてのピアに送信し( ブロードキャスト )、CRDTとともにこれにより複製の問題を解決します。

      ノードA



      B



      があるとしますB



      A



      は要素xがなく、 B



      xがあるとします。 B



      がブロードキャストを行った後、 A



      xを追加します-コンセンサスに達しました、など。

      GSetではなく通常のセットがあれば、何も起こりませんでしたA



      B



      要素yがあるとします。 A



      yを削除させます。 B



      がブロードキャストを行った後、 A



      yを返します。
    • すべてのノードにメッセージを送信する場合、 サービスの名前を指定する必要があります。実際、このサービスをサポートするものとしてレジスタに登録されているノードにのみメッセージを送信します。 ここでは、ノードを「bees」サービスをサポートするものとして登録します。 register "bees" self



      ます。
    • 野田は、他の人にいつ幸運を送るべきかを知らなければなりません。 最も簡単な解決策は、タイマーでそれを行うことです。1秒待ってから行動しますが、その後、メインのメッセージ処理フローをブロックします。 ここでは、 spawnLocal



      spawnLocal



      てプロセスを開始します。最初にTickメッセージをメインプロセスに送信し(メインプロセスがTickを検出すると、その状態をノードに送信します)、1秒待機して繰り返します。


4. OK、今(最終的に!)メインプロセスのロジック(ノード実行コード)を開始できます。



 runNode :: NodeConfig -> Flowers -> Process () -- (1)    runNode config@(NodeConfig repl) flowers = do let run = runNode config receiveWait -- (2)   [ match (\command -> -- (3)    -  Command  REPL,  newFlowers <- handleReplCommand config flowers --     run newFlowers) , match (\Tick -> do --   ,        P2P.nsendPeers "bees" flowers --     run flowers) , match (\newFlowers -> do -- -    run $ newFlowers `union` flowers) --     -     ]
      
      





  1. シグネチャを見てみましょうrunNode



    は、タイプNodeConfig



    ノードの構成を受け入れます-実行時に変更されない情報。 私たちの場合、これは単なるPID REPLです。 彼女は現在の状態である花のGSetも受け入れます。 しかし、GSetは不変のデータ型なので、花を追加する方法は? 非常に簡単:関数を再帰的にし、状態が変化するたびに関数を再起動します。
  2. receiveWait



    は、1つの引数(着信メッセージ)を持つ関数のリストを受け取り、メッセージを引き出して、メッセージのタイプに適した関数を呼び出します。
  3. このタイプのメッセージを受信した場合: data Command = Add Flower | Show



    data Command = Add Flower | Show



    、これはREPLからのコマンドです。 handleReplCommmand



    コマンドを処理するための関数:



     handleReplCommand :: NodeConfig -> Flowers -> Command -> Process Flowers handleReplCommand (NodeConfig repl) flowers (Add flower) = do --      send repl (Added flower) --  REPL ,    return $ S.add flower flowers --       handleReplCommand (NodeConfig repl) flowers Show = do --    send repl (HereUR $ toList flowers) --      return flowers
          
          



  4. ティックがティッカーから来た場合、ステータスを送信する必要があります: P2P.nsendPeers "bees" flowers



    。 ここで、「bees」はサービスの名前です。つまり、「bees」として登録されているノードにのみ花を転送します。
  5. 他のミツバチから花を受け取った場合、馴染みのない花をすべて自分自身に追加する必要があります。つまり、多くの新しい花と多くの既存の花を単純に組み合わせます。


5.以上です! 完全なソースコードをダウンロードしてコンパイルします。



 git clone https://github.com/SenchoPens/cloud-bees.git cd cloud-bees stack setup # Stack  GHC stack build # 
      
      





1つのターミナルで次の行を実行します。



 stack exec cloud-bees-exe 9000 9001 2>/dev/null
      
      





そして別のこれで:



 stack exec cloud-bees-exe 9001 9000 2>/dev/null
      
      





REPLがプロンプトを出します。 1つの端末にAdd (1, 2)



を追加してみてください。 座標(1、2)を持つ花を追加し、別の-Showで、2番目のノードにもそのような花があることがわかります。








Haskellで分散システムを作成することはそれほど怖くないことをあなたに納得させていただければ幸いです:)



同様のシステムの多くの実際のユーザーケースを考え出すことができます:たとえば、公共交通機関で野ウサギの問題を解決する:カード上の交通機関に通行する人はログイン済みとしてマークされ(最初のGSetに自分のIDを追加)、出力時に-ログアウト済みとして(IDを追加します) 2番目のGSet)。 夜(輸送が機能しないとき)に、チェックが行われます-人が出入りした場合、彼は野ウサギではありません。



興味のある方は、シフト中に行った暗号化を使用したより大きなプロジェクトをご覧ください。






愛を込めて、アーセニーと仲間、9年生。 wldhxの穏やかな指導の下で



All Articles