Zaman Uyumlu G/Ç kötü modeliSynchronous I/O antipattern

G/Ç tamamlanırken çağıran iş parçacığının engellenmesi, performansı düşürebilir ve dikey ölçeklenebilirliği etkileyebilir.Blocking the calling thread while I/O completes can reduce performance and affect vertical scalability.

Sorun açıklamasıProblem description

Zaman uyumlu bir G/Ç işlemi, G/Ç tamamlanırken çağıran iş parçacığını engelliyor.A synchronous I/O operation blocks the calling thread while the I/O completes. Ç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.The calling thread enters a wait state and is unable to perform useful work during this interval, wasting processing resources.

Yaygın G/Ç örnekleri şunlardır:Common examples of I/O include:

  • Bir veritabanı veya herhangi bir kalıcı depolama türündeki verileri alma ya da bunlarda verileri kalıcı hale getirme.Retrieving or persisting data to a database or any type of persistent storage.
  • Bir web hizmetine istek gönderme.Sending a request to a web service.
  • Bir kuyruğa ileti gönderme veya kuyruktan ileti alma.Posting a message or retrieving a message from a queue.
  • Yerel bir dosyaya yazma veya yerel dosyadan okuma.Writing to or reading from a local file.

Bu kötü model genellikle aşağıdaki nedenlerden dolayı ortaya çıkar:This antipattern typically occurs because:

  • Bir işlemi gerçekleştirmenin en sezgisel yolu gibi görünür.It appears to be the most intuitive way to perform an operation.
  • Uygulama bir istekten yanıt gerektirir.The application requires a response from a request.
  • Uygulama, G/Ç için yalnızca zaman uyumlu metotlar sağlayan bir kitaplık kullanır.The application uses a library that only provides synchronous methods for I/O.
  • Bir dış kitaplık, dahili olarak zaman uyumlu G/Ç işlemleri gerçekleştirir.An external library performs synchronous I/O operations internally. Tek bir zaman uyumlu G/Ç çağrısı, bir çağrı zincirinin tamamını engelleyebilir.A single synchronous I/O call can block an entire call chain.

Aşağıdaki kod, Azure blob depolamaya bir dosya yükler.The following code uploads a file to Azure blob storage. Kod bloklarının zaman uyumlu G/Ç beklediği iki durum vardır: CreateIfNotExists metodu ve UploadFromStream metodu.There are two places where the code blocks waiting for synchronous I/O, the CreateIfNotExists method and the UploadFromStream method.

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.Here's an example of waiting for a response from an external service. GetUserProfile metodu, bir UserProfile döndüren uzak bir hizmeti çağırır.The GetUserProfile method calls a remote service that returns a UserProfile.

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.You can find the complete code for both of these examples here.

Sorunun çözümüHow to fix the problem

Zaman uyumlu G/Ç işlemlerini zaman uyumsuz işlemlerle değiştirin.Replace synchronous I/O operations with asynchronous operations. 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.This frees the current thread to continue performing meaningful work rather than blocking, and helps improve the utilization of compute resources. G/Ç’nin zaman uyumsuz olarak gerçekleştirilmesi, özellikle de istemci uygulamalarından beklenmedik istek artışlarının işlenmesi için verimlidir.Performing I/O asynchronously is particularly efficient for handling an unexpected surge in requests from client applications.

Çoğu kitaplık, metotların hem zaman uyumlu hem de zaman uyumsuz sürümlerini sağlar.Many libraries provide both synchronous and asynchronous versions of methods. Mümkün olduğunda zaman uyumsuz sürümleri kullanın.Whenever possible, use the asynchronous versions. Azure blob depolamaya dosya yükleyen bir önceki örneğin zaman uyumsuz sürümü aşağıda verilmiştir.Here is the asynchronous version of the previous example that uploads a file to Azure blob storage.

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.The await operator returns control to the calling environment while the asynchronous operation is performed. Bu deyimden sonraki kod, zaman uyumsuz işlem tamamlandıktan sonra çalışan bir devam işlemi gibi davranır.The code after this statement acts as a continuation that runs when the asynchronous operation has completed.

