Bermain Skala! ke Ribuan Permintaan Serentak
Diterbitkan: 2022-03-11Pengembang Web Scala sering gagal mempertimbangkan konsekuensi dari ribuan pengguna yang mengakses aplikasi kami pada saat yang bersamaan. Mungkin karena kami senang membuat prototipe dengan cepat; mungkin karena menguji skenario seperti itu sangat sulit .
Terlepas dari itu, saya akan berpendapat bahwa mengabaikan skalabilitas tidak seburuk kedengarannya—jika Anda menggunakan seperangkat alat yang tepat dan mengikuti praktik pengembangan yang baik.
Lojinha dan Permainannya! Kerangka
Beberapa waktu lalu, saya memulai sebuah proyek bernama Lojinha (yang diterjemahkan menjadi “toko kecil” dalam bahasa Portugis), usaha saya untuk membangun sebuah situs lelang. (Omong-omong, proyek ini open source). Motivasi saya adalah sebagai berikut:
- Saya benar-benar ingin menjual beberapa barang lama yang tidak saya gunakan lagi.
- Saya tidak suka situs lelang tradisional, terutama yang kami miliki di sini di Brasil.
- Saya ingin "bermain" dengan Play! Kerangka 2 (pun intended).
Jadi jelas, seperti yang disebutkan di atas, saya memutuskan untuk menggunakan Play! Kerangka. Saya tidak memiliki hitungan pasti berapa lama waktu yang dibutuhkan untuk membangun, tetapi tentu saja tidak lama sebelum situs saya aktif dan berjalan dengan sistem sederhana yang digunakan di http://lojinha.jcranky.com. Sebenarnya, saya menghabiskan setidaknya setengah dari waktu pengembangan untuk desain, yang menggunakan Twitter Bootstrap (ingat: Saya bukan desainer…).
Paragraf di atas harus menjelaskan setidaknya satu hal: Saya tidak terlalu mengkhawatirkan kinerja, jika sama sekali saat membuat Lojinha.
Dan itulah poin saya: ada kekuatan dalam menggunakan alat yang tepat—alat yang membuat Anda tetap di jalur yang benar, alat yang mendorong Anda untuk mengikuti praktik pengembangan terbaik melalui konstruksinya.
Dalam hal ini, alat tersebut adalah Play! Framework dan bahasa Scala, dengan Akka membuat beberapa "penampilan tamu".
Mari saya tunjukkan apa yang saya maksud.
Kekekalan dan Caching
Secara umum disepakati bahwa meminimalkan mutabilitas adalah praktik yang baik. Secara singkat, mutabilitas mempersulit penalaran tentang kode Anda, terutama ketika Anda mencoba memperkenalkan paralelisme atau konkurensi apa pun.
Bermain! Kerangka kerja Scala membuat Anda menggunakan kekekalan sebagian besar waktu, dan begitu juga bahasa Scala itu sendiri. Misalnya, hasil yang dihasilkan oleh pengontrol tidak dapat diubah. Terkadang Anda mungkin menganggap kekekalan ini "mengganggu" atau "mengganggu", tetapi "praktik baik" ini "baik" karena suatu alasan.
Dalam hal ini, kekekalan pengontrol benar-benar penting ketika saya akhirnya memutuskan untuk menjalankan beberapa tes kinerja: Saya menemukan hambatan dan, untuk memperbaikinya, cukup cache respons yang tidak dapat diubah ini.
Dengan caching , maksud saya menyimpan objek respons dan menyajikan instance yang identik, apa adanya, ke klien baru mana pun. Ini membebaskan server dari keharusan menghitung ulang hasilnya lagi. Tidak mungkin memberikan respons yang sama ke banyak klien jika hasil ini dapat diubah.
Kelemahannya: untuk waktu yang singkat (waktu kedaluwarsa cache), klien dapat menerima informasi yang sudah ketinggalan zaman. Ini hanya masalah dalam skenario di mana Anda benar-benar membutuhkan klien untuk mengakses data terbaru, tanpa toleransi untuk penundaan.
Untuk referensi, berikut adalah kode Scala untuk memuat halaman awal dengan daftar produk, tanpa caching:
def index = Action { implicit request => Ok(html.index(body = html.body(Items.itemsHigherBids(itemDAO.all(false))), menu = mainMenu)) }
Sekarang, menambahkan cache:
def index = Cached("index", 5) { Action { implicit request => Ok(html.index(body = html.body(Items.itemsHigherBids(itemDAO.all(false))), menu = mainMenu)) } }
Cukup sederhana, bukan? Di sini, "indeks" adalah kunci untuk digunakan dalam sistem cache dan 5 adalah waktu kedaluwarsa, dalam hitungan detik.
Untuk menguji efek dari perubahan ini, saya menjalankan beberapa tes JMeter (termasuk dalam repo GitHub) secara lokal. Sebelum menambahkan cache, saya mencapai throughput sekitar 180 permintaan per detik. Setelah caching, throughput naik menjadi 800 permintaan per detik. Itu peningkatan lebih dari 4x untuk kurang dari dua baris kode.

Konsumsi Memori
Area lain di mana alat Scala yang tepat dapat membuat perbedaan besar adalah dalam konsumsi memori. Di sini, sekali lagi, Mainkan! mendorong Anda ke arah yang benar (dapat diukur). Di dunia Java, untuk aplikasi web "normal" yang ditulis dengan servlet API (yaitu, hampir semua kerangka kerja Java atau Scala di luar sana), sangat menggoda untuk menaruh banyak sampah di sesi pengguna karena API menawarkan kemudahan-ke- metode panggilan yang memungkinkan Anda melakukannya:
session.setAttribute("attrName", attrValue);
Karena sangat mudah untuk menambahkan informasi ke sesi pengguna, hal ini sering disalahgunakan. Akibatnya, risiko menggunakan terlalu banyak memori tanpa alasan yang baik juga sama tinggi.
Dengan Bermain! framework, ini bukan opsi—framework tidak memiliki ruang sesi sisi server. Bermain! sesi pengguna kerangka kerja disimpan dalam cookie browser, dan Anda harus hidup dengannya. Ini berarti bahwa ruang sesi terbatas dalam ukuran dan jenis: Anda hanya dapat menyimpan string. Jika Anda perlu menyimpan objek, Anda harus menggunakan mekanisme caching yang telah kita bahas sebelumnya. Misalnya, Anda mungkin ingin menyimpan alamat email atau nama pengguna pengguna saat ini di sesi, tetapi Anda harus menggunakan cache jika Anda perlu menyimpan seluruh objek pengguna dari model domain Anda.
Sekali lagi, ini mungkin tampak menyakitkan pada awalnya, tetapi sebenarnya, Mainkan! membuat Anda tetap di jalur yang benar, memaksa Anda untuk mempertimbangkan penggunaan memori dengan cermat, yang menghasilkan kode first-pass yang praktis siap untuk cluster—terutama mengingat bahwa tidak ada sesi sisi server yang harus disebarkan ke seluruh cluster Anda, membuat hidup jauh lebih mudah.
Dukungan Asinkron
Berikutnya dalam Putar ini! review framework, kita akan membahas bagaimana Play! juga bersinar dalam dukungan async(hronous). Dan di luar fitur aslinya, Mainkan! memungkinkan Anda untuk menyematkan Akka, alat yang ampuh untuk pemrosesan asinkron.
Meskipun Lojinha belum sepenuhnya memanfaatkan Akka, integrasinya yang sederhana dengan Play! membuatnya sangat mudah untuk:
- Jadwalkan layanan email asinkron.
- Proses penawaran untuk berbagai produk secara bersamaan.
Secara singkat, Akka merupakan implementasi dari Model Aktor yang dipopulerkan oleh Erlang. Jika Anda belum familiar dengan Akka Actor Model, bayangkan saja sebagai unit kecil yang hanya berkomunikasi melalui pesan.
Untuk mengirim email secara tidak sinkron, pertama-tama saya membuat pesan dan aktor yang tepat. Kemudian, yang perlu saya lakukan adalah sesuatu seperti:
EMail.actor ! BidToppedMessage(item.name, itemUrl, bidderEmail)
Logika pengiriman email diimplementasikan di dalam aktor, dan pesan tersebut memberi tahu aktor email mana yang ingin kita kirim. Ini dilakukan dalam skema fire-and-forget, artinya baris di atas mengirimkan permintaan dan kemudian melanjutkan mengeksekusi apa pun yang kita miliki setelah itu (yaitu, tidak memblokir).
Untuk informasi lebih lanjut tentang Async asli Play!, lihat dokumentasi resmi.
Kesimpulan
Ringkasnya: Saya dengan cepat mengembangkan aplikasi kecil, Lojinha, yang dapat ditingkatkan dan dikembangkan dengan sangat baik. Ketika saya mengalami masalah atau menemukan kemacetan, perbaikannya cepat dan mudah, dengan banyak pujian karena alat yang saya gunakan (Play!, Scala, Akka, dan sebagainya), yang mendorong saya untuk mengikuti praktik terbaik dalam hal efisiensi dan skalabilitas. Dengan sedikit perhatian pada kinerja, saya dapat menskalakan hingga ribuan permintaan bersamaan.
Saat mengembangkan aplikasi Anda berikutnya, pertimbangkan alat Anda dengan cermat.