Einfache Distributed Hash Table (DHT)-Implementation in Python mit libdht back to frontpage

Wie bereits im vorigen Post angedeutet, habe ich heute meine DHT-Bibliothek libdht in einer ersten Version veröffentlicht. Da ich hier Release early, release often folge, befindet sich libdht noch in einem sehr frühen Stadium. Viele TODOs und FIXMEs finden sich noch in libdht, weshalb ich noch vom produktiven Einsatz abraten möchte. Dennoch will ich die Grundidee an dieser Stelle bereits vorstellen.

libdht implementiert überwiegend Kademlia mit einigen wenigen Abwandlungen und stellt dem Entwickler eine abstrakte Klasse DHT zur Verfügung. Von dieser Klasse wird eine eigene Klasse für das eigene Netzwerk abgeleitet, die die vier Operationen handle_save(key, value), handle_load(key), handle_delete(key) und handle_has_key(key) implementiert. Diese Methoden werden stets dann aufgerufen, wenn das Netzwerk den lokalen Node auffordert, einen Schlüssel zu speichern/laden/löschen/auf Verfügbarkeit abzufragen. Abhängig von dem eigenen Anwendungszweck wäre hier beispielsweise, in den einfachsten Fällen, eine In-Memory-Datenbank (z. B. ganz einfach ein Dict) oder ein einfacher lokaler Datei-Storage (z. B. je Key ein File) denkbar. Die Klasse stellt zudem die vier komplementären Standardoperationen save(key, value), load(key), delete(key) und has_key(key) zur Verfügung, mit denen der lokale Node Operationen im Netzwerk durchführen kann.

Eine In-Memory-Beispielimplementation liefert die beigefügt example.py:

class MyNetwork(dht.DHT):
    def __init__(self, *args, **kwargs):
        self._my_db = {}
        super(MyNetwork, self).__init__(*args, **kwargs)

    def handle_save(self, key, value):
        self._my_db[key] = value
        return True

    def handle_load(self, key):
        return self._my_db.get(key)

    def handle_delete(self, key):
        del self._my_db[key]
        return True

    def handle_has_key(self, key):
        return key in self._my_db

Da sich mit dem Starten des Netzwerks auf jedem Node die Nodes untereinander noch nicht kennen, sind stets “Eintrittsnodes” in das Netzwerk notwendig. Hier genügt mindestens ein verfügbarer Node. Das Ermitteln solcher Einstiegsnodes und das Betreten des Netzwerkes nennt sich Bootstrapping. Das der libdht beigefügte Bootstrapping-Modul stellt Möglichkeiten zur Verfügung, das Bootstrapping so einfach wie möglich zu halten. Denkbar wären zum Beispiel das Treffen der Nodes während des Bootstraps auf einem zentralen IRC-Server, das Austauschen von stets verfügbaren Nodes via Nopastes oder zentralen Bootstrapping-Servern sowie Broadcasting im Netzwerk, um lokale andere Nodes zu finden (alternativ ist ein einfacher join([(id, ip, host), …]) auf dem Netzwerk-Objekt unter Angabe von ID/IP/Hosts von anderen Nodes möglich). Die Auto-Broadcasting-Methode habe ich bereits implementiert (die anderen in Teilen auch, aber noch nicht öffentlich verfügbar):

n = MyNetwork(node_id=my_id, port=port)

bootstrapper = bootstrap.Bootstrapper(network_id="test", node_id=my_id,
                                      dht_port=port)
bootstrapper.start_network(n)

try:

    ... # Hier kann auf save/load/delete/has_key zugegriffen werden

finally:

    bootstrapper.stop_network(n) 

Diese Möglichkeit erlaubt auch ein einfaches lokales Testen mit beliebig vielen Clients ohne Konfigurationsaufwand. Die Ports wie auch IDs werden beim Testen zum Beispiel zufällig ermittelt. Den Platz, an dem die vier Netzwerk-Operationen verfügbar sind, habe ich bereits im Beispiel gekennzeichnet (zwischen try+finally).

Der Output meiner example.py sieht folgendermaßen aus, wenn bereits zwei andere Clients gestartet sind:

$ python example.py
My ID = 315266357664053077240863549245018455720435477024

Saving: True
Loading: Hallo!
How many nodes have this key? 3
Removing: True
How many nodes have this key now? 0
Enter to exit.

libdht erfordert ein installiertes PyCrypto. Kleiner Tipp: libdht enthält eine Funktion hash_data(str), das einen Hash-Key auf Basis der Input-Daten generiert, der direkt als Key für die Netzwerk-Operationen genutzt werden kann.

Es sind noch einige Arbeiten in der libdht zu erledigen. Zu diesen gehören unter anderem Rebalancing/ständige Replikation und Überwachung von Keys und eine fehlertolerantere Kommunikation zu Nodes (und deren Ausscheiden aus dem Netzwerk).

Ich arbeite an zwei weiteren Abkömmlingen, die auf libdht beruhen: liblense, eine Peer-to-Peer Instant-Messenger-Bibliothek und lense, die passende Qt-GUI dazu. liblense erfordert insbesondere eine zuverlässige Replikation und auch eine besonders fehlertolerante Kommunikation. libdht wird daher von der Arbeit an liblense profitieren.


New comment

Comments are moderated and therefore are not published instantly.





Comments

  • Oct 17 2013 23:32:08 +0200 CEST

    __________! by Marcel *?$%123"!

    Hallo. Code schon eine ganze Zeit lang so vor mich hin und für "mein neustes Projekt" :D musste dann auch erstmals eine DHT her, die möglichst einfach zu implementieren ist.
    Kurzum: Mir hat deine Arbeit wirklich sehr gefallen und ich fände es echt cool von dir, wenn du die Qualität der Bibliothek verbessern könntest, da sie im produktiven Einsatz leider einige Kinderkrankheiten aufweist.
    So kommt sie beispielsweise nicht damit zurecht, dass ein Knoten das Netz "einfach mal so verlässt":D
    Aber großes Lob an dich, auch aufgrund des sehr kompakten Codes, der doch sehr effizient arbeitet, wenn man die Menge ansieht;D
    Auch hat mich die erstaunlich leichte Implementation, die ich für mein Prototyping benötige fasziniert:D

    Marcel
    [ Twitter: @awetechnic ]
    [ BitMessage (https://github.com/Bitmessage/PyBitmessage) : BM-2cX7hKB4xpCypeuN7n2kKqytYgzgWPrs2Q ]