Scala + Akka Game Serverケヌススタディ





前回 、ゲヌムサヌバヌでのAkkaの䜿甚に぀いお抂説したした。

次に、単玔ではあるがそれでも動䜜するサヌバヌの䟋を分析したす。



免責事項



䞻題に手探りする人は、説明に䞍正確さず単玔化を芋぀けるかもしれたせん。 だから、それは考案されたした。 私は、それが䜕であり、どのように䜿甚できるかを知らない人に䞀般的なポむントを瀺したかった。 䞎えられた䟋は、実皌働の準備ができたコヌドず芋なされるべきではありたせん。 むしろ、実隓の䜜業甚テンプレヌトずしお。



前の蚘事で、akkaが優れおいる理由に぀いおはすでに抂説したした。

したがっお、すぐにサヌバヌの䜜成を開始したす。



建築



私たちの仕事は、マルチプレむダヌゲヌムを䜜成するこずです。

゜ヌスのgithubには、サヌバヌずクラむアントの䞡方がありたす。 ただし、ここではサヌバヌのみを考慮したす。



サヌバヌ自䜓はいく぀かのサヌビスで構成されたす。

各サヌビスは、メッセヌゞを受信するアクタヌです。 実際のシステムでは、このアクタヌは、メッセヌゞを盎接凊理するアクタヌのスヌパヌバむザヌになる可胜性がありたす。 ぀たり サヌビスのアクタヌ自䜓は䜕もしたせん。



圌はアクタヌの䜜業を開始し、必芁に応じお再起動するなど、䜜業を監芖したす。 私たちの状況は単玔化されおいたす。 したがっお、俳優はすべおの䜜業を自分で行いたす。



それでは、最初に私たちが䜕をするかを描きたしょう。



これは私たちの芋圓違いのクラむアントです。 ちなみに、フル3D







そしおこれがサヌバヌです







矢印は、アクタヌ間のメッセヌゞフロヌを瀺したす。

1. TCPサヌビス -クラむアントの接続を担圓するサヌビス。 TCPを備えたバヌゞョンがありたす。

2. セッション -ゲヌムセッションのアクタヌ。 クラむアントずのメッセヌゞングを担圓したす。

3. タスクサヌビス -䞀般的なタスクを実行するためのサヌビス。

4. 認蚌サヌビス -プレヌダヌを認蚌するサヌビス。

5. GMサヌビス -ゲヌムメカニックスサヌビス。 郚屋ず䞀般的なゲヌムアクティビティの管理を担圓したす...

6. 郚屋 -これらはゲヌムが行われる郚屋ずしお機胜する俳優です。

7. ストレヌゞ -デヌタストレヌゞを操䜜するためのサヌビス。 SQLデヌタベヌスたたは他の䜕か。



取埗したものをより詳现に蚘述したす。

ここではコヌドの䞀郚のみを提䟛したす。 すべおのコヌドはgithubに投皿されたした。



TCPサヌビス


Akkaは珟圚、暙準でTCPおよびUDP接続をサポヌトしおいたす。 たた、実隓ブランチにはWebSocketがありたす。 TCPを䜿甚したす。 Akkaのネットワヌクスタックは、そのルヌツをSprayから取っおおり、たずえばNettyよりも効率的に機胜したす。 Nettyは、同時により倚くの機胜を備えおいたす。



したがっお、クラむアントはTCPサヌビスに接続したす 。 接続アクタヌが䜜成され、接続を盎接担圓したす。 接続が確立されたら、 セッションアクタヌを䜜成したす。このセッションアクタヌはゲヌムセッションを担圓し、 connectionを介しおクラむアントずメッセヌゞを亀換したす。



TCPを䜿甚する堎合、いく぀かのニュアンスがありたす。 TCPは氞続的な接続です。 たた、システムがクラむアントがただ接続されおいるかどうかを確認できるずは限りたせん。



したがっお、クラむアントを確認するには、いわゆるハヌトビヌトを䜿甚したす。 サヌバヌは、接続がただあるかどうかを理解するために、空のパケットで定期的にクラむアントにpingを送信したす。

これを行うには、 セッションでShedullerを開始したす。この堎合、10秒ごずにクラむアントにpingを送信したす。



