Redis从入门到放弃系列(十) Cluster

Redis Cluster集群高可用方案,去中心化,最基本三主多从,主从切换类似Sentinel,关于Sentinel内容可以查看编者另外一篇【Redis从入门到放弃系列(九) Sentinel】.

在Redis Cluster中,只存在index为0的数据库,而且其实Redis作为单线程,如果在同一个实例上创建多个库的话,也是需要上下文切换的.

slot
由于Redis Cluster是采用16384个slot来划分数据的,也就是说你当前插入的数据会存在不同的节点上,简而言之不支持比较复杂的多建操作(可以对key打上hash tags来解决).

我们说Cluster是按照16384个slot来划分数据的,那么是如何来确定一个key落在那个节点上呢?

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

每个节点会拥有一部分的slot,通过上述获取到具体key的slot即知道应该去哪儿找对应的节点啦.可是在网络中,一切都会有不存稳定因素,网络抖动.

当在Cluster中存在网络抖动的时候,当时间过长,有可能产生下线,其实原理跟Sentinel里面讲的很相似,因为都是依赖Gossip协议来实现的.可以通过以下配置来设置确定下线的时间.

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

由于数据是按照16384个slot去划分的,那么当我们在请求某个key到错误的节点,这时候key不在该节点上,Redis会向我们发送一个错误

-MOVED 3999 127.0.0.1:6381

该消息是提示我们该key应该是存在127.0.0.1这台服务器上面的3999slot,这时候就需要我们的redis客户端去纠正本地的slot映射表,然后请求对应的地址.

增删集群节点
当我们在增加或者删除某个节点的时候,其实就只是将slot从某个节点移动到另外一个节点.可以使用一下命令来完成这一件事

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 有时候运维需要对redis节点的某些数据做迁移,官方提供了redis-trib工具来完成这件事情。
在迁移的时候,redis节点会存在两种状态,一种是MIGRATING和IMPORTING,用于将slot从一个节点迁移到另外一个节点.

节点状态设置为MIGRATING时,将接受与此散列槽有关的所有查询,但仅当有问题的key存在时才能接受,否则将使用-Ask重定向将查询转发到作为迁移目标的节点。
节点状态设置为IMPORTING时,节点将接受与此哈希槽有关的所有查询,但前提是请求前面有ASKING命令。如果客户端没有发出ASKING命令,查询将通过-MOVED重定向错误重定向到真正的散列槽所有者
多线程批量获取/删除
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;
	}
}

}
深圳网站建设www.sz886.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值