2017-12-10 3 views
0

Ich habe versucht zu verstehen, wie Rate-Begrenzer funktioniert.Rate Limiter mit Java und Redis ohne Sperre

Problemstellung ist: zehn Anfragen pro Sekunde pro IP-Adresse

Soln: i in Blogs sehen kann, ist:

public void makeApiCall(String ip){ 
    Long currentTime=Timestamp timestamp = System.currentTimeMillis(); 

    String key=ip+":"+currentTime; 

    Integer count=redisClient.get(key); 

    if(count!=null && count > 10){ 
     throw LimitExceededException(); 
    } 
    else{ 
     redisClient.incr(key,1); 
     callApi(); 
    } 
} 

Ab jetzt habe ich zu entfernten alten Schlüssel bin ignorieren. Ich kann nicht verstehen, wie diese Lösung funktionieren wird? Wie oben erwähnt wird Code mehr als 10 API-Aufruf in Multi-Thread-Umgebung mit in einer Sekunde. Es kann nur gelöst werden, indem man den Schlüssel redisClient.get (key) zum callApi() -Code synchronisiert.

Ich habe unter diesen Code aus

https://redis.io/commands/incr.

Kann mir jemand helfen zu verstehen (durch die Änderung des oben genannten Codes), was ist der richtige Weg, um redis in diesem Szenario zu verwenden?

in Angenommen aktuellen zweiten 9 Anfragen haben served.now gleichzeitig 5 neue Anfragen kommen, sind alle diese neuen Threads redisClient.get nennen (key), bevor jeder dieser 5 Threads ausführen „else“ block.So für jeden Thread count wird 9 und sonst Block wird ausgeführt und incr wird 5 mal mehr aufgerufen werden und für jeden Thread api wird aufgerufen werden.

+0

Der oben angegebene Code (obwohl EXPIRE nicht vorhanden ist) beschränkt sich auf 10 CallApi() - Aufrufe in einer einzigen Millisekunde. Was ist deine Frage? –

+0

Ich habe die Frage aktualisiert. – Nishat

Antwort

1

Wie ist, ist der Code in der Tat anfällig für Race-Bedingungen (und Speicher aufgebläht, wie Sie Ablauf abgelaufen haben). Es gibt grundsätzlich zwei Möglichkeiten, um dies zu beheben: ein MULTI/EXEC transaction with a WATCH oder EVAL in einem Lua-Skript.

Unter der Annahme, dass Sie Jedis als Java-Client verwenden, so etwas wie die folgenden sollte den Trick mit Transaktionen tun:

public void makeApiCall(String ip){ 
    Long currentTime=Timestamp timestamp = System.currentTimeMillis(); 

    String key=ip+":"+currentTime; 

    redisClient.watch(key); 
    Integer count=redisClient.get(key); 

    if(count!=null && count > 10){ 
     throw LimitExceededException(); 
    } 
    else{ 
     Transaction t = redisClient.multi(); 
     t.incr(key,1); 
     List<Object> resp = t.exec(); 
     if(resp != null){ 
      callApi(); 
     } 
    } 
} 

Lua ist eine andere Sache, aber im Grunde vorausgesetzt, Sie das folgende Skript den gesamten Schlüssel liefern Name (ip + ts), es so ziemlich die gleiche Sache tun, solange Sie Ihren Code mit callApi bis folgt ein OK auf immer:

local count = redis.call('GET', KEYS[1]) 
if count and tonumber(count) > 10 then 
    return redis.error('count exceeded') 
else 
    redis.call('INCR', KEYS[1]) 
    redis.call('EXPIRE', KEYS[1], 10) 
    return 'OK' 
end 

Beachten Sie, dass mit Lua Sie nicht brauchen, um für Änderungen zu beobachten, wie das gesamte Skript ist atomar.

Schließlich scheint es, dass die Zähler Muster, das Sie in der Dokumentation genannt habe die WATCH Sache fehlten - ich eine PR eingereicht habe, dass (https://github.com/antirez/redis-doc/pull/888) zu korrigieren.