15 Stimmen

Verteilter Sperrmanager für Python

Ich habe eine Reihe von Servern mit mehreren Instanzen, die auf eine Ressource zugreifen, die eine harte Grenze für Anfragen pro Sekunde hat.

Ich brauche einen Mechanismus, um den Zugriff auf diese Ressource für alle laufenden Server und Instanzen zu sperren.

Es gibt einen restful verteilten Sperr-Manager, den ich auf Github gefunden habe: https://github.com/thefab/restful-distributed-lock-manager

Leider scheint es eine Mindestsperrzeit von 1 Sekunde zu geben und er ist relativ unzuverlässig. Bei mehreren Tests dauerte es zwischen 1 und 3 Sekunden, um ein 1-Sekunden-Sperrung aufzuheben.

Gibt es etwas gut Getestetes mit einer Python-Schnittstelle, das ich für diesen Zweck verwenden kann?

Bearbeitung: Ich brauche etwas, das sich automatisch in weniger als 1 Sekunde entsperrt. Die Sperre wird in meinem Code niemals freigegeben.

23voto

Jan Vlcinsky Punkte 40541

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).

2voto

Keyless Punkte 106

Der einfachste Weg, dies umzusetzen, ist die Verwendung von lockable.

Es bietet Low-Level-Lock-Semantik und kommt mit einem Python-Client. Wichtig ist, dass Sie keine Datenbank oder Server einrichten müssen, es funktioniert, indem der Lock auf den Lockable-Servern gespeichert wird.

Locks haben variable TTLs, aber Sie können sie auch frühzeitig freigeben:

$ pip install lockable-dev

from lockable import Lock

my_lock = Lock('my-lock-name')

# den Lock erwerben
my_lock.acquire()

# den Lock freigeben
my_lock.release()

0voto

Alexander Gryanko Punkte 662

Für meinen Cluster verwende ich ZooKeeper mit der python-kazoo-Bibliothek für Warteschlangen und Sperren.

Modifiziertes Beispiel aus der kazoo-API-Dokumentation für Ihren Zweck: http://kazoo.readthedocs.org/en/latest/api/recipe/lock.html

zk = KazooClient()
lock = zk.Lock("/lockpath", "meine-Kennung")
if lock.acquire(timeout=1):

   Code hier

   lock.release()

Aber Sie benötigen mindestens drei Knoten für ZooKeeper, soweit ich mich erinnern kann.

-1voto

h4unt3r Punkte 826

Ihre Anforderungen scheinen sehr spezifisch zu sein. Ich würde in Betracht ziehen, einen einfachen Sperrserver zu schreiben und dann die Sperren clientseitig mit einer Klasse zu implementieren, die ein Sperrung erwirbt, wenn sie erstellt wird, und die Sperre löscht, wenn sie außerhalb des Gültigkeitsbereichs geht.

class Lock(object):
    def __init__(self,resource):
        print "Sperre erhalten für",resource
        # Verbindung zum Sperrserver herstellen und Ressource sperren

    def __del__(self):
        print "Sperre aufgehoben"
        # Verbindung zum Sperrserver herstellen und Ressource freigeben, wenn gesperrt

def callWithLock(resource,call,*args,**kwargs):
    lock = Lock(resource)
    return call( *args, **kwargs )

def test( asdf, something="Anderes" ):
    return asdf + " " + something

if __name__ == "__main__":
    import sys
    print "Aufruf test:",callWithLock( "resource.test", test, sys.argv[0] )

Beispiel-Ausgabe

$ python locktest.py 
Aufruf test: Sperre erhalten für resource.test
Sperre aufgehoben
locktest.py Anderes

-1voto

David S Punkte 19

Der verteilte Sperr-Manager Taooka http://taooka.com hat eine TTL-Genauigkeit im Nanosekundenbereich. Aber er hat nur eine Golang-Client-Bibliothek.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X