JedisCluster详解

JedisCluster详解redisredisclusterjedisjediscluster

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

在一些高并发+大数据量的场景中,经常会用到redis的cluster集群模式,此篇文章对redis的客户端jedis、jediscluster进行讲解,主要讲明白以下几个问题:

1、Jedis客户端是非线程安全的,为什么?需要注意什么?

2、JedisCluster的初始化过程,,和执行JedisCluster.get等指令经过了哪些流程

3、为什么cluster模式下,客户端无法支持pipline和mget等指令?但是某些场景下mget又是可以执行成功?

1、Jedis客户端是非线程安全的,为什么?需要注意什么?

原理解析: Jedis的请求流和响应流都是一个全局变量,如果同一个jedis同时被多个线程使用的话,比如A线程执行了jedis.get(“a”)  B线程执行了jedis.get(“b”),那么完全有可能出线,get(“a”)的指令拿到b结果的情况,会出现数据错乱。其实知道了有个全局变量之后,相信线程不安全的原因就很好理解了。

例子如下:

public static void main(String[] args) {

    Jedis jedis = new Jedis("localhost");
    int inti = 0;
    new Thread(()->{
        for (int j = 0;j<10;j++){
                jedis.set("a" + inti,String.valueOf(inti));
                System.out.println("a" + inti +" is:" + jedis.get("a" + inti));
        }
    }).start();

    int intij = 1;
    new Thread(()->{
        for (int j = 0;j<10;j++){
            jedis.set("a" + intij,String.valueOf(intij));
            System.out.println("a" + intij +" is:" + jedis.get("a" + intij));
        }
    }).start();

}

结果如下:

JedisCluster详解

预期结果 应该是“a0 is 0”或“a1 is 1” ,但是出现了“a0 is OK”   “a0 is 1” 的情况,这显然就是一个set指令的内容也被作为了get的指令了,a0 is 1也同理,B线程的结果反而被A线程收到了

怎么避免这个问题?

当然是一个线程用一个jedis,同一个jedis实例同一时刻只会被一个客户端线程使用即可。考虑到反复创建jedis是一个耗时操作,所以建议是使用池化技术,比如jedispool,这也是在哨兵模式下最常用的一个池化技术。但是,如果是jediscluster的话,单一的一个jedispool也就不够用了。

2、JedisCluster的初始化过程,,和执行JedisCluster.get等指令经过了哪些流程

上一部分讲解了,在哨兵模式下,使用jedispool来解决jedis多线程下线程不安全的问题,我们知道在哨兵模式下,其实只是有一个主节点的,如果没有额外程序控制读写分离的话,其实从节点只是作为备份(会有主从复制和故障转移),而不会被真正业务使用到的。这也说明一个问题:其实客户端只是跟一个实例节点在交互而已,这时使用一个jedispool,然后jedispool中的所有jedis对象都指向同一个主节点实例的ip和port,当然没有问题。但是在cluster集群模式下,情况就不一样了,因为此时是有多个主节点了,每个主节点还占据了一部分的槽位,那么也就意味着客户端在和redis交互的时候,是需要和多个主节点交互的,比如get(“a”)这个指令,可能是到了ip1:port1这个主节点,get(“b”)这个指令,是需要到ip2:port2这个主节点上执行的(要知道,最终都会归于jedis这个客户端),此时也就带来一个问题,客户端需要通过key值,来确认到底是要用哪个ip:port的jedis客户端,也就是说jediscluster的客户端至少需要维护一个主节点和jedispool的一个map:

Map<String,JedisPool>  nodes  

然后这个map的key值其实就是cluster每个主实例的ip:port(如果集群模式有3个主节点,那么这就是一个size=3的map),然后map的value其实就是对应ip:port的jedispool(本质大概就是一个200个指定ip:port 的jedis对象)

然后又考虑到,其实客户端(业务端)在执行指令的时候,其实是不会直接知道这个key到底会到哪个主实例上去执行的,客户端知道的只是一个key,而我们通过key,通过CRC16算法,是可以算的槽位的,然后我们知道key对应的槽位之后,也就能够反找到对应的主实例节点,所以会想到维护另一个Map对象

Map<Integer,Jedispool> slots

这个map的key值就是1-16384这个key,假设cluster是3个主节点的话,那么其实1-5461 5462-10922  10923-16384 为三组数据,然后第一组数据对应的jedispool其实都是nodes节点中的第一个ip:port组成的jedispool,以此类推。通过这个slots对象,客户端执行的get  set指令的时候,通过客户端传入的key值,通过crc16(key)%16384,就可以找到对应jedispool,然后拿到其中的一个jedis客户端,进行指令的执行。

上面的这些原理,其实正是JedisCluster 、 JedisClusterConnectionhandler、JedisClusterCacheInfo的执行步骤:

我们初始化一个JedisCLuster往往是通过这么一个步骤:

Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
jedisClusterNode.add(new HostAndPort("127.0.0.1", 7379));
JedisCluster jc = new JedisCluster(jedisClusterNode, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT,
    DEFAULT_REDIRECTIONS, "cluster", DEFAULT_CONFIG);
jc.set("foo", "bar");

assertEquals("bar", jc.get("foo"));

翻看源码,逐步跟进去构造方法,你可以发现这么一个时序图,这个就是jediscluster的真正的初始化过程:

JedisCluster详解

