繁体   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