كيف تتعايش مع حدود API الخارجية على عدد الطلبات

توفر العديد من الخدمات القدرة على التفاعل معها ليس فقط للمستخدمين العاديين من خلال واجهات رسومية مصقولة ومحسنة ، ولكن أيضًا للمطورين الخارجيين من برامجهم من خلال واجهة برمجة التطبيقات. في الوقت نفسه ، من المهم للخدمات أن تتحكم في الحمل على بنيتها التحتية. في حالة المستخدمين العاديين ، لن تظهر معظم مشكلات التحميل بسبب التحكم في كود التطبيق الذي يرسل طلبات إلى الخدمة من قبل مطوري الخدمة (يحاول المستخدمون القيام بشيء ما في التطبيق خارج إطار عمل الواجهات المقترحة من قبل المطورين والإمكانيات الموثقة ، نحن في هذه المقالة لا يعتبر). في حالة المطورين الخارجيين ، يقتصر نطاق إنشاء حمل على الخدمة فقط من خلال خيال هؤلاء المطورين الخارجيين جدًا. لتقييد هذه المساحة قليلاً ،أصبحت ممارسة إدخال قيود على عدد الطلبات لكل وحدة زمنية لواجهة برمجة التطبيقات للخدمة واسعة الانتشار. 

لقد تحدثنا بالفعل عن كيفية تنفيذ هذه القيود إذا قمت أنت بنفسك بتطوير واجهة برمجة التطبيقات للخدمة ، واليوم نريد التحدث عن كيفية العيش من جانب "العميل" واستخدام واجهة برمجة التطبيقات المحدودة في عدد الطلبات بشكل ملائم.

المدخلات

اسمي يوري جافريلوف ، أعمل في فريق Data Platfrom في ManyChat. لدينا قسم تسويق في شركتنا يحب ، من بين أمور أخرى ، التواصل مع عملائنا من خلال خدمة الاتصال الداخلي, In-App -. , Intercom (, , ..). Intercom - -, . , , ( ), , -. , ML- . , Intercom.

: , , API 1000 . , Intercom, .

, API , . «» «» -, .

- , « » API, API, API. , , API .

« »

, ManyChat Redis — . « » - , API . , API, «», , - . , , «» , - Intercom, , «» .

Redis, List .

, API, consumer API. rate-limit, , , .

— «» - ( BackendQueue), «» (AnalyticsQueue). , , consumer, , .

(JSON):

{
    "method_name": "users_update", //  ,   
    "parameters": {"user_id": 123} // ,       
}

MVP consumer'a (PHP)
class APICaller
{
    private const RETRIES_LIMIT = 5;
    private const RATE_LIMIT_TIMEFRAME = 10;
    
    ...
    
    public function callMethod(array $payload): void
    {
        switch ($payload['method_name']) {
            case 'users_update':
                $this->getIntercomAPI()->users->update($payload['parameters']);
                break;
            default:
                throw new \RuntimeException('Unknown method in API call');
        }
    }

    public function actionProcessQueue(): void
    {
        while (true) {
            $payload = $this->getRedis()->rawCommand('LPOP', 'BackendQueue');
            if ($payload === null) {
                $payload = $this->getRedis()->rawCommand('LPOP', 'AnalyticsQueue');
            }

            if ($payload) {
                $retries = 0;
                $processed = false;
                while ($processed === false && $retries < self::RETRIES_LIMIT)
                {
                    try {
                        $this->callMethod(json_decode($payload));
                        $processed = true;
                    } catch (IntercomRateLimitException $e) {
                        $retries++;
                        sleep(self::RATE_LIMIT_TIMEFRAME);
                    }
                }
            } else {
                sleep(1);
            }
        }
    }
}

, , — .

:

Backend (PHP):

...
$payload = [
    'method_name' => 'users_update',
    'parameters' => ['user_id' => 123, 'registration_date' => '2020-10-01'],
];
$this->getRedis()->rawCommand('RPUSH', 'BackendQueue', json_encode($payload));
...

(Python):

...
payload = {
    'method_name': 'users_update',
    'parameters': {'user_id': 123, 'advanced_metric': 42},
}
redis_client.rpush('AnalyticsQueue', json.dumps(payload))
...

— , Intercom, . — - , API «» , rate-limit, customer'a rate-limit', , - . Redis ( ) consumer'. , , consumer', , . , , , , .

, , consumer' , . consumer' , API .

consumer'a (PHP)
class APICaller
{
    private const RETRIES_LIMIT = 5;
    private const RATE_LIMIT_TIMEFRAME = 10;
    private const INTERCOM_RATE_LIMIT = 150;
    private const INTERCOM_API_WORKERS = 5;

    ...

    public function callMethod(array $payload): void
    {
        switch ($payload['method_name']) {
            case 'users_update':
                $this->getIntercomAPI()->users->update($payload['parameters']);
                break;
            default:
                throw new \RuntimeException('Unknown method in API call');
        }
    }

    public function actionProcessQueue(): void
    {
        $currentTimeframe = $this->getCurrentTimeframe();
        $currentRequestCount = 0;
        
        while (true) {
            if ($currentTimeframe !== $this->getCurrentTimeframe()) {
                $currentTimeframe = $this->getCurrentTimeframe();
                $currentRequestCount = 0;
            } elseif ($currentRequestCount > $this->getProcessRateLimit()) {
                usleep(100 * 1000);
                continue;
            }
            
            $payload = $this->getRedis()->rawCommand('LPOP', 'BackendQueue');
            if ($payload === null) {
                $payload = $this->getRedis()->rawCommand('LPOP', 'AnalyticsQueue');
            }

            if ($payload) {
                $retries = 0;
                $processed = false;
                while ($processed === false && $retries < self::RETRIES_LIMIT)
                {
                    try {
                        $this->callMethod(json_decode($payload));
                        $processed = true;
                    } catch (IntercomRateLimitException $e) {
                        $retries++;
                        sleep(self::RATE_LIMIT_TIMEFRAME);
                    }
                }
            } else {
                sleep(1);
            }
        }
    }

    private function getProcessRateLimit(): int
    {
        return (int) floor(self::INTERCOM_RATE_LIMIT / self::INTERCOM_API_WORKERS);
    }

    private function getCurrentTimeframe(): int
    {
        return (int) ceil(time() / self::RATE_LIMIT_TIMEFRAME);
    }
}

API

- API, . API . — . , , callback'e, consumer' . callback', , .

, , , , .

, , //?

, API , rate-limit, . , . , , , , , .

, ,   .

API, , , API , API .

, - . , API .




All Articles