Zaman Uyumsuz İstek-Yanıt deseni

Azure
Azure Logic Apps

Arka uç işlemesinin zaman uyumsuz olması gereken, ancak ön ucun yine de net bir yanıta ihtiyaç duyduğu noktada arka uç işlemeyi ön uç konağından ayırın.

Bağlam ve sorun

Modern uygulama geliştirmede, istemci uygulamalarının (genellikle bir web istemcisinde (tarayıcıda) çalışan kodlar) iş mantığı sağlamak ve işlevsellik oluşturmak için uzak API'lere bağımlı olması normaldir. Bu API'ler doğrudan uygulamayla ilgili olabilir veya üçüncü taraf tarafından sağlanan paylaşılan hizmetler olabilir. Bu API çağrıları genellikle HTTP(S) protokolü üzerinden gerçekleşir ve REST semantiğini izler.

Çoğu durumda, bir istemci uygulamasının API'leri 100 ms veya daha kısa bir sırada hızlı yanıt verecek şekilde tasarlanmıştır. Aşağıdakiler dahil olmak üzere yanıt gecikme süresini birçok faktör etkileyebilir:

  • Bir uygulamanın barındırma yığını.
  • Güvenlik bileşenleri.
  • Arayanın ve arka ucun göreli coğrafi konumu.
  • Ağ altyapısı.
  • Geçerli yük.
  • İstek yükünün boyutu.
  • Kuyruk uzunluğu işleniyor.
  • Arka ucun isteği işleme süresi.

Bu faktörlerden herhangi biri yanıta gecikme süresi ekleyebilir. Bazıları arka ucun ölçeği genişletilerek azaltılabilir. Ağ altyapısı gibi diğerleri büyük ölçüde uygulama geliştiricisinin denetiminden çıkar. Çoğu API, yanıtların aynı bağlantı üzerinden geri gelmesi için yeterince hızlı yanıt verebilir. Uygulama kodu, zaman uyumlu API çağrısını engelleyici olmayan bir şekilde yapabilir ve G/Ç bağlı işlemler için önerilen zaman uyumsuz işlemenin görünümünü verir.

Ancak bazı senaryolarda arka uç tarafından yapılan çalışmalar uzun süre çalışıyor, saniye sırasına göre çalışıyor olabilir veya dakikalar, hatta saatler içinde yürütülen bir arka plan işlemi olabilir. Bu durumda, isteği yanıtlamadan önce çalışmanın tamamlanmasını beklemek mümkün değildir. Bu durum, zaman uyumlu istek-yanıt desenleri için olası bir sorundur.

Bazı mimariler, istek ve yanıt aşamalarını ayırmak için bir ileti aracısı kullanarak bu sorunu çözer. Bu ayrım genellikle Kuyruk Tabanlı Yük Dengeleme deseninin kullanılmasıyla elde edilir. Bu ayrım, istemci işleminin ve arka uç API'sinin bağımsız olarak ölçeklendirilmesine olanak sağlayabilir. Ancak bu ayrım, istemcinin başarı bildirimi gerektirdiğinde de ek karmaşıklık getirir, bu adımın zaman uyumsuz hale gelmesi gerekir.

İstemci uygulamaları için tartışılan konuların çoğu, dağıtılmış sistemlerdeki sunucudan sunucuya REST API çağrıları için de geçerlidir ( örneğin, bir mikro hizmet mimarisinde).

Çözüm

