Pengenalan Akka Aktor di Jawa

1. Perkenalan

Akka adalah pustaka sumber terbuka yang membantu mengembangkan aplikasi bersamaan dan terdistribusi dengan mudah menggunakan Java atau Scala dengan memanfaatkan Model Aktor.

Dalam tutorial ini, kami akan menyajikan fitur-fitur dasar seperti mendefinisikan aktor, bagaimana mereka berkomunikasi dan bagaimana kita dapat membunuh mereka . Di catatan akhir, kami juga akan mencatat beberapa praktik terbaik saat bekerja dengan Akka.

2. Model Aktor

Model Aktor bukanlah hal baru bagi komunitas ilmu komputer. Ini pertama kali diperkenalkan oleh Carl Eddie Hewitt pada tahun 1973, sebagai model teoritis untuk menangani komputasi bersamaan.

Ini mulai menunjukkan penerapan praktisnya ketika industri perangkat lunak mulai menyadari kesulitan dalam mengimplementasikan aplikasi secara bersamaan dan terdistribusi.

Aktor mewakili unit komputasi independen. Beberapa karakteristik penting adalah:

  • seorang aktor merangkum statusnya dan bagian dari logika aplikasi
  • aktor berinteraksi hanya melalui pesan asinkron dan tidak pernah melalui panggilan metode langsung
  • Setiap aktor memiliki alamat unik dan kotak surat di mana aktor lain dapat mengirimkan pesan
  • aktor akan memproses semua pesan di kotak surat secara berurutan (implementasi default dari kotak surat menjadi antrian FIFO)
  • sistem aktor diatur dalam hierarki seperti pohon
  • seorang aktor dapat menciptakan aktor lain, dapat mengirim pesan ke aktor lain dan menghentikan dirinya sendiri atau aktor mana pun telah dibuat

2.1. Keuntungan

Mengembangkan aplikasi bersamaan itu sulit karena kita perlu berurusan dengan sinkronisasi, kunci dan memori bersama. Dengan menggunakan aktor Akka kita dapat dengan mudah menulis kode asinkron tanpa perlu kunci dan sinkronisasi.

Salah satu keuntungan menggunakan message daripada panggilan metode adalah thread pengirim tidak akan memblokir untuk menunggu nilai kembali ketika mengirim pesan ke aktor lain . Aktor penerima akan merespon dengan hasilnya dengan mengirimkan pesan balasan ke pengirim.

Manfaat besar lainnya dari penggunaan pesan adalah kita tidak perlu khawatir tentang sinkronisasi dalam lingkungan multi-utas. Ini karena fakta bahwa semua pesan diproses secara berurutan .

Keunggulan lain dari model aktor Akka adalah penanganan error. Dengan mengatur para aktor dalam hierarki, setiap aktor dapat memberi tahu orang tuanya tentang kegagalan tersebut, sehingga dapat bertindak sesuai dengan itu. Aktor induk dapat memutuskan untuk menghentikan atau memulai kembali aktor cilik.

3. Penyiapan

Untuk memanfaatkan para aktor Akka, kita perlu menambahkan dependensi berikut dari Maven Central:

 com.typesafe.akka akka-actor_2.12 2.5.11  

4. Membuat Aktor

Seperti disebutkan, para aktor didefinisikan dalam sistem hierarki. Semua aktor yang berbagi konfigurasi umum akan ditentukan oleh ActorSystem.

Untuk saat ini, kami hanya akan menentukan ActorSystem dengan konfigurasi default dan nama kustom:

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

Walaupun kita belum membuat satu pun pelaku, sistem sudah akan memuat 3 pelaku utama:

  • Aktor pelindung akar memiliki alamat "/" yang sebagai nama menyatakan merupakan akar dari hierarki sistem aktor
  • aktor wali pengguna yang beralamat "/ pengguna". Ini akan menjadi induk dari semua aktor yang kita definisikan
  • aktor penjaga sistem yang beralamat “/ system”. Ini akan menjadi induk untuk semua aktor yang ditentukan secara internal oleh sistem Akka

Semua aktor Akka akan memperluas kelas abstrak AbstractActor dan mengimplementasikan metode createReceive () untuk menangani pesan masuk dari aktor lain:

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

Ini adalah aktor paling dasar yang bisa kita ciptakan. Ia dapat menerima pesan dari aktor lain dan akan membuangnya karena tidak ada pola pesan yang cocok yang ditentukan di ReceiveBuilder. Kami akan membicarakan tentang pencocokan pola pesan nanti di artikel ini.

Sekarang setelah kita membuat aktor pertama kita, kita harus memasukkannya ke dalam ActorSystem :

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

4.1. Konfigurasi Aktor

Kelas Props berisi konfigurasi aktor. Kita dapat mengkonfigurasi hal-hal seperti dispatcher, mailbox atau konfigurasi penerapan. Kelas ini tidak dapat diubah, sehingga aman untuk thread, sehingga dapat dibagikan saat membuat aktor baru.

Sangat disarankan dan dianggap sebagai praktik terbaik untuk menentukan metode pabrik di dalam objek actor yang akan menangani pembuatan objek Props .

Sebagai contoh, mari kita definisikan aktor yang akan melakukan pemrosesan teks. Aktor akan menerima objek String yang akan diproses:

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

Sekarang, untuk membuat instance dari tipe aktor ini kita hanya menggunakan metode pabrik props () untuk meneruskan argumen String ke konstruktor:

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

Sekarang kita tahu bagaimana mendefinisikan aktor, mari kita lihat bagaimana mereka berkomunikasi di dalam sistem aktor.

5. Pesan Aktor

Untuk berinteraksi satu sama lain, para aktor dapat mengirim dan menerima pesan dari aktor lain dalam sistem. Pesan ini dapat berupa objek jenis apa pun dengan syarat tidak dapat diubah .

Ini adalah praktik terbaik untuk menentukan pesan di dalam kelas aktor. Ini membantu untuk menulis kode yang mudah dipahami dan mengetahui pesan apa yang dapat ditangani oleh aktor.

5.1. Mengirim Pesan

Di dalam pesan sistem Akka actor dikirim menggunakan metode:

  • menceritakan()
  • meminta()
  • meneruskan()

Saat kita ingin mengirim pesan dan tidak mengharapkan respon, kita bisa menggunakan metode tell () . Ini adalah metode paling efisien dari sudut pandang kinerja:

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

Parameter pertama mewakili pesan yang kita kirim ke alamat aktor 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 () daripada ask () ketika kinerja menjadi perhatian
  • saat menggunakan ask () kita harus selalu menangani pengecualian dengan mengirimkan pesan Kegagalan
  • aktor tidak boleh berbagi keadaan yang bisa berubah
  • seorang aktor tidak boleh dideklarasikan dalam aktor lain
  • aktor tidak dihentikan secara otomatis saat mereka tidak lagi direferensikan. Kita harus secara eksplisit menghancurkan seorang aktor ketika kita tidak membutuhkannya lagi untuk mencegah kebocoran memori
  • pesan yang digunakan oleh aktor harus selalu tidak berubah

Seperti biasa, kode sumber artikel tersedia di GitHub.