Share
Embed
PHPでアクターモデルを理解・体験しよう / Understand and experience the actor model in PHP
This presentation was given at PHP Conference Okinawa 2024.
Welcome to
the World of the Actor Model.
yuuki takezawa
September 24, 2024
Transcript
89 slides
-
PHPͰΞΫλʔϞσϧΛཧղɾମݧ͠Α͏ Understand and experience the actor model in PHP phpcon
okinawa 2024 yuuki takezawa a.k.a ytake -
Pro fi le • ᖒ ༗و a.k.a ytake • ઍגࣜձࣾ
CTO / ΄͔ٕज़ސʢωοτϓϩςΫγϣϯζͳͲʣ • Go / Scala / Kotlin / PHP • ΞΫλʔϞσϧେ͖ -
Phluxor • Phluxor / Actor System • Phluxor Persistence /
Event Sourcing etc.. • Phluxor Remote / Network (Experimental / WIP) • Phluxor Cluster / Virtual Actors (WIP) -
What is The Actor Model? • 1973ʹൃද͞ΕͨฒߦܭࢉͷֶతϞσϧͷҰछ A type of
mathematical model for concurrent computation introduced in 1973. • ErlangScalaͳͲͰ͓ͳ͡Έ Well-known in languages like Erlang and Scala. -
What is The Actor Model? • ϝοηʔδΛ༻͍ͯΓऔΓΛߦ͏ Communication is performed
using messages. • ΞΫλʔ Ϟσϧɺ͕ͯ͢ΞΫλʔ The actor model adopts the philosophy that everything is an actor. -
Actor Responsibilities • ΞΫλʔΛੜ͢Δ Create Another Actor • ϝοηʔδΛૹ৴͢Δ Send
a Message • ϝοηʔδʹద༻͢Δಈ࡞Λߦ͏ Handle Message -
Internal State • No Share Memory • State Change ->
Event, and Persistence • Immutable Message -
Actor Reference • ͯ͢ͷΞΫλʔΞυϨεΛ࣋ͭ All actors have an address. •
͜ͷΞυϨεϢχʔΫͰࣝผ͞ΕΔ This address is unique and identi fi able. • ૹ৴ݩૹ৴ઌΛܾΊΔ͜ͱ͕Ͱ͖Δ You can specify the sender and recipient. -
Actor Lifecycle • ͯ͢ͷΞΫλʔϥΠϑαΠΫϧΛ࣋ͭ All actors have a lifecycle •
ΞΫλʔͷఀࢭɺ࠶ىಈͳͲ including stopping, restarting, etc. • Կཁٻ͞Εͳ͍߹Կ͠ͳ͍ If nothing is requested of them, they do nothing. -
Hierarchy • ΞΫλʔΞΫλʔΛੜͰ͖Δ Actors can create actors. • ࢠ͕ؔܗ͞ΕΔ Parent-child
relationships are formed. • ࢠΞΫλʔʹࣄ͕ґཔͰ͖Δ You can assign tasks to child actors. -
Fault Tolerance • ΞΫλʔ͕ࢠΞΫλʔΛಜɾཧ͢ΔΛ࣋ͭ Parent actors have the responsibility to
supervise and manage child actors. • ࢠؔʹͳ͍ΞΫλʔࢹ͢Δ͜ͱ͕Ͱ͖Δ Actors that are not in a parent-child relationship can also be monitored. -
Fault Tolerance • ࣦഊ࣌ʹͲͷΑ͏ʹ෮چͤ͞Δ͔ΛࢦࣔͰ͖Δ You can instruct how to recover
in case of failure. • ΞΫλʔͷোଞʹ͠ͳ͍ Actor failures do not propagate to others. -
<?php declare(strict_types=1); namespace Example; use Example\Message\Hello; use Phluxor\ActorSystem\Context\ContextInterface; use Phluxor\ActorSystem\Message\ActorInterface;
use Phluxor\ActorSystem\Props; use Phluxor\ActorSystem\ProtoBuf\Terminated; class ParentActor implements ActorInterface { public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Hello: $ref = $context->spawn(Props::fromProducer(fn() => new ChildActor())); $context->send($ref, $message); break; case $message instanceof Terminated: $context->logger()->info('terminated', [ 'who' => $message->getWho()->getId(), 'why' => $message->getWhy(), ]); break; } } } -
<?php declare(strict_types=1); namespace Example; use Example\Message\Hello; use Phluxor\ActorSystem\Context\ContextInterface; use Phluxor\ActorSystem\Message\ActorInterface;
use Phluxor\ActorSystem\Props; use Phluxor\ActorSystem\ProtoBuf\Terminated; class ParentActor implements ActorInterface { public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Hello: $ref = $context->spawn(Props::fromProducer(fn() => new ChildActor())); $context->send($ref, $message); break; case $message instanceof Terminated: $context->logger()->info('terminated', [ 'who' => $message->getWho()->getId(), 'why' => $message->getWhy(), ]); break; } } } 1BSFOU"DUPS -
<?php declare(strict_types=1); namespace Example; use Example\Message\Hello; use Phluxor\ActorSystem\Context\ContextInterface; use Phluxor\ActorSystem\Message\ActorInterface;
use Phluxor\ActorSystem\Props; use Phluxor\ActorSystem\ProtoBuf\Terminated; class ParentActor implements ActorInterface { public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Hello: $ref = $context->spawn(Props::fromProducer(fn() => new ChildActor())); $context->send($ref, $message); break; case $message instanceof Terminated: $context->logger()->info('terminated', [ 'who' => $message->getWho()->getId(), 'why' => $message->getWhy(), ]); break; } } } $SFBUF$IJME "DUPS -
<?php declare(strict_types=1); namespace Example; use Example\Message\Hello; use Phluxor\ActorSystem\Context\ContextInterface; use Phluxor\ActorSystem\Message\ActorInterface;
use Phluxor\ActorSystem\Message\Restarting; class ChildActor implements ActorInterface { public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Restarting: $context->logger()->info('restarting...'); break; case $message instanceof Hello: $context->logger()->info('Hello ' . $message->name); $context->stop($context->self()); break; } } } $IJME"DUPS -
<?php declare(strict_types=1); namespace Example; use Example\Message\Hello; use Phluxor\ActorSystem\Context\ContextInterface; use Phluxor\ActorSystem\Message\ActorInterface;
use Phluxor\ActorSystem\Message\Restarting; class ChildActor implements ActorInterface { public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Restarting: $context->logger()->info('restarting...'); break; case $message instanceof Hello: $context->logger()->info('Hello ' . $message->name); $context->stop($context->self()); break; } } } 4UPQ4FMG -
<?php declare(strict_types=1); namespace Example; use Example\Message\Hello; use Phluxor\ActorSystem\Context\ContextInterface; use Phluxor\ActorSystem\Message\ActorInterface;
use Phluxor\ActorSystem\Props; use Phluxor\ActorSystem\ProtoBuf\Terminated; class ParentActor implements ActorInterface { public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Hello: $ref = $context->spawn(Props::fromProducer(fn() => new ChildActor())); $context->send($ref, $message); break; case $message instanceof Terminated: $context->logger()->info('terminated', [ 'who' => $message->getWho()->getId(), 'why' => $message->getWhy(), ]); break; } } } /PUJ fi DBUJPO GSPN$IJME -
<?php declare(strict_types=1); namespace Example; use Example\Message\Hello; use Phluxor\ActorSystem\Context\ContextInterface; use Phluxor\ActorSystem\Message\ActorInterface;
use Phluxor\ActorSystem\Message\Restarting; class ChildActor implements ActorInterface { public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Restarting: $context->logger()->info('restarting...'); break; case $message instanceof Hello: $context->logger()->info('Hello ' . $message->name); throw new \Exception('hi, I am an exception'); break; } } } UISPX&YDFQUJPO -FUJUDSBTI -
ActorInterface • No Return / Only sending and receiving messages.
• All actors have a receive method that receives a context. • All actors are asynchronous and independent. -
<?php declare(strict_types=1); namespace Phluxor\ActorSystem\Message; use Phluxor\ActorSystem\Context\ContextInterface; interface ActorInterface { /**
* Receives a context. * * @param ContextInterface $context The context to receive. * @return void */ public function receive(ContextInterface $context): void; } -
Spawning Actors • spawn method is used to create a
child actor or root actor. • spawnNamed method is used to create a named actor. • spawnPre fi x method is used to create an actor with a pre fi x. • When an actor is spawned, Phluxor\ActorSystem\Ref is returned. -
use Phluxor\ActorSystem; use Phluxor\ActorSystem\Props; $system = ActorSystem::create(); $spawned = $system->root()->spawn(
Props::fromProducer( fn() => new YourActor() ) ); 8IFODSFBUJOHDIJMEBDUPST UIFZBSFHFOFSBUFEGSPNUIFBDUPSDPOUFYU 5IFJOUFSGBDFJTUIFTBNFBTUIFSPPU -
$context->spawnNamed( Props::fromProducer( fn() => new YourChildActor() ), 'unique-name' ); $context->spawnPrefix(
Props::fromProducer( fn() => new YourChildActor() ), 'prefix-' ); "TTJHOBOZOBNFUPUIFBDUPS *GZPVBSFVTJOHQFSTJTUFODF BTTJHOBVOJRVFTQFDJ fi DOBNFSBUIFSUIBOBSBOEPNPOF DSFBUFBOBDUPSXJUIBQSF fi Y -
Props • The Props are a con fi guration object
used to create an actor. • Phluxor\ActorSystem\Props is a class that creates a Props object. • and more!!!! -
Props::fromProducer(fn() => new YourActor()) Props::fromFunction( new ActorSystem\Message\ReceiveFunction( fn( ActorSystem\Context\ContextInterface $context
) => $context->message() ) ); :PVDBOTQFDJGZBDMBTTUIBUJNQMFNFOUTUIF"DUPS*OUFSGBDF PSVTFBGVODUJPOTUZMF -
Props::fromProducer( fn() => new VoidActor(), ActorSystem\Props::withSupervisor( new ActorSystem\Strategy\OneForOneStrategy( 10, new
\DateInterval('PT10S'), fn() => ActorSystem\Directive::Restart ) ) ); Props::fromProducer( fn() => new YourActor(), Props::withMailboxProducer(new ActorSystem\Mailbox\Unbounded()) ); :PVDBONPEJGZUIF.BJMCPY 5IFSFBSFPQUJPOTGPS#BUDI #PVOEFE BOE6OCPVOEFENBJMCPYFT :PVDBONPEJGZUIFTVQFSWJTPSTUSBUFHZ TJNJMBSUP"LLB 1FLLP PS1SPUP"DUPS -
Send Message • send is a non-blocking, fi re-and-forget •
request is very similar to send, Only use it when request/reply communication is required between two actors. • Futures and promises -
$context->send($ref, new PrepareTest(['subject' => $msg->getSubject()]) ); $context->request($ref, new PrepareTest(['subject' =>
$msg->getSubject()]) ); $future = $context->requestFuture( $ref, new PrepareTest(['subject' => $msg->getSubject()]), 1 ); 5IFSFTVMUJTFYQFDUFEUPCFPCUBJOFEXJUIJOUIFTQFDJ fi FEUJNF *GOPU BOFSSPSXJMMCFSFUVSOFEBTUIFSFTVMU -
example (the answer sheet • Class time begins • The
teacher comes to the classroom • Test begins • Write your answers on the answer sheet. • Submit test answer sheets -
readonly class ClassroomActor implements ActorInterface { // omit public function
receive(ContextInterface $context): void { $msg = $context->message(); switch (true) { case $msg instanceof StartsClass: $ref = $context->spawn( Props::fromProducer( fn() => new TeacherActor( $this->subject, $this->students, $context->self() ) ) ); $context->send($ref, new PrepareTest($msg->subject)); break; case $msg instanceof FinishTest: $context->send( $this->stream, new ClassFinished($msg->subject) ); \Swoole\Coroutine::sleep(0.1); $context->stop($context->self()); break; } } } -
readonly class ClassroomActor implements ActorInterface { // omit public function
receive(ContextInterface $context): void { $msg = $context->message(); switch (true) { case $msg instanceof StartsClass: $ref = $context->spawn( Props::fromProducer( fn() => new TeacherActor( $this->subject, $this->students, $context->self() ) ) ); $context->send($ref, new PrepareTest($msg->subject)); break; case $msg instanceof FinishTest: $context->send( $this->stream, new ClassFinished($msg->subject) ); \Swoole\Coroutine::sleep(0.1); $context->stop($context->self()); break; } } } 'SPNSPPU -
readonly class ClassroomActor implements ActorInterface { // omit public function
receive(ContextInterface $context): void { $msg = $context->message(); switch (true) { case $msg instanceof StartsClass: $ref = $context->spawn( Props::fromProducer( fn() => new TeacherActor( $this->subject, $this->students, $context->self() ) ) ); $context->send($ref, new PrepareTest($msg->subject)); break; case $msg instanceof FinishTest: $context->send( $this->stream, new ClassFinished($msg->subject) ); \Swoole\Coroutine::sleep(0.1); $context->stop($context->self()); break; } } } 4QBXO5FBDIFS -
readonly class ClassroomActor implements ActorInterface { // omit public function
receive(ContextInterface $context): void { $msg = $context->message(); switch (true) { case $msg instanceof StartsClass: $ref = $context->spawn( Props::fromProducer( fn() => new TeacherActor( $this->subject, $this->students, $context->self() ) ) ); $context->send($ref, new PrepareTest($msg->subject)); break; case $msg instanceof FinishTest: $context->send( $this->stream, new ClassFinished($msg->subject) ); \Swoole\Coroutine::sleep(0.1); $context->stop($context->self()); break; } } } 4FOEBNFTTBHFUPZPVSUFBDIFS -
readonly class ClassroomActor implements ActorInterface { // omit public function
receive(ContextInterface $context): void { $msg = $context->message(); switch (true) { case $msg instanceof StartsClass: $ref = $context->spawn( Props::fromProducer( fn() => new TeacherActor( $this->subject, $this->students, $context->self() ) ) ); $context->send($ref, new PrepareTest($msg->subject)); break; case $msg instanceof FinishTest: $context->send( $this->stream, new ClassFinished($msg->subject) ); \Swoole\Coroutine::sleep(0.1); $context->stop($context->self()); break; } } } 3FQMZUPUIFTQFDJ fi FEBDUPS 0ODFBMMTUVEFOUTIBWFDPNQMFUFEUIFUFTU -
readonly class ClassroomActor implements ActorInterface { // omit public function
receive(ContextInterface $context): void { $msg = $context->message(); switch (true) { case $msg instanceof StartsClass: $ref = $context->spawn( Props::fromProducer( fn() => new TeacherActor( $this->subject, $this->students, $context->self() ) ) ); $context->send($ref, new PrepareTest($msg->subject)); break; case $msg instanceof FinishTest: $context->send( $this->stream, new ClassFinished($msg->subject) ); \Swoole\Coroutine::sleep(0.1); $context->stop($context->self()); break; } } } 4UPQBOESFMFBTFSFTPVSDFT -
class TeacherActor implements ActorInterface { // omit public function receive(ContextInterface
$context): void { $msg = $context->message(); switch (true) { case $msg instanceof PrepareTest: $context->logger()->info( sprintf("Teacher has issued a %s test", $msg->subject) ); foreach ($this->students as $student) { $ref = $context->spawnNamed( Props::fromProducer(fn() => new StudentActor()), sprintf('student-%d', $student) ); $context->send($ref->getRef(), new StartTest($msg->subject)); } break; case $msg instanceof SubmitTest: $this->endOfTests[] = $msg; if (count($this->endOfTests) === count($this->students)) { $context->send($this->replyTo, new FinishTest($this->subject)); $context->poison($context->self()); } break; } } } -
class TeacherActor implements ActorInterface { // omit public function receive(ContextInterface
$context): void { $msg = $context->message(); switch (true) { case $msg instanceof PrepareTest: $context->logger()->info( sprintf("Teacher has issued a %s test", $msg->subject) ); foreach ($this->students as $student) { $ref = $context->spawnNamed( Props::fromProducer(fn() => new StudentActor()), sprintf('student-%d', $student) ); $context->send($ref->getRef(), new StartTest($msg->subject)); } break; case $msg instanceof SubmitTest: $this->endOfTests[] = $msg; if (count($this->endOfTests) === count($this->students)) { $context->send($this->replyTo, new FinishTest($this->subject)); $context->poison($context->self()); } break; } } } 'SPN $MBTTSPPN -
class TeacherActor implements ActorInterface { // omit public function receive(ContextInterface
$context): void { $msg = $context->message(); switch (true) { case $msg instanceof PrepareTest: $context->logger()->info( sprintf("Teacher has issued a %s test", $msg->subject) ); foreach ($this->students as $student) { $ref = $context->spawnNamed( Props::fromProducer(fn() => new StudentActor()), sprintf('student-%d', $student) ); $context->send($ref->getRef(), new StartTest($msg->subject)); } break; case $msg instanceof SubmitTest: $this->endOfTests[] = $msg; if (count($this->endOfTests) === count($this->students)) { $context->send($this->replyTo, new FinishTest($this->subject)); $context->poison($context->self()); } break; } } } 4QBXO4UVEFOUT 6OJRVF -
class TeacherActor implements ActorInterface { // omit public function receive(ContextInterface
$context): void { $msg = $context->message(); switch (true) { case $msg instanceof PrepareTest: $context->logger()->info( sprintf("Teacher has issued a %s test", $msg->subject) ); foreach ($this->students as $student) { $ref = $context->spawnNamed( Props::fromProducer(fn() => new StudentActor()), sprintf('student-%d', $student) ); $context->send($ref->getRef(), new StartTest($msg->subject)); } break; case $msg instanceof SubmitTest: $this->endOfTests[] = $msg; if (count($this->endOfTests) === count($this->students)) { $context->send($this->replyTo, new FinishTest($this->subject)); $context->poison($context->self()); } break; } } } *OUFSOBM4UBUF$IBOHF 0ODFBMMTUVEFOUTIBWFDPNQMFUFEUIFUFTU 4UPQBOESFMFBTFSFTPVSDFT -
class StudentActor implements ActorInterface { public function receive(ContextInterface $context): void
{ $msg = $context->message(); if ($msg instanceof StartTest) { sleep(random_int(1, 9)); $context->logger()->info( sprintf( '%s is submitting the answer to the %s test', $context->self(), $msg->subject ) ); $context->send( $context->parent(), new SubmitTest( $msg->subject, (string)$context->self(), ) ); $context->poison($context->self()); } } } -
class StudentActor implements ActorInterface { public function receive(ContextInterface $context): void
{ $msg = $context->message(); if ($msg instanceof StartTest) { sleep(random_int(1, 9)); $context->logger()->info( sprintf( '%s is submitting the answer to the %s test', $context->self(), $msg->subject ) ); $context->send( $context->parent(), new SubmitTest( $msg->subject, (string)$context->self(), ) ); $context->poison($context->self()); } } } 'SPN5FBDIFS -
class StudentActor implements ActorInterface { public function receive(ContextInterface $context): void
{ $msg = $context->message(); if ($msg instanceof StartTest) { sleep(random_int(1, 9)); $context->logger()->info( sprintf( '%s is submitting the answer to the %s test', $context->self(), $msg->subject ) ); $context->send( $context->parent(), new SubmitTest( $msg->subject, (string)$context->self(), ) ); $context->poison($context->self()); } } } 3BOEPNMZTFMFDUUIFUJNFUPBOTXFSUIFUFTU TT -
class StudentActor implements ActorInterface { public function receive(ContextInterface $context): void
{ $msg = $context->message(); if ($msg instanceof StartTest) { sleep(random_int(1, 9)); $context->logger()->info( sprintf( '%s is submitting the answer to the %s test', $context->self(), $msg->subject ) ); $context->send( $context->parent(), new SubmitTest( $msg->subject, (string)$context->self(), ) ); $context->poison($context->self()); } } } QBSFOU BSF 5FBDIFS"DUPS %POUGPSHFUUIBUJUTBIJFSBSDIJDBM -
class StudentActor implements ActorInterface { public function receive(ContextInterface $context): void
{ $msg = $context->message(); if ($msg instanceof StartTest) { sleep(random_int(1, 9)); $context->logger()->info( sprintf( '%s is submitting the answer to the %s test', $context->self(), $msg->subject ) ); $context->send( $context->parent(), new SubmitTest( $msg->subject, (string)$context->self(), ) ); $context->poison($context->self()); } } } 4UVEFOU/BNF 'PSFYBNQMF TUVEFOU TQBXO/BNFE 4UPQBOESFMFBTFSFTPVSDFT -
Example • Teacher -> Actor / Aggregate • Students ->
Actor • Classroom -> Actor / Aggregate -
In closing • It's a somewhat different programming paradigm. •
It's quite dif fi cult to understand just by listening, so please try it out yourself. • Once you understand it, you can apply it to your regular programming and modeling!