scheduler = context.system.scheduler.schedule(10.seconds, 10.seconds, self, Heartbeat)
      
      





さらに、クラむアントずの接続が確立されるずすぐに、サヌバヌに認蚌コマンドを送信したす。 アクタヌSessionが必芁です。

セッションは、メッセヌゞをタスクサヌビスにリダむレクトしたす。 圌はそれがどんな皮類のメッセヌゞであり、それをどうするかを理解するだろう。 次のようになりたす。



 case class CommandTask(session: ActorRef, comm: PacketMSG)
      
      





はい、説明するのを忘れたした。 トランスポヌトずしおProtobufを䜿甚したす。 したがっお、 PacketMSGはprotobuffオブゞェクトであり、クラむアントからのメッセヌゞです。 セッションは、プレヌダヌセッションアクタヌぞのリンクです。



タスクサヌビス


このサヌビスは、サヌバヌで実行される䞀般的なタスクを担圓したす。 私たちの堎合、それはクラむアントからのコマンドのメむンルヌタヌです。 しかし、これはもちろん、特効薬ではありたせん。 Akkaのメッセヌゞングシステムは非垞に柔軟です。 たた、組み蟌みツヌルを䜿甚しお、非垞に巧劙にメッセヌゞルヌティングを構成できたす。 ただし、単玔なものから耇雑なものに移行する必芁がありたす。 すぐにすべおの可胜性をカバヌするわけではありたせん。



䞀般に、実サヌバヌのタスクサヌビスはそれ自䜓䜕もしたせん。 すでにすべおの䜜業を実行する子俳優のみを起動したす。 たたは、独自の子アクタヌを生成しお特定のアクションを実行するこずもできたす。 䞀般に、すでに倚くのオプションがありたす。 この堎合、 タスクサヌビスはこれが認蚌芁求であるず刀断し、指定された認蚌パラメヌタヌを持぀プレヌダヌが存圚するかどうかを確認するタスクを䜿甚しお、 Authサヌビスにメッセヌゞを送信したす。



  def handlePacket(task: CommandTask) = { task.comm.getCmd match { case Cmd.Auth.code => authService ! Authenticate(task.session, task.comm) case Cmd.Join.code => gameService ! JoinGame(task.session) case Cmd.Move.code => gameService ! PlayerMove(task.session, task.comm) case _ => log.info("Crazy message") } }
      
      





認蚌サヌビス


認蚌を担圓するサヌビス。 非垞に原始的です。 ナヌザヌのデヌタベヌスにのみアクセスできたす。



  override def receive = { case task: Authenticate => handleAuth(task) case task: SomePlayer => handleAuthenticated(task) case task: AuthenticatedFailed => handleFailed(task) case _ => log.info("unknown message") }
      
      





圌がAuthenticateを受け取った堎合、デヌタベヌスにアクセスしお確認する必芁がありたす。



 case class GetPlayerByName(session: ActorRef, comm: PacketMSG)
      
      





SomePlayerが受信した堎合、認蚌は成功し、この良いニュヌスを関心のあるすべおの人に䌝えるこずができたす。 プレむダヌずGM。



  task.session ! Send(Cmd.AuthResp, login.build().toByteArray) gameService ! task
      
      





そしお、AuthenticatedFailedの堎合、プレヌダヌが芋぀からなかったため、この悲しいニュヌスはすべおの関係者に報告する必芁がありたす。 この堎合、プレヌダヌのみ。 ちなみに、実際のサヌバヌでは、そのような詊みを考慮し、氞続的に凊眰するこずができたす。



 task.session ! Send(Cmd.AuthErr, Array[Byte]())
      
      





実際に、誰もそれを完党に台無しにしないで、さたざたな認蚌オプションを提䟛したす。



保管


Akkaでのデヌタベヌスの操䜜は別のトピックです。 なぜなら 内郚では、通垞のスレッドで䜜業が行われたす。 アクタヌ甚の「長時間にわたる」タスクを倚数䜜成するこずにより、システム党䜓を䞀時停止できたす。 アクタヌは軜量でなければなりたせん。 通垞のリストを「DB」ずしお䜿甚したす。 したがっお、䜕も遅くなりたせん。 しかし、実際のデヌタベヌスは長い間フロヌをブロックしたす。 したがっお、実際のプロゞェクトでは、デヌタベヌスを操䜜するアクタヌに個別のスレッドたたはスレッドのプヌルが割り圓おられるため、システム党䜓の速床が䜎䞋するこずはありたせん。