Bu sorunun bir çözümü HTTP yoklamasını kullanmaktır. Yoklama, geri arama uç noktaları sağlamak veya uzun süre çalışan bağlantılar kullanmak zor olabileceği için istemci tarafı kodu için yararlıdır. Geri çağırmalar mümkün olsa bile, gereken ek kitaplıklar ve hizmetler bazen çok fazla karmaşıklık ekleyebilir.

  • İstemci uygulaması, arka uçta uzun süre çalışan bir işlemi tetikleyerek API'ye zaman uyumlu bir çağrı yapar.

  • API mümkün olan en kısa sürede zaman uyumlu bir şekilde yanıt verir. İsteğin işlenmek üzere alındığını onaylayan bir HTTP 202 (Kabul Edildi) durum kodu döndürür.

    Not

    API, uzun süre çalışan işleme başlamadan önce hem isteği hem de gerçekleştirilecek eylemi doğrulamalıdır. İstek geçersizse http 400 (Hatalı İstek) gibi bir hata koduyla hemen yanıtlayın.

  • Yanıt, istemcinin uzun süre çalışan işlemin sonucunu denetlemek için yoklaması için bir uç noktaya işaret eden bir konum başvurusu tutar.

  • API, işlemeyi ileti kuyruğu gibi başka bir bileşene boşaltıyor.

  • Durum uç noktasına yapılan her başarılı çağrı için HTTP 200 döndürür. Çalışma hala beklemede olsa da durum uç noktası, çalışmanın hala devam ettiğini gösteren bir kaynak döndürür. Çalışma tamamlandıktan sonra, durum uç noktası tamamlandığını belirten bir kaynak döndürebilir veya başka bir kaynak URL'sine yönlendirebilir. Örneğin, zaman uyumsuz işlem yeni bir kaynak oluşturursa, durum uç noktası bu kaynağın URL'sine yönlendirilir.

Aşağıdaki diyagramda tipik bir akış gösterilmektedir:

Zaman uyumsuz HTTP istekleri için istek ve yanıt akışı

  1. İstemci bir istek gönderir ve bir HTTP 202 (Kabul Edildi) yanıtı alır.
  2. İstemci, durum uç noktasına bir HTTP GET isteği gönderir. Çalışma hala beklemede olduğundan bu çağrı HTTP 200 döndürür.
  3. Bir noktada çalışma tamamlanır ve durum uç noktası kaynağa yeniden yönlendiren 302 (Bulundu) değerini döndürür.
  4. İstemci, belirtilen URL'deki kaynağı getirir.

Sorunlar ve dikkat edilmesi gerekenler

  • Bu düzeni HTTP üzerinden uygulamanın çeşitli yolları vardır ve tüm yukarı akış hizmetleri aynı semantiklere sahip değildir. Örneğin, uzak işlem tamamlanmadığında çoğu hizmet GET yönteminden HTTP 202 yanıtı döndürmez. Saf REST semantiğine göre HTTP 404 (Bulunamadı) döndürmelidir. Aramanın sonucunun henüz mevcut olmadığını düşündüğünüzde bu yanıt mantıklıdır.

  • HTTP 202 yanıtı, istemcinin yanıt için yoklaması gereken konumu ve sıklığı göstermelidir. Aşağıdaki ek üst bilgileri içermelidir:

    Üst bilgi Açıklama Notlar
    Konum İstemcinin yanıt durumunu yoklaması gereken bir URL. Bu URL, bu konumun erişim denetimine ihtiyacı varsa Vale Anahtarı Düzeni'nin uygun olduğu bir SAS belirteci olabilir. Yanıt yoklamanın başka bir arka uçtan boşaltılması gerektiğinde vale anahtarı deseni de geçerlidir
    Sonra Yeniden Dene İşlemenin ne zaman tamamlanacağına yönelik bir tahmin Bu üst bilgi, yoklama istemcilerinin arka ucu yeniden denemelerle bunaltmasını önlemek için tasarlanmıştır.
  • Kullanılan temel hizmetlere bağlı olarak yanıt üst bilgilerini veya yükünü işlemek için bir işlem ara sunucusu veya cephe kullanmanız gerekebilir.

  • Durum uç noktası tamamlandığında yeniden yönlendiriliyorsa, http 302 veya HTTP 303 , tam olarak desteklediğiniz semantiklere bağlı olarak uygun dönüş kodlarıdır.

  • İşlem başarılı olursa, Konum üst bilgisi tarafından belirtilen kaynak 200 (Tamam), 201 (Oluşturuldu) veya 204 (İçerik Yok) gibi uygun bir HTTP yanıt kodu döndürmelidir.

  • İşleme sırasında bir hata oluşursa, hatayı Konum üst bilgisinde açıklanan kaynak URL'sinde kalıcı hale getirir ve ideal olarak bu kaynaktan istemciye uygun bir yanıt kodu döndürür (4xx kodu).

  • Tüm çözümler bu düzeni aynı şekilde uygulamaz ve bazı hizmetler ek veya alternatif üst bilgiler içerir. Örneğin, Azure Resource Manager bu desenin değiştirilmiş bir değişkenini kullanır. Daha fazla bilgi için bkz . Azure Resource Manager Zaman Uyumsuz İşlemleri.

  • Eski istemciler bu düzeni desteklemeyebilir. Bu durumda, zaman uyumsuz işlemeyi özgün istemciden gizlemek için zaman uyumsuz API'nin üzerine bir cephe yerleştirmeniz gerekebilir. Örneğin, Azure Logic Apps bu deseni yerel olarak destekler, zaman uyumsuz API ile zaman uyumlu çağrılar yapan bir istemci arasında tümleştirme katmanı olarak kullanılabilir. Bkz . Web kancası eylem düzeniyle uzun süre çalışan görevler gerçekleştirme.

  • Bazı senaryolarda, istemcilerin uzun süre çalışan bir isteği iptal edebilmesi için bir yol sağlamak isteyebilirsiniz. Bu durumda, arka uç hizmetinin bir tür iptal yönergesini desteklemesi gerekir.

