MagicOnionから始めるリアルタイム通信 (前編)

Happy Elements Advent Calendar 2020 7日目の記事です。

「<a href="https://mercstoria.happyelements.co.jp/">**メルクストーリア**</a>」エンジニアの 岸本 と申します。

今回は、**メルクストーリア** で現在開発の進めている **リアルタイム通信** の取り組みについて、
ご紹介したいと思います。

基本的な部分も含めてのご紹介になるため、
**MagicOnion**、**gRPC**、**Kubernetes**、**Agones** などなど、
マシマシな感じでかなりボリュームのある内容なため、2日に分割しての記事となります。

しばしお付き合いいただければ幸いですので、何卒よろしくおねがいします！

<font color="#FF0000">**本内容ですが、現在開発中のものであり、実際の仕様とは異なる場合があります！**</font>

## 前提

**メルクストーリア** は、おかげさまでもうすぐ7周年を迎えます！🎉🎉🎉

そんな **メルクストーリア** では、**チャット** や **ギルドバトル** をはじめ、
MMOのような見た目の **急襲！降臨モンスターイベント** など、
ユーザー様同士のマルチプレイができる遊びを多く提供していますが、
基本的には **ポーリング** で実現しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352646/65fafeb0-c53e-6ae6-6436-47ea647b5895.png" width="800" alt="1207_01.png">

**ポーリング** である以上、一定のタイムラグが発生し、
ユーザーの皆様にご不便をおかけしている部分もあると感じており、
さらなる遊びの提供やユーザー体験の向上を目標に、**リアルタイム通信** を用いた開発に取り組んでいます。

ただし、現在運用中の **Ruby on Rails (Ruby)** の APIサーバー が存在していますが、
そちらを **リアルタイム通信** に対応するのではなく、
**リアルタイム通信専用** に別系統のサーバーを立てて、**部分的なリアルタイム通信の導入** を目指しています。
なお、クライアントは **Unity** です。

