В Redis основната единица за разпространение е хеш слот. Разпределените версии на redis – включително Redis Cluster с отворен код, търговският Redis Enterprise и дори AWS ElastiCache – могат да се движат само 1 слот за данни наведнъж.
Това води до интересен проблем - слотове с изкривени страни. Ами ако един слот (или няколко слота) в крайна сметка разполага с повечето данни?
Въобще възможно ли е това?
Redis решава хеш-слота за ключ, използвайки добре публикуван алгоритъм. Този алгоритъм обикновено гарантира, че ключовете са добре разпределени.
Но разработчиците могат да повлияят на алгоритъма, като посочат хеш маркер . Хеш маркерът е част от ключа, затворена в къдрави скоби {...}
. Когато е посочен хеш маркер, той ще се използва за определяне на слота за хеш.
Хеш-тегът в redis е това, което повечето бази данни биха нарекли ключ на дял. Ако изберете грешен ключ за дял, ще получите изкривени слотове.
Като пример, ако вашите ключове са като {users}:1234
и {users}:5432
, redis ще съхранява всички потребители в един и същ хеш слот.
Какво е поправката?
Поправката е концептуална просто - трябва да преименувате ключа, за да премахнете неправилния хеш маркер. Така че преименуване на {users}:1234
до users:{1234}
или дори users:1234
трябва да свърши работа...
… с изключение на това, че командата за преименуване не работи в редис клъстер.
Така че единственият изход е първо да изхвърлите ключа и след това да го възстановите срещу новото име.
Ето как изглежда в кода:
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)