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利用率

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

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

(0)
上一篇 2022年10月14日 下午5:00
下一篇 2022年10月14日 下午5:00


相关推荐

  • 快递物流查询接口查询类API接口介绍_快递鸟

    快递物流查询接口查询类API接口介绍_快递鸟快递物流查询接口是指快递查询网对外开放的应用程序接口 开发人员能够通过调用该接口与快递查询网进行交互 并基于该接口开发自己的快递查询应用程序 目前比较常用的接口有快递鸟 菜鸟 快递 100 等

    2026年3月20日
    3
  • J1939协议之通俗易懂—-简介

    J1939协议之通俗易懂—-简介J1939简介 J1939协议简介J1939协议是由美国汽车工程师协会(SAE)(SAE协会简介)定义的一组标准。J1939标准用于卡车、公共汽车和移动液压等重型车辆。在许多方面,J1939标准类似于旧版J1708和J1587标准,但J1939标准协议建立在CAN(控制器区域网络,ISO11898)上。物理层(J1939/11)描述了针对客车的电气接口。数据链路层描述…

    2022年5月1日
    319
  • Python学习笔记22:Django下载并安装

    Python学习笔记22:Django下载并安装Django它是一个开源Web应用程序框架。由Python书面。通过MVC软件设计模式,这种模式M,视图V和控制器C。它最初是一个数字新闻内容为主的网站已经发展到管理劳伦斯出版集团。那是,CMS(内容

    2022年7月5日
    24
  • linux如何修改用户名_linux修改IP

    linux如何修改用户名_linux修改IP以下步骤都需要进入root权限操作suroot如果没有root权限,设置root密码sudopasswdrootsudovi/etc/passwd找到原先的用户名,将其改为自己的用户名sudovi/etc/shadow找到原先用户名(所有的名字都要改),改为自己的用户名将home目录下的用户目录改为自己的用户名:例如原先目录名为xxxx,现要改为用户yyyy。用命令mvxxxxyyyy即可。reboot重启即可发现用户名已经修

    2026年1月16日
    5
  • 永恒之蓝病毒解决方法蠕虫_永恒之蓝病毒解决方法

    永恒之蓝病毒解决方法蠕虫_永恒之蓝病毒解决方法辛亏“永恒之蓝”爆发在周末,绝大部分员工在家休息,为我们避免内网病毒爆发赢取了时间,整个周末一直加固已有系统和准备应急预案,避免周一发生大规模“永恒之蓝”在内部大面积爆发的可能。整体措施和预防传染病的原理类似:控制传染源、切断传播途径,保护易感人群。1控制传染源:所有的办公电脑开机前都必须网络隔离,所有计算机严禁插入U盘,一旦出现感染电脑,直接拔电源。就内网环境而言,一旦出现一例,大概率爆…

    2022年10月10日
    6
  • MySQL declare语句用法介绍

    MySQL declare语句用法介绍MySQLdeclare 语句是我们经常用到的语句 下文就为您举例说明了 MySQLdeclare 语句的用法 希望对您学习 MySQLdeclare 语句的使用能有所帮助 MySQLdeclare 语句是在复合语句中声明变量的指令 1 Examplewitht 两个 DECLARE 语句的实例 CREATEPROCED

    2026年3月19日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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