| サーバー | フレームワーク | インフラ |
| --- | --- | --- |
| APIサーバー (既存) | Ruby on Rails (Ruby) | オンプレミス |
| リアルタイム通信サーバー (新規) | .NET (C#) | クラウド (AWS) |

また、インフラ面でも課題があり、
既存のAPIサーバーは、**オンプレミス** での運用に対して、
現在開発中のリアルタイム通信サーバーは、**クラウド (AWS)** での運用が前提になっています。

そのため、運用中のユーザーデータなどへのアクセスに制限があり、
**リアルタイム通信サーバー (クラウド)** と **APIサーバー (オンプレミス)** で通信することも可能ですが、
APIサーバーの負荷等も考慮すると、頻繁なアクセスはできないため、
基本的に運用中のユーザーデータへのアクセスはできない前提とします。

技術的にいろいろハイブリッドな構成となっていますし、
いろいろな事情も絡んで、**リアルタイム通信サーバー** で実現できることも限られていますが、
そのあたりも含めて、全体像をご紹介したいと思います。

## 使用している技術 (2020年12月時点)

### サーバーサイド

- .NET 5.0 (C# 9.0)
- MagicOnion 4.0
- MessagePack for C# 2.2
- ZLogger 1.3
- StackExchange.Redis 2.1
- System.Reactive

### クライアントサイド

- Unity 2018.4 (C# 7.3)
- MagicOnion 4.0
- MessagePack for C# 2.2
- UniTask 2.0

### インフラ

- Kubernetes 1.18
- Agones 1.0.9
- AWS
  - ElasticKubernetesService (EKS)
  - ElasticContainerRegistry (ECR)
  - ElastiCache (Redis)
  - SecretsManager
  - CodeBuild

## MagicOnion

<a href="https://github.com/Cysharp/MagicOnion">**MagicOnion**</a> とは、<a href="https://grpc.io/">**gRPC**</a> と <a href="https://github.com/neuecc/MessagePack-CSharp">**MessagePack for C#**</a> がベースの通信フレームワークで、
クライアントもサーバーも <font color="#FF0000">**「C#で大統一！」**</font> できる特徴があります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352646/8c2eb6f8-debd-aff2-60fb-05d63b26b841.png" width="800" alt="1207_02.png">

**MagicOnion** では、C# の **interface** で gRPC のメソッドを表現できるようになっていたり、
また、通信に用いるデータを **MessagePack for C#** でシリアライズすることで、
gRPCベース の **Remote Procedure Call** を利用できるようになっています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352646/32233424-1801-8fb7-c2e2-616aa56b80d5.png" width="800" alt="1207_03.png">

機能的には、**2種類** の通式方式がサポートされていて、
**単発のAPI通信に向いている「Service」** と、
**双方向通信も可能でリアルタイム通信に向いている「StreamingHub」** があり、
用途によって使い分けることができます。

また、**Filter** という共通処理のための仕組みや、
**Group** というマルチプレイのための仕組みなども実装されており、
通信まわりは一通り **MagicOnion** におまかせできるようになっています。

今回は、リアルタイム通信での使用を目的としていますが、
単純なAPIサーバーとしても使用できるようになっています。

## サーバー構成

さて、**MagicOnion** を用いることで **リアルタイム通信** は実装できそうですが、
インフラ面で問題が出てきます。

マルチプレイでの遊びを考えた場合、
複数ユーザー間でデータを同期する必要があり、どうやってデータを管理・共有するか問題になってきます。

方法としては、おおまかに 2種類 あります。

### ステートレスな構成

ロードバランサーによる分散を前提としたサーバー構成の場合、
当然、ユーザーの接続するサーバーもバラバラになるため、
マルチプレイに必要なデータは、**Redis Pub/Sub** など、
サーバー外部の共通ストレージで管理・共有することで解決できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352646/30b074d8-be2a-c4ef-8b3f-39c043110860.png" width="800" alt="1207_04.png">

**MagicOnion** には、**Redis Pub/Sub** での繋ぎ込みをサポートする、
**MagicOnion.Server.Redis** というライブラリも用意されています。

### ステートフルな構成

**サービスディスカバリー** という **リアルタイム通信サーバー** への誘導用サーバーを立てることで、
ロードバランサーによる分散はせず、
マルチプレイさせたいユーザー同士を直接同じサーバーに接続させ、
マルチプレイに必要なデータは、サーバー内部のメモリ上で管理・共有することで解決できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352646/8c7961d4-d0b0-be00-a3da-956e1098609b.png" width="800" alt="1207_05.png">

**MagicOnion** には、サーバー内でユーザー同士を束ねることのできる、
**Group** というブロードキャストシステムが内蔵されています。

### メルクストリーアでは

どちらで構成してもメリット・デメリットがあり、
一概にどっちが良いかはケースバイケースとなりますが、
今回は、**ステートフルな構成** を採用しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352646/cee2695a-f5f2-2d88-951e-24d3743c7dde.png" width="800" alt="1207_06.png">

マルチプレイさせたいユーザー同士をひとつのサーバーに集めて、**Group** で束ねることで、
**リアルタイム通信** を実現します。

たとえば、**チャットルーム** のような機能を実現したい場合、
チャットルームごとに **Group** を作って、ユーザー同士を束ねることができます。

**Group** は、**チャットルームID** のような任意の文字列で簡単に作ることができ、
**Group** に所属しているユーザーに対して、サーバーからクライアントへのブロードキャストが可能となります。

データの管理・共有は、共通のインスタンスを利用することで解決し、
どうしても外部に保存しておきたいデータのみ、外部のストレージに保存するようにします。

## 前編まとめ

ということで、前編は **MagicOnion** を用いた **マルチプレイなリアルタイム通信** を実現する上で、
基本的な仕組みについて、ご紹介させていただきました！

**gRPC** 含めて **サーバーサイドC#** ということだけであれば、
**.NET標準** だけでも十分開発できるようになっており、
先月リリースされた **.NET 5** では、**gRPC** のパフォーマンスもトップレベルで良くなっているようです。

**MagicOnion** は、**.NET標準** の実装に沿いつつ **MessagePack for C#** によるシリアライズもサポートし、
さらに **いい感じ** に **C#** で **gRPC** できるようになっています！

機能面でも複雑に多機能な感じではなく、基本的な部分は README や サンプル が用意されているため、
それほど学習コストも高くないように感じました。
どちらかというと、Kubernetes など他の技術の学習コストの方が高めです…。

明日は、本日の内容を前提に **Kubernetes** や **Agones** といったインフラ寄りの話題に加えて、
具体的な処理の流れなどをご紹介したいと思います。

引き続きお楽しみいただければ幸いです！

## メンバー募集

Happy Elements株式会社 カカリアスタジオでは、
いっしょに【熱狂的に愛されるコンテンツ】をつくっていただけるメンバーを大募集中です！

もし弊社にご興味持っていただけましたら、是非一度
下記採用サイトをご覧ください。
[Happy Elements株式会社 採用特設サイト](https://recruit.happyelements.co.jp/)