redisでどのようにして分散レイトリミットを実現するか
Redisは、トークンバケットアルゴリズムを使って分散レート制限を実装できます。トークンバケットアルゴリズムは、一般的なレート制限アルゴリズムの1つで、固定容量のトークンバケットを保持し、毎秒特定数のトークンをバケットに入れます。リクエストが到着すると、トークンバケットに十分なトークンがあれば、リクエストが許可され、1つのトークンが消費されます。トークンバケットに十分なトークンがなければ、リクエストは拒否されます。
Redisを使用した分散型レート制限の導入手順:
- Redis の Lua スクリプトを使用して、トークンバケットアルゴリズムのレイトリミッターを作成します。Lua スクリプトは Redis サーバー上で実行可能で、アトミシティが保証されます。以下は、トークンバケットアルゴリズムによるシンプルな Lua スクリプトの例です。
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local tokens_key = key .. ":tokens"
local timestamp_key = key .. ":timestamp"
local tokens = tonumber(redis.call("get", tokens_key))
if not tokens then
tokens = capacity
end
local last_refreshed = tonumber(redis.call("get", timestamp_key))
if not last_refreshed then
last_refreshed = now
end
local delta = math.max(0, now - last_refreshed)
local filled_tokens = math.min(capacity, tokens + delta * rate)
local allowed = filled_tokens >= 1
local new_tokens = filled_tokens
local new_timestamp = last_refreshed
if allowed then
new_tokens = filled_tokens - 1
new_timestamp = now
end
redis.call("set", tokens_key, new_tokens)
redis.call("set", timestamp_key, new_timestamp)
return allowed
- コード内でそのLuaスクリプトを呼び出す。リミッターの一意識別子、リミッターの容量、リミッターのレート、現在のタイムスタンプをパラメーターとして受け取り、リミットの結果を取得する。
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class RedisRateLimiter {
private JedisPool jedisPool;
public RedisRateLimiter(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
public boolean allowRequest(String key, int capacity, int rate) {
try (Jedis jedis = jedisPool.getResource()) {
long now = System.currentTimeMillis();
Object result = jedis.eval(
"local key = KEYS[1]\n" +
"local capacity = tonumber(ARGV[1])\n" +
"local rate = tonumber(ARGV[2])\n" +
"local now = tonumber(ARGV[3])\n" +
"\n" +
"local tokens_key = key .. \":tokens\"\n" +
"local timestamp_key = key .. \":timestamp\"\n" +
"\n" +
"local tokens = tonumber(redis.call(\"get\", tokens_key))\n" +
"if not tokens then\n" +
" tokens = capacity\n" +
"end\n" +
"\n" +
"local last_refreshed = tonumber(redis.call(\"get\", timestamp_key))\n" +
"if not last_refreshed then\n" +
" last_refreshed = now\n" +
"end\n" +
"\n" +
"local delta = math.max(0, now - last_refreshed)\n" +
"local filled_tokens = math.min(capacity, tokens + delta * rate)\n" +
"local allowed = filled_tokens >= 1\n" +
"\n" +
"local new_tokens = filled_tokens\n" +
"local new_timestamp = last_refreshed\n" +
"if allowed then\n" +
" new_tokens = filled_tokens - 1\n" +
" new_timestamp = now\n" +
"end\n" +
"\n" +
"redis.call(\"set\", tokens_key, new_tokens)\n" +
"redis.call(\"set\", timestamp_key, new_timestamp)\n" +
"\n" +
"return allowed",
1,
key,
Integer.toString(capacity),
Integer.toString(rate),
Long.toString(now)
);
return (Boolean) result;
}
}
}
- RedisRateLimiterのallowRequestメソッドを呼び出すことによって、制限が必要な場所で