Zaman Uyumlu G/Ç kötü modeli

G/Ç tamamlanırken çağıran iş parçacığının engellenmesi, performansı düşürebilir ve dikey ölçeklenebilirliği etkileyebilir.

Sorun açıklaması

Zaman uyumlu bir G/Ç işlemi, G/Ç tamamlanırken çağıran iş parçacığını engelliyor. Çağıran iş parçacığı bekleme durumuna giriyor ve bu aralık sırasında yararlı işlemler gerçekleştiremeyerek işleme kaynağı israfına yol açıyor.

Yaygın G/Ç örnekleri şunlardır:

  • Bir veritabanı veya herhangi bir kalıcı depolama türündeki verileri alma ya da bunlarda verileri kalıcı hale getirme.
  • Bir web hizmetine istek gönderme.
  • Bir kuyruğa ileti gönderme veya kuyruktan ileti alma.
  • Yerel bir dosyaya yazma veya yerel dosyadan okuma.

Bu kötü model genellikle aşağıdaki nedenlerden ortaya çıkar:

  • Bir işlemi gerçekleştirmenin en sezgisel yolu gibi görünür.
  • Uygulama bir istekten yanıt gerektirir.
  • Uygulama, G/Ç için yalnızca zaman uyumlu metotlar sağlayan bir kitaplık kullanır.
  • Bir dış kitaplık, dahili olarak zaman uyumlu G/Ç işlemleri gerçekleştirir. Tek bir zaman uyumlu G/Ç çağrısı, bir çağrı zincirinin tamamını engelleyebilir.

Aşağıdaki kod, Azure blob depolamaya bir dosya yükler. Kod bloklarının zaman uyumlu G/Ç beklediği iki durum vardır: CreateIfNotExists metodu ve UploadFromStream metodu.

var blobClient = storageAccount.CreateCloudBlobClient();
var container = blobClient.GetContainerReference("uploadedfiles");

container.CreateIfNotExists();
var blockBlob = container.GetBlockBlobReference("myblob");

// Create or overwrite the "myblob" blob with contents from a local file.
using (var fileStream = File.OpenRead(HostingEnvironment.MapPath("~/FileToUpload.txt")))
{
    blockBlob.UploadFromStream(fileStream);
}

Aşağıda, bir dış hizmetten yanıt bekleme örneği verilmiştir. GetUserProfile metodu, bir UserProfile döndüren uzak bir hizmeti çağırır.

public interface IUserProfileService
{
    UserProfile GetUserProfile();
}

public class SyncController : ApiController
{
    private readonly IUserProfileService _userProfileService;

    public SyncController()
    {
        _userProfileService = new FakeUserProfileService();
    }

    // This is a synchronous method that calls the synchronous GetUserProfile method.
    public UserProfile GetUserProfile()
    {
        return _userProfileService.GetUserProfile();
    }
}

Bu örneklerin her ikisi için tam kodu burada bulabilirsiniz.

Sorunun çözümü

Zaman uyumlu G/Ç işlemlerini zaman uyumsuz işlemlerle değiştirin. Bu, geçerli iş parçacığını engellemek yerine anlamlı işlemler gerçekleştirme özgürlüğü tanır ve işlem kaynakları kullanımının geliştirilmesine yardımcı olur. G/Ç’nin zaman uyumsuz olarak gerçekleştirilmesi, özellikle de istemci uygulamalarından beklenmedik istek artışlarının işlenmesi için verimlidir.

Çoğu kitaplık, metotların hem zaman uyumlu hem de zaman uyumsuz sürümlerini sağlar. Mümkün olduğunda zaman uyumsuz sürümleri kullanın. Azure blob depolamaya dosya yükleyen bir önceki örneğin zaman uyumsuz sürümü aşağıda verilmiştir.

var blobClient = storageAccount.CreateCloudBlobClient();
var container = blobClient.GetContainerReference("uploadedfiles");

await container.CreateIfNotExistsAsync();

var blockBlob = container.GetBlockBlobReference("myblob");

// Create or overwrite the "myblob" blob with contents from a local file.
using (var fileStream = File.OpenRead(HostingEnvironment.MapPath("~/FileToUpload.txt")))
{
    await blockBlob.UploadFromStreamAsync(fileStream);
}

