![](/img/trans.png)
[英]how to handle 1000 concurrent user requests per second in asp.net web api
[英]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.