Anti pola Instansiasi yang tidak tepat
Terkadang, instans baru dari suatu kelas terus menerus dibuat, saat dimaksudkan untuk dibuat sekali lalu dibagikan. Perilaku ini dapat merusak performa, dan disebut sebagai antipola instantiasi yang tidak tepat. Sebuah anti pola adalah respons umum untuk masalah berulang yang biasanya tidak efektif dan bahkan mungkin kontra-produktif.
Deskripsi masalah
Banyak pustaka menyediakan abstraksi sumber daya eksternal. Secara internal, kelas ini biasanya mengelola koneksinya sendiri ke sumber daya, yang bertindak sebagai broker yang dapat digunakan klien untuk mengakses sumber daya. Berikut adalah beberapa contoh kelas broker yang relevan dengan aplikasi Azure:
System.Net.Http.HttpClient
. Berkomunikasi dengan layanan web menggunakan HTTP.Microsoft.ServiceBus.Messaging.QueueClient
. Mengirim dan menerima pesan ke antrean Bus Layanan.Microsoft.Azure.Documents.Client.DocumentClient
. Menyambungkan ke instans Azure Cosmos DB.StackExchange.Redis.ConnectionMultiplexer
. Terhubung ke Redis, termasuk Azure Cache for Redis.
Kelas ini dimaksudkan untuk dipakai sekali dan digunakan kembali sepanjang masa aplikasi. Namun, itu adalah kesalahpahaman umum bahwa kelas ini harus diperoleh hanya jika diperlukan dan dirilis dengan cepat. (Yang tercantum di sini kebetulan adalah pustaka .NET, tetapi polanya tidak unik untuk .NET.) Contoh ASP.NET berikut membuat instans HttpClient
untuk berkomunikasi dengan layanan jarak jauh. Anda dapat menemukan sampel lengkap di sini.
public class NewHttpClientInstancePerRequestController : ApiController
{
// This method creates a new instance of HttpClient and disposes it for every call to GetProductAsync.
public async Task<Product> GetProductAsync(string id)
{
using (var httpClient = new HttpClient())
{
var hostName = HttpContext.Current.Request.Url.Host;
var result = await httpClient.GetStringAsync(string.Format("http://{0}:8080/api/...", hostName));
return new Product { Name = result };
}
}
}
Dalam aplikasi web, teknik ini tidak skalabel. Objek HttpClient
baru dibuat untuk setiap permintaan pengguna. Di bawah beban berat, server web mungkin menghabiskan jumlah soket yang tersedia, yang mengakibatkan kesalahan SocketException
.
Masalah ini tidak terbatas pada kelas HttpClient
. Kelas lain yang membungkus sumber daya atau mahal untuk dibuat dapat menyebabkan masalah serupa. Contoh berikut membuat instans dari kelas ExpensiveToCreateService
. Di sini masalahnya belum tentu gangguan soket, tetapi berapa lama waktu yang dibutuhkan untuk membuat setiap instans. Membuat dan menghancurkan instans kelas ini secara terus-menerus dapat berdampak buruk pada skalabilitas sistem.
public class NewServiceInstancePerRequestController : ApiController
{
public async Task<Product> GetProductAsync(string id)
{
var expensiveToCreateService = new ExpensiveToCreateService();
return await expensiveToCreateService.GetProductByIdAsync(id);
}
}
public class ExpensiveToCreateService
{
public ExpensiveToCreateService()
{
// Simulate delay due to setup and configuration of ExpensiveToCreateService
Thread.SpinWait(Int32.MaxValue / 100);
}
...
}
Cara memperbaiki anti pola instantiasi yang tidak tepat
Jika kelas yang membungkus sumber daya eksternal dapat dibagikan dan aman untuk utas, buat instans database tunggal bersama atau kumpulan instans kelas yang dapat digunakan kembali.
Contoh berikut menggunakan instans HttpClient
statis, sehingga berbagi koneksi di semua permintaan.
public class SingleHttpClientInstanceController : ApiController
{
private static readonly HttpClient httpClient;
static SingleHttpClientInstanceController()
{
httpClient = new HttpClient();
}
// This method uses the shared instance of HttpClient for every call to GetProductAsync.
public async Task<Product> GetProductAsync(string id)
{
var hostName = HttpContext.Current.Request.Url.Host;
var result = await httpClient.GetStringAsync(string.Format("http://{0}:8080/api/...", hostName));
return new Product { Name = result };
}
}
Pertimbangan
Elemen utama dari anti pola ini adalah berulang kali membuat dan menghancurkan instans objek yang dapat dibagikan. Jika suatu kelas tidak dapat dibagikan (tidak aman untuk utas), anti pola ini tidak berlaku.
Jenis sumber daya bersama mungkin menentukan apakah Anda harus menggunakan database tunggal atau membuat kumpulan. Kelas
HttpClient
dirancang untuk dibagikan, bukan dikumpulkan. Objek lain mungkin mendukung pengumpulan, yang memungkinkan sistem menyebarkan beban kerja ke beberapa instans.Objek yang Anda bagikan di beberapa permintaan harus aman dari utas. Kelas
HttpClient
dirancang untuk digunakan dengan cara ini, tetapi kelas lain mungkin tidak mendukung permintaan bersamaan, jadi periksa dokumentasi yang tersedia.Berhati-hatilah dalam mengatur properti pada objek bersama, karena hal ini dapat menyebabkan kondisi perlombaan. Misalnya, mengatur
DefaultRequestHeaders
pada kelasHttpClient
sebelum setiap permintaan dapat membuat kondisi perlombaan. Atur properti tersebut satu kali (misalnya, selama startup), dan buat instans terpisah jika Anda perlu mengonfigurasi pengaturan yang berbeda.Beberapa jenis sumber daya langka dan tidak boleh digunakan. Koneksi database adalah contohnya. Memegang koneksi database terbuka yang tidak diperlukan dapat mencegah pengguna konkuren lainnya mendapatkan akses ke database.
Dalam .NET Framework, banyak objek yang membuat koneksi ke sumber daya eksternal dibuat dengan menggunakan metode pabrik statis dari kelas lain yang mengelola koneksi ini. Objek ini dimaksudkan untuk disimpan dan digunakan kembali, bukan dibuang dan dibuat kembali. Misalnya, di Azure Service Bus, objek
QueueClient
dibuat melalui objekMessagingFactory
. Secara internal,MessagingFactory
mengelola koneksi. Untuk informasi selengkapnya, lihat Praktik Terbaik untuk peningkatan performa menggunakan Pesan Bus Layanan.
Cara mendeteksi anti pola instantiasi yang tidak tepat
Gejala masalah ini termasuk penurunan throughput atau tingkat kesalahan yang meningkat, beserta satu atau beberapa hal berikut:
- Peningkatan pengecualian yang menunjukkan kelelahan sumber daya seperti soket, koneksi database, pegangan file, dan sebagainya.
- Peningkatan penggunaan memori dan pengumpulan sampah.
- Peningkatan aktivitas jaringan, disk, atau database.
Anda dapat melakukan langkah-langkah berikut untuk membantu mengidentifikasi masalah ini:
- Melakukan pemantauan proses sistem produksi, untuk mengidentifikasi titik-titik ketika waktu respons melambat atau sistem gagal karena kekurangan sumber daya.
- Periksa data telemetri yang ditangkap pada titik-titik ini untuk menentukan operasi mana yang mungkin menciptakan dan menghancurkan objek yang menghabiskan sumber daya.
- Uji beban setiap operasi yang dicurigai, dalam lingkungan pengujian yang terkontrol daripada sistem produksi.
- Tinjau kode sumber dan periksa bagaimana objek broker dikelola.
Lihat pelacakan tumpukan untuk operasi yang berjalan lambat atau yang menghasilkan pengecualian saat sistem sedang dimuat. Informasi ini dapat membantu mengidentifikasi bagaimana operasi ini menggunakan sumber daya. Pengecualian dapat membantu menentukan apakah kesalahan disebabkan oleh habisnya sumber daya bersama.
Contoh diagnosis
Bagian berikut menerapkan langkah-langkah ini ke aplikasi contoh yang dijelaskan sebelumnya.
Identifikasi titik pelambatan atau kegagalan
Gambar berikut menunjukkan hasil yang dihasilkan menggunakan New Relic APM, yang menunjukkan operasi yang memiliki waktu respons yang buruk. Dalam hal ini, metode GetProductAsync
di pengontrol NewHttpClientInstancePerRequest
perlu diselidiki selengkapnya. Perhatikan bahwa tingkat kesalahan juga meningkat saat operasi ini berjalan.
Memeriksa data telemetri dan menemukan korelasinya
Gambar berikutnya menunjukkan data yang diambil menggunakan pembuatan profil utas, selama periode yang sama sesuai dengan gambar sebelumnya. Sistem menghabiskan banyak waktu untuk membuka koneksi soket, dan bahkan lebih banyak waktu untuk menutupnya dan menangani pengecualian soket.
Melakukan pengujian beban
Gunakan pengujian beban untuk menyimulasikan operasi umum yang mungkin dilakukan pengguna. Tindakan ini dapat membantu mengidentifikasi bagian mana dari sistem yang mengalami gangguan sumber daya di bawah beban yang bervariasi. Lakukan pengujian ini di lingkungan yang terkontrol, bukan di sistem produksi. Grafik berikut menunjukkan throughput permintaan yang ditangani oleh pengontrol NewHttpClientInstancePerRequest
saat beban pengguna meningkat menjadi 100 pengguna bersamaan.
Pada awalnya, volume permintaan yang ditangani per detik meningkat seiring dengan meningkatnya beban kerja. Namun, pada sekitar 30 pengguna, volume permintaan yang berhasil mencapai batas, dan sistem mulai menghasilkan pengecualian. Sejak saat itu, volume pengecualian secara bertahap meningkat seiring dengan beban pengguna.
Pengujian beban melaporkan kegagalan ini sebagai kesalahan HTTP 500 (Server Internal). Meninjau telemetri menunjukkan bahwa kesalahan ini disebabkan oleh sistem kehabisan sumber daya soket, karena semakin banyak HttpClient
objek yang dibuat.
Grafik berikutnya menunjukkan pengujian serupa untuk pengontrol yang membuat objek ExpensiveToCreateService
kustom.
Kali ini, pengontrol tidak menghasilkan pengecualian apa pun, tetapi throughput masih mencapai dataran tinggi, sementara waktu respons rata-rata meningkat dengan faktor 20. (Grafik menggunakan skala logaritma untuk waktu respons dan throughput.) Telemetri menunjukkan bahwa membuat instans baru adalah ExpensiveToCreateService
penyebab utama masalah.
Menerapkan solusi dan memverifikasi hasilnya
Setelah mengganti metode GetProductAsync
untuk berbagi satu instans HttpClient
, pengujian beban kedua menunjukkan peningkatan performa. Tidak ada kesalahan yang dilaporkan, dan sistem mampu menangani peningkatan beban hingga 500 permintaan per detik. Waktu respons rata-rata dipotong setengahnya, dibandingkan dengan pengujian sebelumnya.
Sebagai perbandingan, gambar berikut menunjukkan telemetri jejak tumpukan. Kali ini, sistem menghabiskan sebagian besar waktunya untuk melakukan pekerjaan nyata, daripada membuka dan menutup soket.
Grafik berikutnya menunjukkan pengujian beban serupa menggunakan instans bersama dari objek ExpensiveToCreateService
. Sekali lagi, volume permintaan yang ditangani meningkat sejalan dengan beban pengguna, sementara waktu respons rata-rata tetap rendah.