Redis
 sql >> Datenbank >  >> NoSQL >> Redis

So beheben Sie einseitige Hash-Slots in Redis

In Redis ist die primäre Verteilungseinheit ein Hash-Slot. Verteilte Versionen von Redis – einschließlich des Open-Source-Redis-Clusters, des kommerziellen Redis Enterprise und sogar AWS ElastiCache – können Daten nur jeweils um einen Slot verschieben.

Dies führt zu einem interessanten Problem – schiefe Schlitze. Was ist, wenn ein Slot (oder ein paar Slots) am Ende die meisten Daten enthält?

Ist das überhaupt möglich?

Redis entscheidet den Hash-Slot für einen Schlüssel mithilfe eines gut veröffentlichten Algorithmus. Dieser Algorithmus stellt normalerweise sicher, dass die Schlüssel gut verteilt sind.

Aber Entwickler können den Algorithmus beeinflussen, indem sie ein Hash-Tag angeben . Ein Hash-Tag ist ein in geschweiften Klammern eingeschlossener Teil des Schlüssels {...} . Wenn ein Hash-Tag angegeben wird, wird es verwendet, um den Hash-Slot festzulegen.

Das Hash-Tag in Redis ist das, was die meisten Datenbanken als Partitionsschlüssel bezeichnen würden. Wenn Sie einen falschen Partitionsschlüssel wählen, erhalten Sie schiefe Slots.

Als Beispiel, wenn Ihre Schlüssel wie folgt lauten:{users}:1234 und {users}:5432 , speichert redis alle Benutzer im selben Hash-Slot.

Was ist die Lösung?

Die Lösung ist konzeptionell einfach - Sie müssen den Schlüssel umbenennen, um das falsche Hash-Tag zu entfernen. Also Umbenennung von {users}:1234 an users:{1234} oder sogar users:1234 sollte reichen...

… außer dass der Umbenennungsbefehl im Redis-Cluster nicht funktioniert.

Der einzige Ausweg besteht also darin, zuerst den Schlüssel zu sichern und ihn dann unter dem neuen Namen wiederherzustellen.

So sieht es im Code aus:



from redis import StrictRedis
try:
    from itertools import izip_longest
except:
    from itertools import zip_longest as izip_longest


def get_batches(iterable, batch_size=2, fillvalue=None):
    """
    Chunks a very long iterable into smaller chunks of `batch_size`
    For example, if iterable has 9 elements, and batch_size is 2,
    the output will be 5 iterables - each of length 2. 
    The last iterable will also have 2 elements, 
    but the 2nd element will be `fillvalue`
    """
    args = [iter(iterable)] * batch_size
    return izip_longest(fillvalue=fillvalue, *args)


def migrate_keys(allkeys, host, port, password=None):
    db = 0
    red = StrictRedis(host=host, port=port, password=password)

    batches = get_batches(allkeys)
    for batch in batches:
        pipe = red.pipeline()
        keys = list(batch)
        for key in keys:
            if not key:
                continue
            pipe.dump(key)
            
        response = iter(pipe.execute())
        # New pipeline to run the restore command
        pipe = red.pipeline(transaction=False)
        for key in keys:
            if not key:
                continue
            obj = next(response)
            new_key = "restored." + key
            pipe.restore(new_key, 0, obj)

        pipe.execute()


if __name__ == '__main__':
    allkeys = ['users:18245', 'users:12328:answers_by_score', 'comments:18648']
    migrate_keys(allkeys, host="localhost", port=6379)