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

栏目: Java · 发布时间: 6年前

内容简介:本文例子基于:5.0.4Redis Cluster集群高可用方案,去中心化,最基本三主多从,主从切换类似Sentinel,关于Sentinel内容可以查看编者另外一篇【在Redis Cluster中,只存在index为0的数据库,而且其实Redis作为单线程,如果在同一个实例上创建多个库的话,也是需要上下文切换的.

本文例子基于:5.0.4

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;
		}
	}
}
复制代码

写在最后

Redis从入门到放弃系列终于完结啦!!!!!!!!!!!

写博客,真的是非常耗时间,真的,本来星期六日要写的,然而因为某些问题而没有写出来(PS:纯粹是因为打游戏.hhhh),终于在今天痛定思痛,顶着脖子酸的压力(PS:贴着狗皮膏药在撸码),终于完结了.

感谢各位看官那么辛苦看我码字,真心感谢.

希望写的东西对各位看官有启发.

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

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Masterminds of Programming

Masterminds of Programming

Federico Biancuzzi、Chromatic / O'Reilly Media / 2009-03-27 / USD 39.99

Description Masterminds of Programming features exclusive interviews with the creators of several historic and highly influential programming languages. Think along with Adin D. Falkoff (APL), Jame......一起来看看 《Masterminds of Programming》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

MD5 加密
MD5 加密

MD5 加密工具

html转js在线工具
html转js在线工具

html转js在线工具