Redis from entry to abandonment series (ten) Cluster

Redis from entry to abandonment series (ten) Cluster

The example in this article is based on: 5.0.4

Redis Cluster cluster high-availability solution, decentralization, the most basic three masters and multiple slaves, master-slave switching is similar to Sentinel, about Sentinel content, please refer to the editor's other article [ Redis from entry to abandonment series (9) Sentinel ].

In Redis Cluster, there is only a database with index 0, and in fact, Redis is a single thread. If multiple databases are created on the same instance, context switching is also required.

slot

Since Redis Cluster uses 16384 slots to divide data, that is to say, the data you are currently inserting will exist on different nodes. In short, it does not support more complex multi-build operations (you can add hash tags to the key to solve) .

We say that Cluster divides data according to 16384 slots, so how to determine which node a key falls on?

//计算slot
HASH_SLOT = CRC16(key) mod 16384

Each node will have a part of the slot, and you will know where to find the corresponding node by obtaining the slot with the specific key above. However, in the network, everything will have unstable factors and network jitter.

When there is network jitter in the Cluster, when the time is too long, it may be offline. In fact, the principle is very similar to that in Sentinel, because it is implemented by relying on the Gossip protocol. The following configuration can be used to determine the offline time.

//节点持续timeout的时间,才认定该节点出现故障,需要进行主从切换,
cluster-node-timeout
//作为上面timeout的系数来放大时间
cluster-replica-validity-factor

Since the data is divided according to 16384 slots, when we are requesting a key to the wrong node and the key is not on the node, Redis will send us an error

-MOVED 3999 127.0.0.1:6381

The message is to remind us that the key should be 127.0.0.1the 3999 slot on this server. At this time, we need our redis client to correct the local slot mapping table, and then request the corresponding address.

Add or delete cluster nodes

When we add or delete a node, we just move the slot from a node to another node. You can use the following command to do this

  • CLUSTER ADDSLOTS slot1 [slot2] ... [slotN]
  • CLUSTER DELSLOTS slot1 [slot2] ... [slotN]
  • CLUSTER SETSLOT slot NODE node
  • CLUSTER SETSLOT slot MIGRATING node
  • CLUSTER SETSLOT slot IMPORTING node Sometimes operation and maintenance need to migrate some data of redis nodes. The official redis-trib tool is provided to complete this.

When migrating, the redis node will have two states, one is MIGRATING and IMPORTING, which are used to migrate the slot from one node to another.

  • When the node state is set to MIGRATING, all queries related to this hash slot will be accepted, but only if the key in question exists, otherwise the query will be forwarded to the node that is the migration target using -Ask redirection.
  • When the node state is set to IMPORTING, the node will accept all queries related to this hash slot, but only if the request is preceded by an ASKING command. If the client does not issue the ASKING command, the query will be redirected to the real hash slot owner with a -MOVED redirect error

Multi-threaded batch get/delete

public class RedisUtils {

	private static final String LOCK_SUCCESS = "OK";
	private static final String SET_IF_NOT_EXIST = "NX";
	private static final String SET_WITH_EXPIRE_TIME = "PX";
	private static final Long RELEASE_SUCCESS = 1L;

	private final ThreadLocal<String> requestId = new ThreadLocal<>();

	private final static ExecutorService executorService = new ThreadPoolExecutor(
			//核心线程数量
			1,
			//最大线程数量
			8,
			//当线程空闲时,保持活跃的时间
			1000,
			//时间单元 ,毫秒级
			TimeUnit.MILLISECONDS,
			//线程任务队列
			new LinkedBlockingQueue<>(1024),
			//创建线程的工厂
			new RedisTreadFactory("redis-batch"));

	@Autowired
	private JedisCluster jedisCluster;

	public String set(String key, String value) {
		return jedisCluster.set(key, value);
	}

	public String get(String key) {
		return jedisCluster.get(key);
	}

	public Map<String, String> getBatchKey(List<String> keys) {
		Map<Jedis, List<String>> nodeKeyListMap = jedisKeys(keys);
		//结果集
		Map<String, String> resultMap = new HashMap<>();
		CompletionService<Map<String,String>> batchService = new ExecutorCompletionService(executorService);
		nodeKeyListMap.forEach((k,v)->{
			batchService.submit(new BatchGetTask(k,v));
		});
		nodeKeyListMap.forEach((k,v)->{
			try {
				resultMap.putAll(batchService.take().get());
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace();
			}
		});
		return resultMap;
	}