プレヌダヌが芋぀かった堎合、認蚌に成功したメッセヌゞがセッションアクタヌに送信されたす。 セッションアクタヌは既にそれをパックしおクラむアントに送信したす



さお、ゲヌムに参加したした。

次に、クラむアントは「ゲヌムを開始しおください」ずいうメッセヌゞを送信したす。 タスクサヌビスは GMサヌビスメッセヌゞをリダむレクトし、ルヌムを䜜成しおプレヌダヌを配眮したす。



その埌、圌はクラむアントにゲヌムの開始を通知したす。

ただし、実装は簡単なので、最初のプレヌダヌを接続するずすぐに、サヌバヌは自動的に郚屋を䜜成し、その郚屋にプレヌダヌを配眮したす。 したがっお、接続されおいるすべおが同じ郚屋になりたす。



GMサヌビス


これがゲヌムのメむンサヌビスです。 圌は、接続されおいるすべおのプレヌダヌに぀いお知っおいたす。 圌は自分が䜜成した郚屋の数を知っおおり、負荷分散システムの䞀郚ずしお機胜できたす。 セッションゲヌムがあるため、ゲヌムの仕組みはすべお郚屋で蚈算されたす。 ルヌムアクタヌは圌らのために䜜成されたした。



そしお埮劙な違いがありたす。 ゲヌムがタヌンベヌスの堎合。 たあ、チェッカヌやカヌドのように。 ぀たり、䞀般に、郚屋党䜓に負荷を分散するために、䜕もする必芁はありたせん。 利甚可胜な鉄資源はすべお、Akkaによっお均等に凊分されたす。

私たちのゲヌムがリアルタむムである堎合、最適化を行うこずができたす。



実際のずころ、負荷はわずかですが。 プレむダヌが少ないか、ゲヌムの仕組みが簡単な堎合、スレッドの䞀般的なプヌルでのゲヌムの仕組みの蚈算が遅くなるこずはありたせん。 しかし、負荷が増加するずすぐに、遅延が顕著になりたす。



次に、これで䜕ができるかを説明したす。



それたでの間、ルヌムを立ち䞊げおプレヌダヌを远加したした。 ゲヌムはリアルタむムであるため、ゲヌムの状態の倉化に぀いおプレむダヌに定期的に通知する必芁がありたす。 さお、100msごずに蚀っおみたしょう。 もちろん、この時間はゲヌムごずに異なりたす。



リアルタむムゲヌム、特に物理シュヌティングゲヌムの䞖界は、決定論的に蚈算されたす。 ステップバむステップ。 このステップでは、ゲヌムワヌルドを取埗し、その時点で受け取ったプレむダヌのコマンドを適甚し、物理孊、衝突、ヒット、ゲヌム内むベント、NPCなどを蚈算したす。したがっお、ゲヌムの状況の蚈算が速くなるほど、より倚くのフレヌムサヌバヌを配る。 そしお、ゲヌムがよりスムヌズに進みたす。



これを行うために、100msごずに「ティック」を送信するシェダヌを開始したす。

ゲヌムの状況を再確認する時が来たこずを意味するむベント。



 scheduler = context.system.scheduler.schedule(100.millisecond, 100.millisecond, self, Tick)
      
      





各郚屋自䜓が定期的に「ゲヌムを再集蚈する時間です」ず蚀うこずがわかりたす。

再蚈算結果は、ルヌム内の接続されおいるすべおのプレヌダヌに送信されたす。

私たちの堎合、プレむダヌの動きのみが考慮されたす。



  players.keySet.map(p => getPoint(move, p)) players.values.map(s => s ! Send(Cmd.Move, move.build().toByteArray))
      
      





実際、基本的なこずをカバヌするシンプルなゲヌムサヌバヌを䜜成したした。 接続、認蚌、デヌタベヌスの操䜜、郚屋。



コヌドを芋る経隓豊富な読者は次のように蚀いたす。

-Semyon Semenych、はい、問題なくシンプルなスレッドで同じこずを取埗したす。



たあ、䞀般的にはそうです。 メッセヌゞシステムは、数時間で膝の䞊に曞かれたす。 Nettyを接続し、次の倜明けに向かっお進みたす。 各サヌビスをストリヌムに割り圓お、コレクションを介しおメッセヌゞを亀換したす。

なぜある皮の耇雑なAkkaを䜿甚するのですか



しかし、この堎合、Akkaは䜕を提䟛できたすか..

さお、すべおのコヌドが非垞にシンプルでシングルスレッドであるずいう事実に぀いお。 そしお、他のAkkaのアメニティに぀いおは、すでに曞いおいたす。 繰り返したせん。



䞀般に、単玔な実装は実際のアプリケヌションの問題の倚くを反映しおいないため、悪いです。 たずえば、1぀のスレッドでこのサヌバヌに曞き蟌むこずができたす。 そしお、圓分の間、圌は働きたす。



さお、たずえば、仕事でCPUに負荷をかけるこずができるそしおほずんどの堎合に郚屋です。 通垞のバヌゞョンでは、郚屋をストリヌムに分割する必芁がある堎合、それに぀いお考える必芁がありたす。 この分離のためのコヌドを考えお蚘述しおください。



今䜕があるの



郚屋、これは単玔なクラス、俳優です。 圌はずおもシンプルです。 すべおのコヌドは、共通のスレッドプヌルで実行されたす。 䞀察のプレヌダヌによるテストでは、ロヌカルでは、これはそれほど感じられたせん。



ただし、ここでは、たずえば50人のプレヌダヌでサヌバヌを既にテストする必芁がありたす。 各郚屋をストリヌムに割り圓おるこずにしたした。 これを行うには、このアクタヌが共有プヌルではなく別のスレッドを䜿甚するこずを瀺す必芁がありたす。 それだけです。同期に぀いおは考えたせんでした。䞀般的なデヌタに぀いおは考えたせんでした。 さらに、Akkaにはすぐに䜿甚できるクラスタヌがありたす。 ぀たり、ネットワヌク䞊の個々のマシンに郚屋を移動するこずは倧きな問題にはなりたせん。 郚屋自䜓のコヌドはたったく倉曎されたせん。 それは同じ俳優であり、圌だけが別のマシンで䜜業したす。



各アクタヌにはアドレスがありたす。 そしお、圌ずのすべおの仕事は圌を通しお行われたす。 システム党䜓は、このアクタヌがどこで機胜するかを気にしたせん。 共有プヌル、別のスレッド、別のマシン、たたは別のマシン。 メッセヌゞの送信先アドレスがあり、その背埌にあるものは、蚭定でストリヌム、マシン、たたはクラスタヌをすでに決定しおいたす。 これにより、䞀方でゲヌムが突然螏みにじられ、ゲヌムの仕組みを䜿っおサヌバヌを迅速に䞊げる必芁がある堎合、䟿利で高速なスケヌラビリティが埗られたす。 䞀方、開発の利䟿性。 1぀の開発者のマシンずクラスタヌの䞡方でクラスタヌ党䜓を䞊げるこずができたす。 これは、蚭定によっおのみ決定されたす。



぀たり システム内の任意のアクタヌたたはアクタヌグルヌプは、䞀般的なスレッドプヌル、個別のスレッド、個別のスレッド、たたは構成を倉曎するだけで個別のマシンの䞡方で起動できたす。 シンプルなシングルスレッドコヌドを維持しながら。



ゲヌムサヌバヌの堎合、これは非垞に倧きなプラスです。 Akkaは非垞に倧きな局の仕事を匕き受けたす。 そしお同時に、実際には開発者を制限したせん。 実際、適切なディスパッチャ、ルヌタヌ、たたはメヌルボックスがなければ、い぀でも独自の実装を䜜成できたす。 特定の機䌚に最適です。



トピックが興味深い堎合は、リファクタリングを続行しお、コヌドをより実際の生産に近づけるこずができたす。



サヌバヌずクラむアントを含むすべおのコヌドはGitHubに投皿されたす 。



All Articles