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)


相关推荐

  • 91p.wido.ws_tttzzzvipAPP

    91p.wido.ws_tttzzzvipAPP104.27.179.100北美地区IP网段:104.16.0.0-104.31.255.255更新时间:2014年07月19日18:47:41NetRange:104.16.0.0-104.31.255.255CIDR:104.16.0.0/12OriginAS:AS13335NetName:CLOUDFLARENETNetHandle:NET-104-16-0-0-1P…

    2022年10月30日
  • MySQL的四种事务隔离级别

    MySQL的四种事务隔离级别

  • python mkv转mp4,如何将mkv格式转换成mp4视频呢

    python mkv转mp4,如何将mkv格式转换成mp4视频呢在日常生活中都会使用到MKV视频文件的。MKV视频文件主要是视频文件、音频文件和字幕压制的。MKV视频一般在网上都是可以直接下载的。各种种子和磁链下载的也基本都是MKV视频。但有时可能会碰到视频播放错误。无法播放或者不支持文件播放的。一般都是可以通过转换视频格式修改的。那今天就教大家怎么将mkv格式转换成mp4格式吧。1、首先点击下方的立即下载按钮然后弹出下载迅捷视频转换器的下载框。下载打开之后,…

    2022年10月16日
  • 搜空白符号_excel筛选空白格

    搜空白符号_excel筛选空白格这是网络上很多人都在找的空白格符号,游戏ID、文本编辑、设计等等,这些是确实可用的,如果不小心帮到了你,点赞评论支持。➧ 空白格符号:【】【】【 】【】➧ 小空白格符号:【】【⠀】【⠀】【⠀】【】➧ 极小空白格符号:【⁡】【⁡】 注:【括号中】是有字符的带着括号复制,把括号删除即可下面是一些其他符号:▲÷↑↓◆◇⊙■□△▽─│♂♀▼≈←→◎☉★☆⊿※━┃ツΣ卐√↖↗●Θ◤◥︻〖〗┄┆℃℉°¢€£∞★×↙↘○⊕◣◢︼【】┅┇〓▂▃▄▅▆▇█▉▊▋▌▍▎▏の┈┊①②③④⑤⑥⑦⑧⑨⑩

  • mybatiscodehelperpro激活码[最新免费获取]

    (mybatiscodehelperpro激活码)2021最新分享一个能用的的激活码出来,希望能帮到需要激活的朋友。目前这个是能用的,但是用的人多了之后也会失效,会不定时更新的,大家持续关注此网站~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.cn/100143.html…

  • springcloud用feign调用非eruka注册接口报错「建议收藏」

    springcloud用feign调用非eruka注册接口报错「建议收藏」1.查了一大圈,后来发现把get请求改为post就好了,原来报4042.之所以这么耗时没有定位到问题,是因为确定不了报什么错,其实feign执行日志里面有报404.但是我以为那不是报错,而且他的报错跟正常报错不一样,没有第几行第几行throw异常那种,导致我以为那不是实际的异常,后来是直接debug源码,断点打到底层实际发送http请求的地方,发现返回信息也是404,才知道原来404了!!3….

发表回复

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

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