Meine erste Idee war die Verwendung von Redis. Aber es gibt mehr großartige Tools und einige sind sogar leichter, daher basiert meine Lösung auf zmq. Aus diesem Grund müssen Sie Redis nicht ausführen, es reicht aus, ein kleines Python-Skript auszuführen.
Anforderungsüberprüfung
Lassen Sie mich Ihre Anforderungen überprüfen, bevor ich die Lösung beschreibe.
-
Begrenzen Sie die Anzahl der Anfragen an eine Ressource auf eine Anzahl von Anfragen innerhalb eines festen Zeitraums.
-
Automatisches Entsperren
-
Die Ressourcen (Auto-) Freischaltung muss in einer Zeit erfolgen, die kürzer als 1 Sekunde ist.
-
Es soll verteilt sein. Ich gehe davon aus, dass Sie damit meinen, dass mehrere verteilte Server, die auf eine Ressource zugreifen, in der Lage sein sollen und es in Ordnung ist, nur einen Locker-Service zu haben (mehr dazu unter Schlussfolgerungen).
Konzept
Begrenzung der Anzahl von Anfragen innerhalb eines Zeitfensters
Das Zeitfenster kann eine Sekunde, mehrere Sekunden oder eine kürzere Zeit sein. Die einzige Einschränkung ist die Genauigkeit der Zeitmessung in Python.
Wenn Ihre Ressource pro Sekunde eine harte Grenze hat, sollten Sie das Zeitfenster 1.0 verwenden
Überwachen der Anzahl von Anfragen pro Zeitfenster bis zum Beginn des nächsten
Bei der ersten Anforderung zum Zugriff auf Ihre Ressource legen Sie die Startzeit für das nächste Zeitfenster fest und initialisieren den Anforderungszähler.
Bei jeder Anforderung erhöhen Sie den Anforderungszähler (für das aktuelle Zeitfenster) und erlauben die Anfrage, es sei denn, Sie haben die maximale Anzahl von erlaubten Anfragen im aktuellen Zeitfenster erreicht.
Verwenden von zmq mit REQ/REP
Die Server, die Ihre Ressourcen verbrauchen, könnten auf mehreren Computern verteilt sein. Um Zugriff auf den LockerServer zu ermöglichen, verwenden Sie zmq.
Beispielcode
zmqlocker.py:
import time
import zmq
class Locker():
def __init__(self, max_requests=1, in_seconds=1.0):
self.max_requests = max_requests
self.in_seconds = in_seconds
self.requests = 0
now = time.time()
self.next_slot = now + in_seconds
def __iter__(self):
return self
def next(self):
now = time.time()
if now > self.next_slot:
self.requests = 0
self.next_slot = now + self.in_seconds
if self.requests < self.max_requests:
self.requests += 1
return "los"
else:
return "sorry"
class LockerServer():
def __init__(self, max_requests=1, in_seconds=1.0, url="tcp://*:7777"):
locker=Locker(max_requests, in_seconds)
cnt = zmq.Context()
sck = cnt.socket(zmq.REP)
sck.bind(url)
while True:
msg = sck.recv()
sck.send(locker.next())
class LockerClient():
def __init__(self, url="tcp://localhost:7777"):
cnt = zmq.Context()
self.sck = cnt.socket(zmq.REQ)
self.sck.connect(url)
def next(self):
self.sck.send("lassen Sie mich gehen")
return self.sck.recv()
Führen Sie Ihren Server aus:
run_server.py:
from zmqlocker import LockerServer
svr = LockerServer(max_requests=5, in_seconds=0.8)
Von der Befehlszeile aus:
$ python run_server.py
Dies startet den Locker-Dienst auf dem Standardport 7777 auf localhost.
Führen Sie Ihre Clients aus
run_client.py:
from zmqlocker import LockerClient
import time
locker_cli = LockerClient()
for i in xrange(100):
print time.time(), locker_cli.next()
time.sleep(0.1)
Von der Befehlszeile aus:
$ python run_client.py
Sie sollen "los", "los", "sorry"... Antworten gedruckt sehen.
Versuchen Sie, mehr Clients auszuführen.
Etwas Stresstest
Sie können zuerst die Clients starten und später den Server starten. Clients blockieren, bis der Server hochgefahren ist, und laufen dann glücklich.
Schlussfolgerungen
- beschriebene Anforderungen sind erfüllt
- die Anzahl der Anfragen ist begrenzt
- keine Notwendigkeit zum Entsperren, es ermöglicht mehr Anfragen, sobald das nächste Zeitfenster verfügbar ist
- LockerService ist über Netzwerk oder lokale Sockets verfügbar.
- es soll zuverlässig sein, zmq ist eine ausgereifte Lösung, der Python-Code ist recht einfach
- es erfordert keine Zeitsynchronisierung zwischen allen Teilnehmern
- die Leistung wird sehr gut sein
Andererseits könnten Sie feststellen, dass die Grenzen Ihrer Ressource nicht so vorhersehbar sind, wie Sie annehmen, also seien Sie darauf vorbereitet, mit Parametern zu spielen, um ein richtiges Gleichgewicht zu finden und seien Sie immer auf Ausnahmen von dieser Seite vorbereitet.
Es gibt auch Raum für die Optimierung der Bereitstellung von "Sperren" - z.B. wenn der Locker die erlaubten Anfragen ausgeht, aber das aktuelle Zeitfenster bereits fast abgeschlossen ist, könnten Sie in Erwägung ziehen, mit Ihrem "sorry" zu warten und nach einer Sekunde ein "los" zu liefern.
Erweiterung zu einem echten verteilten Sperrmanager
Unter "verteilt" könnten wir auch verstehen, dass mehrere Locker-Server zusammen laufen. Dies ist schwieriger zu realisieren, aber möglich. zmq ermöglicht eine sehr einfache Verbindung zu mehreren URLs, sodass Client sich wirklich einfach mit mehreren Locker-Servern verbinden könnten. Es stellt sich die Frage, wie sich die Locker-Server koordinieren können, um nicht zu viele Anfragen an Ihre Ressource zuzulassen. zmq ermöglicht die Kommunikation zwischen Servern. Ein mögliches Modell könnte sein, dass jeder Locker-Server jedes bereitgestellte "los" auf PUB/SUB veröffentlichen würde. Alle anderen Locker-Server wären abonniert und würden jedes "los" verwenden, um ihren lokalen Anforderungszähler zu erhöhen (mit etwas modifizierter Logik).