Computer >> 컴퓨터 >  >> 프로그래밍 >> Redis

Redis와 Lua로 확장 가능한 분산 속도 제한기(Rate Limiter) 구축하기 – 단계별 완벽 가이드

Redis와 Lua로 확장 가능한 분산 속도 제한기(Rate Limiter) 구축하기 – 단계별 완벽 가이드

이 가이드에서는 Redis와 Lua 스크립팅을 활용해 트래픽이 많은 환경에서 사용자 요청을 효과적으로 제어하는 분산 속도 제한기(Distributed Rate Limiter)를 직접 구축해 보겠습니다.

속도 제한(Rate Limiting)은 시스템 남용을 방지하고, 트래픽을 안정적으로 관리하며, 소중한 서버 리소스를 보호하는 데 필수적인 기술입니다. Redis와 Lua를 조합하면 수많은 동시 요청을 처리하면서도 백엔드 서비스를 안전하게 지킬 수 있는 효율적이고 확장 가능한 속도 제한 시스템을 만들 수 있습니다.

또한 실제로 트래픽을 시뮬레이션하고, 속도 제한이 적용되는 과정을 눈으로 확인하며, 차단된 요청 로그를 살펴볼 수 있는 인터랙티브 데모까지 함께 다룹니다.

이 글에서 배울 내용

  • Redis를 활용한 속도 제한 시스템 구축 방법

  • Lua 스크립트로 Redis에서 원자적(Atomic) 연산을 구현하는 방법

  • 효율적인 요청 추적을 위한 Redis 데이터 구조의 이해

  • 분산 시스템에서 대량 트래픽을 처리하는 핵심 기법

  • Docker로 분산 속도 제한기를 시뮬레이션하고 확장하는 방법

사전 준비 사항

시작하기 전에 아래 항목들이 준비되어 있는지 확인하세요.

  • Node.js (v14 이상)

  • Redis

  • Docker (분산 환경 시뮬레이션용)

  • Node.js, Redis, Lua 스크립팅에 대한 기본적인 이해

프로젝트 개요

이 튜토리얼에서 우리는 다음을 수행합니다.

  1. Redis와 Lua를 사용해 요청 할당량(Request Quota)을 강제하는 속도 제한기를 구축합니다.

  2. Lua 스크립트로 모든 연산을 원자적으로 처리해 경쟁 상태(Race Condition)를 방지합니다.

  3. 요청 흐름 제어를 위한 토큰 버킷(Token Bucket) 알고리즘을 적용합니다.

  4. 높은 트래픽을 시뮬레이션하고 속도 제한이 작동하는 모습을 시각화하는 인터랙티브 데모를 만듭니다.

시스템 아키텍처

구축할 시스템은 다음 네 가지 구성 요소로 이루어집니다.

  1. API 서버: 들어오는 사용자 요청을 처리합니다.

  2. Redis: 요청 데이터를 저장하고 속도 제한을 강제합니다.

  3. Lua 스크립트: Redis에 대한 업데이트가 원자적으로 수행되도록 보장합니다.

  4. Docker: 여러 인스턴스로 분산 환경을 시뮬레이션합니다.

1단계: 프로젝트 설정하기

먼저 Node.js 프로젝트를 생성합니다.

mkdir distributed-rate-limiter
cd distributed-rate-limiter
npm init -y

다음으로 필요한 의존성을 설치합니다.

npm install express redis dotenv
  • express: 가볍고 빠른 웹 서버 프레임워크입니다.

  • redis: Redis와 통신하기 위한 클라이언트 라이브러리입니다.

  • dotenv: 환경 변수를 손쉽게 관리해 줍니다.

아래 내용으로 .env 파일을 생성합니다.

REDIS_HOST=localhost
REDIS_PORT=6379
PORT=3000
RATE_LIMIT=5
TIME_WINDOW=60

각 변수는 Redis 호스트와 포트, 허용되는 최대 요청 수(RATE_LIMIT), 그리고 시간 윈도우(TIME_WINDOW, 초 단위)를 정의합니다.

2단계: Redis 설정하기

코드를 작성하기 전에 Redis가 설치되어 실행 중인지 확인하세요. Redis가 없다면 Docker로 간편하게 띄울 수 있습니다.

docker run -p 6379:6379 --name redis-rate-limiter -d redis

3단계: Redis와 Lua로 속도 제한기 구현하기

효율적인 속도 제한을 위해 토큰 버킷 알고리즘을 사용합니다. 이 알고리즘은 다음과 같이 동작합니다.

  1. 각 사용자는 자신만의 "토큰 버킷"을 가집니다.

  2. 요청이 발생할 때마다 토큰 하나가 소모됩니다.

  3. 토큰은 설정된 주기마다 일정 비율로 재충전됩니다.

원자성을 보장하고 경쟁 상태를 피하기 위해 Lua 스크립트를 사용합니다. Redis에서 Lua 스크립트는 원자적으로 실행되므로, 실행 도중 다른 연산에 의해 끼어들 일이 없습니다.

속도 제한용 Lua 스크립트 작성하기

rate_limiter.lua 파일을 생성하고 아래 코드를 작성합니다.

local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])

local current = redis.call("get", key)

if current and tonumber(current) >= limit then
  return 0
else
  if current then
    redis.call("incr", key)
  else
    redis.call("set", key, 1, "EX", window)
  end
  return 1
end
  1. 입력 값:

    • KEYS[1]: 사용자별 요청 횟수를 나타내는 Redis 키입니다.

    • ARGV[1]: 허용되는 최대 요청 수(속도 제한)입니다.

    • ARGV[2]: 속도 제한이 적용되는 시간 윈도우(초 단위)입니다.

  2. 동작 로직:

    • 사용자가 이미 제한 횟수에 도달했다면 0(요청 차단)을 반환합니다.

    • 제한 범위 내라면 요청 횟수를 증가시키거나, 첫 요청이라면 만료 시간과 함께 새 카운트를 설정합니다.

    • 1(요청 허용)을 반환합니다.

4단계: Node.js API 서버 만들기

server.js 파일을 생성하고 아래 코드를 작성합니다.

require('dotenv').config();
const express = require('express');
const redis = require('redis');
const fs = require('fs');
const path = require('path');

const app = express();

const client = redis.createClient({
  host: process.env.REDIS_HOST,
  port: process.env.REDIS_PORT
});

const rateLimitScript = fs.readFileSync(path.join(__dirname, 'rate_limiter.lua'), 'utf8');
const RATE_LIMIT = parseInt(process.env.RATE_LIMIT);
const TIME_WINDOW = parseInt(process.env.TIME_WINDOW);

// 속도 제한 미들웨어
async function rateLimiter(req, res, next) {
  const ip = req.ip;
  try {
    const allowed = await client.eval(rateLimitScript, 1, ip, RATE_LIMIT, TIME_WINDOW);
    if (allowed === 1) {
      next();
    } else {
      res.status(429).json({ message: 'Too many requests. Please try again later.' });
    }
  } catch (err) {
    console.error('Error in rate limiter:', err);
    res.status(500).json({ message: 'Internal server error' });
  }
}

app.use(rateLimiter);

app.get('/', (req, res) => {
  res.send('Welcome to the Rate Limited API!');
});

const PORT = process.env.PORT;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
  1. 속도 제한 미들웨어:

    • 클라이언트 IP 주소를 가져온 뒤, Lua 스크립트를 통해 제한 범위 내인지 확인합니다.

    • 사용자가 한도를 초과하면 HTTP 429(Too Many Requests) 응답을 반환합니다.

  2. API 엔드포인트:

    • 루트 경로 역시 속도 제한이 적용되므로, 사용자는 지정된 시간 윈도우 내에서 제한된 횟수만 접근할 수 있습니다.

5단계: 속도 제한기 테스트하기

  1. Redis 시작:

    docker start redis-rate-limiter
    
  2. Node.js 서버 실행:

    node server.js
    
  3. 요청 시뮬레이션:

    • curl 또는 Postman으로 속도 제한기를 테스트합니다.

      curl https://localhost:3000
      
    • 여러 요청을 빠르게 연속 전송하면 속도 제한이 실제로 작동하는 모습을 확인할 수 있습니다.

6단계: 속도 제한 지표 시각화하기

허용/차단된 요청 같은 속도 제한 지표를 모니터링하려면 미들웨어에 로깅을 추가하면 됩니다. server.js의 미들웨어를 아래와 같이 수정합니다.

async function rateLimiter(req, res, next) {
  const ip = req.ip;
  try {
    const allowed = await client.eval(rateLimitScript, 1, ip, RATE_LIMIT, TIME_WINDOW);
    if (allowed === 1) {
      console.log(`Allowed request from ${ip}`);
      next();
    } else {
      console.log(`Blocked request from ${ip}`);
      res.status(429).json({ message: 'Too many requests. Please try again later.' });
    }
  } catch (err) {
    console.error('Error in rate limiter:', err);
    res.status(500).json({ message: 'Internal server error' });
  }
}

이렇게 하면 어떤 IP의 요청이 허용되었고 어떤 요청이 차단되었는지 콘솔에서 실시간으로 확인할 수 있습니다.

7단계: Docker로 배포하기

애플리케이션을 컨테이너화하여 분산 환경에서 실행해 보겠습니다.

Dockerfile을 생성합니다.

FROM node:14
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "server.js"]

이미지를 빌드하고 컨테이너를 실행합니다.

docker build -t rate-limiter .
docker run -p 3000:3000 rate-limiter

이제 여러 인스턴스를 실행해 속도 제한기를 손쉽게 수평 확장(Scale-out)할 수 있습니다.

마무리: 배운 내용 정리

축하합니다! Redis와 Lua 스크립트를 활용한 분산 속도 제한기를 성공적으로 구축했습니다. 이 튜토리얼을 통해 다음을 학습했습니다.

  1. 분산 시스템에서 사용자 요청을 제어하는 속도 제한 구현 방법

  2. Redis에서 Lua 스크립트로 원자적 연산을 수행하는 방법

  3. 토큰 버킷 알고리즘으로 요청 할당량을 관리하는 방법

  4. 속도 제한 지표를 모니터링해 성능을 최적화하는 방법

  5. Docker로 확장 가능한 분산 환경을 시뮬레이션하는 방법

다음 단계

  1. 사용자 ID 기반 속도 제한 추가: IP가 아닌 사용자 단위로 제한을 적용하도록 시스템을 확장해 보세요.

  2. Nginx 연동: Nginx를 리버스 프록시로 사용하고 Redis 기반 속도 제한과 결합해 보세요.

  3. Kubernetes 배포: Kubernetes를 활용해 고가용성을 갖춘 속도 제한기로 확장해 보세요.

즐거운 코딩 되세요!

freeCodeCamp의 오픈소스 커리큘럼은 무료로 코딩을 배울 수 있는 기회를 제공하며, 이미 4만 명 이상의 사람들이 개발자로 취업하는 데 성공했습니다. 지금 바로 시작해 보세요!