Elixirで気軽に分散ノード間で遠隔手続き呼出し（RPC）



分散Erlang・Elixirノード間の通信する際に使える便利な[rpc]というErlangモジュールがあります。ネット調べてもあまり情報が出てきません。忘れないうちにメモしておきます。

[rpc]は[phoenix_live_dashboard]で利用されています。

https://qiita.com/torifukukaiou/items/17d55cf896c24b13350e

## 一般論としてのRPC

[ウィキペディア][遠隔手続き呼出し]によると、一般論としては

> 遠隔手続き呼出し（英: remote procedure call、リモートプロシージャコール、略してRPC）とは、プログラムから別のアドレス空間（通常、共有ネットワーク上の別のコンピュータ上）にあるサブルーチンや手続きを実行することを可能にする技術。

## Erlangの[rpc]モジュール

Erlangの[rpc]モジュールはErlang・Elixirノード間限定で使用できるものであるようです。ですので、[BEAM (Erlang virtual machine)]が使えないシステムに対して[遠隔手続き呼出し]したい場合は別途調査が必要と思われます。

[ドキュメント][rpc]の内容をざっくり要約すると

- [遠隔手続き呼出し]に似た機能
- 遠隔ノードで情報を収集したり、 遠隔ノードで特定の副作用を伴う機能を実行したりするために使用される
- [:rpc.call/4]やそれに似た関数では、成功した結果、発生した例外、およびその他のエラーを区別することが非常に困難
- 互換性の理由により変更できないため、異なる結果を区別できる[erpc]モジュールがOTP 23から新しく導入された

ちなみに`erl -man`コマンドをOSのシェルに打ち込んでドキュメントを閲覧することもできます。

```sh:CMD
erl -man rpc
```

## 論よりRun

- 二つのノードをそれぞれ別々の[IEx]セッションで起動
- 二つのノードを接続
- [:rpc.call/4]関数を使い遠隔ノード内でコード実行

### ノードを起動

[IEx]セッションで起動時に以下のオプションを指定します。

- `--sname`オプションで一意のノード名を指定
- `--cookie`オプションに同じクッキー（クラスターの名前ようなもの）を指定

二つのノード（`hoge`、`fuga`）をそれぞれ別々の[IEx]セッションで起動します。

```sh:CMD
iex --sname hoge@localhost --cookie mycookie
```

```sh:CMD
iex --sname fuga@localhost --cookie mycookie
```

![ノードを起動.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82804/7ebcf175-1e25-e7b1-cb1b-eff8dc4cbd18.png)

### ノードを接続

ここでは`hoge`ノードの[IEx]から`fuga`ノードに接続します。逆でも構いません。

```elixir:hogeノードのIEx
:pong = Node.ping(:"fuga@localhost")
```

[Node.ping/1]は、接続に成功した場合は `:pong`、失敗した場合は`:pang`を返します。

新しいノードがクラスタに加わると、[メッシュネットワーク]が形成されます。クラスタの全てのノードは他の全てのノードに接続されます。

[Node.list/0]で現在接続されているノードのリストが確認できます。

![ノードを接続.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82804/d997774a-6d0e-4d42-2019-81862f76572a.png)

### 遠隔ノード内でコード実行

[:rpc.call/4]を用いて別ノード内でコードを実行します。[Node.spawn/2]と似ていますが、[:rpc.call/4]を使うと別ノード内で計算した結果を簡単に受信できます。

検証用に簡単な挨拶をする関数を定義します。この関数は[ノード](https://hexdocs.pm/elixir/Node.html#t:t/0)を受け取り、挨拶の[文字列](https://hexdocs.pm/elixir/String.html#t:t/0)を返します。

```elixir:それぞれのノードのIExに貼り付ける
defmodule Person do
  @spec say_hello(Node.t()) :: String.t()
  def say_hello(other_node) do
    "Hello, #{node_name_string(other_node)}! My name is #{node_name_string(Node.self())}."
  end

  defp node_name_string(other_node) do
    node
    |> to_string()
    |> String.split("@")
    |> hd()
    |> String.capitalize()
  end
end
```

`hoge`ノードの[IEx]から`fuga`ノードに挨拶させます。

```elixir:hogeノードのIEx
:rpc.call(:"fuga@localhost", Person, :say_hello, [node()])
```

`fuga`ノードの[IEx]から`hoge`ノードに挨拶させます。

```elixir:fugaノードのIEx
:rpc.call(:"hoge@localhost", Person, :say_hello, [node()])
```

![遠隔ノード内でコード実行.png](https://user-images.githubusercontent.com/7563926/205494453-c77d747a-fff6-41fa-ae17-259cc68e1a02.png)

この技を使うといろんな情報を遠隔ノードから収集することができます。

## [phoenix_live_dashboard]

[phoenix_live_dashboard]ではシステムの情報を収集する目的で[:rpc.call/4]が利用されています。

https://github.com/phoenixframework/phoenix_live_dashboard/blob/7c093249e570f483894133f53fbd0f228c83da0e/lib/phoenix/live_dashboard/system_info.ex#L18

## ご参考までに

https://speakerdeck.com/elijo/elixirkomiyunitei-falsebu-kifang-guo-nei-onrainbian

https://qiita.com/piacerex/items/e0b6e46b1325bb931122

https://qiita.com/torifukukaiou/items/1edb3e961acf002478fd

https://qiita.com/piacerex/items/e5590fa287d3c89eeebf

[Dashbit]: https://dashbit.co/
[Elixir]: https://elixir-lang.org/
[Erlang]: https://www.erlang.org/
[Phoenix]: https://www.phoenixframework.org/
[Nerves]: https://hexdocs.pm/nerves
[Livebook]: https://livebook.dev/
[IEx]: https://elixirschool.com/ja/lessons/basics/basics/#%E5%AF%BE%E8%A9%B1%E3%83%A2%E3%83%BC%E3%83%89
[Node | hexdocs]: https://hexdocs.pm/elixir/Node.html
[otp_distribution | elixirschool]: https://elixirschool.com/ja/lessons/advanced/otp_distribution
[Node.ping/1]: https://hexdocs.pm/elixir/Node.html#ping/1
[Node.connect/1]: https://hexdocs.pm/elixir/Node.html#connect/1
[Node.spawn/2]: https://hexdocs.pm/elixir/Node.html#spawn/2
[Node.list/0]: https://hexdocs.pm/elixir/Node.html#list/0
[Node.set_cookie/2]: https://hexdocs.pm/elixir/Node.html#set_cookie/2
[Node.get_cookie/0]: https://hexdocs.pm/elixir/Node.html#get_cookie/0
[epmd]: https://www.erlang.org/doc/man/epmd.html
[rpc]: https://www.erlang.org/doc/man/rpc.html
[erpc]: https://www.erlang.org/doc/man/erpc.html
[phoenix_live_dashboard]: https://github.com/phoenixframework/phoenix_live_dashboard
[phoenix_pubsub]: https://github.com/phoenixframework/phoenix_pubsub
[遠隔手続き呼出し]: https://ja.wikipedia.org/wiki/%E9%81%A0%E9%9A%94%E6%89%8B%E7%B6%9A%E3%81%8D%E5%91%BC%E5%87%BA%E3%81%97
[BEAM (Erlang virtual machine)]: https://en.wikipedia.org/wiki/BEAM_(Erlang_virtual_machine)
[:rpc.call/4]: https://www.erlang.org/doc/man/rpc.html#call-4
[メッシュネットワーク]: https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%83%E3%82%B7%E3%83%A5%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF