簡體   English   中英

並發Web API請求以及如何處理ASP.NET Core中的狀態

[英]Concurrent web api requests and how to handle state in ASP.NET core

由多個Web api端點組成的ASP.NET core 2.1應用程序(實體框架)。 其中之一是“加入”端點,用戶可以在其中加入隊列。 另一個是“離開”端點,用戶可以在其中離開隊列。

隊列中有10個可用位置。

如果所有10個地點均已填寫完畢,我們將向您返回一條消息,說“隊列已滿”。

如果剛好有3個用戶加入,則返回true。

如果加入的用戶數不是3,則返回false。

200個觸發滿意的用戶准備加入並離開不同的隊列。 他們都同時調用“ join”和“ leave”端點。

這意味着我們必須按順序處理傳入的請求,以確保以良好且可控制的方式將用戶添加和刪除到正確的隊列。 (對?)

一種選擇是將QueueService類添加為IServiceCollection中的AddSingleton<> ,然后進行lock()以確保一次只能輸入一個用戶。 但是,如何處理dbContext ,因為它已注冊為AddTransient<>AddScoped<>

連接部分的偽代碼:

public class QueueService
{
    private readonly object _myLock = new object();
    private readonly QueueContext _context;

    public QueueService(QueueContext context)
    {
        _context = context;
    }

    public bool Join(int queueId, int userId)
    {
        lock (_myLock)
        {
            var numberOfUsersInQueue = _context.GetNumberOfUsersInQueue(queueId); <- Problem. 
            if (numberOfUsersInQueue >= 10)
            {
                throw new Exception("Queue is full.");
            }
            else
            {
                _context.AddUserToQueue(queueId, userId); <- Problem.
            }

            numberOfUsersInQueue = _context.GetNumberOfUsersInQueue(queueId); <- Problem.

            if (numberOfUsersInQueue == 3)
            {
                return true;
            }
        }
        return false;
    }
}

另一個選擇是使QueueService瞬態狀態,但是隨后我丟失了服務的狀態,並且為每個請求提供了一個新實例,從而使lock()毫無意義。

問題:

[1]我應該改為處理內存中隊列的狀態嗎? 如果是,如何使其與數據庫對齊?

[2]是否有我錯過的出色模式? 還是我該如何處理?

這意味着我們必須按順序處理傳入的請求,以確保以良好且可控制的方式將用戶添加和刪除到正確的隊列。 (對?)

本質上是。 這些請求可以並發執行,但是它們必須以某種方式彼此同步(例如,鎖定或數據庫事務)。

我應該處理內存中隊列的狀態嗎?

通常,最好使Web應用程序成為無狀態且獨立的請求。 在這種模式下狀態存儲在數據庫中。 巨大的優勢在於,無需進行同步,並且該應用程序可以在多台服務器上運行。 另外,如果應用程序重新啟動(或崩潰),則不會丟失任何狀態。

在這里,這似乎是完全可行和適當的。

將狀態放在關系數據庫中,並使用並發控制使並發訪問安全。 例如,使用可序列化的隔離級別,並在出現死鎖的情況下重試。 這為您提供了一個非常不錯的編程模型,在該模型中,您假裝自己是數據庫的唯一用戶,但它絕對安全。 必須將所有訪問權限放入事務中,如果出現死鎖,則必須重試整個事務。

如果您堅持使用內存中狀態,則請拆分單例和瞬態組件。 將全局狀態置於單例類中,並將對該狀態進行操作的操作置於瞬時狀態中。 通過這種方式,臨時組件(例如數據庫訪問)的依賴項注入非常容易且干凈。 全局類應該很小(也許只是數據字段)。

我最終得到了這個簡單的解決方案。

創建一個帶有ConcurrentDictionary<int, object>的靜態(或單例)類,該類帶有queueId和一個鎖。

創建新隊列后,將queueId和新的鎖定對象添加到字典中。

使QueueService類AddTransient<> ,然后:

public bool Join(int queueId, int userId)
    {
        var someLock = ConcurrentQueuePlaceHolder.Queues[queueId];
        lock (someLock)
        {
            var numberOfUsersInQueue = _context.GetNumberOfUsersInQueue(queueId); <- Working
            if (numberOfUsersInQueue >= 10)
            {
                throw new Exception("Queue is full.");
            }
            else
            {
                _context.AddUserToQueue(queueId, userId); <- Working
            }

            numberOfUsersInQueue = _context.GetNumberOfUsersInQueue(queueId); <- Working

            if (numberOfUsersInQueue == 3)
            {
                return true;
            }
        }
        return false;
    }

_context不再有問題。

這樣,我就可以以一種可控的方式處理並發請求。

如果某個時候可以使用多個服務器,那么消息代理或ESB也可以成為解決方案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM