Pengenalan Akka Aktor di Jawa

1. Pengenalan

Akka adalah perpustakaan sumber terbuka yang membantu mengembangkan aplikasi serentak dan diedarkan dengan mudah menggunakan Java atau Scala dengan memanfaatkan Model Pelakon.

Dalam tutorial ini, kami akan memaparkan ciri-ciri asas seperti menentukan pelakon, bagaimana mereka berkomunikasi dan bagaimana kita dapat membunuhnya . Dalam nota akhir, kami juga akan mencatat beberapa amalan terbaik ketika bekerja dengan Akka.

2. Model Pelakon

Model Pelakon bukanlah perkara baru bagi komuniti sains komputer. Ini pertama kali diperkenalkan oleh Carl Eddie Hewitt pada tahun 1973, sebagai model teori untuk menangani pengiraan serentak.

Ini mulai menunjukkan keberlangsungan praktisnya ketika industri perangkat lunak mulai menyedari masalah dalam melaksanakan aplikasi bersamaan dan diedarkan.

Pelakon mewakili unit pengiraan bebas. Beberapa ciri penting adalah:

  • pelakon merangkumi keadaan dan sebahagian logik aplikasi
  • pelakon hanya berinteraksi melalui mesej tidak segerak dan tidak pernah melalui panggilan kaedah langsung
  • setiap pelakon mempunyai alamat yang unik dan peti surat di mana pelakon lain dapat menyampaikan mesej
  • pelakon akan memproses semua mesej di peti surat mengikut urutan (pelaksanaan lalai dari peti mel menjadi antrian FIFO)
  • sistem pelakon disusun dalam hierarki seperti pokok
  • pelakon boleh membuat pelakon lain, dapat menghantar mesej kepada pelakon lain dan menghentikan dirinya atau pelakon mana pun telah dibuat

2.1. Kelebihan

Membangunkan aplikasi serentak sukar kerana kita perlu menangani penyegerakan, kunci dan memori bersama. Dengan menggunakan pelakon Akka kita dapat menulis kod tak segerak dengan mudah tanpa memerlukan kunci dan penyegerakan.

Salah satu kelebihan menggunakan mesej dan bukannya kaedah panggilan adalah bahawa utas pengirim tidak akan menyekat untuk menunggu nilai pengembalian ketika menghantar mesej kepada pelakon lain . Pelakon yang menerima akan membalas dengan hasilnya dengan menghantar mesej balasan kepada pengirim.

Manfaat besar lain dari menggunakan mesej adalah bahawa kita tidak perlu bimbang tentang penyegerakan dalam persekitaran berbilang utas. Ini kerana kenyataan bahawa semua mesej diproses secara berurutan .

Kelebihan lain dari model pelakon Akka adalah pengendalian ralat. Dengan mengatur pelakon dalam hierarki, setiap pelakon dapat memberitahu orang tuanya tentang kegagalan, sehingga dapat bertindak sesuai. Pelakon ibu bapa boleh memutuskan untuk menghentikan atau memulakan semula pelakon anak.

3. Persediaan

Untuk memanfaatkan pelakon Akka, kita perlu menambahkan kebergantungan berikut dari Maven Central:

 com.typesafe.akka akka-actor_2.12 2.5.11  

4. Menciptakan Pelakon

Seperti disebutkan, para pelaku didefinisikan dalam sistem hierarki. Semua pelakon yang mempunyai konfigurasi yang sama akan ditentukan oleh ActorSystem.

Buat masa ini, kami hanya akan menentukan Sistem Actor dengan konfigurasi lalai dan nama tersuai:

ActorSystem system = ActorSystem.create("test-system"); 

Walaupun kami belum membuat pelakon, sistem ini akan mengandungi 3 pelakon utama:

  • pelakon pelindung akar yang mempunyai alamat "/" yang seperti namanya mewakili akar hierarki sistem pelakon
  • pelakon penjaga pengguna yang mempunyai alamat "/ pengguna". Ini akan menjadi ibu bapa semua pelakon yang kita tentukan
  • pelakon penjaga sistem yang mempunyai alamat "/ sistem". Ini akan menjadi ibu bapa bagi semua pelakon yang ditentukan secara dalaman oleh sistem Akka

Mana-mana pelakon Akka akan melanjutkan kelas abstrak AbstractActor dan melaksanakan kaedah createReceive () untuk menangani mesej masuk dari pelakon lain:

public class MyActor extends AbstractActor { public Receive createReceive() { return receiveBuilder().build(); } }

Ini adalah pelakon paling asas yang boleh kita buat. Ia dapat menerima mesej dari pelaku lain dan akan membuangnya kerana tidak ada corak mesej yang sepadan yang ditentukan dalam ReceiveBuilder. Kami akan membincangkan mengenai pemadanan corak mesej di kemudian hari dalam artikel ini.

Sekarang kita telah mencipta pelakon pertama kita, kita harus memasukkannya ke dalam Sistem Pelakon :

ActorRef readingActorRef = system.actorOf(Props.create(MyActor.class), "my-actor");

4.1. Konfigurasi Pelakon

The prop kelas mengandungi konfigurasi pelakon. Kami boleh mengkonfigurasi perkara seperti penghantar, kotak surat atau konfigurasi penyebaran. Kelas ini tidak berubah, jadi selamat untuk utas, sehingga dapat dikongsi semasa membuat pelakon baru.

Sangat disyorkan dan dianggap sebagai amalan terbaik untuk menentukan kaedah kilang di dalam objek pelakon yang akan menangani penciptaan objek Props .

Sebagai contoh, mari kita tentukan pelakon yang akan melakukan beberapa pemprosesan teks. Pelakon akan menerima objek String di mana ia akan melakukan pemprosesan:

public class ReadingActor extends AbstractActor { private String text; public static Props props(String text) { return Props.create(ReadingActor.class, text); } // ... }

Sekarang, untuk membuat contoh pelakon jenis ini, kita hanya menggunakan kaedah kilang alat peraga () untuk menyampaikan argumen String kepada pembina:

ActorRef readingActorRef = system.actorOf( ReadingActor.props(TEXT), "readingActor");

Sekarang setelah kita mengetahui cara menentukan pelakon, mari kita lihat bagaimana mereka berkomunikasi di dalam sistem pelakon.

5. Pemesejan Pelakon

To interact with each other, the actors can send and receive messages from any other actor in the system. These messages can be any type of object with the condition that it's immutable.

It's a best practice to define the messages inside the actor class. This helps to write code that is easy to understand and know what messages an actor can handle.

5.1. Sending Messages

Inside the Akka actor system messages are sent using methods:

  • tell()
  • ask()
  • forward()

When we want to send a message and don't expect a response, we can use the tell() method. This is the most efficient method from a performance perspective:

readingActorRef.tell(new ReadingActor.ReadLines(), ActorRef.noSender()); 

The first parameter represents the message we send to the actor address readingActorRef.

The second parameter specifies who the sender is. This is useful when the actor receiving the message needs to send a response to an actor other than the sender (for example the parent of the sending actor).

Usually, we can set the second parameter to null or ActorRef.noSender(), because we don't expect a reply. When we need a response back from an actor, we can use the ask() method:

CompletableFuture future = ask(wordCounterActorRef, new WordCounterActor.CountWords(line), 1000).toCompletableFuture();

When asking for a response from an actor a CompletionStage object is returned, so the processing remains non-blocking.

A very important fact that we must pay attention to is error handling insider the actor which will respond. To return a Future object that will contain the exception we must send a Status.Failure message to the sender actor.

This is not done automatically when an actor throws an exception while processing a message and the ask() call will timeout and no reference to the exception will be seen in the logs:

@Override public Receive createReceive() { return receiveBuilder() .match(CountWords.class, r -> { try { int numberOfWords = countWordsFromLine(r.line); getSender().tell(numberOfWords, getSelf()); } catch (Exception ex) { getSender().tell( new akka.actor.Status.Failure(ex), getSelf()); throw ex; } }).build(); }

We also have the forward() method which is similar to tell(). The difference is that the original sender of the message is kept when sending the message, so the actor forwarding the message only acts as an intermediary actor:

printerActorRef.forward( new PrinterActor.PrintFinalResult(totalNumberOfWords), getContext());

5.2. Receiving Messages

Each actor will implement the createReceive() method, which handles all incoming messages. The receiveBuilder() acts like a switch statement, trying to match the received message to the type of messages defined:

public Receive createReceive() { return receiveBuilder().matchEquals("printit", p -> { System.out.println("The address of this actor is: " + getSelf()); }).build(); }

When received, a message is put into a FIFO queue, so the messages are handled sequentially.

6. Killing an Actor

When we finished using an actor we can stop it by calling the stop() method from the ActorRefFactory interface:

system.stop(myActorRef);

We can use this method to terminate any child actor or the actor itself. It's important to note stopping is done asynchronously and that the current message processing will finish before the actor is terminated. No more incoming messages will be accepted in the actor mailbox.

By stopping a parent actor, we'll also send a kill signal to all of the child actors that were spawned by it.

When we don't need the actor system anymore, we can terminate it to free up all the resources and prevent any memory leaks:

Future terminateResponse = system.terminate();

This will stop the system guardian actors, hence all the actors defined in this Akka system.

We could also send a PoisonPill message to any actor that we want to kill:

myActorRef.tell(PoisonPill.getInstance(), ActorRef.noSender());

The PoisonPill message will be received by the actor like any other message and put into the queue. The actor will process all the messages until it gets to the PoisonPill one. Only then the actor will begin the termination process.

Another special message used for killing an actor is the Kill message. Unlike the PoisonPill, the actor will throw an ActorKilledException when processing this message:

myActorRef.tell(Kill.getInstance(), ActorRef.noSender());

7. Conclusion

In this article, we presented the basics of the Akka framework. We showed how to define actors, how they communicate with each other and how to terminate them.

We'll conclude with some best practices when working with Akka:

  • gunakan tell () dan bukannya ask () apabila prestasi menjadi perhatian
  • semasa menggunakan ask () kita harus selalu menangani pengecualian dengan menghantar mesej Gagal
  • pelakon tidak boleh berkongsi keadaan yang boleh berubah
  • pelakon tidak boleh dinyatakan dalam pelakon lain
  • pelakon tidak dihentikan secara automatik apabila mereka tidak lagi dirujuk. Kita mesti secara terang-terangan menghancurkan pelakon apabila kita tidak memerlukannya lagi untuk mengelakkan kebocoran memori
  • mesej yang digunakan oleh pelakon harus selalu tidak berubah

Seperti biasa, kod sumber untuk artikel tersebut terdapat di GitHub.