Masalah makro layanan mikro



Hanya dalam 20 tahun, pengembangan perangkat lunak telah berpindah dari monolit arsitektural dengan database tunggal dan status terpusat ke layanan mikro, di mana semuanya didistribusikan ke berbagai kontainer, server, pusat data, dan bahkan benua. Distribusi membuat penskalaan lebih mudah, tetapi juga menghadirkan tantangan yang sama sekali baru, banyak di antaranya yang sebelumnya diselesaikan dengan monolit.



Mari kita ikuti tur singkat sejarah aplikasi jaringan untuk mencari tahu bagaimana kita sampai di sini hari ini. Dan kemudian mari kita bicara tentang model eksekusi stateful yang digunakan dalam Temporal.dan bagaimana memecahkan masalah arsitektur berorientasi layanan (SOA). Saya mungkin bias karena saya menjalankan departemen grosir di Temporal, tetapi saya percaya bahwa pendekatan ini adalah masa depan.



Pelajaran sejarah singkat



Dua puluh tahun lalu, pengembang hampir selalu membuat aplikasi monolitik. Ini adalah model yang sederhana dan konsisten, mirip dengan cara Anda memprogram di lingkungan lokal Anda. Pada dasarnya, monolit bergantung pada satu database, yaitu, semua status terpusat. Dalam satu transaksi, monolit dapat mengubah salah satu statusnya, yaitu memberikan hasil biner: apakah berfungsi atau tidak. Tidak ada ruang untuk inkonsistensi. Artinya, hal yang indah tentang monolit adalah tidak akan ada keadaan yang tidak konsisten karena transaksi yang gagal. Dan ini berarti bahwa pengembang tidak perlu menulis kode, terus menebak-nebak tentang status elemen yang berbeda.



Untuk waktu yang lama, monolit masuk akal. Belum banyak pengguna yang terhubung, jadi persyaratan penskalaan perangkat lunaknya minimal. Bahkan raksasa perangkat lunak terbesar pun mengoperasikan sistem yang remeh menurut standar modern. Hanya segelintir perusahaan seperti Amazon dan Google yang menggunakan solusi skala besar, tetapi itu adalah pengecualian dari aturan tersebut.



Orang-orang sebagai perangkat lunak







Selama 20 tahun terakhir, kebutuhan perangkat lunak terus berkembang. Saat ini, aplikasi harus berfungsi di pasar global sejak hari pertama. Perusahaan seperti Twitter dan Facebook telah menjadikan 24/7 online sebagai prasyarat. Aplikasi tidak lagi menyediakan apa pun, tetapi telah menjadi pengalaman pengguna itu sendiri. Setiap perusahaan saat ini pasti memiliki produk software. "Keandalan" dan "ketersediaan" bukan lagi properti, tetapi persyaratan.



Sayangnya, monolit mulai berantakan ketika "skalabilitas" dan "ketersediaan" ditambahkan ke persyaratan. Pengembang dan bisnis sama-sama perlu menemukan cara untuk mengimbangi pertumbuhan global yang eksplosif dan menuntut ekspektasi pengguna. Saya harus mencari arsitektur alternatif yang mengurangi masalah yang muncul terkait dengan penskalaan.



Microservices (well, arsitektur berorientasi layanan) adalah jawabannya. Awalnya, mereka tampaknya menjadi solusi yang bagus karena memungkinkan Anda untuk membagi aplikasi menjadi modul yang relatif mandiri yang dapat diskalakan secara independen. Dan karena setiap layanan mikro mempertahankan statusnya sendiri, aplikasi tidak lagi terbatas pada kapasitas satu mesin! Pengembang akhirnya dapat membuat program yang dapat disesuaikan dengan jumlah koneksi yang terus bertambah. Layanan mikro juga memberi tim dan perusahaan fleksibilitas dalam pekerjaan mereka karena transparansi dalam tanggung jawab dan pemisahan arsitektur.





Tidak ada keju gratis



Sementara layanan mikro telah memecahkan masalah skalabilitas dan ketersediaan yang telah menghambat pertumbuhan perangkat lunak, banyak hal yang terjadi tanpa awan. Pengembang mulai menyadari bahwa layanan mikro memiliki kekurangan yang serius.



Monolith biasanya memiliki satu database dan satu server aplikasi. Dan karena monolit tidak dapat dipisahkan, hanya ada dua cara untuk menskalakan:



  • Vertikal : Mengupgrade perangkat keras untuk meningkatkan throughput atau kapasitas. Penskalaan ini bisa efektif, tetapi mahal. Dan itu pasti tidak akan memperbaiki masalah selamanya jika aplikasi Anda perlu terus berkembang. Dan jika Anda cukup berkembang, Anda tidak akan memiliki cukup peralatan untuk meningkatkan.
  • : , . , .


Microservices berbeda, nilainya terletak pada kemampuan untuk memiliki banyak "tipe" database, antrian dan layanan lain yang diskalakan dan dikelola secara independen satu sama lain. Namun, masalah pertama yang mulai diperhatikan ketika beralih ke layanan mikro justru adalah kenyataan bahwa sekarang Anda harus menangani banyak jenis server dan database.



Untuk waktu yang lama, semuanya bergantung pada peluang, pengembang dan operator keluar sendiri. Masalah manajemen infrastruktur yang ditimbulkan oleh layanan mikro sulit untuk diatasi, paling banter menurunkan keandalan aplikasi.



Namun, penawaran muncul sebagai respons terhadap permintaan. Semakin banyak layanan mikro tersebar, semakin banyak pengembang termotivasi untuk menyelesaikan masalah infrastruktur. Perlahan tapi pasti, alat mulai bermunculan, dan teknologi seperti Docker, Kubernetes, dan AWS Lambda mengisi celah tersebut. Mereka membuat arsitektur layanan mikro sangat mudah dioperasikan. Alih-alih menulis kode mereka sendiri untuk mengatur container dan resource, developer dapat mengandalkan alat yang telah dibuat sebelumnya. Pada tahun 2020, kami akhirnya mencapai tonggak sejarah dimana ketersediaan infrastruktur kami tidak lagi mengganggu keandalan aplikasi kami. Sempurna!





Tentu saja, kita belum hidup dalam utopia perangkat lunak yang stabil secara sempurna. Infrastruktur tidak lagi menjadi sumber ketidakamanan aplikasi; kode aplikasi telah menggantikannya.



Masalah lain dengan layanan mikro



Dalam monolit, pengembang menulis kode yang mengubah status dalam cara biner: entah sesuatu terjadi atau tidak. Dan dengan layanan mikro, status tersebar di berbagai server. Untuk mengubah status aplikasi, beberapa database harus diperbarui pada saat yang bersamaan. Kemungkinannya adalah satu database akan berhasil diperbarui dan yang lain akan mogok, membuat Anda berada dalam status perantara yang tidak konsisten. Tetapi karena layanan adalah satu-satunya solusi untuk masalah penskalaan horizontal, pengembang tidak memiliki pilihan lain.





Masalah mendasar dengan status didistribusikan di seluruh layanan adalah bahwa setiap panggilan ke layanan eksternal akan memiliki hasil acak dalam hal ketersediaan. Tentu saja, pengembang dapat mengabaikan masalah dalam kode mereka dan menganggap setiap panggilan ke dependensi eksternal selalu berhasil. Tapi kemudian beberapa ketergantungan dapat menghentikan aplikasi tanpa peringatan. Oleh karena itu, pengembang harus menyesuaikan kode mereka dari era monolit untuk menambahkan pemeriksaan atas kegagalan operasi di tengah transaksi. Acara berikut terus mengambil status terakhir yang direkam dari penyimpanan myDB khusus untuk menghindari kondisi balapan. Sayangnya, implementasi inipun tidak membantu. Jika status akun berubah tanpa memperbarui myDB, inkonsistensi dapat terjadi.



public void transferWithoutTemporal(
  String fromId, 
  String toId, 
  String referenceId, 
  double amount,
) {
  boolean withdrawDonePreviously = myDB.getWithdrawState(referenceId);
  if (!withdrawDonePreviously) {
      account.withdraw(fromAccountId, referenceId, amount);      
      myDB.setWithdrawn(referenceId);
  }
  boolean depositDonePreviously = myDB.getDepositState(referenceId);
  if (!depositDonePreviously) {
      account.deposit(toAccountId, referenceId, amount);                
      myDB.setDeposited(referenceId);
  }
}


Sayangnya, tidak mungkin untuk menulis kode tanpa kesalahan. Dan semakin kompleks kodenya, semakin besar kemungkinan bug akan muncul. Seperti yang Anda duga, kode yang bekerja dengan "middleware" tidak hanya rumit tetapi juga berbelit-belit. Setidaknya beberapa keandalan lebih baik daripada tidak ada keandalan, jadi pengembang harus menulis kode yang awalnya bermasalah untuk mempertahankan pengalaman pengguna. Kami menghabiskan waktu dan tenaga, dan majikan menghabiskan banyak uang. Meskipun layanan mikro diskalakan dengan baik, itu datang dengan harga kesenangan dan produktivitas pengembang, dan keandalan aplikasi.



