Banyak layanan menyediakan kemampuan untuk berinteraksi dengannya tidak hanya untuk pengguna biasa melalui antarmuka grafis yang dipoles dan dioptimalkan, tetapi juga untuk pengembang eksternal dari program mereka melalui API. Pada saat yang sama, penting bagi layanan untuk mengontrol beban pada infrastrukturnya. Dalam situasi dengan pengguna biasa, sebagian besar masalah pemuatan tidak akan muncul karena kontrol kode aplikasi yang mengirim permintaan ke layanan oleh pengembang layanan (pengguna mencoba melakukan sesuatu dalam aplikasi di luar kerangka antarmuka yang diusulkan oleh pengembang dan kemampuan yang didokumentasikan, kami berada di artikel ini tidak dipertimbangkan). Dalam kasus pengembang eksternal, ruang lingkup untuk membuat beban pada layanan hanya dibatasi oleh imajinasi para pengembang yang sangat eksternal ini. Untuk membatasi ruang ini sedikit,Praktik menerapkan pembatasan jumlah permintaan per unit waktu ke API layanan telah tersebar luas.
, 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 .