本文例子基于:5.0.4java
Redis Cluster集群高可用方案,去中心化,最基本三主多从,主从切换相似Sentinel,关于Sentinel内容能够查看编者另一篇【Redis从入门到放弃系列(九) Sentinel】.node
在Redis Cluster中,只存在index为0的数据库,并且其实Redis做为单线程,若是在同一个实例上建立多个库的话,也是须要上下文切换的.redis
因为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映射表,而后请求对应的地址.dom
当咱们在增长或者删除某个节点的时候,其实就只是将slot从某个节点移动到另一个节点.可使用一下命令来完成这一件事ide
在迁移的时候,redis节点会存在两种状态,一种是MIGRATING和IMPORTING,用于将slot从一个节点迁移到另一个节点.
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:贴着狗皮膏药在撸码),终于完结了.
感谢各位看官那么辛苦看我码字,真心感谢.
但愿写的东西对各位看官有启发.