Lakukan Matematika: Menskalakan Aplikasi Layanan Mikro Dengan Orkestra

Diterbitkan: 2022-03-11

Tidak mengherankan bahwa arsitektur aplikasi layanan mikro terus menyerang desain perangkat lunak. Jauh lebih nyaman untuk mendistribusikan beban, membuat penerapan yang sangat tersedia, dan mengelola pemutakhiran sambil memudahkan pengembangan dan manajemen tim.

Tapi ceritanya pasti tidak sama tanpa orkestra kontainer.

Sangat mudah untuk ingin menggunakan semua fitur utama mereka, terutama penskalaan otomatis. Sungguh suatu berkah, menyaksikan penyebaran kontainer berfluktuasi sepanjang hari, berukuran lembut untuk menangani beban saat ini, membebaskan waktu kita untuk tugas-tugas lain. Kami bangga puas dengan apa yang ditampilkan alat pemantauan kontainer kami; sementara itu, kami baru saja mengonfigurasi beberapa pengaturan—ya, hanya itu (hampir) yang diperlukan untuk menciptakan keajaiban!

Itu tidak berarti tidak ada alasan untuk bangga dengan hal ini: Kami yakin bahwa pengguna kami memiliki pengalaman yang baik, dan bahwa kami tidak membuang-buang uang dengan infrastruktur yang terlalu besar. Ini sudah cukup besar!

Dan tentu saja, perjalanan yang luar biasa untuk sampai ke sana! Karena meskipun pada akhirnya tidak banyak pengaturan yang perlu dikonfigurasi, itu jauh lebih rumit daripada yang biasanya kita pikirkan sebelum kita dapat memulai. Jumlah minimum/maksimum replika, ambang batas atas/bawah, periode sinkronisasi, penundaan pendinginan—semua pengaturan itu sangat terkait satu sama lain. Memodifikasi satu kemungkinan besar akan mempengaruhi yang lain, tetapi Anda masih harus mengatur kombinasi seimbang yang sesuai dengan aplikasi/penempatan dan infrastruktur Anda. Namun, Anda tidak akan menemukan buku masak atau formula ajaib apa pun di Internet, karena itu sangat tergantung pada kebutuhan Anda .

Sebagian besar dari kita pertama-tama mengaturnya ke nilai "acak" atau default yang kita sesuaikan kemudian sesuai dengan apa yang kita temukan saat memantau. Hal itu membuat saya berpikir: Bagaimana jika kita dapat membuat prosedur yang lebih “matematis” yang akan membantu kita menemukan kombinasi yang unggul?

Menghitung Parameter Orkestrasi Kontainer

Ketika kami memikirkan tentang layanan mikro penskalaan otomatis untuk suatu aplikasi, kami sebenarnya melihat peningkatan pada dua poin utama:

  1. Memastikan penerapan dapat ditingkatkan dengan cepat jika terjadi peningkatan beban yang cepat (sehingga pengguna tidak menghadapi batas waktu atau HTTP 500)
  2. Menurunkan biaya infrastruktur (yaitu, menjaga agar instans tidak kekurangan muatan)

Ini pada dasarnya berarti mengoptimalkan ambang batas perangkat lunak penampung untuk meningkatkan dan menurunkan. (Algoritme Kubernetes memiliki satu parameter untuk keduanya).

Saya akan menunjukkan nanti bahwa semua parameter terkait instance terikat dengan ambang batas kelas atas. Ini adalah yang paling sulit untuk dihitung—maka artikel ini.

Catatan: Mengenai parameter yang diatur ke seluruh cluster, saya tidak memiliki prosedur yang baik untuk mereka, tetapi di akhir artikel ini, saya akan memperkenalkan perangkat lunak (halaman web statis) yang memperhitungkannya saat menghitung parameter penskalaan otomatis instance. Dengan begitu, Anda akan dapat memvariasikan nilainya untuk mempertimbangkan dampaknya.

Menghitung Ambang Peningkatan Skala

Agar metode ini berfungsi, Anda harus memastikan bahwa aplikasi Anda memenuhi persyaratan berikut:

  1. Beban harus didistribusikan secara merata di setiap instance aplikasi Anda (secara round-robin)
  2. Waktu permintaan harus lebih pendek dari interval pemeriksaan beban kluster penampung Anda .
  3. Anda harus mempertimbangkan untuk menjalankan prosedur pada sejumlah besar pengguna (ditentukan nanti).

Alasan utama untuk kondisi tersebut berasal dari fakta bahwa algoritme tidak menghitung beban sebagai per pengguna tetapi sebagai distribusi (dijelaskan nanti).

Mendapatkan Semua Gaussian