这里面,关键点在于:在初始化时,虽然只传递了一个主节点的信息(我们知道:redis cluster是区中心化的,传递一个节点就足够了),但是客户端通过initializeSlotCache方法会和redis集群做交互(具体可以看initializeSlotCache方法的执行步骤),通过一个command,拿到所有的主节点相关信息,以及每个主节点分别含有哪些槽位的信息,从而可以构造出上述我们说的Map<String,JedisPool>  nodes  、Map<Integer,Jedispool> slots这两个map,从而为后续客户端的指令执行打下基础

一个客户端指令的执行过程

JedisCluster详解

关键点在于:redis客户端通过crc16(key)%16384找到对应的槽位后,通过getConnectionFromSlot方法,可以拿到对应的jedispool,然后执行execute方法(其实就是jedis get  set方法),如果执行失败,大概率可能是出现了槽位的重新分配,那么此时需要更新替换操作,renewSlotCache之后再执行客户端指令。

3、为什么cluster模式下,客户端无法支持pipline和mget等指令?但是某些场景下mget又是可以执行成功?

问题1:为什么redis集群模式不支持pipline?

我们知道,pipline主要是为了解决多次网络IO的问题,将一系列指令发送到一个服务节点进行执行:

Jedis jedis =  new Jedis(String,  int);
Pipeline p = jedis.pipelined();   //pipline本质上是单个jedis的行为,所以只会有一个目标ip:port
p.set(key,value); //每个操作 都发送请求给redis-server
p.get(key,value);

p.sync(); // 这段代码获取所有的response

但是通过上面讲解,我们也知道在redis cluster模式下,会有很多个实例节点,而pipline的一系列指令中,必然包含了一系列的key值,这些key通过crc16(key)%16384算的的槽位完全可能不在同一个节点上,所以pipline指令在redis cluster模式下,天然不支持(当然,可以通过一些改造的方式实现比如Lettuce框架,但是至少从原理上来说的确pipline就是不是特别适合在redis 集群模式下使用的)

问题2:为什么redis集群模式,有时候又可以执行mget,有时候不行?

不行的原因,其实和pipline是类似的,无非就是多个key,对应的槽位是不在同一个实例节点上的

为什么有时候又可以执行mget这种批量指令?????

原因就是hash_tag,只要你的key中包含了 { } 这个标识符,那么在计算crc16的时候,就只会拿{}里面的内容进行计算,那么只要你保持{} 中的字符串是一样的,那么这些key就一定会落在同一个实例节点上,那么执行mget指令理所当然就没有问题了,  实践如下:

JedisCluster详解

 这里{%s}其实就是代表的一个用户id(比如openid),那么通过这种hash_tag,你就可以将一个用户的各类特征都存储在同一个redis实例中,在一些业务场景中,可能每个请求都需要拿到当前用户对应的各类特征,而这些特征存在于不同的key中,如果不用hashtag,那么必然意味着需要多次IO,分别去获取数据,但是使用了hashtag之后,不仅key变得比较有规则,而且还可以使用mget批量操作指令,高效获取批量特征,从而降低系统的整体延迟,提高cpu利用率

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/182022.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 如何查看Linux操作系统版本

    如何查看Linux操作系统版本参考地址:http://www.ggat.cn/newsInfo.html/71 如何查看Linux操作系统版本1.查看内核版本命令: [root@tg]#cat/proc/version Linuxversion3.10.0-693.2.2.el7.x86_64(builder@kbuilder.dev.centos.org)(gccversion4….

  • PyTorch 最新安装教程(2021-07-27)

    PyTorch 最新安装教程(2021-07-27)PyTorch最新安装教程(2021-07-27)前言1.安装Anaconda2.检查显卡,更新驱动3.创建PyTorch环境4.配置清华TUNA镜像源5.安装PyTorch6.测试前言万事开头难!这句话又一次被我验证。记得前不久刚陷入Tensorflow2.0的安装困境,这一次又被PyTorch搞哭辽。孩子太难了o(╥﹏╥)o,不过还好最终成功安装,感谢全网资源,感谢大佬们的博客!被我一次一次试了出来。1.安装AnacondaAnaconda是一个用于科学计算的P

  • 掌握这些常用Linux命令,一起提升工作效率

    万字长文分享Linux常用命令,一起提升工作效率。开始上班了,新一年的奋斗的之路启程了,要继续【奔赴山海,奔赴热爱】。

  • 推荐几个SQL在线学习网站

    推荐几个SQL在线学习网站适合的群体:SQL初学者,想要复习一下SQL基础知识的朋友,能无障碍阅读基础英文的朋友。SQL算是声明式的数据操纵语言,基本上感觉是对着数据库管理系统在喊:给我什么样的数据!似乎大部分人都不认为SQL十分困难。的确,入门十分简单。这里整理推荐几个我自己学习时用过的在线学习网站,可以帮助初学者快速入门SQL,在交互式的环境里学习,不用自己劳神搭建一个数据库,也不用担心损坏别人的数据库,就可…

  • 计算机的数学思想源头(回复“计算机数学”可下载PDF典藏版)「建议收藏」

    计算机的数学思想源头(回复“计算机数学”可下载PDF典藏版)「建议收藏」计算机的数学思想源头(回复“计算机数学”可下载PDF典藏版)…

  • keil更改黑色背景颜色「建议收藏」

    keil更改黑色背景颜色「建议收藏」1、先将keil安装目录下UV4中global文件复制出来留作备用,然后用记事本打开安装目录下的global文件2、将下面的内容全部替换global里的内容,然后保存。#propertiesforallfiletypesindent.automatic=1virtual.space=0view.whitespace=0view.endofline=0code….

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号