Ограничение доступа при параллельных запросах

Категория: Решение задач | Скилл: php | Дата: 22.12.2019
Появилось 2 задачи на ограничение доступа к данным.

Задача 1. Cron каждую секунду вызывает запрос обработки redis очереди смс на отправку, сообщений может быть сколько угодно и время обработки тоже, поэтому надо чтобы эту самую очередь мог захватить только один запрос. НАДО: Если первый запрос еще работает, а следом приходит второй, отрубаем второй.

Задача 2. В высоко нагруженном сервисе тысячи одновременных запросов от одного пользователя. Пользователи частично привилегированны, и надо ограничить частоту запросов от одного пользователя на «раз в секунду».

Эта задача была решена в тестовом режиме пока юзер был всего один: в бд записывалась инфа о его последнем обращении, но потом, появились юзеры, которые взводят сотни потоков и одновременно посылают запросы.

НАДО: Если второй запрос от юзера (или одновременный) пришел ранее чем секунду назад, тогда отрубаем.

Обе задачи удалось решить при помощи php функции flock со вторым параметром LOCK_EX (эксклюзивная блокировка) | LOCK_NB (не ждать если заблокирован ранее). https://www.php.net/manual/ru/function.flock.php

Для первой задачи просто заводим файл queue_sms.lock который будет блокироваться функцией flock и если она возвращает false - завершаем запрос. Код:

function ComQueueSms()
{
	$sPathLockFile = '/queue_sms.lock';
	$hLockFile = fopen($sPathLockFile, "r+");

	if(!$hLockFile)
		$hLockFile = fopen($sPathLockFile, "w+");

	if(!flock($hLockFile, LOCK_EX | LOCK_NB))
		die("Another instance is already running");
	
	//обработка очереди смс
}

Со второй задачей пришлось повозиться, потому что требовалось еще как-то фиксировать время последнего запроса пользователя, СУБД не вариант.

Использовать файл блокировки для хранения времени мне так и не удалось, в него постоянно писался какой-то мусор (режим c+ - открытие файла на чтение и запись), а клиент торопил с решением, так как пользователи бомбили сервис. Поэтому пришлось выделить отдельный файл для хранения времени. В итоге, имеем файл блокировки и рядом лежащий файл с unixtime последнего запроса пользователя. Файлы называем так: id.lock для блокировки, id.lock2 для времени, где id это идентификатор юзера.

Упрощенно алгоритм такой: если id.lock заблокирован, значит в данный момент пользователь уже запрашивает, возвращаем false, если не заблокирован, тогда смотрим файл id.lock2 с unixtime, сравниваем с текущим временем и если разница больше либо равна секунде (в данном случае PERIOD_UNLIMITED_REQUEST) тогда обновляем дату последнего запроса и возвращаем true, иначе false.

Код:

function CheckBlockOrder($idUser)
{
	$sPathLockFile = "/$idUser.lock";
	$sPathLockFile2 = "/$idUser.lock2";
	$hLockFile = fopen($sPathLockFile, "r+");

	if(!$hLockFile)
		$hLockFile = fopen($sPathLockFile, "w+");

	if(!flock($hLockFile, LOCK_EX | LOCK_NB))
		return false;
	else
	{
		$iTimeOld = file_get_contents($sPathLockFile2);

		if(time() - $iTimeOld < PERIOD_UNLIMITED_REQUEST)
			return false;

		file_put_contents($sPathLockFile2, time());
	}

	fclose($hLockFile);

	return true;
}


php, c++, javascript, sql, hlsl, html, css