Penyetelan performa transaksi bisnis terdistribusi

Azure Kubernetes Service (AKS)
Azure Cache for Redis

Artikel ini menjelaskan bagaimana tim pengembangan menggunakan metrik untuk menemukan penyempitan dan meningkatkan performa sistem terdistribusi. Artikel ini didasarkan pada pengujian beban aktual yang kami lakukan untuk aplikasi sampel. Aplikasi ini berasal dari Dasar Azure Kubernetes Service (AKS) untuk layanan mikro.

Artikel ini adalah bagian dari rangkaian. Baca bagian pertama di sini.

Skenario: Aplikasi klien memulai transaksi bisnis yang melibatkan beberapa langkah.

Skenario ini melibatkan aplikasi pengiriman drone yang berjalan pada AKS. Pelanggan menggunakan aplikasi web untuk menjadwalkan pengiriman dengan drone. Setiap transaksi memerlukan beberapa langkah yang dilakukan oleh layanan mikro terpisah di back end:

  • Layanan Pengiriman mengelola pengiriman.
  • Layanan Penjadwal Drome menjadwalkan pengambilan drone.
  • Layanan Paket mengelola paket.

Ada dua layanan lain: Layanan Penyerapan yang menerima permintaan klien dan menempatkannya pada antrean untuk diproses, dan layanan Alur Kerja yang mengoordinasikan langkah-langkah dalam alur kerja.

Diagram memperlihatkan alur kerja terdistribusi

Untuk informasi selengkapnya tentang skenario ini, lihat Merancang arsitektur layanan mikro.

Tes 1: Garis Besar

Untuk pengujian beban pertama, tim membuat kluster AKS enam node dan menyebarkan tiga replika dari setiap layanan mikro. Pengujian beban adalah pengujian beban langkah, dimulai dari dua pengguna simulasi dan ditingkatkan hingga 40 pengguna simulasi.

Pengaturan Nilai
Simpul kluster 6
Pod 3 per layanan

Grafik berikut menunjukkan hasil pengujian beban, seperti yang ditunjukkan pada Visual Studio. Garis ungu mengatur beban pengguna, dan garis oranye mengatur total permintaan.

Grafik hasil pengujian beban Visual Studio

Hal pertama yang harus disadari tentang skenario ini adalah bahwa permintaan klien per detik bukanlah metrik performa yang berguna. Hal itu karena proses aplikasi meminta secara asinkron, sehingga klien langsung mendapat respons. Kode respons selalu HTTP 202 (Accepted), yang berarti permintaan diterima, tetapi pemrosesan tidak selesai.

Apa yang benar-benar ingin kita ketahui adalah apakah backend mengikuti tingkat permintaan. Antrean Bus Layanan dapat menyerap lonjakan, tetapi jika backend tidak dapat menangani beban yang berkelanjutan, pemrosesan akan makin tertinggal.

Berikut adalah grafik yang lebih informatif. Ini mengatur jumlah pesan masuk dan keluar pada antrean Bus Layanan. Pesan masuk ditampilkan dalam warna biru muda, dan pesan keluar ditampilkan dalam warna biru tua:

Grafik pesan masuk dan keluar

Bagan ini menunjukkan bahwa tingkat pesan masuk meningkat, mencapai puncak, lalu turun kembali ke nol pada akhir pengujian beban. Namun, jumlah pesan keluar mencapai puncak di awal pengujian, lalu benar-benar turun. Artinya layanan Alur Kerja, yang menangani permintaan, tidak mengikuti. Bahkan setelah pengujian beban berakhir (sekitar pukul 9.22 pada grafik), pesan masih diproses karena layanan Alur Kerja terus mengeluarkan antrean.

Apa yang memperlambat pemrosesan? Hal pertama yang harus dicari adalah kesalahan atau pengecualian yang mungkin menunjukkan masalah sistematis. Peta Aplikasi di Azure Monitor menunjukkan grafik panggilan di antara komponen, dan merupakan cara cepat untuk menemukan masalah, lalu mengeklik untuk mendapatkan detail selengkapnya.

Benar saja, Peta Aplikasi menunjukkan bahwa layanan Alur Kerja mendapatkan kesalahan dari layanan Pengiriman:

Cuplikan Layar Peta Aplikasi

