RedisとLuaでスケーラブルな分散レートリミッターを構築する – ステップバイステップ完全ガイド

この包括的なガイドでは、Redis と Lua スクリプトを組み合わせて、高トラフィック環境下におけるユーザーリクエストを制御する「分散レートリミッター」を構築する方法を解説します。
レート制限(Rate Limiting)は、不正利用の防止、トラフィックの管理、そしてリソースの保護のために、あらゆるシステムにおいて欠かせない仕組みです。Redis と Lua を活用することで、大量のリクエストを効率的にさばきながら、バックエンドサービスを安全に守れるスケーラブルなレート制限システムを実現できます。
さらに本記事では、トラフィックをシミュレートし、レート制限が実際に適用される様子を観察したり、ブロックされたリクエストのログを確認できるインタラクティブなデモも一緒に作成します。
この記事で学べること
- Redis を使ったレート制限システムの構築方法
- Redis 上で Lua スクリプトを利用し、アトミック(不可分)操作を実現する方法
- 効率的なリクエスト追跡のための Redis データ構造の理解
- 分散システムで高トラフィックに対処するためのテクニック
- Docker を使った分散レートリミッターのシミュレーションとスケーリング
前提条件
作業を始める前に、以下が準備できていることを確認してください。
- Node.js(v14 以上)
- Redis
- Docker(分散環境のシミュレーション用)
- Node.js、Redis、Lua スクリプティングに関する基礎知識
目次
- この記事で学べること
- 前提条件
- プロジェクト概要
- Step 1:プロジェクトのセットアップ
- Step 2:Redis のセットアップ
- Step 3:Redis と Lua によるレートリミッターの実装
- Step 4:Node.js API サーバーの作成
- Step 5:レートリミッターのテスト
- Step 6:レート制限メトリクスの可視化
- Step 7:Docker によるデプロイ
- まとめ:学んだこと
プロジェクト概要
このチュートリアルでは、次のことを行います。
- Redis と Lua を使って、リクエストの割り当て(クォータ)を強制するレートリミッターを構築する
- Lua スクリプトでアトミック操作を保証し、競合状態(レースコンディション)を回避する
- レート制限のためにトークンバケットアルゴリズムを実装する
- 高トラフィックをシミュレートし、レート制限の動作を可視化するインタラクティブなデモを作成する
システムアーキテクチャ
今回構築するシステムは、以下のコンポーネントで構成されます。
- API サーバー:ユーザーからの受信リクエストを処理します。
- Redis:リクエストデータを保存し、レート制限を適用します。
- Lua スクリプト:Redis への更新をアトミックに行い、正確なレート制限を保証します。
- Docker:複数インスタンスによる分散環境をシミュレートします。
Step 1:プロジェクトのセットアップ方法
まずは Node.js プロジェクトをセットアップしましょう。
mkdir distributed-rate-limiter
cd distributed-rate-limiter
npm init -y
続いて、必要な依存パッケージをインストールします。
npm install express redis dotenv
- express:軽量な Web サーバーフレームワークです。
- redis:Redis と連携するためのクライアントライブラリです。
- dotenv:環境変数を管理するためのライブラリです。
次に、以下の内容で .env ファイルを作成します。
REDIS_HOST=localhost
REDIS_PORT=6379
PORT=3000
RATE_LIMIT=5
TIME_WINDOW=60
これらの変数はそれぞれ、Redis のホスト名・ポート番号、レート制限値(許可されるリクエスト数)、そして時間枠(秒単位)を定義しています。
Step 2:Redis のセットアップ方法
コードを書き始める前に、Redis がシステムにインストールされ、稼働していることを確認してください。まだインストールしていない場合は、Docker を使えば簡単に立ち上げられます。
docker run -p 6379:6379 --name redis-rate-limiter -d redis
Step 3:Redis と Lua によるレートリミッターの実装方法
レート制限を効率的に処理するために、ここではトークンバケットアルゴリズムを採用します。このアルゴリズムの仕組みは以下のとおりです。
- 各ユーザーは「バケット」の中にトークンを持っています。
- リクエストごとにトークンを 1 つ消費します。
- トークンは設定されたレートで定期的に補充されます。
アトミック性を保証し、競合状態を避けるために、Redis 上で Lua スクリプトを使用します。Redis の Lua スクリプトはアトミックに実行されるため、処理中に他の操作が割り込むことがありません。これは、複数の API インスタンスから同時にアクセスされる分散環境では特に重要なポイントです。
レート制限用 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
- 入力:
- KEYS[1]:ユーザーのリクエスト数を表す Redis キーです。
- ARGV[1]:レート制限値(許可される最大リクエスト数)です。
- ARGV[2]:レート制限の時間枠(秒単位)です。
- 処理ロジック:
- ユーザーがレート制限に達している場合、
0(リクエスト拒否)を返します。 - 制限内であれば、リクエスト数をインクリメントします。初回リクエストの場合は、有効期限付きで新しいカウントを設定します。
- 最後に
1(リクエスト許可)を返します。
- ユーザーがレート制限に達している場合、
Step 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}`);
});
- レートリミッターミドルウェア:
- クライアントの IP アドレスを取得し、Lua スクリプトを使ってレート制限内かどうかを判定します。
- 制限を超えている場合は、HTTP ステータス
429(Too Many Requests)を返します。
- API エンドポイント:
- ルートエンドポイントにはレート制限が適用されているため、ユーザーは指定された時間枠内でしか一定回数以上アクセスできません。
Step 5:レートリミッターのテスト方法
- Redis を起動する:
docker start redis-rate-limiter - Node.js サーバーを起動する:
node server.js - リクエストをシミュレートする:
curlや Postman を使ってレートリミッターをテストします。curl https://localhost:3000- 短時間に複数のリクエストを連続送信すると、レート制限が発動する様子を確認できます。
Step 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 アドレスからのリクエストが許可され、どれがブロックされたのかをリアルタイムに把握できるようになります。
Step 7:Docker でのデプロイ方法
最後に、アプリケーションをコンテナ化して、分散環境で実行できるようにしましょう。
Dockerfile を作成します。
FROM node:14
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "server.js"]
Docker コンテナをビルドして実行します。
docker build -t rate-limiter .
docker run -p 3000:3000 rate-limiter
これで、複数のインスタンスを起動するだけでレートリミッターを水平方向にスケールできるようになりました。Redis を共有ストアとして使っているため、どのインスタンスがリクエストを受けても一貫したレート制限が適用されます。
まとめ:学んだこと
おめでとうございます!Redis と Lua スクリプトを使った分散レートリミッターの構築が完了しました。このチュートリアルを通じて、以下のスキルを身につけました。
- 分散システムにおいてユーザーリクエストを制御するレート制限の実装方法
- Redis で Lua スクリプトを用いてアトミック操作を行う方法
- リクエストの割り当て管理にトークンバケットアルゴリズムを適用する方法
- パフォーマンス最適化のためにレート制限メトリクスを監視する方法
- スケーラブルな分散環境をシミュレートするための Docker の活用方法
次のステップ
- ユーザー ID 別のレート制限の追加:IP アドレスだけでなく、ユーザーごとのレート制限にも対応できるようシステムを拡張しましょう。
- Nginx との統合:Redis バックエンドによるレート制限と組み合わせて、Nginx をリバースプロキシとして利用しましょう。
- Kubernetes でのデプロイ:Kubernetes を活用すれば、高可用性を維持しながらレートリミッターを大規模にスケールできます。
それでは、楽しいコーディングを!
無料でプログラミングを学べる freeCodeCamp のオープンソースカリキュラムは、これまでに 40,000 人以上の人々を開発者としての就職へと導いてきました。ぜひあなたも学習を始めてみてください。
-
Upstash Global 2.0発表:超高速・リージョン柔軟性・ダウンタイムゼロのスケーリングを実現
Upstashユーザーの皆様にお知らせできることを嬉しく思います。この度、グローバルデータベース(Redis)の最新版「Global 2.0」をリリースしました。新しいグローバルデータベースでは、利用するリージョンを自由に選択できます。さらに注目すべきは、稼働中のクラスターに対してダウンタイムなしでリージョンの追加・削除が行える点です。各リージョンは、低レイテンシと高可用性を実現するためにデータのコピーを保持するレプリカとして機能します。 エッジ関数向けに設計 Cloudflare WorkersやVercel Edgeなどのエッジランタイムは、ユーザーに最も近いロケーションでコードを実行する
-
RedisとApache Kafkaで実現する時系列データ処理:デバイス監視パイプラインの構築ガイド
RedisTimeSeriesは、Redisにネイティブな時系列データ構造をもたらすRedisモジュールです。従来Sorted SetsやRedis Streamsの上に構築されていた時系列ソリューションは、RedisTimeSeriesが提供する大量データの高速挿入、低レイテンシ読み取り、柔軟なクエリ言語、ダウンサンプリングなどの機能によって、大きくメリットを得られます。 一般的に時系列データ自体は(比較的)シンプルです。しかし、実際の運用では以下のような特性も考慮する必要があります。 データ速度:例えば、数千台のデバイスから毎秒数百のメトリクスが送られてくるようなケースデータ量(ビッグ