Pertama kita harus merumuskan definisi untuk peningkatan beban yang cepat atau, dengan kata lain, skenario terburuk. Bagi saya, cara yang baik untuk menerjemahkannya adalah: memiliki sejumlah besar pengguna yang melakukan tindakan yang menghabiskan sumber daya dalam waktu singkat —dan selalu ada kemungkinan hal itu terjadi saat grup pengguna atau layanan lain melakukan tugas lain. Jadi mari kita mulai dari definisi ini dan mencoba mengekstrak beberapa matematika. (Siapkan aspirin Anda.)

Memperkenalkan beberapa variabel:

  • $N_{u}$, “banyak pengguna”
  • $L_{u}(t)$, beban yang dihasilkan oleh satu pengguna yang melakukan "operasi yang memakan sumber daya" ($t=0$ menunjukkan saat pengguna memulai operasi)
  • $L_{tot}(t)$, total beban (dihasilkan oleh semua pengguna)
  • $T_{tot}$, "waktu singkat"

Di dunia matematika, berbicara tentang sejumlah besar pengguna yang melakukan hal yang sama pada waktu yang sama, distribusi pengguna dari waktu ke waktu mengikuti distribusi Gaussian (atau normal), yang rumusnya adalah:

\[G(t) = \frac{1}{\sigma \sqrt{2 \pi}} e^{\frac{-(t-\mu)^2}{2 \sigma^2}}\]

Di Sini:

  • adalah nilai yang diharapkan
  • adalah simpangan baku

Dan itu digambarkan sebagai berikut (dengan $µ=0$):

Grafik distribusi Gaussian, menunjukkan bagaimana 99,7% area berada di antara plus-minus tiga sigma

Mungkin mengingatkan pada beberapa kelas yang pernah Anda ikuti—bukan hal baru. Namun, kami menghadapi masalah pertama kami di sini: Agar akurat secara matematis, kami harus mempertimbangkan rentang waktu dari $-\infty$ hingga $+\infty$, yang jelas tidak dapat dihitung.

Tetapi melihat grafik, kita melihat bahwa nilai di luar interval $[-3σ, 3σ]$ sangat mendekati nol dan tidak berbeda jauh, yang berarti pengaruhnya benar-benar dapat diabaikan dan dapat dikesampingkan. Ini lebih benar, karena tujuan kami adalah menguji peningkatan aplikasi kami, jadi kami mencari variasi pengguna dalam jumlah besar.

Selain itu, karena interval $[-3σ, 3σ]$ berisi 99,7 persen pengguna kami, interval tersebut cukup dekat dengan total untuk mengerjakannya, dan kami hanya perlu mengalikan $N_{u}$ dengan 1,003 untuk menebusnya perbedaan. Memilih interval ini memberi kita $µ=3σ$ (karena kita akan bekerja dari $t=0$).

Mengenai korespondensi dengan $T_{tot}$, memilihnya agar sama dengan $6σ$ ($[-3σ, 3σ]$) bukanlah pendekatan yang baik, karena 95,4 persen pengguna berada dalam interval $[- 2σ, 2σ]$, yang berlangsung $4σ$. Jadi memilih $T_{tot}$ sama dengan $6σ$ akan menambah separuh waktu hanya untuk 4,3 persen pengguna, yang tidak terlalu representatif. Jadi kami memilih untuk mengambil $T_{tot}=4σ$, dan kami dapat menyimpulkan:

\(σ=\frac{T_{tot}}{4}\) dan \(µ=\frac{3}{4} * T_{tot}\)

Apakah nilai-nilai itu baru saja dikeluarkan? Ya. Tapi inilah tujuan mereka, dan ini tidak akan mempengaruhi prosedur matematika. Konstanta itu untuk kita, dan mendefinisikan gagasan yang terkait dengan hipotesis kita. Ini hanya berarti bahwa sekarang setelah kita mengaturnya, skenario terburuk kita dapat diterjemahkan sebagai:

Beban dihasilkan oleh 99,7 persen dari $N{u}$, melakukan operasi yang memakan $L{u}(t)$ dan di mana 95,4 persen dari mereka melakukannya dalam durasi $T{tot}$.

(Ini adalah sesuatu yang perlu diingat saat menggunakan aplikasi web.)

Dengan menginjeksikan hasil sebelumnya ke dalam fungsi distribusi pengguna (Gaussian), kita dapat menyederhanakan persamaan sebagai berikut:

\[G(t) = \frac{4 N_{u}}{T_{tot} \sqrt{2 \pi}} e^\frac{-(4t-3T_{tot})^2}{T_{tot }^2}\]

