Yanlış Örnek Oluşturma Kötü ModeliImproper Instantiation antipattern

Bir kez oluşturulup ardından paylaşılması amaçlanan bir nesnenin sürekli yeni örneklerini oluşturmak performansı olumsuz etkileyebilir.It can hurt performance to continually create new instances of an object that is meant to be created once and then shared.

Sorun açıklamasıProblem description

Birçok kitaplık dış kaynakların özetlerini sağlar.Many libraries provide abstractions of external resources. 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.Internally, these classes typically manage their own connections to the resource, acting as brokers that clients can use to access the resource. Burada, Azure uygulamalarına uygun bazı aracı sınıfı örnekleri bulabilirsiniz:Here are some examples of broker classes that are relevant to Azure applications:

  • System.Net.Http.HttpClient.System.Net.Http.HttpClient. HTTP kullanarak bir web hizmetiyle iletişim kurar.Communicates with a web service using HTTP.
  • Microsoft.ServiceBus.Messaging.QueueClient.Microsoft.ServiceBus.Messaging.QueueClient. Service Bus kuyruğuna iletiler gönderir ve alır.Posts and receives messages to a Service Bus queue.
  • Microsoft.Azure.Documents.Client.DocumentClient.Microsoft.Azure.Documents.Client.DocumentClient. Cosmos DB örneğine bağlanırConnects to a Cosmos DB instance
  • StackExchange.Redis.ConnectionMultiplexer.StackExchange.Redis.ConnectionMultiplexer. Azure Redis Cache de dahil olmak üzere Redis'e bağlanır.Connects to Redis, including Azure Redis Cache.

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.These classes are intended to be instantiated once and reused throughout the lifetime of an application. Ö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.However, it's a common misunderstanding that these classes should be acquired only as necessary and released quickly. (Buradan listelenenler .NET kitaplıkları olsa da, desten .NET'e özel değildir.) Aşağıdaki ASP.NET örneği, uzak hizmetle iletişim kurmak için HttpClient örneği oluşturur.(The ones listed here happen to be .NET libraries, but the pattern is not unique to .NET.) The following ASP.NET example creates an instance of HttpClient to communicate with a remote service. Örneğin tamamını burada bulabilirsiniz.You can find the complete sample here.

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.In a web application, this technique is not scalable. Her kullanıcı isteği için yeni bir HttpClient nesnesi oluşturulur.A new HttpClient object is created for each user request. 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.Under heavy load, the web server may exhaust the number of available sockets, resulting in SocketException errors.

Bu sorun, HttpClient sınıfıyla sınırlı değildir.This problem is not restricted to the HttpClient class. Kaynakları sarmalayan veya oluşturması pahalıya gelen sınıflar benzer sorunlara neden olabilir.Other classes that wrap resources or are expensive to create might cause similar issues. Aşağıdaki örnek, ExpensiveToCreateService sınıfının bir örneğini oluşturur.The following example creates an instances of the ExpensiveToCreateService class. Burada sorun yuva tükenmesi olmayabilir, ama her kaynağı oluşturmanın ne kadar süreceğiyle ilgilidir.Here the issue is not necessarily socket exhaustion, but simply how long it takes to create each instance. Sürekli olarak bu sınıfın örnekleri oluşturmak ve yok etmek, sistemin ölçeklenebilirliğini olumsuz etkileyebilir.Continually creating and destroying instances of this class might adversely affect the scalability of the system.

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);
    }
    ...
}

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

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.If the class that wraps the external resource is shareable and thread-safe, create a shared singleton instance or a pool of reusable instances of the class.

