Zaman uyumsuz Request-Reply deseninin

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, iş mantığı ve oluşturma işlevselliği sağlamak üzere uzak API 'lere bağlı olmak için istemci uygulamaları (genellikle bir Web istemcisinde çalışan kod) (genellikle bir Web istemcisi (tarayıcı) için) normaldir. Bu API 'Ler uygulamayla doğrudan ilgili olabilir veya bir üçüncü taraf tarafından sunulan paylaşılan hizmetler olabilir. Genellikle bu API çağrıları HTTP (S) protokolü üzerinden gerçekleşir ve REST semantiğini izler.

Çoğu durumda, bir istemci uygulaması için API 'Ler, 100 ms veya daha az bir sırada hızla yanıt verecek şekilde tasarlanmıştır. Birçok etken, aşağıdakiler de dahil olmak üzere yanıt gecikmesini etkileyebilir:

  • Uygulamanın barındırma yığını.
  • Güvenlik bileşenleri.
  • Çağıranın ve arka ucun göreli coğrafi konumu.
  • Ağ altyapısı.
  • Geçerli yük.
  • İstek yükünün boyutu.
  • Sıra uzunluğu işleniyor.
  • İsteğin işlenmesi için arka uçta geçen süre.

Bu faktörlerden herhangi biri yanıta gecikme süresi ekleyebilir. Arka ucun ölçeği ölçeklenerek bazıları azaltılabilir. Ağ altyapısı gibi diğerleri, büyük ölçüde uygulama geliştiricisi 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 bir API çağrısını engellenmeyen bir şekilde yapabilir ve g/ç 'ye sınırlı işlemler için önerilen zaman uyumsuz işlemenin görünümünü verir.

Ancak, arka uç tarafından gerçekleştirilen iş, saniyeler içinde uzun çalışıyor olabilir veya dakikalar içinde veya saat cinsinden yürütülen bir arka plan işlemi olabilir. Bu durumda, isteğe yanıt vermeden önce çalışmanın tamamlanmasını beklemek mümkün değildir. Bu durum, herhangi bir zaman uyumlu istek-yanıt deseninin olası bir sorunudur.

Bazı mimariler, istek ve yanıt aşamalarını ayırmak için bir ileti Aracısı kullanarak bu sorunu çözebilir. Bu ayrım genellikle kuyruk tabanlı yük dengeleme desenininkullanımı ile elde edilir. Bu ayrım, istemci işleminin ve arka uç API 'sinin bağımsız olarak ölçeklendirilmesine izin verebilir. Ancak bu ayrım, bu adımın zaman uyumsuz hale gelmesi gerektiğinden, istemci başarı bildirimi gerektirdiğinde ek karmaşıklık de getirir.

İstemci uygulamaları için ele alınan aynı noktaların çoğu, dağıtılmış sistemlerdeki sunucudan sunucuya REST API çağrılar için de geçerlidir — Örneğin, bir mikro hizmet mimarisi.

Çözüm

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

  • İ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 olduğunca çabuk zaman uyumlu olarak yanıt verir. İsteğin işleme için alındığını bildiren bir HTTP 202 (kabul edildi) durum kodu döndürür.

    Not

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

  • Yanıt, istemcinin uzun süre çalışan işlemin sonucunu denetlemek için yoklayamayacağı bir uç noktayı işaret eden bir konum başvurusunu barındırır.

  • API, işlemeyi bir ileti kuyruğu gibi başka bir bileşene devreder.

  • İş hala beklerken, durum uç noktası HTTP 202 döndürür. İş tamamlandıktan sonra, durum uç noktası tamamlamayı belirten bir kaynak döndürebilir ya da başka bir kaynak URL 'sine yeniden yönlendirebilir. Örneğin, zaman uyumsuz işlem yeni bir kaynak oluşturursa, durum uç noktası söz konusu kaynağın URL 'sine yeniden 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. İş hala bekliyor, bu nedenle bu çağrı HTTP 202 ' i de döndürür.
  3. Bir noktada, çalışma tamamlanır ve durum uç noktası, kaynağa yeniden yönlendirme 302 (bulundu) döndürür.
  4. İstemci, kaynağı belirtilen URL 'ye getirir.