Mulai sekarang, setelah $σ$ dan $µ$ didefinisikan, kita akan mengerjakan interval $t \in [0, \frac{3}{2}T_{tot}]$ (berlangsung $6σ$).

Berapa Total Beban Pengguna?

Langkah kedua dalam layanan mikro penskalaan otomatis adalah menghitung $L_{tot}(t)$.

Karena $G(t)$ adalah distribusi , untuk mengambil jumlah pengguna pada titik waktu tertentu, kita harus menghitung integralnya (atau menggunakan fungsi distribusi kumulatifnya). Tetapi karena tidak semua pengguna memulai operasi mereka pada saat yang sama, akan sangat merepotkan jika mencoba memperkenalkan $L_{u}(t)$ dan mengurangi persamaan menjadi rumus yang dapat digunakan.

Jadi untuk membuatnya lebih mudah, kita akan menggunakan jumlah Riemann, yang merupakan cara matematis untuk mendekati integral menggunakan jumlah hingga bentuk kecil (kita akan menggunakan persegi panjang di sini). Semakin banyak bentuk (subdivisi), semakin akurat hasilnya. Manfaat lain menggunakan subdivisi berasal dari fakta bahwa kami dapat menganggap semua pengguna dalam subdivisi telah memulai operasi mereka pada saat yang sama.

Kembali ke jumlah Riemann, ia memiliki properti berikut yang terhubung dengan integral:

\[\int_{a}^{b} f( x )dx = \lim_{n \rightarrow \infty } \sum_{k=1}^{n} ( x_{k} - x_{k-1} ) f( x_{k} )\]

Dengan $x_k$ didefinisikan sebagai berikut:

\[x_{ k } = a + k\frac{ b - a }{ n }, 0 \leq k \leq n\]

Ini benar di mana:

  • $n$ adalah jumlah subdivisi.
  • $a$ adalah batas bawah, di sini 0.
  • $b$ adalah batas tertinggi, di sini $\frac{3}{2}*T_{tot}$.
  • $f$ adalah fungsi—di sini $G$—untuk mendekati luasnya.

Grafik jumlah Riemann untuk fungsi G. Sumbu X bergerak dari nol menjadi tiga bagian T-sub-tot, dan satu persegi panjang disorot menunjukkan antara x-sub-k-minus-1 dan x- sub-k.

Catatan: Jumlah pengguna yang ada di subdivisi bukan bilangan bulat. Inilah alasan untuk dua prasyarat: Memiliki banyak pengguna (sehingga bagian desimal tidak terlalu berpengaruh), dan kebutuhan beban untuk didistribusikan secara merata di setiap instans.

Perhatikan juga bahwa kita dapat melihat bentuk persegi panjang dari subdivisi di sisi kanan definisi penjumlahan Riemann.

Sekarang kita memiliki rumus jumlah Riemann, kita dapat mengatakan bahwa nilai beban pada waktu $t$ adalah jumlah dari setiap subdivisi jumlah pengguna dikalikan dengan fungsi beban pengguna pada waktu yang sesuai . Ini dapat ditulis sebagai:

\[L_{ tot }( t ) = \lim_{n \rightarrow \infty} \sum_{ k=1 }^{ n } ( x_{k} - x_{k-1} )G( x_{k} ) L_{ u }( t - x_{k} )\]

Setelah mengganti variabel dan menyederhanakan rumus, ini menjadi:

\[L_{ tot }( t ) = \frac{6 N_{u}}{\sqrt{2 \pi}} \lim_{n \rightarrow \infty} \sum_{ k=1 }^{ n } (\ frac{1}{n}) e^{-{(\frac{6k}{n} - 3)^{2}}} L_{ u }( t - k \frac{3 T_{tot}}{2n } )\]

Dan voila ! Kami membuat fungsi beban!

Menemukan Ambang Skala Peningkatan

Untuk menyelesaikannya, kita hanya perlu menjalankan algoritma dikotomi yang memvariasikan ambang batas untuk menemukan nilai tertinggi di mana beban per instance tidak pernah melebihi batas maksimumnya di seluruh fungsi beban. (Inilah yang dilakukan oleh aplikasi.)

Mendeduksi Parameter Orkestrasi Lainnya

Segera setelah Anda menemukan ambang peningkatan skala ($S_{up}$), parameter lain cukup mudah untuk dihitung.

Dari $S_{up}$ Anda akan mengetahui jumlah maksimum instance Anda. (Anda juga dapat mencari beban maksimum pada fungsi beban Anda dan membagi per beban maksimum per instance, dibulatkan ke atas.)