Untuk melihat detail selengkapnya, Anda dapat memilih node dalam grafik dan mengeklik tampilan transaksi end-to-end. Dalam hal ini, ini menunjukkan bahwa layanan Pengiriman mengembalikan kesalahan HTTP 500. Pesan kesalahan menunjukkan bahwa pengecualian dibuat karena batasan memori di Azure Cache for Redis.

Cuplikan layar tampilan transaksi end-to-end

Anda mungkin memperhatikan bahwa panggilan ke Redis ini tidak muncul di Peta Aplikasi. Itu karena pustaka .NET untuk Application Insights tidak memiliki dukungan bawaan untuk melacak Redis sebagai dependensi. (Untuk daftar apa yang didukung di luar kotak, lihat Pengumpulan otomatis dependensi.) Sebagai fallback, Anda dapat menggunakan TRACKDependency API untuk melacak dependensi apa pun. Pengujian beban sering mengungkapkan celah semacam ini dalam telemetri, yang dapat diperbaiki.

Uji 2: Peningkatan ukuran cache

Untuk pengujian beban kedua, tim pengembangan meningkatkan ukuran cache di Azure Cache for Redis. (Lihat Cara Menskalakan Azure Cache for Redis.) Perubahan ini menyelesaikan pengecualian di luar memori, dan sekarang Peta Aplikasi menunjukkan kesalahan nol:

Cuplikan layar Peta Aplikasi yang menunjukkan bahwa peningkatan ukuran cache menyelesaikan pengecualian di luar memori.

Namun, masih ada banyak kelambatan dalam memproses pesan. Pada puncak pengujian beban, tingkat pesan masuk lebih dari 5× tingkat keluar:

Grafik pesan masuk dan keluar yang menunjukkan tingkat pesan masuk lebih dari 5x tingkat keluar.

Grafik berikut mengukur throughput dalam hal penyelesaian pesan — yaitu, tingkat ketika layanan Alur Kerja menandai pesan Bus Layanan sebagai selesai. Setiap titik pada grafik mewakili 5 detik data, menunjukkan throughput maksimum ~ 16/detik.

Grafik throughput pesan

Grafik ini dihasilkan dengan menjalankan kueri di ruang kerja Analitik Log, menggunakan bahasa kueri Kusto:

let start=datetime("2020-07-31T22:30:00.000Z");
let end=datetime("2020-07-31T22:45:00.000Z");
dependencies
| where cloud_RoleName == 'fabrikam-workflow'
| where timestamp > start and timestamp < end
| where type == 'Azure Service Bus'
| where target has 'https://dev-i-iuosnlbwkzkau.servicebus.windows.net'
| where client_Type == "PC"
| where name == "Complete"
| summarize succeeded=sumif(itemCount, success == true), failed=sumif(itemCount, success == false) by bin(timestamp, 5s)
| render timechart

Uji 3: Meluaskan skala layanan backend

Tampaknya back end mengalami penyempitan. Langkah selanjutnya yang mudah adalah meluaskan skala layanan bisnis (Paket, Pengiriman, dan Penjadwal Drone), dan melihat apakah throughput meningkat. Untuk pengujian beban berikutnya, tim meningkatkan skala layanan ini dari tiga replika menjadi enam replika.

Pengaturan Nilai
Simpul kluster 6
Layanan penyerapan 3 replika
Layanan alur kerja 3 replika
Layanan Paket, Pengiriman, Penjadwal Drone Masing-masing 6 replika

Sayangnya pengujian beban ini hanya menunjukkan peningkatan sederhana. Pesan keluar masih tidak sama dengan pesan masuk:

Grafik pesan masuk dan keluar yang menunjukkan bahwa pesan keluar masih tidak sama dengan pesan masuk.

Throughput lebih konsisten, tetapi maksimum yang dicapai hampir sama dengan pengujian sebelumnya:

Graph throughput pesan yang menunjukkan bahwa maksimum yang dicapai hampir sama dengan pengujian sebelumnya.

Selain itu, melihat wawasan kontainer Azure Monitor, tampaknya masalahnya tidak disebabkan oleh kelelahan sumber daya dalam kluster. Pertama, metrik tingkat node menunjukkan bahwa pemanfaatan CPU tetap di bawah 40% bahkan pada persentil ke-95, dan pemanfaatan memori sekitar 20%.