Sorunlar ve dikkat edilmesi gerekenler

  • Bu düzenin HTTP üzerinden uygulanması için bazı olası yollar vardır ve tüm yukarı akış hizmetleri aynı semantiğe sahip değildir. Örneğin, hizmetlerin çoğu, uzak bir işlem bitmemişse bir GET yönteminden HTTP 202 yanıtını geri döndürmez. Saf REST semantiklerinden sonra HTTP 404 (bulunamadı) döndürmelidir. Bu yanıt, çağrının sonucunun henüz mevcut olmadığını düşünsün sonra anlamlı hale gelir.

  • Bir HTTP 202 yanıtı, istemcinin yanıtı yoklayacağını belirten konumu ve sıklığı göstermelidir. Aşağıdaki ek üstbilgilere sahip olmalıdır:

    Üst bilgi Açıklama Notlar
    Konum İstemcinin yanıt durumunu yoklamasını gereken bir URL. Bu URL, bu konumun erişim denetimine ihtiyacı varsa, Valet anahtar deseninin uygun olması için gerekli olan bir SAS belirteci olabilir. Yanıt yoklamanın başka bir arka uca yük boşaltma yapması gerektiğinde Vale anahtar deseninin de geçerli olduğu
    Retry-After İşlemin tamamlanacağı tahmin Bu üst bilgi, istemci yoklama istemcilerinin yeniden denemeler ile arka uca hızlandırmasını engellemek için tasarlanmıştır.
  • Kullanılan temel hizmetlere bağlı olarak yanıt üstbilgilerini veya yükünü yönetmek için bir işlem proxy 'si veya façlade kullanmanız gerekebilir.

  • Durum uç noktası tamamlanmayı yeniden yönlendirirse, destekledikleri tam anlamına bağlı olarak http 302 veya http 303 uygun dönüş kodlarıdır.

  • İşlem başarılı olduğunda, konum üst bilgisi tarafından belirtilen kaynak 200 (Tamam), 201 (oluşturuldu) veya 204 (Içerik yok) gibi uygun bir HTTP yanıt kodu döndürmelidir.

  • İşlem sırasında bir hata oluşursa, konumu konum üstbilgisinde açıklanan kaynak URL 'sinde kalıcı hale getirin ve bu kaynaktan istemciye uygun bir yanıt kodu döndürün (4xx kod).

  • Tüm çözümler bu kalıbı aynı şekilde uygulayacağından, bazı hizmetlerde ek veya alternatif üst bilgiler yer alır. Örneğin Azure Resource Manager, bu düzenin değiştirilmiş bir türevini kullanır. Daha fazla bilgi için bkz. Azure Resource Manager zaman uyumsuz işlemler.

  • Eski istemciler bu kalıbı desteklemeyebilir. Bu durumda, zaman uyumsuz API 'ye ilk istemciden zaman uyumsuz işlemeyi gizlemek için bir façlade 'yi yerleştirmeniz gerekebilir. örneğin, Azure Logic Apps bu kalıbı yerel olarak destekler, zaman uyumsuz bir apı ve zaman uyumlu çağrılar yapan bir istemci arasında bir 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 etmek için bir yol sağlamak isteyebilirsiniz. Bu durumda, arka uç hizmetinin bazı iptal yönergesi biçimini desteklemesi gerekir.

Bu düzenin kullanılacağı durumlar

Bu kalıbı şu şekilde kullanın:

  • Tarayıcı uygulamaları gibi istemci tarafı kodu, geri dönüş uç noktaları sağlamanın zor olduğu veya uzun süre çalışan bağlantıların kullanılması çok fazla ek karmaşıklık ekliyor.

  • Hizmet çağrıları yalnızca HTTP protokolünün kullanılabildiği ve döndürülen hizmetin, istemci tarafında güvenlik duvarı kısıtlamaları nedeniyle geri çağırmaları tetikleyemedi.

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

