Ответ
На практике я реализовывал это через механизм эксклюзивной блокировки на уровне приложения или базы данных. Основная задача — гарантировать, что в один момент времени с API работает только одна сессия для заданного пользователя или ресурса.
1. Блокировка через Redis (рекомендуемый подход):
Используем атомарную операцию SET с флагами NX (только если ключ не существует) и EX (время жизни).
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$lockKey = 'api:user:lock:' . $userId;
$lockTimeout = 30; // секунд
// Пытаемся установить блокировку
$isLockAcquired = $redis->set($lockKey, session_id(), ['nx', 'ex' => $lockTimeout]);
if (!$isLockAcquired) {
// Проверяем, наша ли это блокировка (например, при повторном запросе)
$currentLockOwner = $redis->get($lockKey);
if ($currentLockOwner !== session_id()) {
http_response_code(429);
header('Retry-After: ' . $lockTimeout);
echo json_encode(['error' => 'API is currently locked by another user session']);
exit;
}
// Если блокировка наша — обновляем TTL
$redis->expire($lockKey, $lockTimeout);
}
// Критическая секция API...
// По завершении освобождаем блокировку
$redis->del($lockKey);
2. Блокировка на уровне базы данных (MySQL):
Используем таблицу api_locks с проверкой временной метки.
CREATE TABLE api_locks (
user_id INT PRIMARY KEY,
lock_token VARCHAR(64),
locked_until TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
// Попытка захвата блокировки
$query = "INSERT INTO api_locks (user_id, lock_token, locked_until)
VALUES (?, ?, NOW() + INTERVAL 30 SECOND)
ON DUPLICATE KEY UPDATE
lock_token = IF(locked_until < NOW(), VALUES(lock_token), lock_token),
locked_until = IF(locked_until < NOW(), VALUES(locked_until), locked_until)";
// Проверяем, изменилась ли запись и принадлежит ли lock_token текущей сессии
Ключевые моменты:
- Всегда устанавливайте TTL (Time-To-Live) для блокировки, чтобы избежать вечной блокировки при сбоях.
- Используйте уникальный токен (например,
session_id()) для идентификации владельца блокировки. - Для распределенных систем предпочтительнее Redis или специализированные решения (ZooKeeper, etcd).