Zaman uyumsuz işlem gerçekleştirildiği sırada denetim await işleci tarafından çağıran ortama döndürülür. Bu deyimden sonraki kod, zaman uyumsuz işlem tamamlandıktan sonra çalışan bir devam işlemi gibi davranır.

İyi tasarlanmış bir hizmet, zaman uyumsuz işlemler de sağlamalıdır. Aşağıda, kullanıcı profillerini döndüren web hizmetinin zaman uyumsuz bir sürümü verilmiştir. GetUserProfileAsync metodu, Kullanıcı Profili hizmetinin zaman uyumsuz bir sürümünün olmasına bağımlıdır.

public interface IUserProfileService
{
    Task<UserProfile> GetUserProfileAsync();
}

public class AsyncController : ApiController
{
    private readonly IUserProfileService _userProfileService;

    public AsyncController()
    {
        _userProfileService = new FakeUserProfileService();
    }

    // This is a synchronous method that calls the Task based GetUserProfileAsync method.
    public Task<UserProfile> GetUserProfileAsync()
    {
        return _userProfileService.GetUserProfileAsync();
    }
}

İşlemlerin zaman uyumsuz sürümlerini sağlamayan kitaplıklar için seçili zaman uyumlu metotları kapsayan zaman uyumsuz sarmalayıcılar oluşturmak mümkün olabilir. Bu yaklaşımı izleme konusunda dikkatli olun. Zaman uyumsuz sarmalayıcıyı çağıran iş parçacığının yanıt verme hızını artırabilse de aslında daha fazla kaynak tüketir. Ek bir iş parçacığı oluşturulabilir ve bu iş parçacığı tarafından gerçekleştirilen işin eşitlenmesiyle ilişkili ek yükler vardır. Verilen bazı ödünler şu blog gönderisinde incelenmiştir: Zaman uyumlu metotlar için zaman uyumsuz sarmalayıcıları kullanıma sunmalı mıyım?

Zaman uyumlu bir metodu kapsayan zaman uyumsuz sarmalayıcıların bir örneği aşağıda verilmiştir.

// Asynchronous wrapper around synchronous library method
private async Task<int> LibraryIOOperationAsync()
{
    return await Task.Run(() => LibraryIOOperation());
}

Artık çağıran kod sarmalayıcıda bekleyebilir:

// Invoke the asynchronous wrapper using a task
await LibraryIOOperationAsync();

Dikkat edilmesi gereken noktalar

  • Çok kısa süreli olması beklenen ve çekişmeye neden olma ihtimali düşük olan G/Ç işlemleri, zaman uyumlu işlemler olarak daha yüksek bir performansa sahip olabilir. Buna bir SSD sürücüsündeki küçük dosyaları okuma örneği verilebilir. Bir görevi başka bir iş parçacığına gönderme ve görev tamamlandığında bu iş parçacığıyla eşitleme ek yükü, zaman uyumsuz G/Ç avantajlarını gölgede bırakabilir. Bununla birlikte, bu durumlara görece nadir rastlanır ve çoğu G/Ç işlemi zaman uyumsuz olarak gerçekleştirilmelidir.

  • G/Ç performansının geliştirilmesi, sistemin diğer bölümlerinde performans sorunu yaşanmasına neden olabilir. Örneğin, iş parçacıklarının engellemesinin kaldırılması ortak kaynaklara yönelik daha yüksek hacimde eş zamanlı isteğe yol açarak kaynak kıtlığına veya azalmasına neden olabilir. Bunun bir soruna dönüşmesi durumunda, çekişmeyi azaltmak için web sunucuları sayısının ölçeğini genişletmeniz veya veri depolarını bölümlemeniz gerekebilir.

Sorunu algılama

Kullanıcılar için uygulama belirli aralıklarla yanıt vermiyor. Uygulama, zaman aşımı özel durumlarıyla başarısız olabilir. Bu hatalar HTTP 500 (İç Sunucu) hataları da döndürebilir. Sunucuda bir iş parçacığı kullanılabilir hale gelene kadar gelen istemci istekleri engellenebilir, bunun sonucunda da HTTP 503 (Hizmet Kullanılamıyor) hataları olarak kendini belli eden aşırı uzun istek kuyrukları oluşabilir.