Graph pemanfaatan node AKS

Di lingkungan Kubernetes, setiap pod dapat dibatasi sumber dayanya meskipun node tidak. Namun, tampilan tingkat pod menunjukkan bahwa semua pod sehat.

Graph pemanfaatan pod AKS

Dari pengujian ini, tampaknya hanya menambahkan lebih banyak pod ke back end tidak akan membantu. Langkah selanjutnya adalah melihat lebih dekat pada layanan Alur Kerja untuk memahami apa yang terjadi ketika pesan diproses. Application Insights menunjukkan bahwa durasi rata-rata operasi Process layanan Alur Kerja adalah 246 ms.

Cuplikan Layar Application Insights

Kita juga dapat menjalankan kueri untuk mendapatkan metrik pada masing-masing operasi dalam setiap transaksi:

target percentile_duration_50 percentile_duration_95
https://dev-i-iuosnlbwkzkau.servicebus.windows.net/ | dev-i-iuosnlbwkzkau 86.66950203 283.4255578
delivery 37 57
paket 12 17
dronescheduler 21 41

Baris pertama dalam tabel ini mewakili antrean Bus Layanan. Baris lainnya adalah panggilan ke layanan backend. Untuk referensi, berikut adalah kueri Analitik Log untuk tabel ini:

let start=datetime("2020-07-31T22:30:00.000Z");
let end=datetime("2020-07-31T22:45:00.000Z");
let dataset=dependencies
| where timestamp > start and timestamp < end
| where (cloud_RoleName == 'fabrikam-workflow')
| where name == 'Complete' or target in ('package', 'delivery', 'dronescheduler');
dataset
| summarize percentiles(duration, 50, 95) by target

Cuplikan layar hasil kueri Analitik Log

Latensi ini terlihat masuk akal. Namun, inilah wawasan utamanya: Jika total waktu operasi ~250 ms, itu menempatkan batas atas yang ketat pada seberapa cepat pesan dapat diproses secara seri. Oleh karena itu, kunci untuk meningkatkan throughput adalah paralelisme yang lebih besar.

Itu harus dimungkinkan dalam skenario ini, karena dua alasan:

  • Ini adalah panggilan jaringan, sehingga sebagian besar waktu dihabiskan untuk menunggu penyelesaian I/O
  • Pesan bersifat independen, dan tidak perlu diproses secara berurutan.

Tes 4: Meningkatkan paralelisme

Untuk pengujian ini, tim berfokus pada peningkatan paralelisme. Untuk melakukannya, mereka menyesuaikan dua pengaturan pada klien Bus Layanan yang digunakan oleh layanan Alur Kerja:

Pengaturan Deskripsi Default Nilai baru
MaxConcurrentCalls Jumlah maksimum pesan yang akan diproses secara bersamaan. 1 20
PrefetchCount Berapa banyak pesan yang akan diambil klien sebelumnya ke cache lokalnya. 0 3000

Untuk informasi selengkapnya tentang pengaturan ini, lihat Praktik Terbaik untuk peningkatan performa menggunakan Olahpesan Bus Layanan. Menjalankan pengujian dengan pengaturan ini menghasilkan grafik berikut:

Grafik pesan masuk dan keluar yang menunjukkan jumlah pesan keluar yang sebenarnya melebihi jumlah total pesan masuk.

Ingat bahwa pesan masuk ditampilkan dalam warna biru muda, dan pesan keluar ditampilkan dalam warna biru tua.

Sekilas grafik ini terlihat sangat aneh. Untuk sementara, tingkat pesan keluar melacak tingkat masuk dengan tepat. Namun, selanjutnya sekitar tanda 2.03, tingkat pesan masuk turun, sementara jumlah pesan keluar terus meningkat, sebenarnya melebihi jumlah total pesan masuk. Itu tampaknya mustahil.

Petunjuk untuk misteri ini dapat ditemukan dalam tampilan Dependensi di Application Insights. Bagan ini merangkum semua panggilan yang dilakukan layanan Alur Kerja untuk Bus Layanan:

Grafik panggilan dependensi

Perhatikan entri tersebut untuk DeadLetter. Panggilan tersebut menunjukkan pesan masuk ke anteran surat gagal Bus Layanan.

