Meşgul Ön Uç kötü modeli
Çok sayıda arka plan iş parçacığında zaman uyumsuz işler gerçekleştirilmesi, eş zamanlı diğer ön plan görevlerini kaynaksız bırakarak yanıt sürelerinin kabul edilemez düzeylere düşmesine neden olabilir.
Sorun açıklaması
Yoğun kaynak kullanımlı görevler, kullanıcı isteklerinin yanıt sürelerini artırabilir ve yüksek gecikme sürelerine neden olabilir. Yanıt sürelerini geliştirmenin bir yolu, yoğun kaynak kullanımlı bir görevi ayrı bir iş parçacığına aktarmaktır. Bu yaklaşım sayesinde işlemler arka planda yürütülürken uygulama da yanıt vermeye devam edebilir. Ancak, bir arka plan iş parçacığında çalıştırılan görevler yine de kaynak kullanır. Böyle çok sayıda görev varsa istekleri işleyen iş parçacıkları kaynaksız kalabilir.
Not
Kaynak terimi, pek çok farklı şeyi (CPU kullanımı, bellek doluluğu, ağ veya disk G/Ç’si vb.) kapsar.
Bu sorun, genellikle uygulama tek parça bir kod olarak geliştirildiğinde ve iş mantığının tamamı sunu katmanı ile paylaşılan tek bir katman halinde birleştirildiğinde ortaya çıkar.
İşte sorunu gösteren sahte kod.
public class WorkInFrontEndController : ApiController
{
[HttpPost]
[Route("api/workinfrontend")]
public HttpResponseMessage Post()
{
new Thread(() =>
{
//Simulate processing
Thread.SpinWait(Int32.MaxValue / 100);
}).Start();
return Request.CreateResponse(HttpStatusCode.Accepted);
}
}
public class UserProfileController : ApiController
{
[HttpGet]
[Route("api/userprofile/{id}")]
public UserProfile Get(int id)
{
//Simulate processing
return new UserProfile() { FirstName = "Alton", LastName = "Hudgens" };
}
}
WorkInFrontEnd
denetleyicisindekiPost
yöntemi bir HTTP POST işlemi uygular. Bu işlem, uzun süre çalışan ve CPU kullanımı yoğun bir görevin benzetimini yapar. POST işleminin hızla tamamlanmasını sağlamak için işler ayrı bir iş parçacığı üzerinde gerçekleştirilir.UserProfile
denetleyicisindekiGet
yöntemi bir HTTP GET işlemi uygular. Bu yöntemin CPU kullanımı çok daha az yoğundur.
Düşünülmesi gereken bir numaralı konu Post
yönteminin kaynak gereksinimleridir. İşler arka plan iş parçacığına yerleştirilir ancak yine de ciddi miktarda CPU kaynağı kullanılabilir. Bu kaynaklar, diğer eş zamanlı kullanıcılar tarafından gerçekleştirilmekte olan diğer işlemlerle paylaşılır. Normal bir sayıda kullanıcının hepsi aynı anda bu isteği gönderirse genel performans büyük olasılıkla düşer ve tüm işlemler yavaşlar. Kullanıcılar, örneğin Get
yönteminde ciddi gecikme süreleri yaşayabilir.
Sorunun çözümü
Önemli miktarda kaynak kullanan işlemleri ayrı bir arka uca taşıyın.
Bu yaklaşımla ön uç, yoğun kaynak kullanımlı görevleri bir ileti kuyruğuna yerleştirir. Arka uç, zaman uyumsuz olarak işlemek üzere bu görevleri alır. Kuyruk aynı zamanda yük düzeyi eşitleyici rolünü üstlenerek istekleri arka uç için arabelleğe alır. Kuyruk uzunluğu çok artarsa arka ucun ölçeğini genişletecek şekilde otomatik ölçeklendirme yapılandırabilirsiniz.
Yukarıdaki kodun düzeltilmiş bir hali aşağıdadır. Kodun bu halinde Post
yöntemi, bir Service Bus kuyruğuna bir ileti koyar.
public class WorkInBackgroundController : ApiController
{
private static readonly QueueClient QueueClient;
private static readonly string QueueName;
private static readonly ServiceBusQueueHandler ServiceBusQueueHandler;
public WorkInBackgroundController()
{
string serviceBusNamespace = ...;
QueueName = ...;
ServiceBusQueueHandler = new ServiceBusQueueHandler(serviceBusNamespace);
QueueClient = ServiceBusQueueHandler.GetQueueClientAsync(QueueName).Result;
}
[HttpPost]
[Route("api/workinbackground")]
public async Task<long> Post()
{
return await ServiceBusQueueHandler.AddWorkLoadToQueueAsync(QueueClient, QueueName, 0);
}
}
Arka uç, iletileri Service Bus kuyruğundan çeker ve işlemeyi gerçekleştirir.
public async Task RunAsync(CancellationToken cancellationToken)
{
this._queueClient.OnMessageAsync(
// This lambda is invoked for each message received.
async (receivedMessage) =>
{
try
{
// Simulate processing of message
Thread.SpinWait(Int32.MaxValue / 1000);
await receivedMessage.CompleteAsync();
}
catch
{
receivedMessage.Abandon();
}
});
}
Dikkat edilmesi gereken noktalar
- Bu yaklaşım uygulamayı biraz daha karmaşık hale getirir. Hata durumunda istek kaybını önlemek için kuyruğa alma ve kuyruktan çıkarma işlemlerini güvenli bir şekilde gerçekleştirmeniz gerekir.
- Uygulama, ileti kuyruğu için ek bir hizmetle bağımlılık ilişkisine girer.
- İşleme ortamı, beklenen iş yükünü idare edebilecek ve gerekli aktarım hızı hedeflerini karşılayabilecek düzeyde ölçeklenebilir olmalıdır.
- Bu yaklaşım genel yanıt hızını artırsa da arka uca taşınan görevlerin tamamlanması daha uzun sürebilir.
Sorunu algılama
Meşgul bir ön ucun belirtileri, yoğun kaynak kullanımlı görevler gerçekleştirilirken uzun gecikme sürelerini içerir. Son kullanıcılar büyük olasılıkla genişletilmiş yanıt süreleri veya hizmetlerin zaman aşımına uğramasından kaynaklanan hataları bildirecek. Bu hatalar HTTP 500 (İç Sunucu) hataları veya HTTP 503 (Hizmet Kullanılamıyor) hataları da döndürebilir. Web sunucusunun olay günlüklerini inceleyin. Bu günlüklerde büyük olasılıkla hataların nedenleri ve koşulları hakkında ayrıntılı bilgiler vardır.
Bu sorunun belirlenmesine yardımcı olacak aşağıdaki adımları gerçekleştirebilirsiniz:
- Üretim sistemindeki süreçleri izleyerek uzun yanıt süreleriyle karşılaşılan noktaları belirleyin.
- Bu noktalarda yakalanan telemetri verilerini inceleyerek gerçekleştirilmekte olan işlemler ve kullanılmakta olan kaynaklar kombinasyonunu belirleyin.
- Uzun yanıt süreleri ile bu sırada gerçekleştirilmekte olan işlemlerin hacmi ve kombinasyonu arasındaki bağıntıları bulun.
- Şüphelenilen her işlem üzerinde yük testi yaparak hangi işlemlerin kaynakları harcayıp diğer işlemleri kaynaksız bıraktığını tespit edin.
- Bu işlemlerin kaynak kodunu gözden geçirerek neden aşırı kaynak kullanıyor olabileceklerini belirleyin.
Örnek tanılama
Aşağıdaki bölümlerde, bu adımlar yukarıda açıklanan örnek uygulamaya uygulanmaktadır.
Yavaşlama noktalarını belirleyin
Her yöntemi izleyerek her bir istek tarafından kullanılan süreyi ve kaynakları takip edin. Ardından uygulamayı üretim sırasında izleyin. Böylece, isteklerin birbirleri ile nasıl rekabet ettiğinin bir genel görünümü elde edilebilir. Sistemin baskı altında olduğu zamanlarda yavaş çalışan, kaynak kullanımı yüksek istekler büyük olasılıkla diğer işlemleri etkileyecektir. Bu davranış, sistemin izlenmesi ve performanstaki düşüşün fark edilmesi sayesinde tespit edilebilir.
Aşağıdaki resimde bir izleme panosu gösterilmektedir. (Biz Testlerimiz için AppDynamics .) Başlangıçta sistemin yükü hafiftir. Ardından kullanıcılar, UserProfile
GET yöntemini istemeye başlıyor. Diğer kullanıcılar WorkInFrontEnd
POST yöntemine istek göndermeye başlayana kadar performans makul düzeyde kalıyor. Bu noktada yanıt süreleri ciddi ölçüde uzuyor (ilk ok). Yanıt süreleri, ancak WorkInFrontEnd
denetleyicisine gelen isteklerin hacmi düştüğünde iyileşiyor (ikinci ok).
Telemetri verilerini inceleme ve bağıntılar bulma
Sıradaki resimde, aynı zaman aralığında kaynak kullanımını izlemek için toplanan ölçümlerin bazıları gösterilmektedir. İlk başta, sisteme birkaç kullanıcı erişiyor. Daha fazla kullanıcı bağlandıkça CPU kullanımı çok yüksek bir hal alıyor (%100). Ağ G/Ç hızının da CPU kullanımı yükseldikçe başlangıçta arttığına dikkat edin. Ancak, CPU kullanımı en yüksek değerine ulaştıktan sonra ağ G/Ç’si azalıyor. Bunun nedeni, CPU tam kapasitede çalışmaya başladıktan sonra sistemin yalnızca nispeten düşük sayıda isteği işleyebilmesidir. Kullanıcılar bağlantıyı kestikçe CPU yükü azalarak sıfırlanır.
Bu noktada, WorkInFrontEnd
denetleyicisindeki Post
yönteminin daha yakından incelenmesi oldukça faydalı olabilecekmiş gibi görünüyor. Varsayımı onaylamak için denetimli bir ortamda daha fazla çalışma yapılması gereklidir.
Yük testi gerçekleştirme
Sıradaki adım, denetimli bir ortamda testler yapmaktır. Örneğin, sırasıyla her isteği dahil eden ve çıkaran bir dizi yük testi çalıştırarak oluşan etkilere bakın.
Aşağıdaki grafikte, önceki testlerde kullanılan bulut hizmetinin özdeş bir dağıtımında gerçekleştirilen bir yük testinin sonuçları gösterilmektedir. Testte 500 kullanıcının UserProfile
denetleyicisinde Get
işlemini gerçekleştirmesinden doğan sabit bir yük ve kullanıcıların WorkInFrontEnd
denetleyicisinde Post
işlemini gerçekleştirdiği aşamalı artan bir yük kullanılmıştır.
İlk başta, aşamalı artan yük 0 değerinde ve etkin kullanıcılar UserProfile
istekleri gerçekleştiriyor. Sistem saniyede yaklaşık 500 isteğe yanıt verebiliyor. 60 saniye sonra 100 ek kullanıcılık bir yük, WorkInFrontEnd
denetleyicisine POST istekleri göndermeye başlıyor. UserProfile
denetleyicisine gönderilen iş yükü neredeyse anında saniyede yaklaşık 150 isteğe düşüyor. Bunun nedeni, yük testi çalıştırıcının çalışma şeklidir. Çalıştırıcı, sıradaki isteği göndermek için önce bir yanıt bekler. Bu nedenle, yanıt alma süresi uzadıkça istek hızı da düşer.
Daha fazla sayıda kullanıcı WorkInFrontEnd
denetleyicisine POST istekleri gönderdikçe UserProfile
denetleyicisinin yanıt hızı düşmeye devam ediyor. Ancak denetleyici tarafından işlenen istek hacminin görece sabit kaldığına WorkInFrontEnd
dikkat edin. Her iki isteğin toplam hızı sabit ancak düşük bir sınıra yaklaştıkça sistemin doygunluğu belirgin hale gelir.
Kaynak kodu gözden geçirme
Son adım ise kaynak koda bakmaktır. Geliştirme ekibi, Post
yönteminin oldukça uzun bir süre alabileceğinin farkındaydı. Bu nedenle, ilk uygulamada ayrı bir iş parçacığı kullanılmıştır. Post
yöntemi uzun süre çalışan bir görevin tamamlanmasını beklerken engelleme yapmadığından bu yaklaşım eldeki sorunu çözdü.
Ancak, bu yöntem tarafından gerçekleştirilen işler hala CPU, bellek ve diğer kaynakları kullanmaktadır. Bu sürecin zaman uyumsuz olarak çalışabilmesinin sağlanması, kullanıcılar kontrolsüz bir şekilde çok sayıda bu tip işlemi eşzamanlı olarak tetikleyebileceği için performansa zarar verebilir. Bir sunucunun çalıştırabileceği iş parçacığı sayısı için bir sınır vardır. Bu sınır aşılırsa uygulama yeni bir iş parçacığı başlatmaya çalıştığında büyük olasılıkla bir özel durum alacaktır.
Not
Bu durum, zaman uyumsuz işlemlerden kaçınmanız gerektiği anlamına gelmez. Bir ağ çağrısında zaman uyumsuz bekleme gerçekleştirme önerilen bir uygulamadır. (Bkz. Zaman uyumlu G/Ç kötü modeli.) Buradaki sorun, CPU yoğunluklu çalışmanın başka bir iş parçacığında ortaya çıkmalarıdır.
Çözümü uygulama ve sonucu doğrulama
Aşağıdaki resimde, çözüm uygulandıktan sonraki performans izlemesi gösterilmektedir. Yük, yukarıda gösterilen yüke benziyor ancak UserProfile
denetleyicisinin yanıt süreleri artık çok daha kısa. Aynı süre zarfında, istek hacmi 2.759’dan 23.565’e yükseldi.
WorkInBackground
denetleyicisinin de çok daha büyük bir istek hacmi işlediğine dikkat edin. Ancak, bu denetleyicide gerçekleştirilen iş özgün koda göre çok farklı olduğu için bu durumda doğrudan bir karşılaştırma yapılamaz. Yeni sürüm, zaman alıcı bir hesaplama gerçekleştirmek yerine isteği yalnızca kuyruğa almaktadır. Önemli olan nokta, bu yöntemin artık tüm sistemi yük altına sokmuyor olmasıdır.
CPU ve ağ kullanımında da performans artışı görülmektedir. İşlenen ağ isteği hacmi öncesine göre çok daha büyüktü, ancak CPU kullanımı hiçbir zaman %100’e ulaşmadı ve iş yükü bırakılana kadar azalıp sıfırlanmadı.
Aşağıdaki grafikte bir yük testinin sonuçları gösterilmektedir. Hizmet verilen toplam istek hacmi, önceki testlerle karşılaştırıldığında büyük ölçüde artmıştır.