Aşağıdaki örnek, statik bir HttpClient örneğini kullanır ve böylelikle bağlantıyı tüm istekler arasında paylaşır.The following example uses a static HttpClient instance, thus sharing the connection across all requests.

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 gerekenlerConsiderations

  • Bu kötü modelin anahtar öğesi, paylaşılabilir bir nesnenin örneklerini sürekli oluşturmak ve yok etmektir.The key element of this antipattern is repeatedly creating and destroying instances of a shareable object. Sınıf paylaşılabilir değilse (iş parçacığı güvenli değilse), bu kötü model geçerli olmaz.If a class is not shareable (not thread-safe), then this antipattern does not apply.

  • Tekil örnek mi kullanacağınızı yoksa havuz mu oluşturacağınızı, paylaşılan kaynağın türü belirleyebilir.The type of shared resource might dictate whether you should use a singleton or create a pool. HttpClient sınıfı, havuz oluşturmak yerine paylaşmak için tasarlanmıştır.The HttpClient class is designed to be shared rather than pooled. Diğer nesneler havuz oluşturmayı destekleyerek, sistemin iş yükünü birden çok örneğe dağıtmasına olanak tanıyabilir.Other objects might support pooling, enabling the system to spread the workload across multiple instances.

  • Birden çok istek arasında paylaştığınız nesneler iş parçacığı güvenli olmalıdır.Objects that you share across multiple requests must be thread-safe. 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.The HttpClient class is designed to be used in this manner, but other classes might not support concurrent requests, so check the available documentation.

  • Paylaşılan nesnelerde özellikleri ayarlama konusunda dikkatli olun çünkü bu yarış durumlarına yol açabilir.Be careful about setting properties on shared objects, as this can lead to race conditions. Öneğin, her istekten önce HttpClient sınıfında DefaultRequestHeaders ayarı yapmak yarış durumu oluşturabilir.For example, setting DefaultRequestHeaders on the HttpClient class before each request can create a race condition. 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.Set such properties once (for example, during startup), and create separate instances if you need to configure different settings.

  • Bazı kaynak türleri azdır ve elde tutulmamalıdır.Some resource types are scarce and should not be held onto. Veritabanı bağlantıları buna örnek verilebilir.Database connections are an example. Gerekli olmayan bir veritabanı bağlantısını açık tutmak, diğer eşzamanlı kullanıcıların veritabanına erişmesini engelleyebilir.Holding an open database connection that is not required may prevent other concurrent users from gaining access to the database.

  • .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.In the .NET Framework, many objects that establish connections to external resources are created by using static factory methods of other classes that manage these connections. Bu nesnelerin atılıp yeniden oluşturulması değil, saklanması ve yeniden kullanılması hedeflenmiştir.These objects are intended to be saved and reused, rather than disposed and recreated. Örneğin, Azure Service Bus'ta QueueClient nesnesi bir MessagingFactory nesnesi aracılığıyla oluşturulur.For example, in Azure Service Bus, the QueueClient object is created through a MessagingFactory object. Dahili olarak, MessagingFactory bağlantıları yönetir.Internally, the MessagingFactory manages connections. Daha fazla bilgi için bkz. Service Bus Mesajlaşması kullanarak en iyi performans geliştirme en deneyimleri.For more information, see Best Practices for performance improvements using Service Bus Messaging.

Sorunu algılamaHow to detect the problem

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:Symptoms of this problem include a drop in throughput or an increased error rate, along with one or more of the following:

  • Yuvalar, veritabanı bağlantıları ve dosya tanıtıcıları gibi kaynakların tükendiğini gösteren özel durumlarda artış.An increase in exceptions that indicate exhaustion of resources such as sockets, database connections, file handles, and so on.
  • Bellek kullanımı ve atık toplamada artış.Increased memory use and garbage collection.
  • Ağ, disk ve veritabanı etkinliğinde artış.An increase in network, disk, or database activity.

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

  1. Ü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.Performing process monitoring of the production system, to identify points when response times slow down or the system fails due to lack of resources.
  2. Bu noktalarda yakalanan telemetri verilerini inceleyerek kaynağı tüketen nesneleri oluşturuyor veya yok ediyor olabilecek işlemleri saptayın.Examine the telemetry data captured at these points to determine which operations might be creating and destroying resource-consuming objects.
  3. Kuşkulanılan her işlem için üretim ortamı yerine denetimli bir test ortamında yük testi yapın.Load test each suspected operation, in a controlled test environment rather than the production system.
  4. Kaynak kodu gözden geçirin ve aracı nesnelerin nasıl yönetildiğini inceleyin.Review the source code and examine the how broker objects are managed.

Yavaş çalışan veya sistem yüklü olduğunda özel durumlar oluşturan işlemler için yığın izlemelerine bakın.Look at stack traces for operations that are slow-running or that generate exceptions when the system is under load. Bu bilgiler, bu işlemlerin kaynakları nasıl kullandığını belirlemeye yardımcı olabilir.This information can help to identify how these operations are using resources. Özel durumlar hataların, paylaşılan kaynakların tükenmesinden kaynaklanıp kaynaklanmadığını saptamaya yardımcı olabilir.Exceptions can help to determine whether errors are caused by shared resources being exhausted.

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

Yavaşlama veya hata noktalarını belirlemeIdentify points of slow down or failure

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.The following image shows results generated using New Relic APM, showing operations that have a poor response time. Bu durumda, NewHttpClientInstancePerRequest denetleyicisindeki GetProductAsync yöntemini daha fazla incelemek yararlı olacaktır.In this case, the GetProductAsync method in the NewHttpClientInstancePerRequest controller is worth investigating further. Bu işlemler çalışırken hata oranının da arttığına dikkat edin.Notice that the error rate also increases when these operations are running.

