То, что вы просите, в основном не в том, как работает Интернет. Протоколы HTTP и нижележащие IP-адреса не имеют состояния, что означает, что каждый запрос должен выполняться независимо от каких-либо сведений о том, что произошло ранее (или одновременно, в зависимости от обстоятельств). Если вы беспокоитесь о чрезмерной нагрузке, лучше всего использовать ограничение / регулирование скорости, связанное с аутентификацией. Таким образом, как только пользователь прожигает свои выделенные запросы, они отключаются. Это будет иметь естественный побочный эффект, так как разработчики, программирующие под ваш API, будут более осторожны при отправке чрезмерных запросов.
Просто, чтобы быть немного более тщательным, главная проблема с подходом, который вы предлагаете, заключается в том, что я не знаю, каким образом он может быть практически реализован. Вы можете использовать что-то вроде SemaphoreSlim
для создания блокировки, но это должно быть статическим, чтобы один и тот же экземпляр использовался для каждого запроса. Быть статичным ограничит вашу способность использовать их словарь, который вам необходим для этого. Технически это можно сделать, я полагаю, но вам придется использовать ConcurrentDictionary
, и даже в этом случае нет гарантии на однопоточное добавление. Таким образом, одновременные запросы одного и того же пользователя могут загружать в него параллельные семафоры, что наносит ущерб всей точке. Я полагаю, что вы могли бы сразу загрузить словарь с семафором для каждого пользователя с самого начала, но это может стать огромной тратой ресурсов, в зависимости от вашей пользовательской базы. Короче говоря, это одна из тех вещей, когда, когда вы находите решение, которое чертовски сложно, это хороший признак того, что вы, вероятно, пытаетесь сделать то, чего не должны делать.
EDIT
После прочтения вашего примера я думаю, что на самом деле все сводится к проблеме попытки справиться с работой в конвейере запросов. Когда нужно выполнить какое-то долгосрочное задание или просто выполнить тяжелую работу, первым шагом всегда должно быть его передача фоновой службе. Это позволяет быстро вернуть ответ. Веб-серверы имеют ограниченное количество потоков для обработки запросов, и вы хотите обслуживать запрос и возвращать ответ как можно быстрее, чтобы не исчерпать пул потоков.
Вы можете использовать библиотеку, такую как Hangfire, для обработки фоновой работы, или вы можете реализовать IHostedService
, как описано здесь , чтобы поставить работу в очередь. Как только у вас будет готов фоновый сервис, вы сразу же получите его в любое время, когда получите запрос к этой конечной точке, и вернете ответ 202 Accepted
с URL-адресом, по которому клиент может нажать, чтобы проверить состояние. Это решает вашу непосредственную проблему нежелания разрешать кучу запросов на эту длительную работу, чтобы вывести из строя ваш API. Теперь он, по сути, больше ничего не делает, просто говорит что-то еще, чтобы сделать это, а затем сразу же возвращается.
Что касается фактической фоновой работы, которую вы поставили бы в очередь, там вы можете проверить надбавки пользователя, и если они превысили 10 запросов (ваш лимит скорости), вы сразу же провалите работу, ничего не делая. Если нет, то вы действительно можете начать работу.
Если хотите, вы также можете включить поддержку webhook, чтобы уведомить клиента о завершении задания. Вы просто позволяете клиенту установить URL-адрес обратного вызова, который вы должны уведомлять о завершении, а затем, когда вы закончите работу в фоновом режиме, вы нажимаете этот обратный вызов. Клиент сам решает, что делать, когда происходит обратный вызов. Например, они могут решить использовать SignalR для отправки сообщения своим пользователям / клиентам.
РЕДАКТИРОВАТЬ # 2
Я на самом деле немного заинтригован этим. Хотя я все еще думаю, что вам лучше перенести работу в фоновый процесс, я смог создать решение, используя SemaphoreSlim
. По сути, вы просто пропускаете каждый запрос через семафор, где проверяете оставшиеся запросы текущего пользователя. Это означает, что другие пользователи должны ждать завершения этой проверки, но затем вы можете освободить семафор и фактически выполнить всю работу. Таким образом, по крайней мере, вы не блокируете других пользователей во время фактической длительной работы.
Сначала добавьте поле в любой класс, в котором вы это делаете:
private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
Затем в методе, который на самом деле вызывается:
await _semaphore.WaitAsync();
// get remaining requests for user
if (remaining > 0)
{
// decrement remaining requests for user (this must be done before this next line)
_semaphore.Release();
// now do the work
}
else
{
_semaphore.Release();
// handle user out of requests (return error, etc.)
}
По сути, это бутылочное горлышко. Чтобы выполнить соответствующую проверку и уменьшение, только один поток может проходить через семафор одновременно. Это означает, что если ваш API перегружен, запросы будут поставлены в очередь и могут занять некоторое время. Однако, поскольку это, вероятно, будет что-то вроде запроса SELECT
, за которым следует запрос UPDATE
, выпуск семафора не должен занимать так много времени. Тем не менее, если вы собираетесь идти по этому пути, вам определенно стоит провести нагрузочное тестирование и посмотреть его.