Jutaan pengembang menghabiskan waktu setiap hari untuk menemukan kembali salah satu roda yang paling banyak ditemukan kembali - keandalan pelat baja. Pendekatan modern untuk bekerja dengan layanan mikro tidak mencerminkan persyaratan untuk keandalan dan skalabilitas aplikasi modern.





Sementara



Sekarang kita sampai pada solusi kita. Itu tidak didukung oleh Stack Overflow, dan kami tidak mengklaim sebagai sempurna. Kami hanya ingin berbagi ide dan mendengarkan pendapat Anda. Tempat apa yang lebih baik untuk mendapatkan masukan tentang peningkatan kode Anda selain Stack?



Hingga saat ini, belum ada solusi yang memungkinkan Anda menggunakan layanan mikro tanpa menyelesaikan masalah yang dijelaskan di atas. Anda dapat menguji dan meniru status kerusakan, menulis kode yang memperhitungkan kerusakan, tetapi masalah ini masih muncul. Kami percaya Temporal menyelesaikannya. Ini adalah lingkungan stateful open-source (MIT no-nonsense) untuk orkestrasi layanan mikro.



Temporal memiliki dua komponen utama: backend stateful yang berjalan pada database pilihan Anda, dan framework klien dalam salah satu bahasa yang didukung. Aplikasi dibuat menggunakan kerangka kerja klien dan kode lawas biasa yang secara otomatis menyimpan perubahan status di backend saat dijalankan. Anda dapat menggunakan dependensi, pustaka, dan rantai build yang sama seperti yang Anda lakukan saat membuat aplikasi lain. Sejujurnya, backend sangat terdistribusi, jadi tidak seperti J2EE 2.0. Faktanya, ini adalah distribusi backend yang memungkinkan penskalaan horizontal hampir tak terbatas. Temporal menghadirkan konsistensi, kesederhanaan, dan keandalan ke lapisan aplikasi, seperti halnya infrastruktur Docker, Kubernetes, dan arsitektur tanpa server.



Temporal menyediakan sejumlah mekanisme yang sangat andal untuk orkestrasi layanan mikro. Tapi yang terpenting adalah menjaga negara. Fungsi ini menggunakan event emitting untuk secara otomatis menyimpan setiap perubahan stateful ke aplikasi yang sedang berjalan. Artinya, jika komputer yang menjalankan Temporal macet, kode akan secara otomatis melompat ke komputer lain, seolah-olah tidak terjadi apa-apa. Ini bahkan berlaku untuk variabel lokal, rangkaian eksekusi, dan status khusus aplikasi lainnya.



Izinkan saya memberi Anda analogi. Sebagai pengembang, Anda mungkin saat ini mengandalkan versi SVN (yaitu OG Git) untuk melacak perubahan yang Anda buat pada kode Anda. SVN hanya menyimpan file baru dan kemudian menautkan ke file yang ada untuk menghindari duplikasi. Temporal adalah sesuatu seperti SVN (analogi kasar) untuk riwayat stateful aplikasi yang sedang berjalan. Ketika kode Anda mengubah status aplikasi, Temporal secara otomatis menyimpan perubahan itu (bukan hasilnya) tanpa kesalahan. Artinya, Temporal tidak hanya memulihkan aplikasi yang rusak, tetapi juga memutarnya kembali, bercabang, dan melakukan lebih banyak lagi. Jadi pengembang tidak perlu lagi membangun aplikasi dengan harapan server mungkin macet.



Ini seperti beralih dari menyimpan dokumen secara manual (Ctrl + S) setelah setiap karakter yang diketik ke penyimpanan cloud otomatis Google Docs. Bukan dalam artian Anda tidak lagi menyimpan apa pun secara manual, hanya saja tidak ada lagi satu mesin pun yang terkait dengan dokumen ini. Statefulness berarti bahwa pengembang dapat menulis kode boilerplate yang jauh lebih membosankan yang harus ditulis karena layanan mikro. Selain itu, Anda tidak lagi memerlukan infrastruktur khusus - antrian terpisah, cache, dan database. Ini membuatnya lebih mudah untuk mengoperasikan dan menambahkan fitur baru. Ini juga membuat lebih mudah untuk mendapatkan pemula yang up-to-date, karena mereka tidak perlu memahami kode manajemen negara yang membingungkan dan spesifik.



