Yanlış Örnek Oluşturma Kötü Modeli
Bazen bir sınıfın yeni örnekleri, bir kez oluşturulup sonra paylaşılması amaçlandığında sürekli oluşturulur. Bu davranış performansa zarar verebilir ve hatalı örnek oluşturma kötü modeli olarak adlandırılır. Kötü model, genellikle etkisiz olan ve hatta üretken olmayan yinelenen bir soruna verilen yaygın bir yanıttır.
Sorun açıklaması
Birçok kitaplık dış kaynakların özetlerini sağlar. Dahili olarak, bu sınıflar normalde kaynakla aralarındaki bağlantıları yönetir, istemcilerin kaynağa erişmek için kullanabilecekleri aracı işlevini görürler. Burada, Azure uygulamalarına uygun bazı aracı sınıfı örnekleri bulabilirsiniz:
System.Net.Http.HttpClient
. HTTP kullanarak bir web hizmetiyle iletişim kurar.Microsoft.ServiceBus.Messaging.QueueClient
. Service Bus kuyruğuna iletiler gönderir ve alır.Microsoft.Azure.Documents.Client.DocumentClient
. Azure Cosmos DB örneğine bağlanır.StackExchange.Redis.ConnectionMultiplexer
. Redis için Azure Cache de dahil olmak üzere Redis'e bağlanır.
Bu sınıfların tek bir kez örneğinin oluşturulması ve uygulamanın kullanım ömrü boyunca bu örneğin yeniden kullanılması hedeflenmiştir. Öte yandan, bu sınıfların ancak ihtiyaç duyulduğunda alınması ve hızla serbest bırakılması gerektiği yaygın bir yanlış anlamadır. (Burada listelenenler .NET kitaplıkları olabilir, ancak desen .NET için benzersiz değildir.) Aşağıdaki ASP.NET örnek, uzak bir hizmetle iletişim kurmak için bir örneği HttpClient
oluşturur. Örneğin tamamını burada bulabilirsiniz.
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 };
}
}
}
Web uygulamasında, bu teknik ölçeklenebilir değildir. Her kullanıcı isteği için yeni bir HttpClient
nesnesi oluşturulur. Ağır yük koşullarında, web sunucusu kullanılabilir yuva sayısını tüketebilir ve bunun sonucunda SocketException
hataları alınır.
Bu sorun, HttpClient
sınıfıyla sınırlı değildir. Kaynakları sarmalayan veya oluşturması pahalıya gelen sınıflar benzer sorunlara neden olabilir. Aşağıdaki örnek, ExpensiveToCreateService
sınıfının bir örneğini oluşturur. Burada sorun yuva tükenmesi olmayabilir, ama her kaynağı oluşturmanın ne kadar süreceğiyle ilgilidir. Sürekli olarak bu sınıfın örnekleri oluşturmak ve yok etmek, sistemin ölçeklenebilirliğini olumsuz etkileyebilir.
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);
}
...
}
Yanlış örnek oluşturma kötü modeli nasıl düzeltilir
Dış kaynağı sarmalayan sınıf paylaşılabilir ve iş parçacığı güvenli bir sınıfsa, bu sınıfın paylaşılan tekil bir örneğini veya yeniden kullanılabilir örnekler havuzunu oluşturun.
Aşağıdaki örnek, statik bir HttpClient
örneğini kullanır ve böylelikle bağlantıyı tüm istekler arasında paylaşır.
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 };
}
}
Dikkat edilmesi gereken noktalar
Bu kötü modelin anahtar öğesi, paylaşılabilir bir nesnenin örneklerini sürekli oluşturmak ve yok etmektir. Sınıf paylaşılabilir değilse (iş parçacığı güvenli değilse), bu kötü model geçerli olmaz.
Tekil örnek mi kullanacağınızı yoksa havuz mu oluşturacağınızı, paylaşılan kaynağın türü belirleyebilir.
HttpClient
sınıfı, havuz oluşturmak yerine paylaşmak için tasarlanmıştır. Diğer nesneler havuz oluşturmayı destekleyerek, sistemin iş yükünü birden çok örneğe dağıtmasına olanak tanıyabilir.Birden çok istek arasında paylaştığınız nesneler iş parçacığı güvenli olmalıdır.
HttpClient
sınıfı bu şekilde kullanılmak üzere tasarlanmıştır, ama diğer sınıflar eşzamanlı istekleri desteklemeyebilir; bu nedenle, sağlanan belgeleri gözden geçirin.Paylaşılan nesnelerde özellikleri ayarlama konusunda dikkatli olun çünkü bu yarış durumlarına yol açabilir. Öneğin, her istekten önce
HttpClient
sınıfındaDefaultRequestHeaders
ayarı yapmak yarış durumu oluşturabilir. Bu tür özellikleri bir kez ayarlayın (örneğin başlatma sırasında) ve farklı ayarlar yapılandırmanız gerekirse ayrı örnekler oluşturun.Bazı kaynak türleri azdır ve elde tutulmamalıdır. Veritabanı bağlantıları buna örnek verilebilir. Gerekli olmayan bir veritabanı bağlantısını açık tutmak, diğer eşzamanlı kullanıcıların veritabanına erişmesini engelleyebilir.
.NET Framework'te dış kaynaklara bağlantı oluşturan birçok nesne, bu bağlantıları yöneten diğer sınıfların statik fabrika yöntemleri kullanılarak oluşturulur. Bu nesnelerin atılıp yeniden oluşturulması değil, kaydedilmesi ve yeniden kullanılması hedeflenmiştir. Örneğin, Azure Service Bus'ta
QueueClient
nesnesi birMessagingFactory
nesnesi aracılığıyla oluşturulur. Dahili olarak,MessagingFactory
bağlantıları yönetir. Daha fazla bilgi için bkz. Service Bus Mesajlaşması kullanarak en iyi performans geliştirme en deneyimleri.
Yanlış örnek oluşturma kötü modeli algılama
Bu sorunun belirtileri, aktarım hızında düşüş ve hata oranında artışla birlikte aşağıdakilerden birini veya birden çoğunu da içerir:
- Yuvalar, veritabanı bağlantıları ve dosya tanıtıcıları gibi kaynakların tükendiğini gösteren özel durumlarda artış.
- Bellek kullanımı ve atık toplamada artış.
- Ağ, disk ve veritabanı etkinliğinde artış.
Bu sorunun belirlenmesine yardımcı olacak aşağıdaki adımları gerçekleştirebilirsiniz:
- Üretim sistemindeki süreçleri izleyerek uzun yanıt sürelerinin yaşandığı veya kaynak eksikliği nedeniyle sistemin başarısız olduğu noktaları belirleyin.
- Bu noktalarda yakalanan telemetri verilerini inceleyerek kaynağı tüketen nesneleri oluşturuyor veya yok ediyor olabilecek işlemleri saptayın.
- Kuşkulanılan her işlem için üretim ortamı yerine denetimli bir test ortamında yük testi yapın.
- Kaynak kodu gözden geçirin ve aracı nesnelerinin nasıl yönetildiğini inceleyin.
Yavaş çalışan veya sistem yüklü olduğunda özel durumlar oluşturan işlemler için yığın izlemelerine bakın. Bu bilgiler, bu işlemlerin kaynakları nasıl kullandığını belirlemeye yardımcı olabilir. Özel durumlar hataların, paylaşılan kaynakların tükenmesinden kaynaklanıp kaynaklanmadığını saptamaya yardımcı olabilir.
Örnek tanılama
Aşağıdaki bölümlerde, bu adımlar yukarıda açıklanan örnek uygulamaya uygulanmaktadır.
Yavaşlama veya hata noktalarını belirleme
Aşağıdaki resimde, New Relic APM, kullanılarak oluşturulan ve yanıt süresi uzun olan uygulamaları gösteren sonuçlar görüntülenir. Bu durumda, NewHttpClientInstancePerRequest
denetleyicisindeki GetProductAsync
yöntemini daha fazla incelemek yararlı olacaktır. Bu işlemler çalışırken hata oranının da arttığına dikkat edin.
Telemetri verilerini inceleme ve bağıntılar bulma
Sonraki resimde, önceki resimle aynı dönemde iş parçacığı profili oluşturma kullanılarak yakalanan veriler gösterilir. Sistem, yuva bağlantılarını açmak için önemli bir zaman harcar ve hatta bunları kapatmak ve yuva özel durumlarını işlemek için daha fazla zaman harcar.
Yük testi yapma
Kullanıcıların gerçekleştirebileceği tipik işlemlerin benzetimini yapmak için yük testi kullanın. Bu, çeşitli yükler alında sistemin hangi bölümlerinde kaynak tükenmesi yaşandığını belirlemeye yardımcı olabilir. Bu testleri üretim sisteminde değil denetimli bir ortamda gerçekleştirin. Aşağıdaki grafikte, kullanıcı yükü 100 eşzamanlı kullanıcıya çıktığında NewHttpClientInstancePerRequest
denetleyicisi tarafından işlenen isteklerin aktarım hızı gösterilir.
Başlangıçta, iş yükü arttıkça saniyede işlenen istek hacmi de artar. Ancak, yaklaşık 30 kullanıcıda başarılı istek hacmi bir sınıra ulaşır ve sistem özel durumlar oluşturmaya başlar. Ondan sonra, kullanıcı yüküyle birlikte özel durumların hacmi de aşamalı olarak artar.
Yük testi bu hataları HTTP 500 (İç Sunucu) hataları olarak bildirir. Telemetri gözden geçirildiğinde, giderek daha çok HttpClient
nesnesi oluşturuldukça sistemin yuva kaynaklarının tükenmesinin bu hatalara neden olduğunu görülür.
Sonraki grafikte, özel ExpensiveToCreateService
nesnesini oluşturan denetleyici için benzer bir test gösterilir.
Bu kez, denetleyici özel durum oluşturmaz ancak yine ortalama yanıt süresi 20'nin katlarıyla artarken aktarım hızı yatay bir düzeye ulaşır. (Grafik, yanıt süresi ve aktarım hızı için logaritmik ölçek kullanır.) Telemetri, yeni örneklerinin oluşturulmasının ExpensiveToCreateService
sorunun ana nedeni olduğunu gösterdi.
Çözümü uygulama ve sonucu doğrulama
Tek bir HttpClient
örneğini paylaşmak için GetProductAsync
yöntemine geçtikten sonra, ikinci bir yük testi performans artışını göstermiştir. Hiçbir hata bildirilmemiş ve sistem saniyede 500 isteğe kadar çıkan bir yükü işleyebilmiştir. Önceki testle karşılaştırıldığında, ortalama yanıt süresi yarıya inmiştir.
Karşılaştırma için, aşağıdaki resimde yığın izleme telemetrisi gösterilir. Bu kez, sistem zamanının çoğunu yuvaları açıp kapamak yerine gerçek çalışmayı yapmaya harcar.
Sonraki grafikte ExpensiveToCreateService
nesnesinin paylaşılan örneğinin kullanıldığı benzer bir yük testi gösterilir. Yinelemek gerekirse, işlenen isteklerin hacmi kullanıcı yüküyle birlikte artarken, ortalama yanıt süresi kısa kalmayan devam eder.