	public boolean lock(String lockKey, long expireTime){
		String uuid = UUID.randomUUID().toString();
		requestId.set(uuid);
		String result = jedisCluster.set(lockKey, uuid, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
		return LOCK_SUCCESS.equals(result);
	}

	public boolean unLock(String lockKey){
		String uuid = requestId.get();
		String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
		Object result = jedisCluster.eval(script, Collections.singletonList(lockKey), Collections.singletonList(uuid));
		requestId.remove();
		return RELEASE_SUCCESS.equals(result);
	}

	private Map<Jedis, List<String>> jedisKeys(List<String> keys){
		Map<Jedis, List<String>> nodeKeyListMap = new HashMap<>();
		for (String key : keys) {
			//计算slot
			int slot = JedisClusterCRC16.getSlot(key);
			Jedis jedis = jedisCluster.getConnectionFromSlot(slot);
			if (nodeKeyListMap.containsKey(jedis)) {
				nodeKeyListMap.get(jedis).add(key);
			} else {
				nodeKeyListMap.put(jedis, Arrays.asList(key));
			}
		}
		return nodeKeyListMap;
	}

	public long delBatchKey(List<String> keys){
		Map<Jedis, List<String>> nodeKeyListMap = jedisKeys(keys);
		CompletionService<Long> batchService = new ExecutorCompletionService(executorService);
		nodeKeyListMap.forEach((k,v)->{
			batchService.submit(new BatchDelTask(k,v));
		});
		Long result = 0L;
		for (int i=0;i<nodeKeyListMap.size();i++){
			try {
				result += batchService.take().get();
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace();
			}
		}
		return result;
	}

	class BatchGetTask implements Callable<Map<String,String>>{

		private Jedis jedis;

		private List<String> keys;

		private BatchGetTask(Jedis jedis, List<String> keys) {
			this.jedis = jedis;
			this.keys = keys;
		}

		@Override
		public Map<String, String> call() throws Exception {
			Map<String, String> resultMap = new HashMap<>();
			String[] keyArray = keys.toArray(new String[]{});
			try {
				List<String> nodeValueList = jedis.mget(keyArray);
				for (int i = 0; i < keys.size(); i++) {
					resultMap.put(keys.get(i),nodeValueList.get(i));
				}
			}finally {
				jedis.close();
			}
			return resultMap;
		}
	}

	class BatchDelTask implements Callable<Long>{

		private Jedis jedis;

		private List<String> keys;

		private BatchDelTask(Jedis jedis, List<String> keys) {
			this.jedis = jedis;
			this.keys = keys;
		}

		@Override
		public Long call() throws Exception {
			String[] keyArray = keys.toArray(new String[]{});
			try {
				return jedis.del(keyArray);
			}finally {
				jedis.close();
			}
		}
	}

	 static class RedisTreadFactory implements ThreadFactory{

		private final AtomicInteger threadNumber = new AtomicInteger(0);

		private final String namePredix;

		public RedisTreadFactory(String namePredix) {
			this.namePredix = namePredix +"-";
		}

		@Override
		public Thread newThread(Runnable r) {
			Thread t = new Thread( r,namePredix + threadNumber.getAndIncrement());
			if (t.isDaemon())
				t.setDaemon(true);
			if (t.getPriority() != Thread.NORM_PRIORITY)
				t.setPriority(Thread.NORM_PRIORITY);
			return t;
		}
	}
}

write at the end

Redis's series from entry to abandonment is finally over! ! ! ! ! ! ! ! ! ! !

Writing a blog is really time-consuming, really, I was going to write it on Saturday and Sunday, but I didn't write it due to some problems (PS: It's purely because of playing games. hhhh), I finally came to a painful conclusion today, and my neck is sore. The pressure (PS: sticking to the dog skin plaster while masturbating) is finally over.

Thank you for watching me so hard to read my code, I really appreciate it.

I hope what I wrote will inspire you.

{{o.name}}
{{m.name}}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=324031734&siteId=291194637