Ich habe gerade ein Python-Shelve -Modul als persistenten Cache für von außen abgerufene Daten ausprobiert Bedienung. Das vollständige Beispiel finden Sie hier.

Ich habe mich gefragt, was der beste Ansatz wäre, wenn ich diesen Multiprozess sicher machen möchte. Ich kenne Redis, Memcached und solche "echten Lösungen", aber ich möchte nur die Teile der Python-Standardbibliothek oder sehr minimale Abhängigkeiten verwenden, um meinen Code kompakt zu halten und keine unnötige Komplexität einzuführen, wenn der Code in einem einzigen Prozess ausgeführt wird. Einzelfadenmodell.

Es ist einfach, eine Einzelprozesslösung zu finden, aber dies funktioniert bei den aktuellen Python-Weblaufzeiten nicht gut. Insbesondere wäre das Problem, dass in Apache + mod_wsgi Umgebung

  • Nur ein Prozess aktualisiert die zwischengespeicherten Daten einmal (Dateisperren, irgendwie?)

  • Andere Prozesse verwenden die zwischengespeicherten Daten, während das Update ausgeführt wird

  • Wenn der Prozess die zwischengespeicherten Daten nicht aktualisiert, wird eine Strafe von N Minuten verhängt, bevor ein anderer Prozess es erneut versuchen kann (um Donner zu verhindern) Herde und so) - wie man dies zwischen mod_wsgi-Prozessen signalisiert

  • Sie verwenden hierfür keine "schweren Tools", sondern nur Python-Standardbibliotheken und UNIX

Auch wenn ein PyPi-Paket dies ohne externe Abhängigkeiten tut, lassen Sie es mich bitte wissen. Alternative Ansätze und Empfehlungen wie "nur SQLite verwenden" sind willkommen.

Beispiel:

import datetime
import os
import shelve
import logging


logger = logging.getLogger(__name__)


class Converter:

    def __init__(self, fpath):
        self.last_updated = None
        self.data = None

        self.data = shelve.open(fpath)

        if os.path.exists(fpath):
            self.last_updated = datetime.datetime.fromtimestamp(os.path.getmtime(fpath))

    def convert(self, source, target, amount, update=True, determiner="24h_avg"):
        # Do something with cached data
        pass

    def is_up_to_date(self):
        if not self.last_updated:
            return False

        return datetime.datetime.now() < self.last_updated + self.refresh_delay

    def update(self):
        try:
            # Update data from the external server
            self.last_updated = datetime.datetime.now()
            self.data.sync()
        except Exception as e:
            logger.error("Could not refresh market data: %s %s", self.api_url, e)
            logger.exception(e)
6
Mikko Ohtamaa 6 Dez. 2013 im 13:18

3 Antworten

Beste Antwort

Ich würde sagen, Sie möchten eine vorhandene Caching-Bibliothek verwenden, dogpile.cache fällt mir ein, es hat bereits viele Funktionen und Sie können problemlos die Backends anschließen, die Sie möglicherweise benötigen.

Die Dokumentation dogpile.cache enthält Folgendes:

Dieses "get-or-create" -Muster ist der gesamte Schlüssel zum "Dogpile" -System, das eine einzelne Wertschöpfungsoperation unter vielen gleichzeitigen Abrufoperationen für einen bestimmten Schlüssel koordiniert, wodurch das Problem beseitigt wird, dass ein abgelaufener Wert von redundant neu generiert wird viele Arbeiter gleichzeitig.

2
Antti Haapala 6 Dez. 2013 im 15:46

Ich habe einen Sperr-Wrapper (thread- und mulitprocess-sicher) um das Standardmodul shelve ohne externe Abhängigkeiten geschrieben:

https://github.com/cristoper/shelfcache

Es erfüllt viele Ihrer Anforderungen, verfügt jedoch über keine Backoff-Strategie, um donnernde Herden zu verhindern. Wenn Sie eine Reader-Writer-Sperre wünschen (damit mehrere Threads lesen können, aber nur ein Schreibzugriff), müssen Sie eine eigene RW bereitstellen sperren.

Wenn ich es jedoch noch einmal machen würde, würde ich wahrscheinlich "nur SQLite verwenden". Das shelve -Modul, das über mehrere verschiedene DBM-Implementierungen abstrahiert, die selbst über verschiedene Betriebssystemsperrmechanismen abstrahieren, ist ein Problem (z. B. mithilfe der Option flock für Shelfcache mit gdbm unter Mac OS X (oder Busybox) führt zu einem Deadlock).

Es gibt mehrere Python-Projekte, die versuchen, eine Standard-Diktierschnittstelle für SQLite oder andere persistente Speicher bereitzustellen, z. B.: https: //github.com/RaRe-Technologies/sqlitedict

(Beachten Sie, dass sqldict selbst für dieselbe Datenbankverbindung thread sicher ist, es jedoch nicht sicher ist, dieselbe Datenbankverbindung zwischen Prozessen zu teilen.)

0
cristoper 28 Sept. 2018 im 19:24

Betrachten wir Ihre Anforderungen systematisch:

Minimale oder keine externen Abhängigkeiten

Ihr Anwendungsfall bestimmt, ob Sie die In-Band-Synchronisation (Dateideskriptor oder Speicherbereich, die über Fork vererbt wird) oder die Out-of-Band-Synchronisation (Posix-Dateisperren, gemeinsamer Sys V-Speicher) verwenden können.

Dann können Sie andere Anforderungen haben, z. plattformübergreifende Verfügbarkeit der Tools usw.

Es gibt wirklich nicht so viel in der Standardbibliothek, außer nackten Werkzeugen. Ein Modul fällt jedoch auf: sqlite3. Sqlite verwendet fcntl / posix-Sperren, es gibt jedoch Leistungseinschränkungen, mehrere Prozesse implizieren eine dateibasierte Datenbank, und sqlite erfordert beim Festschreiben fdatasync.

Daher gibt es eine Begrenzung für Transaktionen in SQLite, die durch die Festplatten-Drehzahl festgelegt werden. Letzteres ist keine große Sache, wenn Sie einen HW-Überfall haben, kann aber ein großes Handicap für Standardhardware sein, z. ein Laptop oder USB-Flash oder SD-Karte. Planen Sie ~ 100 tps ein, wenn Sie eine normale, rotierende Festplatte verwenden.

Ihre Prozesse können auch auf SQLite blockiert werden, wenn Sie spezielle Transaktionsmodi verwenden.

Donnernde Herde verhindern

Hierfür gibt es zwei Hauptansätze:

  • Aktualisieren Sie das Cache-Element wahrscheinlich früher als erforderlich, oder
  • Aktualisieren Sie nur bei Bedarf, blockieren Sie jedoch andere Anrufer

Wenn Sie einem anderen Prozess den Cache-Wert anvertrauen, haben Sie vermutlich keine Sicherheitsüberlegungen. Somit funktioniert entweder oder eine Kombination aus beiden.

2
Dima Tisnek 11 Dez. 2013 im 15:17