Redis从入门到放弃系列(十) Cluster
Redis从入门到放弃系列(十) Cluster
本文例子基于: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:贴着狗皮膏药在撸码),终于完结了.
感谢各位看官那么辛苦看我码字,真心感谢.
希望写的东西对各位看官有启发.

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
谈谈机器学习模型的可解释性
随着AI和机器学习的发展,越来越多的决策会交给自动化的机器学习算法来做。但是当我们把一些非常重要的决定交给机器的时候,我们真的放心么?当波音飞机忽略驾驶员的指令,决定义无反顾的冲向大地;当银行系统莫名其妙否决你的贷款申请的时候;当自动化敌我识别武器系统决定向无辜平民开火的时候;人类的内心应该是一万个草泥马飞过,大声的质问,“为什么?” 机器学习算法可以看成是如上图所示的黑盒子模型,训练数据流入黑盒子,训练出一个函数(这个函数也可以称之为模型),输入新的数据到该函数得出预测结果。关于模型的可解释性,就是要回答为什么的问题,如何解释该函数,它是如何预测的? 可解释的模型 在机器学习的众多算法中,有的模型很难解释,例如深度神经网络。深度神经网络可以拟合高度复杂的数据,拥有海量的参数,但是如何解释这些非常困难。但是还是有相当一部分算法是可以比较容易的解释的。 例如线性回归: 线性回归目标Y和特征X之间的关系如上图的公式所示。那么对于线性回归模型的解释就很简单,对于一个特定的特征Xi,每增加一个单位,目标Y增加βi。 线性回归简单易用,也能保证找到最优解。但是毕竟不是所有的问题都是线性的。 另外...
-
下一篇
BeeGFS源码分析1-元数据服务概要分析
元数据服务是BeeGFS中用来维护文件和目录关系及其属性配置的服务,其多线程epoll设计实现非常高效,主要流程如下: ConnAcceptor(PThread)类(一个线程)负责监听端口,并接受客户端连接,然后把;连接信息(包含接收的套接字)写入管道; StreamListenerV2(PThread)类(多个线程,可配置)从管道读取连接信息,使用epoll轮询接收数据,然后生成IncomingPreprocessedMsgWork(Work),写入MultiWorkQueue先进先出队列; Worker(PThread)类(多个线程,可配置)从MultiWorkQueue队列取出消息进行处理。 程序初始化 主函数 创建App对象,App对象是程序的主要载体: // fhgfs_meta\source\program\main.cpp #include "Program.h" int main(int argc, char** argv) { return Program::main(argc, argv); } // fhgfs_meta\source\program\Progra...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- MySQL数据库在高并发下的优化方案
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Dcoker安装(在线仓库),最新的服务器搭配容器使用
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Docker快速安装Oracle11G,搭建oracle11g学习环境