Bu model şu durumlarda uygun olmayabilir:

  • Azure Event Grid gibi, zaman uyumsuz bildirimler için oluşturulmuş bir hizmeti kullanabilirsiniz.
  • Yanıtların istemciye gerçek zamanlı akışı olması gerekir.
  • İstemcinin birçok sonuç toplaması ve bu sonuçların gecikme süresinin alınması önemlidir. Bunun yerine bir Service Bus modelini göz önünde bulundurun.
  • WebSockets veya SignalR gibi sunucu tarafı kalıcı ağ bağlantılarını kullanabilirsiniz. Bu hizmetler, sonucu çağırana bildirmek için kullanılabilir.
  • Ağ tasarımı, zaman uyumsuz geri çağırmaları veya Web kancalarını almak için bağlantı noktalarını açmanıza olanak tanır.

Örnek

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

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

Işlevlerde zaman uyumsuz Istek yanıtı deseninin yapısının görüntüsü

GitHub logo bu örnek GitHubkullanılabilir.

AsyncProcessingWorkAcceptor işlevi

AsyncProcessingWorkAcceptorİşlevi bir istemci uygulamasından iş kabul eden bir uç nokta uygular ve bunu işlenmek üzere kuyruğa koyar.

  • İşlevi bir istek KIMLIĞI oluşturur ve onu kuyruk iletisine meta veri olarak ekler.
  • HTTP yanıtı, bir 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<Message> OutMessage,
        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);
        Message m = new Message(Encoding.UTF8.GetBytes(messagePayload));
        m.UserProperties["RequestGUID"] = reqid;
        m.UserProperties["RequestSubmittedAt"] = DateTime.Now;
        m.UserProperties["RequestStatusURL"] = rqs;

        await OutMessage.AddAsync(m);

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

AsyncProcessingBackgroundWorker işlevi

AsyncProcessingBackgroundWorkerİşlev işlemi kuyruktan alır, ileti yüküne göre bazı işler yapar ve sonucu SAS imza konumuna yazar.

public static class AsyncProcessingBackgroundWorker
{
    [FunctionName("AsyncProcessingBackgroundWorker")]
    public static void Run(
        [ServiceBusTrigger("outqueue", Connection = "ServiceBusConnectionAppSetting")]Message myQueueItem,
        [Blob("data", FileAccess.ReadWrite, Connection = "StorageConnectionAppSetting")] CloudBlobContainer inputBlob,
        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 = myQueueItem.UserProperties["RequestGUID"] as string;

        CloudBlockBlob cbb = inputBlob.GetBlockBlobReference($"{id}.blobdata");

        // Now write the results to blob storage.
        cbb.UploadFromByteArrayAsync(myQueueItem.Body, 0, myQueueItem.Body.Length);
    }
}

AsyncOperationStatusChecker işlevi

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

  • İstek tamamlanırsa, işlev yanıta bir Valet-Key döndürür ya da çağrıyı hemen Valet-Key URL 'sine yönlendirir.
  • İstek hala beklediğinde, http Retry-After üst bilgisinde, tamamlanmış bir yanıt için bir ETA yerleştirerek, kendine başvuran bir konum üstbilgisiyle kabul edilen bir 202 döndürmelidir.
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")] CloudBlockBlob inputBlob, string thisGUID,
        ILogger log)
    {

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

        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, check the optional "OnPending" parameter.
            string rqs = $"http://{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/api/RequestStatus/{thisGUID}";

            switch (OnPending)
            {
                case OnPendingEnum.Accepted:
                    {
                        // Return an HTTP 202 status code.
                        return (ActionResult)new AcceptedResult() { 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 (ActionResult)new NotFoundResult();
                        }
                    }

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

    private static async Task<IActionResult> OnCompleted(OnCompleteEnum OnComplete, CloudBlockBlob inputBlob, string thisGUID)
    {
        switch (OnComplete)
        {
            case OnCompleteEnum.Redirect:
                {
                    // Redirect to the SAS URI to blob storage
                    return (ActionResult)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 (ActionResult)new OkObjectResult(await inputBlob.DownloadTextAsync());
                }

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

public enum OnCompleteEnum {

    Redirect,
    Stream
}

public enum OnPendingEnum {

    Accepted,
    Synchronous
}

Sonraki adımlar

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