Untuk memahami apa yang terjadi, Anda perlu memahami semantik Peek-Lock dalam Bus Layanan. Ketika klien menggunakan Peek-Lock, Bus Layanan secara atomik mengambil dan mengunci pesan. Meskipun kunci disimpan, pesan dijamin tidak akan dikirim ke penerima lain. Jika kunci kedaluwarsa, pesan akan tersedia untuk penerima lain. Setelah jumlah maksimum upaya pengiriman (yang dapat dikonfigurasi), Bus Layanan akan menempatkan pesan ke antrean surat gagal , tempat pesan dapat diperiksa nanti.

Ingat bahwa layanan Alur Kerja sedang menyiapkan sejumlah besar pesan — 3000 pesan sekaligus). Itu berarti total waktu untuk memproses setiap pesan lebih lama, yang menyebabkan waktu habis pesan, kembali ke antrean, dan akhirnya masuk ke antrean surat gagal.

Anda juga dapat melihat perilaku ini dalam pengecualian, yang merekam banyak pengecualian MessageLostLockException:

Cuplikan layar pengecualian Application Insights yang menunjukkan banyak pengecualian MessageLostLockException.

Uji 5: Meningkatkan durasi penguncian

Untuk penguncian beban ini, durasi penguncian pesan diatur ke 5 menit, untuk mencegah batas waktu penguncian. Grafik pesan masuk dan keluar sekarang menunjukkan bahwa sistem mengikuti tingkat pesan masuk:

Graph pesan masuk dan keluar yang menunjukkan bahwa sistem mengikuti tingkat pesan masuk.

Selama total durasi pengujian beban 8 menit, aplikasi menyelesaikan operasi 25 K, dengan throughput puncak 72 operasi/detik, mewakili peningkatan throughput maksimum sebesar 400%.

Grafik throughput pesan yang menunjukkan peningkatan throughput maksimum sebesar 400%.

Namun, menjalankan pengujian yang sama dengan durasi yang lebih lama menunjukkan bahwa aplikasi tidak dapat mempertahankan tingkat ini:

Grafik pesan masuk dan keluar yang menunjukkan bahwa aplikasi tidak dapat mempertahankan tingkat ini.

Metrik kontainer menunjukkan bahwa pemanfaatan CPU maksimum mendekati 100%. Pada titik ini, aplikasi tampaknya terikat CPU. Penskalaan kluster sekarang dapat meningkatkan performa, tidak seperti upaya sebelumnya untuk meningkatan skala.

Grafik pemanfaatan node AKS yang menunjukkan bahwa pemanfaatan CPU maksimum mendekati 100%.

Uji 6: Meluaskan skala layanan backend (lagi)

Untuk pengujian beban akhir dalam seri, tim meningkatkan skala kluster dan pod Kubernetes sebagai berikut:

Pengaturan Nilai
Simpul kluster 12
Layanan penyerapan 3 replika
Layanan alur kerja 6 replika
Layanan Paket, Pengiriman, Penjadwal Drone Masing-masing 9 replika

Pengujian ini menghasilkan throughput berkelanjutan yang lebih tinggi, tanpa kelambatan yang signifikan dalam memproses pesan. Selain itu, pemanfaatan CPU node tetap di bawah 80%.

Grafik throughput pesan yang menunjukkan throughput berkelanjutan yang lebih tinggi, tanpa kelambatan yang signifikan dalam memproses pesan.

Ringkasan

Untuk skenario ini, penyempitan berikut diidentifikasi:

  • Pengecualian di luar memori di Azure Cache for Redis.
  • Kurangnya paralelisme dalam pemrosesan pesan.
  • Durasi penguncian pesan yang tidak mencukupi, yang menyebabkan batas waktu penguncian dan pesan ditempatkan dalam antrean surat gagal.
  • Kelelahan CPU.

Untuk mendiagnosis masalah ini, tim pengembangan mengandalkan metrik berikut:

  • Tingkat pesan Bus Layanan masuk dan keluar.
  • Peta Aplikasi di Application Insights.
  • Kesalahan dan pengecualian.
  • Kueri Log Analytics kustom.
  • Pemanfaatan CPU dan memori dalam wawasan kontainer Azure Monitor.

Langkah berikutnya

Untuk informasi selengkapnya tentang rancangan skenario ini, lihat Merancang arsitektur layanan mikro.