def _transfer_slots(redis_conn_from: Redis, redis_id_from: str, redis_conn_to: Redis, redis_id_to: str, slots: list): """ Documentation from http://redis.io/commands/cluster-setslot 1. Set the destination node slot to importing state using CLUSTER SETSLOT <slot> IMPORTING <source-node-id>. 2. Set the source node slot to migrating state using CLUSTER SETSLOT <slot> MIGRATING <destination-node-id>. 3. Get keys from the source node with CLUSTER GETKEYSINSLOT command and move them into the destination node using the MIGRATE command. 4. Use CLUSTER SETSLOT <slot> NODE <destination-node-id> in the source or destination. """ print('Transfering %d slots from %s to %s...' % (len(slots), redis_id_from, redis_id_to)) dest_host = redis_conn_to.connection_pool.connection_kwargs['host'] dest_port = redis_conn_to.connection_pool.connection_kwargs['port'] pipeline_to = redis_conn_to.pipeline() pipeline_from = redis_conn_from.pipeline() for slot in slots: # 1, 2 pipeline_to.execute_command('CLUSTER SETSLOT', slot, 'IMPORTING', redis_id_from) pipeline_from.execute_command('CLUSTER SETSLOT', slot, 'MIGRATING', redis_id_to) pipeline_to.execute() pipeline_from.execute() for slot in slots: # 3 keys = redis_conn_from.execute_command('CLUSTER GETKEYSINSLOT', slot, 1000000) if len(keys) > 0: redis_conn_from.execute_command('MIGRATE', dest_host, dest_port, "", 0, 180000, 'KEYS', *keys) # 4 redis_conn_to.execute_command('CLUSTER SETSLOT', slot, 'NODE', redis_id_to)
def _add_new_nodes(self, cluster_size): old_nodes = self.nodes.copy() nodes_before = self._get_nodes_primitive() self._docker_scale(cluster_size) nodes_after = self._get_nodes_primitive() new_ips = [ ':'.join(map(str, x)) for x in set(nodes_after) - set(nodes_before) ] print(new_ips) master_ip_port = old_nodes[0]['ip_port'] master_ip, master_port = master_ip_port.split(':') master_conn = Redis(master_ip, master_port) print("Adding nodes to the cluster") for ip in new_ips: new_ip, new_port = ip.split(':') master_conn.execute_command('CLUSTER MEET', new_ip, new_port) print("Preventive fix") sleep(3) fix = subprocess.Popen( ['ruby', 'redis-trib.rb', 'fix', master_ip_port], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL) fix.communicate(b'yes\n') fix.wait() sleep(3) new_nodes = [ x for x in self._get_running_nodes() if x['ip_port'] in new_ips ] slots_per_node = round(16384 / cluster_size) old_redises = { x[0]: Redis(x[0], x[1]) for x in (y['ip_port'].split(':') for y in old_nodes) } new_redises = [ Redis(x[0], x[1]) for x in (y['ip_port'].split(':') for y in new_nodes) ] slots_repartition = self._get_slots_repartition( list(old_redises.values())[0]) for dest_node, dest_redis, i in zip(new_nodes, new_redises, range(len(new_nodes))): slots = slots_repartition[i * slots_per_node:(i + 1) * slots_per_node] sources_ip = {x[1] for x in slots} for source_ip in sources_ip: slots_for_source = [x for x in slots if x[1] == source_ip] source_redis = old_redises[source_ip] self._transfer_slots(source_redis, slots_for_source[0][3], dest_redis, dest_node['id'], [x[0] for x in slots_for_source]) subprocess.check_call( ['ruby', 'redis-trib.rb', 'info', master_ip_port])
def _add_new_nodes(self, cluster_size): old_nodes = self.nodes.copy() nodes_before = self._get_nodes_primitive() self._docker_scale(cluster_size) nodes_after = self._get_nodes_primitive() new_ips = [':'.join(map(str, x)) for x in set(nodes_after) - set(nodes_before)] print(new_ips) master_ip_port = old_nodes[0]['ip_port'] master_ip, master_port = master_ip_port.split(':') master_conn = Redis(master_ip, master_port) print("Adding nodes to the cluster") for ip in new_ips: new_ip, new_port = ip.split(':') master_conn.execute_command('CLUSTER MEET', new_ip, new_port) print("Preventive fix") sleep(3) fix = subprocess.Popen(['ruby', 'redis-trib.rb', 'fix', master_ip_port], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL) fix.communicate(b'yes\n') fix.wait() sleep(3) new_nodes = [x for x in self._get_running_nodes() if x['ip_port'] in new_ips] slots_per_node = round(16384 / cluster_size) old_redises = {x[0]: Redis(x[0], x[1]) for x in (y['ip_port'].split(':') for y in old_nodes)} new_redises = [Redis(x[0], x[1]) for x in (y['ip_port'].split(':') for y in new_nodes)] slots_repartition = self._get_slots_repartition(list(old_redises.values())[0]) for dest_node, dest_redis, i in zip(new_nodes, new_redises, range(len(new_nodes))): slots = slots_repartition[i * slots_per_node: (i + 1) * slots_per_node] sources_ip = {x[1] for x in slots} for source_ip in sources_ip: slots_for_source = [x for x in slots if x[1] == source_ip] source_redis = old_redises[source_ip] self._transfer_slots(source_redis, slots_for_source[0][3], dest_redis, dest_node['id'], [x[0] for x in slots_for_source]) subprocess.check_call(['ruby', 'redis-trib.rb', 'info', master_ip_port])
def _get_slots_repartition(any_redis_conn: Redis): """ Returns a shuffled list of (slot_number, node_ip, node_port, node_id) """ # List of [10923, 16383, [b'10.0.0.4', 6379, b'f1dc21d0b7a24aaea3b3fcd0ef943a35fa2ebb42']] cluster_slots = any_redis_conn.execute_command('CLUSTER SLOTS') output = [] for slot in cluster_slots: for i in range(slot[0], slot[1] + 1): output.append((i, slot[2][0].decode(), slot[2][1], slot[2][2].decode())) random.shuffle(output) return output
def _get_slots_repartition(any_redis_conn: Redis): """ Returns a shuffled list of (slot_number, node_ip, node_port, node_id) """ # List of [10923, 16383, [b'10.0.0.4', 6379, b'f1dc21d0b7a24aaea3b3fcd0ef943a35fa2ebb42']] cluster_slots = any_redis_conn.execute_command('CLUSTER SLOTS') output = [] for slot in cluster_slots: for i in range(slot[0], slot[1] + 1): output.append( (i, slot[2][0].decode(), slot[2][1], slot[2][2].decode())) random.shuffle(output) return output