İyi tasarlanmış bir hizmet, zaman uyumsuz işlemler de sağlamalıdır.A well designed service should also provide asynchronous operations. Aşağıda, kullanıcı profillerini döndüren web hizmetinin zaman uyumsuz bir sürümü verilmiştir.Here is an asynchronous version of the web service that returns user profiles. GetUserProfileAsync metodu, Kullanıcı Profili hizmetinin zaman uyumsuz bir sürümünün olmasına bağımlıdır.The GetUserProfileAsync method depends on having an asynchronous version of the User Profile service.

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

public class AsyncController : ApiController
{
    private readonly IUserProfileService _userProfileService;

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

    // This is an 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.For libraries that don't provide asynchronous versions of operations, it may be possible to create asynchronous wrappers around selected synchronous methods. Bu yaklaşımı izleme konusunda dikkatli olun.Follow this approach with caution. 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.While it may improve responsiveness on the thread that invokes the asynchronous wrapper, it actually consumes more resources. 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.An extra thread may be created, and there is overhead associated with synchronizing the work done by this thread. Bu blog gönderisinde bazı ödünler açıklanmıştır: Zaman uyumlu yöntemler için zaman uyumsuz sarmalayıcıları ortaya koymalı mıyım?Some tradeoffs are discussed in this blog post: Should I expose asynchronous wrappers for synchronous methods?

Zaman uyumlu bir metodu kapsayan zaman uyumsuz sarmalayıcıların bir örneği aşağıda verilmiştir.Here is an example of an asynchronous wrapper around a synchronous method.

// 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:Now the calling code can await on the wrapper:

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

Dikkat edilmesi gerekenlerConsiderations

  • Ç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.I/O operations that are expected to be very short lived and are unlikely to cause contention might be more performant as synchronous operations. Buna bir SSD sürücüsündeki küçük dosyaları okuma örneği verilebilir.An example might be reading small files on an SSD drive. 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.The overhead of dispatching a task to another thread, and synchronizing with that thread when the task completes, might outweigh the benefits of asynchronous I/O. Bununla birlikte, bu durumlara görece nadir rastlanır ve çoğu G/Ç işlemi zaman uyumsuz olarak gerçekleştirilmelidir.However, these cases are relatively rare, and most I/O operations should be done asynchronously.

