700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Java中高级核心知识全面解析——Redis(集群【概述{主从复制 哨兵 集群化} 数据分区

Java中高级核心知识全面解析——Redis(集群【概述{主从复制 哨兵 集群化} 数据分区

时间:2023-05-04 04:56:55

相关推荐

Java中高级核心知识全面解析——Redis(集群【概述{主从复制 哨兵 集群化} 数据分区

目录

一、[集群]入门实践教程1.Redis 集群概述1)Redis 主从复制2)Redis 哨兵3)Redis 集群化2.主从复制1)主从复制主要的作用2)快速体验①、第一步:本地启动两个节点②、第二步:建立复制③、第三步:观察效果④、第四步:断开复制3)实现原理简析①、身份验证 | 主从复制安全问题②、SYNC 命令是一个非常耗费资源的操作③、PSYNC 命令的引入3.Redis Sentinel 哨兵1)快速体验①、第一步:创建主从节点配置文件并启动②、第二步:创建哨兵节点配置文件并启动③、第三步:演示故障转移2)客户端访问哨兵系统代码演示①、客户端原理3)新的主服务器是怎样被挑选出来的?4.Redis 集群1)基本原理2)集群的主要作用3)快速体验①、第一步:创建集群节点配置文件②、第二步:分别启动 6 个 Redis 实例③、第三步:建立集群④、第四步:验证集群5)数据分区方案简析①、方案一:哈希值 % 节点数②、方案二:一致性哈希分区③、方案三:带有虚拟节点的一致性哈希分区6)节点通信机制简析①、两个端口②Gossip 协议③、消息类型7)数据结构简析①、clusterNode 结构②、clusterState 结构

一、[集群]入门实践教程

1.Redis 集群概述

1)Redis 主从复制

到目前为止,我们所学习的 Redis 都是单机版的,这也就意味着一旦我们所依赖的 Redis 服务宕机了,我们的主流程也会受到一定的影响,这当然是我们不能够接受的。

所以一开始我们的想法是:搞一台备用机。这样我们就可以在一台服务器出现问题的时候切换动态地到另一台去:

幸运的是,两个节点数据的同步我们可以使用 Redis 的主从同步功能帮助到我们,这样一来,有个备份,心里就踏实多了。

2)Redis 哨兵

后来因为某种神秘力量,Redis 老会在莫名其妙的时间点出问题 (比如半夜 2 点),我总不能 24 小时时刻守在电脑旁边切换节点吧,于是另一个想法又开始了:给所有的节点找一个**“管家”,自动帮我监听照顾节点的状态并切换:

这大概就是Redis 哨兵 (Sentinel)** 的简单理解啦。什么?管家宕机了怎么办?相较于有大量请求的Redis服务来说,管家宕机的概率就要小得多啦… 如果真的宕机了,我们也可以直接切换成当前可用的节点保证可用…

3)Redis 集群化

好了,通过上面的一些解决方案我们对 Redis 的稳定性稍微有了一些底气了,但单台节点的计算能力始终有限,所谓人多力量大,如果我们把多个节点组合成 一个可用的工作节点,那就大大增加了Redis 的高可用可扩展分布式容错等特性:

2.主从复制

主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。前者称为 主节点(master),后者称为从节点(slave)。且数据的复制是单向的,只能由主节点到从节点。Redis 主从复制支持主从同步从从同步两种,后者是 Redis 后续版本新增的功能,以减轻主节点的同步负担。

1)主从复制主要的作用

数据冗余: 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。故障恢复: 当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复 (实际上是一种服务的冗余)。负载均衡: 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务 (即写 Redis 数据时应用连接主节点,读 Redis 数据时应用连接从节点),分担服务器负载。尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 Redis 服务器的并发量。高可用基石: 除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是 Redis 高可用的基础。

2)快速体验

Redis中,用户可以通过执行SLAVEOF命令或者设置slaveof选项,让一个服务器去复制另一个服务器,以下三种方式是完全等效的:

配置文件:在从服务器的配置文件中加入:slaveof <masterip> <masterport>启动命令:redis-server 启动命令后加入--slaveof <masterip> <masterport>客户端命令:Redis 服务器启动后,直接通过客户端执行命令:slaveof <masterip> <masterport>,让该 Redis 实例成为从节点。

需要注意的是:主从复制的开启,完全是在从节点发起的,不需要我们在主节点做任何事情。

①、第一步:本地启动两个节点

在正确安装好 Redis 之后,我们可以使用redis-server --port <port>的方式指定创建两个不同端口的 Redis 实例,例如,下方我分别创建了一个63796380的两个 Redis 实例:

# 创建一个端口为 6379 的 Redis 实例 redis-server --port 6379 # 创建一个端口为 6380 的 Redis 实例 redis-server --port 6380

此时两个 Redis 节点启动后,都默认为主节点

②、第二步:建立复制

我们在6380端口的节点中执行slaveof命令,使之变为从节点:

# 在 6380 端口的 Redis 实例中使用控制台 redis-cli -p 6380 # 成为本地 6379 端口实例的从节点 127.0.0.1:6380> SLAVEOF 127.0.0.1ø 6379 OK

③、第三步:观察效果

下面我们来验证一下,主节点的数据是否会复制到从节点之中:

先在从节点中查询一个不存在的 key:

127.0.0.1:6380> GET mykey (nil)

再在主节点中添加这个 key:

127.0.0.1:6379> SET mykey myvalue OK

此时再从从节点中查询,会发现已经从主节点同步到从节点

127.0.0.1:6380> GET mykey "myvalue"

④、第四步:断开复制

通过slaveof <masterip> <masterport>命令建立主从复制关系以后,可以通过slaveof no one断开。需要注意的是,从节点断开复制后,不会删除已有的数据,只是不再接受主节点新的数据变化。

从节点执行slaveof no one之后,从节点和主节点分别打印日志如下:

# 从节点打印日志 61496:M 17 Mar 08:10:22.749 # Connection with master lost. 61496:M 17 Mar 08:10:22.749 * Caching the disconnected master state. 61496:M 17 Mar 08:10:22.749 * Discarding previously cached master state. 61496:M 17 Mar 08:10:22.749 * MASTER MODE enabled (user request from 'id=4 addr=127.0.0.1:55096 fd=8 name= age=1664 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=34 qbuf-free=32734 obl=0 oll=0 omem=0 events=r cmd=slaveof') # 主节点打印日志 61467:M 17 Mar 08:10:22.749 # Connection with replica 127.0.0.1:6380 lost.

3)实现原理简析

为了节省篇幅,我把主要的步骤都浓缩在了上图中,其实也可以 **简化成三个阶段:准备阶段-数据同步阶段-命令传播阶段。**下面我们来进行一些必要的说明。

①、身份验证 | 主从复制安全问题

在上面的快速体验过程中,你会发现slaveof这个命令居然不需要验证?这意味着只要知道了 ip 和端口就可以随意拷贝服务器上的数据了?

那当然不能够了,我们可以通过在主节点配置requirepass来设置密码,这样就必须在从节点中对应配置好masterauth参数 (与主节点requirepass保持一致) 才能够进行正常复制了。

②、SYNC 命令是一个非常耗费资源的操作

每次执行SYNC命令,主从服务器需要执行如下动作:

主服务器需要执行BGSAVE命令来生成 RDB 文件,这个生成操作会消耗主服务器大量的CPU内存磁盘 I/O的资源;主服务器需要将自己生成的 RDB 文件 发送给从服务器,这个发送操作会消耗主服务器大量的网络资源 (带宽和流量),并对主服务器响应命令请求的时间产生影响;接收到 RDB 文件的从服务器需要载入主服务器发来的 RBD 文件,并且在载入期间,从服务器会因为阻塞而没办法处理命令请求

特别是当出现断线重复制的情况是时,为了让从服务器补足断线时确实的那一小部分数据,却要执行一次如此耗资源的SYNC命令,显然是不合理的。

③、PSYNC 命令的引入

所以在Redis 2.8中引入了PSYNC命令来代替SYNC,它具有两种模式:

全量复制: 用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作;部分复制: 用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。需要注意的是,如果网络中断时间过长,导致主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制;

部分复制的原理主要是靠主从节点分别维护一个复制偏移量,有了这个偏移量之后断线重连之后一比较,之后就可以仅仅把从服务器断线之后确实的这部分数据给补回来了。

3.Redis Sentinel 哨兵

上图展示了一个典型的哨兵架构图,它由两部分组成,哨兵节点和数据节点:

哨兵节点: 哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的 Redis 节点,不存储数据;数据节点: 主节点和从节点都是数据节点;

在复制的基础上,哨兵实现了 自动化的故障恢复 功能,下方是官方对于哨兵功能的描述:

监控(Monitoring): 哨兵会不断地检查主节点和从节点是否运作正常。自动故障转移(Automatic failover): 当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。配置提供者(Configuration provider): 客户端在初始化时,通过连接哨兵来获得当前 Redis服务的主节点地址。通知(Notification): 哨兵可以将故障转移的结果发送给客户端。

其中,监控和自动故障转移功能,使得哨兵可以及时发现主节点故障并完成转移。而配置提供者和通知功能,则需要在与客户端的交互中才能体现。

1)快速体验

①、第一步:创建主从节点配置文件并启动

正确安装好 Redis 之后,我们去到 Redis 的安装目录 (mac 默认在/usr/local/),找到redis.conf文件复制三份分别命名为redis-master.conf / redis-slave1.conf / redis-slave2.conf,分别作为1个主节点和2个从节点的配置文件 (下图演示了我本机的redis.conf文件的位置)

打开可以看到这个.conf后缀的文件里面有很多说明的内容,全部删除然后分别改成下面的样子:

#redis-master.conf port 6379 daemonize yes logfile "6379.log" dbfilename "dump-6379.rdb" #redis-slave1.conf port 6380 daemonize yes logfile "6380.log" dbfilename "dump-6380.rdb" slaveof 127.0.0.1 6379 #redis-slave2.conf port 6381 daemonize yes logfile "6381.log" dbfilename "dump-6381.rdb" slaveof 127.0.0.1 6379

然后我们可以执行redis-server <config file path>来根据配置文件启动不同的 Redis 实例,依次启动主从节点:

redis-server /usr/local/redis-5.0.3/redis-master.conf redis-server /usr/local/redis-5.0.3/redis-slave1.conf redis-server /usr/local/redis-5.0.3/redis-slave2.conf

节点启动后,我们执行redis-cli默认连接到我们端口为6379的主节点执行info Replication检查一下主从状态是否正常:(可以看到下方正确地显示了两个从节点)

小插曲:

更多阿里、腾讯、美团、京东等一线互联网大厂Java面试真题;包含:基础、并发、锁、JVM、设计模式、数据结构、反射/IO、数据库、Redis、Spring、消息队列、分布式、Zookeeper、Dubbo、Mybatis、Maven、面经等。

更多Java程序员技术进阶小技巧;例如高效学习(如何学习和阅读代码、面对枯燥和量大的知识)高效沟通(沟通方式及技巧、沟通技术)

更多Java大牛分享的一些职业生涯分享文档

请点击这里添加》》》》》》》》》社群,免费获取

比你优秀的对手在学习,你的仇人在磨刀,你的闺蜜在减肥,隔壁老王在练腰, 我们必须不断学习,否则我们将被学习者超越!

趁年轻,使劲拼,给未来的自己一个交代!

②、第二步:创建哨兵节点配置文件并启动

按照上面同样的方法,我们给哨兵节点也创建三个配置文件。(哨兵节点本质上是特殊的 Redis 节点,所以配置几乎没什么差别,只是在端口上做区分就好)

# redis-sentinel-1.conf port 26379 daemonize yes logfile "26379.log" sentinel monitor mymaster 127.0.0.1 6379 2 # redis-sentinel-2.conf port 26380 daemonize yes logfile "26380.log" sentinel monitor mymaster 127.0.0.1 6379 2 # redis-sentinel-3.conf port 26381 daemonize yes logfile "26381.log" sentinel monitor mymaster 127.0.0.1 6379 2

其中,sentinel monitor mymaster 127.0.0.1 6379 2配置的含义是:该哨兵节点监控127.0.0.1:6379这个主节点,该主节点的名称是mymaster,最后的2的含义与主节点的故障判定有关:至少需要2个哨兵节点同意,才能判定主节点故障并进行故障转移。

执行下方命令将哨兵节点启动起来:

redis-server /usr/local/redis-5.0.3/redis-sentinel-1.conf --sentinel redis-server /usr/local/redis-5.0.3/redis-sentinel-2.conf --sentinel redis-server /usr/local/redis-5.0.3/redis-sentinel-3.conf --sentinel

使用redis-cil工具连接哨兵节点,并执行info Sentinel命令来查看是否已经在监视主节点了:

# 连接端口为 26379 的 Redis 节点 ➜ ~ redis-cli -p 26379 127.0.0.1:26379> info Sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3

此时你打开刚才写好的哨兵配置文件,你还会发现出现了一些变化:

③、第三步:演示故障转移

首先,我们使用kill -9命令来杀掉主节点,同时在哨兵节点中执行info Sentinel命令来观察故障节点的过程:

➜ ~ ps aux | grep 6379 longtao 74529 0.3 0.0 4346936 2132 ?? Ss 10:30上午 0:03.09 redis-server *:26379 [sentinel] longtao 73541 0.2 0.0 4348072 2292 ?? Ss 10:18上午 0:04.79 redis-server *:6379 longtao 75521 0.0 0.0 4286728 728 s008 S+ 10:39上午 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git -- exclude-dir=.hg --exclude-dir=.svn 6379 longtao 74836 0.0 0.0 4289844 944 s006 S+ 10:32上午 0:00.01 redis-cli -p 26379 ➜ ~ kill -9 73541

如果刚杀掉瞬间在哨兵节点中执行info命令来查看,会发现主节点还没有切换过来,因为哨兵发现主节点故障并转移需要一段时间:

# 第一时间查看哨兵节点发现并未转移,还在 6379 端口 127.0.0.1:26379> info Sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3

一段时间之后你再执行info命令,查看,你就会发现主节点已经切换成了6381端口的从节点:

# 过一段时间之后在执行,发现已经切换了 6381 端口 127.0.0.1:26379> info Sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=127.0.0.1:6381,slaves=2,sentinels=3

但同时还可以发现,哨兵节点认为新的主节点仍然有两个从节点 (上方 slaves=2),这是因为哨兵在将6381切换成主节点的同时,将6379节点置为其从节点。虽然6379从节点已经挂掉,但是由于哨兵并不会对从节点进行客观下线,因此认为该从节点一直存在。当6379节点重新启动后,会自动变成6381节点的从节点。

另外,在故障转移的阶段,哨兵和主从节点的配置文件都会被改写:

对于主从节点: 主要是slaveof配置的变化,新的主节点没有了slaveof配置,其从节点则slaveof新的主节点。对于哨兵节点: 除了主从节点信息的变化,纪元(epoch) (记录当前集群状态的参数) 也会变化,纪元相关的参数都 +1 了。

2)客户端访问哨兵系统代码演示

上面我们在快速体验中主要感受到了服务端自己对于当前主从节点的自动化治理,下面我们以 Java 代码为例,来演示一下客户端如何访问我们的哨兵系统:

public static void testSentinel() throws Exception {String masterName = "mymaster"; Set<String> sentinels = new HashSet<>(); sentinels.add("127.0.0.1:26379"); sentinels.add("127.0.0.1:26380"); sentinels.add("127.0.0.1:26381"); // 初始化过程做了很多工作 JedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels); Jedis jedis = pool.getResource(); jedis.set("key1", "value1"); pool.close(); }

①、客户端原理

Jedis 客户端对哨兵提供了很好的支持。如上述代码所示,我们只需要向 Jedis 提供哨兵节点集合和masterName,构造JedisSentinelPool对象,然后便可以像使用普通 Redis 连接池一样来使用了:通过pool.getResource()获取连接,执行具体的命令。

在整个过程中,我们的代码不需要显式的指定主节点的地址,就可以连接到主节点;代码中对故障转移没有任何体现,就可以在哨兵完成故障转移后自动的切换主节点。之所以可以做到这一点,是因为在JedisSentinelPool的构造器中,进行了相关的工作;主要包括以下两点:

遍历哨兵节点,获取主节点信息: 遍历哨兵节点,通过其中一个哨兵节点 +masterName获得主节点的信息;该功能是通过调用哨兵节点的sentinel get-master-addr-by-name命令实现;增加对哨兵的监听: 这样当发生故障转移时,客户端便可以收到哨兵的通知,从而完成主节点的切换。具体做法是:利用 Redis 提供的发布订阅 功能,为每一个哨兵节点开启一个单独的线程,订阅哨兵节点的 + switch-master 频道,当收到消息时,重新初始化连接池。

3)新的主服务器是怎样被挑选出来的?

故障转移操作的第一步要做的就是在已下线主服务器属下的所有从服务器中,挑选出一个状态良好、数据完整的从服务器,然后向这个从服务器发送slaveof no one命令,将这个从服务器转换为主服务器。但是这个从服务器是怎么样被挑选出来的呢?

简单来说 Sentinel 使用以下规则来选择新的主服务器:

在失效主服务器属下的从服务器当中, 那些被标记为主观下线、已断线、或者最后一次回复 PING 命令的时间大于五秒钟的从服务器都会被淘汰。在失效主服务器属下的从服务器当中, 那些与失效主服务器连接断开的时长超过 down-after 选项指定的时长十倍的从服务器都会被淘汰。在经历了以上两轮淘汰之后剩下来的从服务器中, 我们选出复制偏移量(replication offset)最大的那个从服务器作为新的主服务器;如果复制偏移量不可用,或者从服务器的复制偏移量相同,那么带有最小运行 ID的那个从服务器成为新的主服务器。

4.Redis 集群

上图 展示了Redis Cluster典型的架构图,集群中的每一个 Redis 节点都互相两两相连,客户端任意直连到集群中的任意一台,就可以对其他 Redis 节点进行读写的操作。

1)基本原理

Redis 集群中内置了16384个哈希槽。当客户端连接到 Redis 集群之后,会同时得到一份关于这个集群的配置信息,当客户端具体对某一个key值进行操作时,会计算出它的一个 Hash 值,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,Redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。

再结合集群的配置信息就能够知道这个key值应该存储在哪一个具体的 Redis 节点中,如果不属于自己管,那么就会使用一个特殊的MOVED命令来进行一个跳转,告诉客户端去连接这个节点以获取数据:

GET x -MOVED 3999 127.0.0.1:6381

MOVED指令第一个参数3999key对应的槽位编号,后面是目标节点地址,MOVED命令前面有一个减号,表示这是一个错误的消息。客户端在收到MOVED指令后,就立即纠正本地的槽位映射表,那么下一次再访问key时就能够到正确的地方去获取了。

2)集群的主要作用

数据分区: 数据分区 (或称数据分片) 是集群最核心的功能。集群将数据分散到多个节点,一方面突破了 Redis 单机内存大小的限制,存储容量大大增加另一方面每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力。Redis 单机内存大小受限问题,在介绍持久化和主从复制时都有提及,例如,如果单机内存太大,bgsavebgrewriteaoffork操作可能导致主进程阻塞,主从环境下主机切换时可能导致从节点长时间无法提供服务,全量复制阶段主节点的复制缓冲区可能溢出……高可用: 集群支持主从复制和主节点的自动故障转移与哨兵类似),当任一节点发生故障时,集群仍然可以对外提供服务。

3)快速体验

①、第一步:创建集群节点配置文件

首先我们找一个地方创建一个名为redis-cluster的目录:

小插曲:

更多阿里、腾讯、美团、京东等一线互联网大厂Java面试真题;包含:基础、并发、锁、JVM、设计模式、数据结构、反射/IO、数据库、Redis、Spring、消息队列、分布式、Zookeeper、Dubbo、Mybatis、Maven、面经等。

更多Java程序员技术进阶小技巧;例如高效学习(如何学习和阅读代码、面对枯燥和量大的知识)高效沟通(沟通方式及技巧、沟通技术)

更多Java大牛分享的一些职业生涯分享文档

请点击这里添加》》》》》》》》》社群,免费获取

比你优秀的对手在学习,你的仇人在磨刀,你的闺蜜在减肥,隔壁老王在练腰, 我们必须不断学习,否则我们将被学习者超越!

趁年轻,使劲拼,给未来的自己一个交代!

mkdir -p ~/Desktop/redis-cluster

然后按照上面的方法,创建六个配置文件,分别命名为:redis_7000.conf/redis_7001.confredis_7005.conf,然后根据不同的端口号修改对应的端口值就好了:

# 后台执行 daemonize yes # 端口号 port 7000 # 为每一个集群节点指定一个 pid_file pidfile ~/Desktop/redis-cluster/redis_7000.pid # 启动集群模式 cluster-enabled yes # 每一个集群节点都有一个配置文件,这个文件是不能手动编辑的。确保每一个集群节点的配置文件不通 cluster-config-file nodes-7000.conf # 集群节点的超时时间,单位:ms,超时后集群会认为该节点失败 cluster-node-timeout 5000 # 最后将 appendonly 改成 yes(AOF 持久化) appendonly yes

记得把对应上述配置文件中根端口对应的配置都修改掉 (port/ pidfile/ cluster-config-file)。

②、第二步:分别启动 6 个 Redis 实例

redis-server ~/Desktop/redis-cluster/redis_7000.conf redis-server ~/Desktop/redis-cluster/redis_7001.conf redis-server ~/Desktop/redis-cluster/redis_7002.conf redis-server ~/Desktop/redis-cluster/redis_7003.conf redis-server ~/Desktop/redis-cluster/redis_7004.conf redis-server ~/Desktop/redis-cluster/redis_7005.conf

然后执行ps -ef | grep redis查看是否启动成功:

可以看到6个 Redis 节点都以集群的方式成功启动了,但是现在每个节点还处于独立的状态,也就是说它们每一个都各自成了一个集群,还没有互相联系起来,我们需要手动地把他们之间建立起联系。

③、第三步:建立集群

执行下列命令:

redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

这里稍微解释一下这个--replicas 1的意思是:我们希望为集群中的每个主节点创建一个从节点。

观察控制台输出:

看到[OK]的信息之后,就表示集群已经搭建成功了,可以看到,这里我们正确地创建了三主三从的集群。

④、第四步:验证集群

我们先使用redic-cli任意连接一个节点:

redis-cli -c -h 127.0.0.1 -p 7000 127.0.0.1:7000>

-c表示集群模式;-h指定 ip 地址;-p指定端口。

然后随便 set 一些值观察控制台输入:

127.0.0.1:7000> SET name wmyskxz -> Redirected to slot [5798] located at 127.0.0.1:7001 OK127.0.0.1:7001>

可以看到这里 Redis 自动帮我们进行了Redirected操作跳转到了7001这个实例上。

我们再使用cluster info(查看集群信息) 和cluster nodes(查看节点列表) 来分别看看:(任意节点输入均可)

127.0.0.1:7001> CLUSTER INFO cluster_state:ok cluster_slots_assigned:16384 cluster_slots_ok:16384 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_known_nodes:6 cluster_size:3 cluster_current_epoch:6 cluster_my_epoch:2 cluster_stats_messages_ping_sent:1365 cluster_stats_messages_pong_sent:1358 cluster_stats_messages_meet_sent:4 cluster_stats_messages_sent:2727 cluster_stats_messages_ping_received:1357 cluster_stats_messages_pong_received:1369 cluster_stats_messages_meet_received:1 cluster_stats_messages_received:2727 127.0.0.1:7001> CLUSTER NODES 56a04742f36c6e84968cae871cd438935081e86f 127.0.0.1:7003@17003 slave 4ec8c022e9d546c9b51deb9d85f6cf867bf73db6 0 1584428884000 4 connected 4ec8c022e9d546c9b51deb9d85f6cf867bf73db6 127.0.0.1:7000@17000 master - 0 1584428884000 1 connected 0-5460 e2539c4398b8258d3f9ffa714bd778da107cb2cd 127.0.0.1:7005@17005 slave a3406db9ae7144d17eb7df5bffe8b70bb5dd06b8 0 1584428885222 6 connected d31cd1f423ab1e1849cac01ae927e4b6950f55d9 127.0.0.1:7004@17004 slave 236cefaa9cdc295bc60a5bd1aed6a7152d4f384d 0 1584428884209 5 connected 236cefaa9cdc295bc60a5bd1aed6a7152d4f384d 127.0.0.1:7001@17001 myself,master - 0 1584428882000 2 connected 5461-10922 a3406db9ae7144d17eb7df5bffe8b70bb5dd06b8 127.0.0.1:7002@17002 master - 0 1584428884000 3 connected 10923-16383 127.0.0.1:7001>

5)数据分区方案简析

①、方案一:哈希值 % 节点数

哈希取余分区思路非常简单:计算key的 hash 值,然后对节点数量进行取余,从而决定数据映射到哪个节点上。

不过该方案最大的问题是,当新增或删减节点时,节点数量发生变化,系统中所有的数据都需要重新计算映射关系,引发大规模数据迁移。

②、方案二:一致性哈希分区

与哈希取余分区相比,一致性哈希分区将增减节点的影响限制在相邻节点。以上图为例,如果在node1node2之间增加node5,则只有node2中的一部分数据会迁移到node5;如果去掉node2,则原node2中的数据只会迁移到node4中,只有node4会受影响。

一致性哈希分区的主要问题在于,当节点数量较少时,增加或删减节点,对单个节点的影响可能很大,造成数据的严重不平衡。还是以上图为例,如果去掉node2node4中的数据由总数据的1/4左右变为1/2左右,与其他节点相比负载过高。

③、方案三:带有虚拟节点的一致性哈希分区

该方案在一致性哈希分区的基础上,引入了虚拟节点的概念。Redis 集群使用的便是该方案,其中的虚拟节点称为槽(slot)。槽是介于数据和实际节点之间的虚拟概念,每个实际节点包含一定数量的槽,每个槽包含哈希值在一定范围内的数据。

在使用了槽的一致性哈希分区中,槽是数据管理和迁移的基本单位。槽 解耦 了 数据和实际节点之间的关系,增加或删除节点对系统的影响很小。仍以上图为例,系统中有4个实际节点,假设为其分配16个槽(0-15);

槽 0-3 位于 node1;4-7 位于 node2;以此类推…

如果此时删除node2,只需要将槽 4-7 重新分配即可,例如槽 4-5 分配给node1,槽 6 分配给node3,槽 7 分配给node4;可以看出删除node2后,数据在其他节点的分布仍然较为均衡。

6)节点通信机制简析

集群的建立离不开节点之间的通信,例如我们上访在 快速体验 中刚启动六个集群节点之后通过redis- cli命令帮助我们搭建起来了集群,实际上背后每个集群之间的两两连接是通过了CLUSTER MEET <ip> <port>命令发送MEET消息完成的,下面我们展开详细说说。

①、两个端口

哨兵系统中,节点分为数据节点哨兵节点:前者存储数据,后者实现额外的控制功能。在 集群中,没有数据节点与非数据节点之分:所有的节点都存储数据,也都参与集群状态的维护。为此,集群中的每个节点,都提供了两个 TCP 端口:

普通端口: 即我们在前面指定的端口 (7000等)。普通端口主要用于为客户端提供服务 (与单机节 点类似);但在节点间数据迁移时也会使用。集群端口: 端口号是普通端口 + 10000 (10000是固定值,无法改变),如7000节点的集群端口为17000集群端口只用于节点之间的通信,如搭建集群、增减节点、故障转移等操作时节点间的通信;不要使用客户端连接集群接口。为了保证集群可以正常工作,在配置防火墙时,要同时开启普通端口和集群端口。

②Gossip 协议

节点间通信,按照通信协议可以分为几种类型:单对单、广播、Gossip 协议等。重点是广播和 Gossip的对比。

广播是指向集群内所有节点发送消息。优点是集群的收敛速度快(集群收敛是指集群内所有节点获得的集群信息是一致的),缺点是每条消息都要发送给所有节点,CPU、带宽等消耗较大。Gossip 协议的特点是:在节点数量有限的网络中,每个节点都 “随机” 的与部分节点通信并不 是真正的随机,而是根据特定的规则选择通信的节点),经过一番杂乱无章的通信,每个节点的状态很快会达到一致。Gossip 协议的优点有负载 (比广播) 低、去中心化、容错性高 (因为通信有冗余) 等;缺点主要是集群的收敛速度慢。

③、消息类型

集群中的节点采用固定频率(每秒10次)定时任务进行通信相关的工作:判断是否需要发送消息及消息类型、确定接收节点、发送消息等。如果集群状态发生了变化,如增减节点、槽状态变更,通过节点间的通信,所有节点会很快得知整个集群的状态,使集群收敛。

节点间发送的消息主要分为5种:meet消息 、ping消息 、pong消息 、fail消息 、publish消 息 。不同的消息类型,通信协议、发送的频率和时机、接收节点的选择等是不同的:

MEET 消息: 在节点握手阶段,当节点收到客户端的CLUSTER MEET命令时,会向新加入的节点发送MEET消息,请求新节点加入到当前集群;新节点收到 MEET 消息后会回复一个PONG消息。PING 消息: 集群里每个节点每秒钟会选择部分节点发送PING消息,接收者收到消息后会回复一个PONG消息。PING 消息的内容是自身节点和部分其他节点的状态信息,作用是彼此交换信息,以及检测节点是否在线。PING消息使用 Gossip 协议发送,接收节点的选择兼顾了收敛速度和带宽成本,具体规则如下:(1)随机找 5 个节点,在其中选择最久没有通信的 1 个节点;(2)扫描节点列表,选择最近一次收到PONG消息时间大于cluster_node_timeout / 2的所有节点,防止这些节点长时间未更新。PONG消息PONG消息封装了自身状态数据。可以分为两种:第一种是在接到MEET/PING消息后回复的PONG消息;第二种是指节点向集群广播PONG消息,这样其他节点可以获知该节点的最新信息,例如故障恢复后新的主节点会广播PONG消息。FAIL 消息: 当一个主节点判断另一个主节点进入FAIL状态时,会向集群广播这一FAIL消息;接收节点会将这一FAIL消息保存起来,便于后续的判断。PUBLISH 消息: 节点收到PUBLISH命令后,会先执行该命令,然后向集群广播这一消息,接收节点也会执行该PUBLISH命令。

7)数据结构简析

节点需要专门的数据结构来存储集群的状态。所谓集群的状态,是一个比较大的概念,包括:集群是否处于上线状态、集群中有哪些节点、节点是否可达、节点的主从状态、槽的分布……

节点为了存储集群状态而提供的数据结构中,最关键的是clusterNodeclusterState结构:前者记录了一个节点的状态,后者记录了集群作为一个整体的状态。

①、clusterNode 结构

clusterNode结构保存了一个节点的当前状态,包括创建时间、节点 id、ip 和端口号等。每个节点都会用一个clusterNode结构记录自己的状态,并为集群内所有其他节点都创建一个clusterNode结构来记录节点状态。

下面列举了clusterNode的部分字段,并说明了字段的含义和作用:

typedef struct clusterNode {//节点创建时间 mstime_t ctime; //节点id char name[REDIS_CLUSTER_NAMELEN]; //节点的ip和端口号 char ip[REDIS_IP_STR_LEN]; int port; //节点标识:整型,每个bit都代表了不同状态,如节点的主从状态、是否在线、是否在握手等 int flags; //配置纪元:故障转移时起作用,类似于哨兵的配置纪元 uint64_t configEpoch; //槽在该节点中的分布:占用16384/8个字节,16384个比特;每个比特对应一个槽:比特值为1,则该 比特对应的槽在节点中;比特值为0,则该比特对应的槽不在节点中 unsigned char slots[16384/8]; //节点中槽的数量 int numslots; ………… } clusterNode;

除了上述字段,clusterNode还包含节点连接、主从复制、故障发现和转移需要的信息等。

②、clusterState 结构

clusterState结构保存了在当前节点视角下,集群所处的状态。主要字段包括:

typedef struct clusterState {//自身节点 clusterNode *myself; //配置纪元 uint64_t currentEpoch; //集群状态:在线还是下线 int state; //集群中至少包含一个槽的节点数量 int size; //哈希表,节点名称->clusterNode节点指针 dict *nodes; //槽分布信息:数组的每个元素都是一个指向clusterNode结构的指针;如果槽还没有分配给任何节点,则为NULL clusterNode *slots[16384]; ………… } clusterState;

除此之外,clusterState还包括故障转移、槽迁移等需要的信息。

Java中高级核心知识全面解析——Redis(集群【概述{主从复制 哨兵 集群化} 数据分区方案 节点通信机制 数据结构简析】)5

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。