토큰 버킷 알고리즘을 사용하여 NodeJS에서 속도 제한기 빌드

5924 단어 nodejavascript
문제...

최근에 저는 "x분 내에 x개의 요청을 허용하는 속도 제한기를 설정합니다. x분 동안 제한을 초과하는 IP 주소의 요청을 차단합니다"와 같은 작업을 선택했습니다. 속도 제한에 대해 처음 듣는다면 이 작업을 받았을 때 내가 어떻게 느꼈는지 정확히 알 것입니다. 혼란스러운. 다행히 상담할 수 있는 새로운 가장 친한 친구가 생겼습니다. Google. 몇 가지 조사를 한 후 타사 라이브러리를 사용하여 솔루션을 구현할 수 있었습니다. 그러나 조사하는 동안 비율 제한기를 구축하기 위한 다양한 알고리즘 목록article을 발견했습니다. 특정 알고리즘이 매우 흥미로워 보였고 시도해야 했습니다.

이 알고리즘은 토큰 버킷 알고리즘으로 알려져 있습니다. 앞서 언급한 기사 및 Wikipedia.)에 따르면 이 알고리즘은 트래픽을 형성하거나 감시하기 위해 패킷 교환 및 통신 네트워크에서 사용됩니다. 데이터 전송이 정의된 대역폭 및 버스트 제한을 준수하는지 확인하는 데 사용됩니다. 알고리즘의 메커니즘과 사용법은 매우 강력하지만, 우리의 경우에는 알고리즘을 가장 간단한 형태로 차용할 것입니다. 단일 레코드(버킷)를 사용하여 IP 주소에서 API로 보낼 수 있는 요청 제한을 결정합니다.

이 구현을 위해 원격 Redis 연결을 사용하여 요청 로그를 저장합니다. 하나를 만들 수 있습니다here. 또한 Moment를 사용하여 요청 타임스탬프를 추적할 것입니다.

필수 패키지 설치

npm install redis moment --save


다음은 rate limiter 미들웨어 rateLimiter.js 생성입니다. 아래 코드는 속도 제한 기능을 제공하는 미들웨어입니다.

import moment from 'moment';
import { createClient } from 'redis';

const redisClient = createClient({
  url: `redis://${process.env.REDIS_USERNAME}:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`,
});

redisClient.connect()

const WINDOW_SIZE_IN_MINUTES = process.env.WINDOW_SIZE_IN_MINUTES || 1;
const MAX_WINDOW_REQUEST_COUNT = process.env.MAX_WINDOW_REQUEST_COUNT || 100;
const REQUEST_BLOCK_DURATION_IN_MINUTES = process.env.REQUEST_BLOCK_DURATION_IN_MINUTES || 15;

const rateLimiter = async (req, res, next) => {
  const currentRequestTime = moment();
  const requestIpAddress = req.ip;
  const existingRequestLog = await redisClient.get(requestIpAddress);

  const newRequestLog = {
    firstRequestTimeStamp: currentRequestTime.unix(),
    requestCount: 1,
    ipAddressBlocked: false,
  };

  if (existingRequestLog === null) {
    await redisClient.set(requestIpAddress, JSON.stringify(newRequestLog));
    await next();
  } else if (existingRequestLog) {
    let requestLog = JSON.parse(existingRequestLog);

    if (requestLog.ipAddressBlocked) {
      // calculate time stamp for elapsed block
      let elapsedBlockTimeStamp = currentRequestTime
        .subtract(REQUEST_BLOCK_DURATION_IN_MINUTES, 'minutes')
        .unix();

      // if block duration has not passed, block request. if passed, reset request log
      if (requestLog.ipBlockTimeStamp > elapsedBlockTimeStamp) {
        return res.status(429).json({
          status: false,
          message: 'Request Blocked. Please try again later,
        });
      } else {
        await redisClient.set(requestIpAddress, JSON.stringify(newRequestLog));
        await next();
      }
    } else if (!requestLog.ipAddressBlocked) {
      // check if the request window has elapsed
      let windowStartTimestamp = moment().subtract(WINDOW_SIZE_IN_MINUTES, 'minutes').unix();

      if (requestLog.firstRequestTimeStamp < windowStartTimestamp) {
        await redisClient.set(requestIpAddress, JSON.stringify(newRequestLog));
        await next();
      } else if (
        requestLog.firstRequestTimeStamp >= windowStartTimestamp &&
        requestLog.requestCount >= MAX_WINDOW_REQUEST_COUNT
      ) {
        let blockedRequestLog = {
          ...requestLog,
          ipAddressBlocked: true,
          ipBlockTimeStamp: currentRequestTime.unix(),
        };

        await redisClient.set(requestIpAddress, JSON.stringify(blockedRequestLog));

        return res.status(429).json({
          status: false,
          message: 'Maximum attempts exceeded. Please try later.',
        });
      } else if (
        requestLog.firstRequestTimeStamp >= windowStartTimestamp &&
        requestLog.requestCount < MAX_WINDOW_REQUEST_COUNT
      ) {
        requestLog.requestCount++;

        await redisClient.set(requestIpAddress, JSON.stringify(requestLog));
        await next();
      }
    }
  }
};