  • G/Ç performansının geliştirilmesi, sistemin diğer bölümlerinde performans sorunu yaşanmasına neden olabilir.Improving I/O performance may cause other parts of the system to become bottlenecks. Ö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.For example, unblocking threads might result in a higher volume of concurrent requests to shared resources, leading in turn to resource starvation or throttling. 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.If that becomes a problem, you might need to scale out the number of web servers or partition data stores to reduce contention.

Sorunu algılamaHow to detect the problem

Kullanıcılar için uygulama belirli aralıklarla yanıt vermiyor veya kilitleniyor gibi görünebilir.For users, the application may seem unresponsive or appear to hang periodically. Uygulama, zaman aşımı özel durumlarıyla başarısız olabilir.The application might fail with timeout exceptions. Bu hatalar HTTP 500 (İç Sunucu) hataları da döndürebilir.These failures could also return HTTP 500 (Internal Server) errors. 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.On the server, incoming client requests might be blocked until a thread becomes available, resulting in excessive request queue lengths, manifested as HTTP 503 (Service Unavailable) errors.

Sorunun belirlenmesine yardımcı olacak aşağıdaki adımları gerçekleştirebilirsiniz:You can perform the following steps to help identify the problem:

  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.Monitor the production system and determine whether blocked worker threads are constraining throughput.

  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.If requests are being blocked due to lack of threads, review the application to determine which operations may be performing I/O synchronously.

  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.Perform controlled load testing of each operation that is performing synchronous I/O, to find out whether those operations are affecting system performance.

Örnek tanılamaExample diagnosis

Aşağıdaki bölümlerde, bu adımlar daha önce açıklanan örnek uygulamaya uygulanmıştır.The following sections apply these steps to the sample application described earlier.

Web sunucusu performansını izleyinMonitor web server performance

Azure web uygulamaları ve web rolleri için IIS web sunucusunun performansını izlemek mantıklıdır.For Azure web applications and web roles, it's worth monitoring the performance of the IIS web server. Ö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.In particular, pay attention to the request queue length to establish whether requests are being blocked waiting for available threads during periods of high activity. Bu bilgileri Azure tanılamayı etkinleştirerek toplayabilirsiniz.You can gather this information by enabling Azure diagnostics. Daha fazla bilgi için bkz.For more information, see:

İsteklerin kabul edildikten sonra nasıl işlendiğini görmek için uygulamayı izleyin.Instrument the application to see how requests are handled once they have been accepted. 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.Tracing the flow of a request can help to identify whether it is performing slow-running calls and blocking the current thread. Ayrıca, iş parçacığı profili oluşturmak engellenen isteklerin vurgulanmasını sağlayabilir.Thread profiling can also highlight requests that are being blocked.

Uygulamaya yük testi uygulamaLoad test the application

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.The following graph shows the performance of the synchronous GetUserProfile method shown earlier, under varying loads of up to 4000 concurrent users. Uygulama, bir Azure Cloud Service web rolünde çalışan bir ASP.NET uygulamasıdır.The application is an ASP.NET application running in an Azure Cloud Service web role.

Zaman uyumlu G/Ç işlemleri gerçekleştiren örnek uygulama için performans grafiği

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.The synchronous operation is hard-coded to sleep for 2 seconds, to simulate synchronous I/O, so the minimum response time is slightly over 2 seconds. 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.When the load reaches approximately 2500 concurrent users, the average response time reaches a plateau, although the volume of requests per second continues to increase. Bu iki ölçü için ölçeğin logaritmik olduğuna dikkat edin.Note that the scale for these two measures is logarithmic. Bu nokta ile testin sonu arasında saniye başına istek sayısı iki katına çıkar.The number of requests per second doubles between this point and the end of the test.

Bağımsız olarak değerlendirildiğinde, bu testten zaman uyumlu G/Ç’nin bir sorun olduğu açıkça anlaşılamayabilir.In isolation, it's not necessarily clear from this test whether the synchronous I/O is a problem. 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.Under heavier load, the application may reach a tipping point where the web server can no longer process requests in a timely manner, causing client applications to receive time-out exceptions.

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.Incoming requests are queued by the IIS web server and handed to a thread running in the ASP.NET thread pool. Her bir işlem zaman uyumlu G/Ç gerçekleştirdiğinden, işlem tamamlanana kadar iş parçacığı engellenir.Because each operation performs I/O synchronously, the thread is blocked until the operation completes. İş yükü arttıkça, sonunda iş parçacığı havuzundaki tüm ASP.NET iş parçacıkları ayrılır ve engellenir.As the workload increases, eventually all of the ASP.NET threads in the thread pool are allocated and blocked. Bu noktada, diğer tüm gelen istekler bir iş parçacığı kullanılabilir hale gelene kadar kuyrukta beklemek zorundadır.At that point, any further incoming requests must wait in the queue for an available thread. Kuyruk uzunluğu arttıkça istekler zaman aşımına uğramaya başlar.As the queue length grows, requests start to time out.

Çözümü uygulama ve sonucu doğrulamaImplement the solution and verify the result

Bir sonraki grafikte, kodun zaman uyumsuz bir sürümüne uygulanan yük testinin sonuçları gösterilmiştir.The next graph shows the results from load testing the asynchronous version of the code.

Zaman uyumsuz G/Ç işlemleri gerçekleştiren örnek uygulama için performans grafiği

Aktarım hızı çok daha yüksektir.Throughput is far higher. 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.Over the same duration as the previous test, the system successfully handles a nearly tenfold increase in throughput, as measured in requests per second. Ayrıca, ortalama yanıt süresi görece sabittir ve önceki testten yaklaşık 25 kat daha kısa kalır.Moreover, the average response time is relatively constant and remains approximately 25 times smaller than the previous test.