Redis의 Rate Limit Pattern 3가지

4691 단어 Redistech
Redis의 Rate Limit 모드를 몇 개 적으십시오.
여기서 예를 들어, 각 IP 주소(이하ip에 Rate Limit이 적용된다고 가정합니다.
※ 전체 모드는 Redis Cluster를 지원합니다.

1. Single Counter 모드


레디스의 키 디자인.


% 포위된 부분은 변수입니다.
(String型) ratelimit:%ip%
ex)
ratelimit:127.0.0.1

위조 응용 프로그램 코드

  • 각 IP에 대해 각각 Key
  • 키가 10 카운트를 초과하면 ERROR
  • 키는 1초에 한 번씩 사라진다
  • INCR & EXPIRE는 LuaScript에서 Atomic
  • 로 실행
    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
    

    위조 응용 프로그램 코드

  • 각 IP에 대해 각각 Key
  • List의value에 임의의 메타 정보 보관
  • List 길이가 10 이상이면 ERROR
  • 키(=List) 1초마다 사라짐
  • INCR & EXPIRE는 LuaScript에서 Atomic
  • 로 실행
    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
    

    위조 응용 프로그램 코드

  • ip*timestamp당 Key 구분
  • INCR current timestamp
  • mget 명령의 Round Trip을 통해 이전 섹션 가져오기
  • Key에서 설정hash tag하여 Redis Cluster에서도 mget
  • 을 진행할 수 있도록 합니다.
  • 옛 키가 Redis의 EXPIRE에서 사라짐
  • 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
    슬라이딩 길이에 비례하는 스토리지 및 계산이 필요합니다.순간적인 돌발은 허용되지만 방문이 허용되지 않는 경우 등 고급 용례에서 사용된다.

    좋은 웹페이지 즐겨찾기