Bu düzenin kullanılacağı durumlar

Aşağıdakiler için bu deseni kullanın:

  • Geri arama uç noktaları sağlamanın zor olduğu tarayıcı uygulamaları gibi istemci tarafı kodu veya uzun süre çalışan bağlantıların kullanılması çok fazla ek karmaşıklık ekler.

  • Yalnızca HTTP protokolünün kullanılabilir olduğu ve istemci tarafındaki güvenlik duvarı kısıtlamaları nedeniyle dönüş hizmetinin geri çağırmaları tetikleyemediği hizmet çağrıları.

  • WebSockets veya web kancaları gibi modern geri çağırma teknolojilerini desteklemeyen eski mimarilerle tümleştirilmesi gereken hizmet çağrıları.

Bu düzen aşağıdaki durumlarda uygun olmayabilir:

  • Bunun yerine Azure Event Grid gibi zaman uyumsuz bildirimler için oluşturulmuş bir hizmeti kullanabilirsiniz.
  • Yanıtların istemciye gerçek zamanlı olarak akışı yapılmalıdır.
  • İstemcinin birçok sonuç toplaması gerekir ve bu sonuçların gecikme süresi önemlidir. Bunun yerine bir hizmet veri yolu deseni düşünün.
  • WebSockets veya SignalR gibi sunucu tarafı kalıcı ağ bağlantılarını kullanabilirsiniz. Bu hizmetler, sonucu çağıranı bilgilendirmek için kullanılabilir.
  • Ağ tasarımı, zaman uyumsuz geri çağırmalar veya web kancaları almak için bağlantı noktalarını açmanıza olanak tanır.

İş yükü tasarımı

Bir mimar, Azure İyi Tasarlanmış Çerçeve yapılarında ele alınan hedefleri ve ilkeleri ele almak için zaman uyumsuz İstek-Yanıt deseninin iş yükünün tasarımında nasıl kullanılabileceğini değerlendirmelidir. Örneğin:

Yapı Taşı Bu desen sütun hedeflerini nasıl destekler?
Performans Verimliliği , ölçeklendirme, veri ve kod iyileştirmeleri aracılığıyla iş yükünüzün talepleri verimli bir şekilde karşılamasını sağlar. Anında yanıtlara ihtiyaç duymayan işlemler için etkileşimlerin istek ve yanıt aşamalarını ayırma, sistemlerin yanıt verme hızını ve ölçeklenebilirliğini artırır. Zaman uyumsuz bir appproach olarak, sunucu tarafında eşzamanlılığı en üst düzeye çıkarabilir ve kapasitenin izin verdiği şekilde çalışmanın tamamlanacağını zamanlayabilirsiniz.

- PE:05 Ölçeklendirme ve bölümleme
- PE:07 Kod ve altyapı

Herhangi bir tasarım kararında olduğu gibi, bu desenle ortaya konulabilecek diğer sütunların hedeflerine karşı herhangi bir dengeyi göz önünde bulundurun.

Örnek

Aşağıdaki kod, bu düzeni uygulamak için Azure İşlevleri kullanan bir uygulamadan alıntıları gösterir. Çözümde üç işlev vardır:

  • Zaman uyumsuz API uç noktası.
  • Durum uç noktası.
  • Kuyruğa alınmış iş öğelerini alan ve yürüten bir arka uç işlevi.

İşlevler'de Zaman Uyumsuz İstek Yanıtı deseninin yapısının görüntüsü

GitHub logosuBu örnek GitHub'da kullanılabilir.

AsyncProcessingWorkAcceptor işlevi

İşlev, AsyncProcessingWorkAcceptor bir istemci uygulamasından çalışmayı kabul eden ve işlenmek üzere bir kuyruğa yerleştiren bir uç nokta uygular.

  • İşlev bir istek kimliği oluşturur ve bunu kuyruk iletisine meta veri olarak ekler.
  • HTTP yanıtı, durum uç noktasına işaret eden bir konum üst bilgisi içerir. İstek kimliği URL yolunun bir parçasıdır.
public static class AsyncProcessingWorkAcceptor
{
    [FunctionName("AsyncProcessingWorkAcceptor")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] CustomerPOCO customer,
        [ServiceBus("outqueue", Connection = "ServiceBusConnectionAppSetting")] IAsyncCollector<ServiceBusMessage> OutMessages,
        ILogger log)
    {
        if (String.IsNullOrEmpty(customer.id) || string.IsNullOrEmpty(customer.customername))
        {
            return new BadRequestResult();
        }

        string reqid = Guid.NewGuid().ToString();

        string rqs = $"http://{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/api/RequestStatus/{reqid}";

        var messagePayload = JsonConvert.SerializeObject(customer);
        var message = new ServiceBusMessage(messagePayload);
        message.ApplicationProperties.Add("RequestGUID", reqid);
        message.ApplicationProperties.Add("RequestSubmittedAt", DateTime.Now);
        message.ApplicationProperties.Add("RequestStatusURL", rqs);

        await OutMessages.AddAsync(message);

        return new AcceptedResult(rqs, $"Request Accepted for Processing{Environment.NewLine}ProxyStatus: {rqs}");
    }
}

AsyncProcessingBackgroundWorker işlevi

İşlev AsyncProcessingBackgroundWorker işlemi kuyruktan alır, ileti yüküne göre bazı çalışmalar yapar ve sonucu bir depolama hesabına yazar.

public static class AsyncProcessingBackgroundWorker
{
    [FunctionName("AsyncProcessingBackgroundWorker")]
    public static async Task RunAsync(
        [ServiceBusTrigger("outqueue", Connection = "ServiceBusConnectionAppSetting")] BinaryData customer,
        IDictionary<string, object> applicationProperties,
        [Blob("data", FileAccess.ReadWrite, Connection = "StorageConnectionAppSetting")] BlobContainerClient inputContainer,
        ILogger log)
    {
        // Perform an actual action against the blob data source for the async readers to be able to check against.
        // This is where your actual service worker processing will be performed

        var id = applicationProperties["RequestGUID"] as string;

        BlobClient blob = inputContainer.GetBlobClient($"{id}.blobdata");

        // Now write the results to blob storage.
        await blob.UploadAsync(customer);
    }
}

