Redis의 Rate Limit Pattern 3가지
여기서 예를 들어, 각 IP 주소(이하
ip
에 Rate Limit이 적용된다고 가정합니다.※ 전체 모드는 Redis Cluster를 지원합니다.
1. Single Counter 모드
레디스의 키 디자인.
※
%
포위된 부분은 변수입니다.(String型) ratelimit:%ip%
ex)
ratelimit:127.0.0.1
위조 응용 프로그램 코드
script = """
local current
current = redis.call("incr",KEYS[1])
if tonumber(current) == 1 then
redis.call("expire",KEYS[1],1)
end
"""
ip = IP_ADDRESS()
keyname = "ratelimit:"+ip
current = redis.GET(keyname)
IF current != NULL AND current > 10 THEN
ERROR "too many requests per second"
ELSE
redis.EVAL(script,1,keyname)
END
Single Counter Rate Limit 설치LuaScript를 실행하는 EVAL에서 INCR & EXPIRE를 실현합니다.
LuaScript화하지 않으면 EXPIRE가 실행되기 전에 프로그램이 죽은 후에 Redis의 키가 계속 저장됩니다.
TTL을 1초부터 변경하여 원하는 시간대에 Rate Limit을 실행할 수 있습니다.
2. RateLimit에 임의의 메타 정보를 보관하는 모드
레디스의 키 디자인.
※
%
포위된 부분은 변수입니다.(List型) ratelimit:%ip%
ex)
ratelimit:127.0.0.1
위조 응용 프로그램 코드
script = """
redis.call("rpush",KEYS[1],VALUE[1])
redis.call("expire",KEYS[1],1)
"""
userid = USER_ID()
ip = IP_ADDRESS()
keyname = "ratelimit:"+ip
current = redis.LLEN(keyname)
IF current != NULL AND current > 10 THEN
ERROR "too many requests per second"
ELSE
IF redis.EXISTS(keyname) == FALSE
redis.EVAL(script,1,keyname,userid)
ELSE
redis.RPUSHX(keyname,userId)
END
END
List형 목록 길이를 사용하여 명령을 받은 LLEN이 O(1)인 경우.List의value는 Rate Limit에 이용되지 않으므로 사용자 ID 등 임의의 메타 정보를 포함할 수 있습니다.
따라서 이번 예에서 IP 주소가 Rate Limit에 도달했을 때 List의value를 보면 어떤 사용자의 방문 원인이 될까요?바로 알 수 있다.
또한 이 코드는 경기 상태
EXISTS
에서 가짜가 되며 여러 클라이언트에서 의도치 않게 LuaScript를 실행할 수 있습니다.하지만 Rate Limit의 컨텍스트에는 문제가 없습니다. ※헛되이 EXPIRE를 재설정했을 뿐이다.
단점은 메타데이터의 크기와 접근 빈도와 비례하는 메모리를 소모하는 것이다.
3. 슬라이딩 윈도 모드 집계 가능
레디스의 키 디자인.
※
%
포위된 부분은 변수입니다.(String型) {ratelimit:%ip%:}%timestamp%
ex)
{ratelimit:127.0.0.1:}1605054195
위조 응용 프로그램 코드
script = """
redis.call("incr",KEYS[1])
redis.call("expire",KEYS[1],16)
"""
timestamp = CURRENT_UNIX_TIME()
ip = IP_ADDRESS()
keyname = "{ratelimit:"+ip+":}"+timestamp
redis.EVAL(script,1,keyname)
keynames = []
FOR(i=0; i<15; i++)
keynames[0] = "{ratelimit:"+ip+":}"+timestamp-i
END
counts = []
counts = redis.MGET(keynames)
sum = 0
FOR(i=0;i<15;i++)
sum += counts[i]
IF i == 0 && sum > 1000 THEN // 1秒間で1000リクエストまで許可(バースト許可)
ERROR "too many requests per second"
END
IF i == 9 && sum > 5000 THEN // 10秒間で5000リクエストまで許可
ERROR "too many requests per 10 seconds"
END
IF i == 14 && sum > 7000 THEN // 15秒間で7000リクエストまで許可
ERROR "too many requests per 15 seconds"
END
END
최근 1초간 돌발 방문을 허용하고 싶지만, 10초간 지속하면 치고 싶을 때 쓰는 슬라이딩 윈도 방법이다.atomic 섹션만 LuaScript로 설정하면 되므로 mget이 LuaScript에 포함되지 않습니다.
mget을 비롯해 레디스에 문의해도 한 번의 라운드 트립에서 할 수 있고, 코드의 전망(LuaScript의 유지보수성)도 나빠지지 않는다.
또 키는 분리돼 있기 때문에 리디스 클러스터에서 멀티 커맨드의 mget을 직접 사용할 수 없기 때문에hash tags 기능
{ }
을 사용한다.mget 명령은 취득한 요소 수 n에 비례하는 O(n)이기 때문에 슬라이딩 길이에 유의해야 한다.
총결산
모드 이름
특징.
단계수기
현재의 방문 수만 기록하기 위해 최소한의 메모리를 이용한다.순간의 부하를 억제하기 위해 이용하다
메타 정보 부여
리스트 길이를 이용하기 위해서는 메모리가 소모된다.임의의 메타 정보를 함께 기록할 수 있기 때문에, 영역에 의존하는 고급 처리를 포함할 여지가 있다
Sliding Window
슬라이딩 길이에 비례하는 스토리지 및 계산이 필요합니다.순간적인 돌발은 허용되지만 방문이 허용되지 않는 경우 등 고급 용례에서 사용된다.
Reference
이 문제에 관하여(Redis의 Rate Limit Pattern 3가지), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/maruloop/articles/dcfc289e6fb33f9f5164텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)