Ограничение на количество запросов и повторный запрос

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
yura1976
Сообщения: 134
Зарегистрирован: 2012.08.06, 13:24

Ограничение на количество запросов и повторный запрос

Сообщение yura1976 »

Делаю интеграцию сайта с сервисом вебинаров webinar.ru. У них ограничение - до 3 запросов / сек. Т.е., если с сайта в течение какой-то одной секунды подключится 4-й пользователь, то вебинар.ру в результате этого подключения вернет ошибку. Посещаемость сайта пока не большая, но планируется его очень активное продвижение. Поэтому не исключаю вероятности того что в дальнейшем с ростом посещаемости может возникнуть ситуация, когда может возникнуть описанная ситуация. Как лучше избавиться от подобной проблемы? Подскажите или хотя бы идею, или, еще лучше, какое-то решение на yii2, если, конечно, такое решение существует.
Были мысли использовать в методе, в котором осуществляется обращение к сервису вебинаров, php-функцию sleep(). Т.е., если сервис вебинаров вернул error, то через, например, 1 сек обратиться к сервису вебинаров повторно. Но очень сомневаюсь, что это хорошее решение.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Ограничение на количество запросов и повторный запрос

Сообщение ElisDN »

Если это запросы на получение данных (чтение), то можно добавить кэширование ответа.

Если это запросы на выполнение операций (запись), то либо делать sleep при обращении, либо все задачи скидывать в очередь и выполнять там последовательно в одном обработчике со sleep.
yura1976
Сообщения: 134
Зарегистрирован: 2012.08.06, 13:24

Re: Ограничение на количество запросов и повторный запрос

Сообщение yura1976 »

Дмитрий, спасибо!
ElisDN писал(а): 2021.06.05, 20:44 Если это запросы на получение данных (чтение), то можно добавить кэширование ответа.
Да, запросы на получение списка вебинаров и др. запросы на чтение кэшируются.
Если это запросы на выполнение операций (запись), то либо делать sleep при обращении, либо все задачи скидывать в очередь и выполнять там последовательно в одном обработчике со sleep.
Запросы "Запись на вебинар". Буду пробовать sleep
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Ограничение на количество запросов и повторный запрос

Сообщение SiZE »

yura1976 писал(а): 2021.06.06, 19:01 Запросы "Запись на вебинар". Буду пробовать sleep
sleep без очереди не решит проблему, т.к. конкурентные запросы никуда не денутся (если я конечно правильно понял), т.е. в любом случае очередь нужна.
german.igortcev
Сообщения: 251
Зарегистрирован: 2014.08.18, 14:01

Re: Ограничение на количество запросов и повторный запрос

Сообщение german.igortcev »

Код: Выделить всё

class RequestLimiter extends Component
{
    private Connection $redis;

    public int $requestsAllowedPerSecond = 1;
    public int $requestsAllowedPerMinute = 60;
    public int $requestsAllowedPerDay = 1000;

    public string $storageKeyPerSecond = 'executed_per_second';
    public string $storageKeyPerMinute = 'executed_per_minute';
    public string $storageKeyPerDay = 'executed_per_day';

    private int $ttl = 0;

    public function __construct( $config = [])
    {
        parent::__construct($config);

        $this->redis = \Yii::$app->redis;
    }

    /**
     * @return int
     */
    public function getTtl(): int
    {
        return $this->ttl;
    }

    /**
     * @param int $ttl
     */
    public function setTtl(int $ttl): void
    {
        $this->ttl = $ttl;
    }

    /**
     * @return int
     */
    public function getRequestsAllowedPerSecond(): int
    {
        return $this->requestsAllowedPerSecond;
    }

    /**
     * @param int $requestsAllowedPerSecond
     */
    public function setRequestsAllowedPerSecond(int $requestsAllowedPerSecond): void
    {
        $this->requestsAllowedPerSecond = $requestsAllowedPerSecond;
    }

    /**
     * @return int
     */
    public function getRequestsAllowedPerMinute(): int
    {
        return $this->requestsAllowedPerMinute;
    }