Retensi status diimplementasikan dalam bentuk "timer tetap". Ini adalah mekanisme gagal-aman yang dapat digunakan dengan sebuah perintah Workflow.sleep. Cara kerjanya sama persis dengan sleep. Namun, Workflow.sleepdapat dengan aman di-eutanasia untuk waktu yang lama. Banyak pengguna Temporal telah tidur selama berminggu-minggu, bahkan bertahun-tahun. Ini dilakukan dengan menyimpan pengatur waktu yang lama di penyimpanan Temporal dan melacak kode untuk bangun. Sekali lagi, bahkan jika server macet (atau Anda baru saja mematikannya), kode akan masuk ke mesin yang tersedia saat penghitung waktu berakhir. Proses tidur tidak menghabiskan sumber daya, Anda dapat memilikinya jutaan dengan overhead yang dapat diabaikan. Mungkin terdengar terlalu abstrak, jadi inilah contoh kode Temporal yang berfungsi:



public class SubscriptionWorkflowImpl implements SubscriptionWorkflow {
  private final SubscriptionActivities activities =
      Workflow.newActivityStub(SubscriptionActivities.class);
  public void execute(String customerId) {
    activities.onboardToFreeTrial(customerId);
    try {
      Workflow.sleep(Duration.ofDays(180));
      activities.upgradeFromTrialToPaid(customerId);
      while (true) {
        Workflow.sleep(Duration.ofDays(30));
        activities.chargeMonthlyFee(customerId);
      }
    } catch (CancellationException e) {
      activities.processSubscriptionCancellation(customerId);
    }
  }
}


Selain status persisten, Temporal menawarkan serangkaian mekanisme untuk membangun aplikasi yang kuat. Fungsi aktivitas dipanggil dari alur kerja, tetapi kode yang berjalan di dalam aktivitas tidak stateful. Meskipun tidak bertahan, aktivitas berisi percobaan ulang otomatis, batas waktu, dan detak jantung. Aktivitas sangat berguna untuk merangkum kode yang mungkin gagal. Misalkan aplikasi Anda menggunakan API perbankan yang seringkali tidak tersedia. Untuk perangkat lunak lawas, Anda perlu menggabungkan semua kode yang memanggil API ini dengan pernyataan coba / tangkap, coba lagi logika, dan waktu tunggu. Tetapi jika Anda memanggil API perbankan dari suatu aktivitas, maka semua fungsi ini disediakan di luar kotak: jika panggilan gagal, aktivitas tersebut akan dicoba ulang secara otomatis. Semuanya bagusnamun terkadang Anda sendiri memiliki layanan yang tidak dapat diandalkan dan ingin melindunginya dari DDoS. Oleh karena itu, panggilan aktivitas juga mendukung waktu tunggu, didukung oleh timer yang lama. Artinya, jeda antar pengulangan kegiatan bisa mencapai jam, hari atau minggu. Ini sangat berguna untuk kode yang perlu dijalankan dengan sukses, tetapi Anda tidak yakin seberapa cepat itu harus terjadi.



Video ini menjelaskan model pemrograman Temporal dalam dua menit:





Kekuatan lain dari Temporal adalah ketelitian aplikasi yang sedang berjalan. Observation API menyediakan antarmuka seperti SQL untuk membuat kueri metadata dari alur kerja apa pun (dapat dieksekusi atau tidak). Anda juga dapat menentukan dan memperbarui nilai metadata Anda langsung dalam proses tersebut. API observasi sangat berguna untuk operator dan pengembang Temporal, terutama saat melakukan debug selama pengembangan. Pemantauan bahkan mendukung tindakan batch pada hasil kueri. Misalnya, Anda bisa mengirim sinyal kill ke semua proses pekerja yang cocok dengan permintaan dengan waktu pembuatan> kemarin. Sementara mendukung fitur pengambilan sinkron yang memungkinkan Anda menarik nilai variabel lokal dari instance yang berjalan. Ini seperti debugger dari IDE Anda telah bekerja dengan aplikasi produksi. Misalnya, ini adalah bagaimana Anda bisa mendapatkan nilainyagreeting dalam contoh berjalan:



public static class GreetingWorkflowImpl implements GreetingWorkflow {

    private String greeting;

    @Override
    public void createGreeting(String name) {
      greeting = "Hello " + name + "!";
      Workflow.sleep(Duration.ofSeconds(2));
      greeting = "Bye " + name + "!";
    }

    @Override
    public String queryGreeting() {
      return greeting;
    }
  }


Kesimpulan



Layanan mikro sangat bagus, dan harga produktivitas serta keandalannya harus dibayar oleh pengembang dan bisnis. Temporal dirancang untuk mengatasi masalah ini dengan menyediakan lingkungan yang membayar layanan mikro untuk pengembang. Statefulness, kegagalan otomatis, dan pengawasan out-of-the-box hanyalah beberapa fitur yang dimiliki Temporal untuk membuat pengembangan layanan mikro menjadi cerdas.



All Articles