Jumlah minimum ($N_{min}$) instance harus ditentukan sesuai dengan infrastruktur Anda. (Saya akan merekomendasikan memiliki minimal satu replika per AZ.) Tetapi juga perlu memperhitungkan fungsi beban: Karena fungsi Gaussian meningkat cukup cepat, distribusi beban lebih intens (per replika) di awal, jadi Anda mungkin ingin menambah jumlah minimum replika untuk meredam efek ini. (Ini kemungkinan besar akan meningkatkan $S_{up}$ Anda.)

Terakhir, setelah Anda menentukan jumlah minimum replika, Anda dapat menghitung ambang penurunan skala ($S_{down}$) dengan mempertimbangkan hal berikut: Karena penskalaan satu replika tidak lebih berpengaruh pada instans lain daripada saat penskalaan dari $N_{min}+1$ hingga $N_{min}$, kita harus memastikan ambang peningkatan skala tidak akan dipicu segera setelah penurunan skala. Jika dibiarkan, ini akan memiliki efek yo-yo. Dengan kata lain:

\[( N_{ mnt } + 1) S_{ turun } < N_{ mnt }S_{ naik }\]

Atau:

\[S_{ turun } < \frac{N_{ min }}{N_{min}+1}S_{ naik }\]

Selain itu, kami dapat mengakui bahwa semakin lama kluster Anda dikonfigurasi untuk menunggu sebelum diturunkan, semakin aman untuk menetapkan $S_{down}$ lebih dekat ke batas yang lebih tinggi. Sekali lagi, Anda harus menemukan keseimbangan yang cocok untuk Anda.

Perhatikan bahwa saat menggunakan sistem orkestrasi Mesosphere Marathon dengan autoscaler-nya, jumlah maksimum instans yang dapat dihapus sekaligus dari penskalaan terkait dengan AS_AUTOSCALE_MULTIPLIER ($A_{mult}$), yang menyiratkan:

\[S_{ turun } < \frac{S_{ naik }}{ A_{mult} }\]

Bagaimana Dengan Fungsi Muat Pengguna?

Ya, itu sedikit masalah, dan bukan yang termudah untuk dipecahkan secara matematis—jika itu mungkin sama sekali.

Untuk mengatasi masalah ini, idenya adalah menjalankan satu instance aplikasi Anda, dan meningkatkan jumlah pengguna yang melakukan tugas yang sama berulang kali hingga beban server mencapai maksimum yang ditetapkan (tetapi tidak melebihi). Kemudian bagi dengan jumlah pengguna dan hitung rata-rata waktu permintaan. Ulangi prosedur ini dengan setiap tindakan yang ingin Anda integrasikan ke dalam fungsi memuat pengguna Anda, tambahkan beberapa waktu, dan di sanalah Anda.

Saya sadar bahwa prosedur ini menyiratkan mengingat bahwa setiap permintaan pengguna memiliki beban konstan selama pemrosesannya (yang jelas salah), tetapi banyak pengguna akan menciptakan efek ini karena masing-masing dari mereka tidak berada pada langkah pemrosesan yang sama pada waktu yang sama. . Jadi saya kira ini adalah perkiraan yang dapat diterima, tetapi sekali lagi menyiratkan bahwa Anda berurusan dengan sejumlah besar pengguna.

Anda juga dapat mencoba dengan metode lain, seperti grafik nyala CPU. Tapi saya pikir akan sangat sulit untuk membuat formula akurat yang akan menghubungkan tindakan pengguna dengan konsumsi sumber daya.

Memperkenalkan app-autoscaling-calculator

Dan sekarang, untuk aplikasi web kecil yang disebutkan di seluruh: Dibutuhkan sebagai input fungsi beban Anda, konfigurasi orkestra penampung Anda, dan beberapa parameter umum lainnya dan mengembalikan ambang peningkatan skala dan angka terkait instance lainnya.

Proyek ini di-host di GitHub, tetapi juga memiliki versi langsung yang tersedia.

Berikut adalah hasil yang diberikan oleh aplikasi web, dijalankan terhadap data uji (di Kubernetes):

Grafik yang menunjukkan jumlah instance dan beban per instance dari waktu ke waktu

Scaling Microservices: Tidak Ada Lagi Meraba-raba dalam Gelap

Ketika datang ke arsitektur aplikasi layanan mikro, penyebaran kontainer menjadi titik sentral dari keseluruhan infrastruktur. Dan semakin baik orkestra dan container dikonfigurasi, semakin lancar runtimenya.

Kami yang bergerak di bidang layanan DevOps selalu mencari cara yang lebih baik untuk menyetel parameter orkestrasi untuk aplikasi kami. Mari kita ambil pendekatan yang lebih matematis untuk layanan mikro penskalaan otomatis!