    /**
     * @param int $requestsAllowedPerMinute
     */
    public function setRequestsAllowedPerMinute(int $requestsAllowedPerMinute): void
    {
        $this->requestsAllowedPerMinute = $requestsAllowedPerMinute;
    }

    /**
     * @return int
     */
    public function getRequestsAllowedPerDay(): int
    {
        return $this->requestsAllowedPerDay;
    }

    /**
     * @param int $requestsAllowedPerDay
     */
    public function setRequestsAllowedPerDay(int $requestsAllowedPerDay): void
    {
        $this->requestsAllowedPerDay = $requestsAllowedPerDay;
    }

    /**
     * @return bool
     * @throws \Exception
     */
    public function canExecute(): bool
    {
        return $this->checkSecondLimit() && $this->checkMinuteLimit() && $this->checkDayLimit();
    }

    /**
     * @return bool
     */
    public function checkSecondLimit(): bool
    {

        if (!$this->redis->exists($this->storageKeyPerSecond)) {
            $this->redis->set($this->storageKeyPerSecond, 1);
            $this->redis->expire($this->storageKeyPerSecond, 1);
            $this->setTtl(0);

        } else {

            $this->redis->incr($this->storageKeyPerSecond);

            /** Delete key if key exist on redis but lost expire date for key */
            if ($this->redis->ttl($this->storageKeyPerSecond) == '-1') {
                $this->redis->del($this->storageKeyPerSecond);
            }

            if ($this->redis->get($this->storageKeyPerSecond) >= $this->requestsAllowedPerSecond) {
                $this->setTtl((int)$this->storageKeyPerSecond);
                return false;
            }
        }

        return true;
    }

    /**
     * @return bool
     */
    public function checkMinuteLimit(): bool
    {
        if (!$this->redis->exists($this->storageKeyPerMinute)) {
            $this->redis->set($this->storageKeyPerMinute, 1);
            $this->redis->expire($this->storageKeyPerMinute, 60);
            $this->setTtl(0);
        } else {

            $this->redis->incr($this->storageKeyPerMinute);

            /** Delete key if key exist on redis but lost expire date for key */
            if ($this->redis->ttl($this->storageKeyPerMinute) == '-1') {
                $this->redis->del($this->storageKeyPerMinute);
            }

            if ($this->redis->get($this->storageKeyPerMinute) >= $this->requestsAllowedPerMinute) {
                $this->setTtl((int)$this->redis->ttl($this->storageKeyPerMinute));
                return false;
            }

        }
        return true;
    }

    /**
     * @return bool
     * @throws \Exception
     */
    public function checkDayLimit(): bool
    {
        $datetime = new \DateTime('now', new \DateTimeZone('PST'));
        $datetime->setTime(23, 59, 59);

        if (!$this->redis->exists($this->storageKeyPerDay)) {
            $this->redis->set($this->storageKeyPerDay, 1);
            $this->redis->expireat($this->storageKeyPerDay, $datetime->getTimestamp());
            $this->setTtl(0);
        } else {
            $this->redis->incr($this->storageKeyPerDay);
            if ($this->redis->get($this->storageKeyPerDay) >= $this->requestsAllowedPerDay) {
                $this->setTtl((int)$this->redis->ttl($this->storageKeyPerDay));

                return false;
            }
        }

        return true;
    }
}

Код: Выделить всё

	!$requestLimiter->canExecute()
	$requestLimiter->getTtl() - delay in seconds when you can execute
german.igortcev
Сообщения: 251
Зарегистрирован: 2014.08.18, 14:01

Re: Ограничение на количество запросов и повторный запрос

Сообщение german.igortcev »

Я использую везде. Если много задач то сервер очередей. Если ограничение частоты запросов то бросаю обратно в очередь с delay = $limiter->getTtl(). Если ограничение по пользователям еще то используйте ключи доп.

Если используете guzzle то там есть delay в опциях
Ответить