module.exports = rateLimiter;



우리가 가지고 있는 것을 단계별로 살펴보겠습니다.

먼저 이전에 설치된 패키지를 가져오고 redis 데이터베이스에 대한 연결을 시작했습니다.

다음으로 속도 제한 미들웨어에 대한 제약 조건을 정의했습니다. WINDOW_SIZE_IN_MINUTES는 요청 제한에 대한 창 크기입니다. MAX_WINDOW_REQUEST_COUNT는 사용자가 차단되기 전에 창 내에서 만들 수 있는 요청 수입니다. REQUEST_BLOCK_DURATION_IN_MINUTES는 사용자가 창 내의 요청 수를 초과할 때 시행되는 차단 기간입니다.

다음으로 속도 제한 논리를 구현합니다. 가장 먼저 할 일은 현재 요청의 타임스탬프currentRequestTime를 저장하는 것입니다. 다음으로 사용자의 IP 주소existingRequestLog를 사용하여 Redis에서 사용자의 레코드req.ip를 가져옵니다. 그런 다음 변수를 초기화합니다newRequestLog. 이름에서 알 수 있듯이 이것은 속도 제한기의 다른 지점에 저장할 수 있는 새로운 요청 로그입니다.

사용자 요청 로그를 가져오는 쿼리에서 null가 반환되면 사용자가 API에 요청한 적이 없음을 의미합니다. 그런 다음 이 사용자에 대한 새 로그를 Redis에 저장하고 사용자를 이동합니다.

쿼리에서 레코드를 찾으면 해당 콘텐츠에 액세스할 수 있도록 반환된 값을 구문 분석합니다. 다음으로 이 구문 분석된 값requestLog을 확인하여 사용자가 차단되었는지requestLog.ipAddressBlocked 확인합니다. 차단된 경우 경과된 블록elapsedBlockTimeStamp의 타임스탬프, 즉 사용자의 차단이 시작되어야 경과된 시간을 계산합니다. 차단이 여전히 활성 상태인 경우requestLog.ipBlockTimeStamp > elapsedBlockTimeStamp 사용자에게 적절한 응답을 보냅니다. 그렇지 않으면 사용자의 요청 로그를 newRequestLog로 재설정하고 사용자를 이동합니다.

사용자가 차단되지 않은 경우!requestLog.ipAddressBlocked 사용자의 요청 기간이 경과했는지 확인합니다. 사용자의 첫 번째 요청이 현재 창 시작 시간 이전에 전송된 경우 사용자 로그를 재설정하고 새 요청 창을 시작하고 사용자를 이동합니다.

사용자의 첫 번째 요청이 현재 기간 내에 있고 사용자의 요청 수가 최대인 경우 사용자를 차단하고 이 차단을 반영하도록 사용자 로그를 업데이트한 다음 적절한 응답을 보냅니다. 그렇지 않으면 현재 창이 여전히 유효하고 사용자의 요청 수가 최대값보다 적으면 요청 수를 늘리고 사용자의 IP 로그를 업데이트하고 사용자를 이동합니다.

그런 다음 애플리케이션의 다른 부분에서 사용하기 위해 미들웨어를 내보냅니다.

좋은 웹페이지 즐겨찾기