Her istek için yeni bir HttpClient nesnesi örneği oluşturan örnek uygulamanın gösterildiği New Relic izleme panosu

Telemetri verilerini inceleme ve bağıntılar bulmaExamine telemetry data and find correlations

Sonraki resimde, önceki resimle aynı dönemde iş parçacığı profili oluşturma kullanılarak yakalanan veriler gösterilir.The next image shows data captured using thread profiling, over the same period corresponding as the previous image. 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.The system spends a significant time opening socket connections, and even more time closing them and handling socket exceptions.

Her istek için yeni bir HttpClient nesnesi örneği oluşturan örnek uygulamanın gösterildiği New Relic iş parçacığı profil oluşturucu

Yük testi yapmaPerforming load testing

Kullanıcıların gerçekleştirebileceği tipik işlemlerin benzetimini yapmak için yük testi kullanın.Use load testing to simulate the typical operations that users might perform. Bu, çeşitli yükler alında sistemin hangi bölümlerinde kaynak tükenmesi yaşandığını belirlemeye yardımcı olabilir.This can help to identify which parts of a system suffer from resource exhaustion under varying loads. Bu testleri üretim sisteminde değil denetimli bir ortamda gerçekleştirin.Perform these tests in a controlled environment rather than the production system. 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.The following graph shows the throughput of requests handled by the NewHttpClientInstancePerRequest controller as the user load increases to 100 concurrent users.

Her istek için yeni bir HttpClient nesnesi örneği oluşturan örnek uygulamanın aktarım hızı

Başlangıçta, iş yükü arttıkça saniyede işlenen istek hacmi de artar.At first, the volume of requests handled per second increases as the workload increases. 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.At about 30 users, however, the volume of successful requests reaches a limit, and the system starts to generate exceptions. Ondan sonra, kullanıcı yüküyle birlikte özel durumların hacmi de aşamalı olarak artar.From then on, the volume of exceptions gradually increases with the user load.

Yük testi bu hataları HTTP 500 (İç Sunucu) hataları olarak bildirir.The load test reported these failures as HTTP 500 (Internal Server) errors. 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.Reviewing the telemetry showed that these errors were caused by the system running out of socket resources, as more and more HttpClient objects were created.

Sonraki grafikte, özel ExpensiveToCreateService nesnesini oluşturan denetleyici için benzer bir test gösterilir.The next graph shows a similar test for a controller that creates the custom ExpensiveToCreateService object.

Her istek için yeni bir ExpensiveToCreateService örneği oluşturan örnek uygulamanın aktarım hızı

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.This time, the controller does not generate any exceptions, but throughput still reaches a plateau, while the average response time increases by a factor of 20. (Grafikte, yanıt süresi ve aktarım hızı için logaritmik ölçek kullanılır.) Telemetri, sorunun asıl nedeninin yeni ExpensiveToCreateService örnekleri oluşturmak olduğunu gösterir.(The graph uses a logarithmic scale for response time and throughput.) Telemetry showed that creating new instances of the ExpensiveToCreateService was the main cause of the problem.

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

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.After switching the GetProductAsync method to share a single HttpClient instance, a second load test showed improved performance. Hiçbir hata bildirilmemiş ve sistem saniyede 500 isteğe kadar çıkan bir yükü işleyebilmiştir.No errors were reported, and the system was able to handle an increasing load of up to 500 requests per second. Önceki testle karşılaştırıldığında, ortalama yanıt süresi yarıya inmiştir.The average response time was cut in half, compared with the previous test.

Her istek için HttpClient nesnesini aynı örneğini yeniden kullanan örnek uygulamanın aktarım hızı

Karşılaştırma için, aşağıdaki resimde yığın izleme telemetrisi gösterilir.For comparison, the following image shows the stack trace telemetry. Bu kez, sistem zamanının çoğunu yuvaları açıp kapamak yerine gerçek çalışmayı yapmaya harcar.This time, the system spends most of its time performing real work, rather than opening and closing sockets.

Tüm istekler tek bir HttpClient nesnesi örneği oluşturan örnek uygulamanın gösterildiği New Relic iş parçacığı profil oluşturucu

Sonraki grafikte ExpensiveToCreateService nesnesinin paylaşılan örneğinin kullanıldığı benzer bir yük testi gösterilir.The next graph shows a similar load test using a shared instance of the ExpensiveToCreateService object. Yinelemek gerekirse, işlenen isteklerin hacmi kullanıcı yüküyle birlikte artarken, ortalama yanıt süresi kısa kalmayan devam eder.Again, the volume of handled requests increases in line with the user load, while the average response time remains low.

Her istek için HttpClient nesnesini aynı örneğini yeniden kullanan örnek uygulamanın aktarım hızı