AsyncOperationStatusChecker işlevi

AsyncOperationStatusChecker işlevi durum uç noktasını uygular. Bu işlev önce isteğin tamamlanıp tamamlanmadığını denetler

public static class AsyncOperationStatusChecker
{
    [FunctionName("AsyncOperationStatusChecker")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "RequestStatus/{thisGUID}")] HttpRequest req,
        [Blob("data/{thisGuid}.blobdata", FileAccess.Read, Connection = "StorageConnectionAppSetting")] BlockBlobClient inputBlob, string thisGUID,
        ILogger log)
    {

        OnCompleteEnum OnComplete = Enum.Parse<OnCompleteEnum>(req.Query["OnComplete"].FirstOrDefault() ?? "Redirect");
        OnPendingEnum OnPending = Enum.Parse<OnPendingEnum>(req.Query["OnPending"].FirstOrDefault() ?? "OK");

        log.LogInformation($"C# HTTP trigger function processed a request for status on {thisGUID} - OnComplete {OnComplete} - OnPending {OnPending}");

        // Check to see if the blob is present
        if (await inputBlob.ExistsAsync())
        {
            // If it's present, depending on the value of the optional "OnComplete" parameter choose what to do.
            return await OnCompleted(OnComplete, inputBlob, thisGUID);
        }
        else
        {
            // If it's NOT present, then we need to back off. Depending on the value of the optional "OnPending" parameter, choose what to do.
            string rqs = $"http://{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/api/RequestStatus/{thisGUID}";

            switch (OnPending)
            {
                case OnPendingEnum.OK:
                    {
                        // Return an HTTP 200 status code.
                        return new OkObjectResult(new { status = "In progress", Location = rqs });
                    }

                case OnPendingEnum.Synchronous:
                    {
                        // Back off and retry. Time out if the backoff period hits one minute.
                        int backoff = 250;

                        while (!await inputBlob.ExistsAsync() && backoff < 64000)
                        {
                            log.LogInformation($"Synchronous mode {thisGUID}.blob - retrying in {backoff} ms");
                            backoff = backoff * 2;
                            await Task.Delay(backoff);
                        }

                        if (await inputBlob.ExistsAsync())
                        {
                            log.LogInformation($"Synchronous Redirect mode {thisGUID}.blob - completed after {backoff} ms");
                            return await OnCompleted(OnComplete, inputBlob, thisGUID);
                        }
                        else
                        {
                            log.LogInformation($"Synchronous mode {thisGUID}.blob - NOT FOUND after timeout {backoff} ms");
                            return new NotFoundResult();
                        }
                    }

                default:
                    {
                        throw new InvalidOperationException($"Unexpected value: {OnPending}");
                    }
            }
        }
    }

    private static async Task<IActionResult> OnCompleted(OnCompleteEnum OnComplete, BlockBlobClient inputBlob, string thisGUID)
    {
        switch (OnComplete)
        {
            case OnCompleteEnum.Redirect:
                {
                    // Redirect to the SAS URI to blob storage

                    return new RedirectResult(inputBlob.GenerateSASURI());
                }

            case OnCompleteEnum.Stream:
                {
                    // Download the file and return it directly to the caller.
                    // For larger files, use a stream to minimize RAM usage.
                    return new OkObjectResult(await inputBlob.DownloadContentAsync());
                }

            default:
                {
                    throw new InvalidOperationException($"Unexpected value: {OnComplete}");
                }
        }
    }
}

public enum OnCompleteEnum
{

    Redirect,
    Stream
}

public enum OnPendingEnum
{

    OK,
    Synchronous
}

Sonraki adımlar

Bu düzeni uygularken aşağıdaki bilgiler yararlı olabilir: