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.

Dekont

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.

ASP.NET'i kullanarak sorunu gösteren bir örnek aşağıda verilmiştir. Örneğin tamamını burada bulabilirsiniz.

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 denetleyicisindeki Post 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 denetleyicisindeki Get 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()
    {
        var serviceBusConnectionString = ...;
        QueueName = ...;
        ServiceBusQueueHandler = new ServiceBusQueueHandler(serviceBusConnectionString);
        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:

  1. Üretim sistemindeki süreçleri izleyerek uzun yanıt süreleriyle karşılaşılan noktaları belirleyin.
  2. Bu noktalarda yakalanan telemetri verilerini inceleyerek gerçekleştirilmekte olan işlemler ve kullanılmakta olan kaynaklar kombinasyonunu belirleyin.
  3. Uzun yanıt süreleri ile bu sırada gerçekleştirilmekte olan işlemlerin hacmi ve kombinasyonu arasındaki bağıntıları bulun.
  4. Şü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.
  5. 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).

AppDynamics Business Transactions pane showing the effects of the response times of all requests when the WorkInFrontEnd controller is used

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.

AppDynamics metrics showing the CPU and network utilization

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.

Initial load test results for the WorkInFrontEnd controller

İ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.

Dekont

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.

AppDynamics Business Transactions pane showing the effects of the response times of all requests when the WorkInBackground controller is used

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ı.

AppDynamics metrics showing the CPU and network utilization for the WorkInBackground controller

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.

Load-test results for the BackgroundImageProcessing controller