Node.jsda tashqi rate limiter masalasi

Node.jsda tashqi rate limiter masalasi

Husniddin Qurbonboyev

Redisdan amaliy foydalanish bo’yicha maqolalar seriyasini davom ettiramiz.

❗ Topshiriq: distributed enviromentda tashqi servisga bo’lgan requestlar sonini belgilangan son bilan cheklash.

Buni yaxshiroq tasavvur qilish uchun real hayotga yaqin misol keltiraman. Tizimda foydalanuvchilarimiz ro’yhatdan o’tganda ularning shaxsini tasdiqlovchi hujjat rasmini so’raymiz. Hujjat yuklangandan keyin bu hujjatni boshqa tashqi servisga tasdiqlatish uchun yuboramiz. Keling bu servisni Verifier servis deb ataylik. Verifier servisiga juda ko’p tizimlardan so’rov kelgani uchun servis asoschilari har bir tizimdan keladigan so’rovlar soniga cheklov qo’ygan. Berilgan cheklovdan o’tib ketilsa 429(Too many requests) statusli javob yoki tizim bloklanishiga sabab bo’ladi. Ya’ni bizning tizimimizdagi hujjat tasdiqlash funksiyasi ishlamay qoladi.


Rate limiter masalasi ham distributed lock, xususiy holatda esa distributed counter mexanizmi bilan hal qilinadi. Ma’lum bir vaqt davomida izlanish qilib mening talablarimga mos keladigan rate-limiter-flexible packagini tanladim.

ℹ️ Bu package ichida lua script va redis transaction(multi) metodlaridan foydalanilgan holda yaratilgan.


rate-limiter-flexible kutubxonasi ioredis va redis packagelari (bundan tashqari boshqa bir nechta databaselar) bilan ishlashi mumkin. Lekin redis bilan muammo mavjudligi uchun ioredisni tanladiim.

const { RateLimiterRedis } = require('rate-limiter-flexible');
const Redis = require('ioredis');

const redisClient = new Redis({
  host: process.env.REDIS_HOST || 'localhost',
  port: process.env.REDIS_PORT || 6379,
});
const rateLimiter = new RateLimiterRedis({
  storeClient: redisClient,
  keyPrefix: 'third_party_api_limiter',
  points: 100,
  duration: 1,
});

RateLimiterRedis konstruktori qabul qiladigan parametrlar bilan tanishtib chiqamiz:

  • storeClient — redis klienti
  • keyPrefix — umumiy counter uchun berilgan kalit so’z
  • points — jami berilgan imkoniyatlar soni
  • duration — berilgan imkoniyatning amal qilish davomiyligi


Tashqi servis uchun so’rov metodi

async function callThirdPartyAPI() {
  return axios.get('<https://api.example.com/data>');
}


Endi esa rateLimiter orqali yuqoridagi metodning chaqiruvlar sonini cheklaymiz.

async function rateLimitedAPICall(retries = 3) {
  try {
    await rateLimiter.consume('third_party_api');
    return await callThirdPartyAPI();
  } catch (error) {
    if (error instanceof Error && 'remainingPoints' in error && retries > 1) {
      const delay = Math.floor(Math.random() * 1000) + 100; // Random delay between 100-1100ms
      await new Promise(resolve => setTimeout(resolve, delay));
      return rateLimitedAPICall(retries - 1);
    }
    throw error;
  }
}

E’tibor bergan bo’lsangiz rateLimitedAPICall metodiga retries parametri ham uzatilyapti. Bu orqali biz pointlar qolmagan holatda yana qayta urinib ko’rishimiz uchun 3 tagacha imkoniyat beriladi. Agar shunda ham cheklov tugamagan bo’lsa xatolik qaytaramiz.


Misolimizning to’liq ko’rinishi:

const express = require('express');
const Redis = require('ioredis');
const { RateLimiterRedis } = require('rate-limiter-flexible');
const axios = require('axios');

const app = express();
const port = process.env.PORT || 3000;
const redisClient = new Redis({
  host: process.env.REDIS_HOST || 'localhost',
  port: process.env.REDIS_PORT || 6379,
});
const rateLimiter = new RateLimiterRedis({
  storeClient: redisClient,
  keyPrefix: 'third_party_api_limiter',
  points: 100,
  duration: 1,
});
async function callThirdPartyAPI() {
  return axios.get('<https://api.example.com/data>');
}
async function rateLimitedAPICall(retries = 3) {
  try {
    await rateLimiter.consume('third_party_api');
    return await callThirdPartyAPI();
  } catch (error) {
    if (error instanceof Error && 'remainingPoints' in error && retries > 1) {
      const delay = Math.floor(Math.random() * 1000) + 100; // Random delay between 100-1100ms
      await new Promise(resolve => setTimeout(resolve, delay));
      return rateLimitedAPICall(retries - 1);
    }
    throw error;
  }
}
app.get('/api/data', async (req, res) => {
  try {
    const result = await rateLimitedAPICall();
    res.json(result.data);
  } catch (error) {
    if (error instanceof Error && 'remainingPoints' in error) {
      res.status(429).json({
        error: 'Too Many Requests',
        retryAfter: error.msBeforeNext / 1000
      });
    } else {
      console.error('Error calling third-party API:', error);
      res.status(500).json({ error: 'Internal Server Error' });
    }
  }
});
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});
process.on('SIGINT', async () => {
  console.log('Shutting down gracefully');
  await redisClient.quit();
  process.exit(0);
});

Agar maqola sizga yoqqan bo’lsa like(clap) bosish va do’stlaringiz bilan ulashish orqali qo’llab quvvatlashingiz mumkin. Maqola bo’yicha savollar yoki shikoyatlar bo’lsa kommentariyada yozib qoldiring.

Kuzatish uchun:



Report Page