Viele Dienste bieten die Möglichkeit, mit ihnen nicht nur für normale Benutzer über ausgefeilte und optimierte grafische Oberflächen zu interagieren, sondern auch für externe Entwickler aus ihren Programmen über die API. Gleichzeitig ist es wichtig, dass die Dienste die Belastung ihrer Infrastruktur kontrollieren. In der Situation mit normalen Benutzern treten die meisten Ladeprobleme nicht auf, wenn der Anwendungscode die Anforderungen des Dienstcodes steuert, die Anforderungen an den Dienst senden (Benutzer, die versuchen, etwas in der Anwendung außerhalb des von den Entwicklern vorgeschlagenen Schnittstellen und der dokumentierten Funktionen zu tun) nicht bedacht). Bei externen Entwicklern ist der Spielraum für die Erstellung einer Last für den Dienst nur durch die Vorstellungskraft dieser sehr externen Entwickler begrenzt. Um diesen Raum ein wenig einzuschränken,Die Praxis, Beschränkungen für die Anzahl der Anforderungen pro Zeiteinheit an die Service-API einzuführen, ist weit verbreitet.

, Data Platfrom ManyChat. , , , Intercom, 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 .