由多个Web api端点组成的ASP.NET core 2.1应用程序(实体框架)。 其中之一是“加入”端点,用户可以在其中加入队列。 另一个是“离开”端点,用户可以在其中离开队列。
队列有10个可用位置。
如果所有10个地点均已填写完毕,我们将向其返回一条消息,指出“队列已满。”
如果刚好有3个用户加入,则返回true。
如果加入的用户数不是3,则返回false。
200个满足触发条件的用户准备加入并离开不同的队列。他们都同时呼叫“ join”和“ leave”端点。
这意味着我们必须按顺序处理传入的请求,以确保以一种良好且可控制的方式将用户添加和删除到正确的队列。 (对吗?)
一种选择是将QueueService
类添加为AddSingleton<>
中的IServiceCollection
,然后创建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
另一种选择是使{{1}}处于瞬态状态,但是随后我失去了服务状态,并且为每个请求提供了一个新实例,从而使lock()毫无意义。
问题:
[1]我应该改为处理内存中队列的状态吗?如果是,如何将其与数据库对齐?
[2]是否有我错过的出色模式?还是我该如何处理?
答案 0 :(得分:2)
这意味着我们必须按顺序处理传入的请求,以确保以一种良好且可控制的方式将用户添加和删除到正确的队列。 (对吗?)
基本上是。这些请求可以并发执行,但是它们必须以某种方式彼此同步(例如,锁或数据库事务)。
我应该改为处理内存中队列的状态吗?
通常,最好使Web应用程序无状态且独立于请求。在这种模式下状态存储在数据库中。巨大的优势在于,无需进行同步,并且该应用程序可以在多台服务器上运行。另外,如果应用程序重新启动(或崩溃),则不会丢失任何状态。
在这里看来这完全有可能并且适当。
将状态放入关系数据库中,并使用并发控制使并发访问安全。例如,使用可序列化的隔离级别,并在出现死锁的情况下重试。这为您提供了一个非常不错的编程模型,在该模型中,您假装自己是数据库的唯一用户,但它绝对安全。必须将所有访问权限都放入事务中,如果发生死锁,则必须重试整个事务。
如果您坚持使用内存中状态,则请拆分单例和瞬态组件。将全局状态置于单例类中,并将对该状态进行操作的操作置于瞬时状态中。通过这种方式,临时组件(例如数据库访问)的依赖项注入非常容易且干净。全局类应该很小(也许只是数据字段)。
答案 1 :(得分:0)
我最终得到了这个简单的解决方案。
使用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也可以作为解决方案。