Sorunun belirlenmesine yardımcı olacak aşağıdaki adımları gerçekleştirebilirsiniz:

  1. Üretim sistemini izleyin ve engellenen çalışan iş parçacıklarının aktarım hızını kısıtlayıp kısıtlamadığını belirleyin.

  2. İş parçacığı yokluğundan dolayı istekler engelleniyorsa, uygulamayı gözden geçirerek hangi işlemlerin zaman uyumlu olarak G/Ç gerçekleştiriyor olabileceğini belirleyin.

  3. Zaman uyumlu G/Ç gerçekleştiren her bir işleme kontrollü yük testi uygulayarak bu işlemlerin sistem performansı etkileyip etkilemediğini öğrenin.

Örnek tanılama

Aşağıdaki bölümlerde, bu adımlar yukarıda açıklanan örnek uygulamaya uygulanmaktadır.

Web sunucusu performansını izleyin

Azure web uygulamaları ve web rolleri için IIS web sunucusunun performansını izlemek mantıklıdır. Özellikle de istek kuyruğunun uzunluğuna dikkat ederek yoğun etkinlik dönemleri sırasında kullanılabilir iş parçacığı beklenirken isteklerin engellenip engellenmediğini belirleyin. Bu bilgileri Azure tanılamayı etkinleştirerek toplayabilirsiniz. Daha fazla bilgi için bkz.

İsteklerin kabul edildikten sonra nasıl işlendiğini görmek için uygulamayı izleyin. Bir isteğin akışını izlemek, yavaş çalışan çağrılar gerçekleştirerek geçerli iş parçacığını engelleyip engellemediğinin belirlenmesine yardımcı olabilir. Ayrıca, iş parçacığı profili oluşturmak engellenen isteklerin vurgulanmasını sağlayabilir.

Uygulamaya yük testi uygulama

Aşağıdaki grafikte, daha önce gösterilen zaman uyumlu GetUserProfile metodunun 4000’e kadar eş zamanlı kullanıcının yol açtığı çeşitli yükler altında performansı gösterilmiştir. Uygulama, bir Azure Cloud Service web rolünde çalışan bir ASP.NET uygulamasıdır.

Performance chart for the sample application performing synchronous I/O operations

Zaman uyumlu işlem, zaman uyumlu G/Ç benzetimi yapmak için 2 saniye uykuda kalacak şekilde sabit olarak kodlandığından, en düşük yanıt süresi 2 saniyenin biraz üzerindedir. Yük yaklaşık 2500 eş zamanlı kullanıcıya ulaştığında, saniye başına istek hacmi artmaya devam etse de ortalama yanıt süresi bir platoya ulaşır. Bu iki ölçü için ölçeğin logaritmik olduğuna dikkat edin. Bu nokta ile testin sonu arasında saniye başına istek sayısı iki katına çıkar.

Bağımsız olarak değerlendirildiğinde, bu testten zaman uyumlu G/Ç’nin bir sorun olduğu açıkça anlaşılamayabilir. Uygulama, yük ağırlaştıkça web sunucusunun artık istekleri zamanında işleyemediği ve istemci uygulamalarının zaman aşımı özel durumları aldığı bir kırılma noktasına ulaşabilir.

Gelen istekler IIS web sunucusu tarafından kuyruğa alınır ve ASP.NET iş parçacığı havuzunda çalışan bir iş parçacığına devredilir. Her bir işlem zaman uyumlu G/Ç gerçekleştirdiğinden, işlem tamamlanana kadar iş parçacığı engellenir. İş yükü arttıkça, sonunda iş parçacığı havuzundaki tüm ASP.NET iş parçacıkları ayrılır ve engellenir. Bu noktada, diğer tüm gelen istekler bir iş parçacığı kullanılabilir hale gelene kadar kuyrukta beklemek zorundadır. Kuyruk uzunluğu arttıkça istekler zaman aşımına uğramaya başlar.

Çözümü uygulama ve sonucu doğrulama

Bir sonraki grafikte, kodun zaman uyumsuz bir sürümüne uygulanan yük testinin sonuçları gösterilmiştir.

Performance chart for the sample application performing asynchronous I/O operations

Aktarım hızı çok daha yüksektir. Sistem, önceki testle aynı süre içinde, saniye başına istek sayısı olarak ölçülen aktarım hızının neredeyse on kat artmasıyla başa çıkabilir. Ayrıca, ortalama yanıt süresi görece sabittir ve önceki testten yaklaşık 25 kat daha kısa kalır.