集群系列:Redis集群方案及新版实战

2,031 阅读56分钟

一.集群概述

1、为什么需要集群

  1. 提高系统性能
  • 在单台服务器的情况下,随着用户量的增加和数据量的增大,系统的响应时间和处理能力可能会受到限制,影响系统的性能。为了解决这个问题,可以使用集群技术将多台服务器组合在一起,共同处理用户的请求和数据,从而提高系统的性能

  • 集群技术的核心思想是将任务分配到不同的服务器上,让每台服务器都参与到任务的处理中来。通过有效的负载均衡机制和任务调度算法,集群可以实现高效的任务分配和处理,从而提高系统的性能和吞吐量

  1. 提高系统可靠性
  • 在单台服务器的情况下,如果服务器发生故障或者停机,那么整个系统将会停止工作,用户的请求和数据也可能会丢失。为了避免这种情况,可以使用集群技术将多台服务器组合在一起,提高系统的可靠性和容错能力

  • 集群技术可以通过多台服务器之间的数据备份和故障转移来保障系统的可靠性。如果其中一台服务器发生故障或者停机,其他的服务器可以接替它的工作,从而确保系统的持续运行和数据安全

2、集群是什么

  1. 随着计算机应用领域的不断扩大,对于计算机系统的性能和可靠性要求也越来越高。在这种背景下,集群成为提高系统性能和可靠性的重要方式之一

  2. 在集群中,通常会有一台主服务器负责任务的分配和调度,其他服务器作为从服务器负责任务的执行和处理。主服务器可以使用负载均衡机制和任务调度算法来均衡各个服务器之间的负载,从而提高集群的性能和可靠性。此外,集群还可以使用数据备份和故障转移机制来提高系统的可靠性和容错能力。如果其中一台服务器出现故障,其他服务器可以接替它的工作,确保系统的持续运行和数据安全

  3. 在集群中,服务器之间的通信通常使用网络协议进行,比如TCP/IP协议、HTTP协议等。集群中的服务器可以在同一地理位置,也可以分布在不同的地理位置,它们之间可以通过Internet或专用网络进行通信

3、集群的分类

  • 集群按照架构分类:主从集群、负载均衡器集群、分布式计算集群、高可用集群、容器集群、大数据集群、分布式存储集群

  • 主从集群:主从集群通常使用数据库的复制机制来实现主从之间的数据同步,比如MySQL、Redis的主从复制技术,一般情况下主节点负责写,从节点负责读,主从节点数据是需要同步的

  • 负载均衡器集群:负载均衡器的集群通常使用一定的负载均衡算法和技术实现请求的分流和负载均衡,比如Nginx、HAProxy、LVS等

二.Redis为什么需要集群

  1. Redis需要集群主要是为了解决以下几个问题
  • 容量限制:单机Redis的容量是有限的,一旦数据量达到单机容量极限,就需要将数据存储到多台机器上进行扩容

  • 高可用性:单机Redis存在单点故障的问题,如果单机Redis宕机,就会导致整个系统的服务中断,因此需要使用集群来实现高可用性和容错能力

  • 并发性能:单机Redis的并发性能是有限的,一旦并发访问量达到一定程度,就会出现性能瓶颈和性能下降的问题,因此需要使用集群来提高并发性能

  1. Redis集群通过数据分片和数据复制的方式来实现数据存储和高可用性,具有以下优点
  • 数据自动分片:Redis集群将数据自动分片存储到多台机器上,实现了数据的横向扩展,可以支持更大规模的数据存储

  • 数据自动复制:Redis集群通过数据复制机制来保证数据的高可用性,即将主节点上的数据自动复制到多个从节点上,实现数据的备份和容错

  • 自动故障转移:Redis集群可以自动进行故障转移,即在主节点宕机时,自动选举一个从节点作为新的主节点,保证系统的持续运行和服务的可用性

  • 增强并发性能:Redis集群通过分片机制来增强并发性能,即将数据分散到多个节点上进行处理,从而避免单机性能的瓶颈问题

三.Redis三种集群模式

1、主从复制模式

1.主从复制概述

  • 主从复制模式是三种模式中最简单的一种,多台Redis实例,以一台实例作为主机,其余的实例作为备份机,主机和从机的数据完全一致,主从复制提高了Redis读请求的吞吐量

  • 主机支持数据的读和写操作,而从机只支持读和数据同步,客户端将数据写入主机后,由主机自动的将数据写入到从机中

  • 在Redis中主从复制的同步策略有两种

    • 全量更新:复制主机所有数据到从机
    • 增量更新:仅复制主机与从机差异数据到从机

2.全量更新

  • runid:主节点的runid,从节点第一次请求主节点时,主节点会将runid返回
  • offset:当前数据的偏移量,执行一条命令后,主节点的偏移量会增大,从节点请求复制主节点的时候,会带上这个信息,主节点会将这个偏移量之后的数据发送给从节点
  1. 原理
  • 建立连接后,从节点向主节点发送psync命令,携带runid和offset请求同步,如果是第一次同步那么runid为空,offset为-1
  • 主节点接收到psync请求后,发现runid为空,offset为-1,则知道从节点希望是全量更新,会将自己的runid和offset返回给从节点,从节点进行保存
  • 主节点执行bgsave,生成rdb文件,在此期间如果主机有写入操作那么会放入到一个环形缓冲区中
  • 主节点将生成后的rdb文件,传输给从节点
  • 从节点收到rdb文件后,会先清除自己的所有数据,然后将rdb文件加载到内存中
  • 全量更新结束之后将环形缓存区中的所有写操作发送给从节点进行同步
  1. 注意事项
  • 全量复制期间,主节点和从节点之间的网络连接是处于阻塞状态的,也就是说,主节点和从节点之间不能进行其他操作,直到全量复制完成为止
  • 全量复制的优点是同步数据完整、准确,但是其缺点是需要占用大量的网络带宽和主节点的内存空间,在大规模集群中使用全量复制可能会对系统性能造成较大的影响。因此Redis在全量复制之后,还提供了增量复制(Incremental Resynchronization)的方式来进一步保证数据同步和性能
  1. 全量复制期间如果有数据写入处理方式
  • Redis会在全量复制期间开启一个复制缓冲区,将主节点接收到的写入操作暂存到缓冲区中。当全量复制结束后,Redis会将复制缓冲区中的所有写入操作发送给从节点,使从节点与主节点的数据保持一致。需要注意的是,如果在全量复制过程中写入操作较多,缓冲区可能会耗尽内存,从而影响Redis的性能和稳定性。
  • 为了避免全量复制期间写入操作对Redis的性能和稳定性产生影响,一些Redis集群部署方案通常采用以下的方法
    • 对写入操作进行限流,避免写入操作过多导致缓冲区耗尽内存
    • 使用高可用方案,保证Redis主节点的高可用性,当主节点发生故障时能够快速切换到备用节点
    • 使用增量复制方案,将全量复制和增量复制相结合,减少数据同步时间和带宽占用

3.增量更新

  1. 原理
  • 从节点向主节点发送psync命令,携带已保存的runid和offset
  • 主节点判断runid是否和自己的runid一致,如果不一致则执行全量更新,一致则判断offset
  • 因为环形缓冲区是一个环形结构,若master的offset一直追加,会将slave未备份的一部分给覆盖,所以主节点判断从节点的offset是否在环形缓冲区内,如果不在则执行全量更新
  • 如果在环形缓存区内,那么会将offset往后一直到环形队列底部的数据,全部同步给从节点,从节点执行命令保存数据到内存中。因为主节点采用的是异步处理,所以主从节点的数据不是强一致性的
  1. 注意

    • 增量复制只会传输主节点与从节点之间差异的部分数据,从而减少了网络带宽的占用和主节点内存的消耗。但是,增量复制的缺点是需要额外的内存空间来保存主节点的复制缓存,因此可能会对主节点的内存占用和性能产生影响
  2. 客户端写入数据到主机后同步流程

  • 当主节点接收到写入操作时,Redis会将这些操作缓存到复制缓冲区中,等待下一次从节点请求增量复制时一次性发送给从节点
  • 因此,在Redis主从节点之间进行数据同步时,并不一定会使用增量复制,具体是使用全量复制还是增量复制,取决于具体的同步策略和实现方式
  1. 增量复制时机
  • Redis增量复制的具体实现是基于主从节点之间的心跳机制和复制缓冲区的数据大小来确定的。当从节点接收到主节点的心跳消息时,会向主节点发送REPLCONF ACK 命令,其中offset表示从节点上次接收到主节点发送的复制偏移量。主节点在接收到ACK命令后,会检查从节点的复制偏移量,如果从节点复制偏移量小于主节点当前复制偏移量,则会将主节点的复制缓冲区中的数据发送给从节点,完成增量复制

  • Redis增量复制的请求频率不是固定的,而是根据主从节点之间的数据同步情况而定。如果从节点上次接收到主节点发送的复制偏移量比较旧,或者从节点的复制缓冲区中的数据比较少,那么从节点会更频繁地向主节点发送心跳消息,并请求增量复制。如果从节点的复制缓冲区中的数据比较多,或者从节点与主节点之间的网络连接较慢或不稳定,那么从节点可能会减少向主节点发送心跳消息的频率,以避免网络带宽的占用和数据同步的延迟

  • 一般情况下,Redis主从节点之间的增量复制请求频率较高,可以实现较实时的数据同步。同时,Redis增量复制的性能和效率也取决于主从节点之间的网络连接质量、硬件配置和数据量等因素

3.主从复制优缺点

  1. 优点
  • 提高了Redis的可扩展性:通过使用Redis主从复制,可以将读操作分摊到从节点上,从而减轻主节点的压力,提高Redis的读性能,同时也增加了Redis的水平扩展能力
  • 提高了Redis的可用性:通过将主节点的数据复制到多个从节点上,可以在主节点发生故障时,切换到某个从节点,从而保证Redis的高可用性
  • 分离了读写操作:将读操作分离到从节点上,可以避免读写操作的冲突,提高Redis的并发处理能力和数据一致性
  1. 缺点
  • 主节点宕机之后需要手动切换主机
  • 多个从节点宕机恢复后,大量的SYNC同步操作会导致主节点IO压力倍增
  • 延迟和数据不一致问题:Redis主从复制是异步复制,从节点与主节点之间存在一定的延迟,因此可能会存在数据不一致的问题,如主节点写入的数据还未同步到从节点,从节点就返回给客户端了
  • 从节点的容错性较差:Redis从节点无法处理写操作,因此从节点发生故障时,只能选择重新启动或者重新配置,无法自动恢复
  • 从节点数量的限制:Redis从节点数量的限制主要是由网络带宽和性能限制所限,因此从节点数量较多时,可能会导致网络拥塞和性能下降的问题

2、哨兵模式

1.哨兵模式概述

  • 主从复制模式当主节点宕机以后,需要手动将一台从节点切换到主节点,这个时候就需要人工干预,不仅麻烦还会造成一段时间内服务不可用,这时候就需要哨兵模式

  • Redis哨兵模式是在Redis 2.8版本中引入的。在此版本之前,Redis只提供了主从复制来实现高可用性和数据备份。但是,主从复制存在单点故障的问题,当主节点宕机时整个系统将无法提供服务。因此,Redis引入了哨兵模式来解决这个问题,使得Redis服务具备更高的可用性和数据一致性

  • 哨兵模式的核心还是主从模式,只不过在主从模式的基础上增加了一个竞选机制,如果主节点宕机了,那么从所有的从节点中选出新的主节点。竞选机制的实现依赖于在系统中启动一个sentinel进程

  • sentinel会监控所有节点是否正常运行,通过发出命令返回监控节点的状态,在多sentinel模式下,sentinel之间也会互相监控

  • 故障切换:当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换master。同时那台有问题的旧主也会变为新主的从,也就是说当旧的主即使恢复时,并不会恢复原来的主身份,而是作为新主的一个从

2.单哨兵模式

  • 单哨兵模式就是只启动一个sentinel进程,用于监控主从节点,但是sentinel本身也有单点故障的问题,所以一般都会采取多哨兵模式

3.多哨兵模式

  • 多哨兵模式下,不仅会监控所有的主从节点,哨兵之间也会相互监控,每一个哨兵都是一个独立的进程,独立运行
  • 一般而言,哨兵都是配置为多台,并且是单数,3台、5台、7台等,因为这涉及到一个竞选机制,如果是偶数可能会产生票数平均的情况

4.哨兵模式实现原理

  1. 监控:当哨兵启动时,会监控所有的主从节点,并且保证哨兵集群能够正常通信,这个阶段称为监控阶段
  • ping:哨兵节点启动后所有哨兵每隔1S会互相发送ping心跳,来检测其他哨兵节点是否正常

  • info:哨兵节点启动后每隔10S会向所有的主节点发送info指令,得到runid、role、各个slave的详细信息。再根据slave信息发送info指令获取slave的详细信息,会得到runid、role、master_host、master_port、offset...

  • pub/sub:每个哨兵节点每隔2S会在一个指定频道发布当前节点保存的主节点信息,其他哨兵节点会订阅该频道获取信息

  1. 通知:当哨兵发现主节点或者从节点出现故障时,当前哨兵会通知其他哨兵,其他哨兵挨个发送ping命令来到出故障的redis节点,如果redis节点没有响应则哨兵集群认为此节点出现故障,我们把这个阶段称为哨兵通知阶段
  • 哨兵监控slave
    • 在哨兵集群中,如果某一个哨兵节点发现某个从节点出现故障后,会将其标记为+sdown,并发布订阅告知给其他哨兵节点,其他哨兵节点收到通知后会将次从节点剔除,并标记为+sdown,等到下次slave回复哨兵时,哨兵会将其从新变为从节点,并标记为-sdown

  • 哨兵监控master

    • 某个哨兵发现主节点出故障后,会将当前主节点标记为+sdown,也就是标记为主观下线,单个哨兵认为有问题可能是网络抖动问题

    • 后续如果其他哨兵节点也发现此主节点出现问题,当发现问题的哨兵数量大于quorym时,那么哨兵集群会认为此主节点出现故障,标记为odown状态,此时主节点被标记为客观下线

  1. 故障转移:当哨兵集群已经发现某个节点出现故障时,如果是从节点则直接剔除,如果是master节点出现故障,那么需要在多个从中推选出一个新的主节点,我们把这个阶段称为故障转移阶段
  • 哨兵leader选举:当主节点被标记为客观下线时,就要在哨兵集群中选出一个哨兵节点来完成后面的故障转移工作,每个哨兵节点都可以成为leader,选举流程如下

    • 主节点被标记为客观下线时,哨兵节点会向其他哨兵节点发送is-master-down-by-addr命令,表明自己想成为leader来处理master的故障转移
    • 当其他哨兵节点收到此命令时,可以同意或者拒绝它成为leader
    • 如果票数大于哨兵集群数量的一半时将成为领导者,如果没有大于,则继续开始下一轮选举,并且票数会类加
  • 故障转移实现流程:哨兵在监控阶段时就保存了主从节点信息,选举出leader哨兵后,哨兵要开始进行主从节点故障转移,从众多从节点中选举出一个主节点。主节点选举流程如下

    • 过滤掉已经下线的从节点
    • 优先级高的推选为主节点,默认都是100,可通过slave-priority参数进行设置,越小优先越高
    • 推选出offset最大的节点,offset大意味着和主节点数据越相近
    • 推选出runid最小的节点,因为runid越小代表越早加入集群,具有更多的复制数据,更适合成为主节点
  • 更新状态:当故障转移后,需要更新当前的主从状态,流程如下

    • 被选举出来的新master将执行slaveof no one 命令,断开与原master连接,将自己从从节点转换为主节点,并在其他的slave配置文件上加上slaveof命令
    • 将已经剔除的主节点设置为新主节点的从节点,后续恢复时,将其变为从节点,并在其配置文件中加上slaveof命令

5.主观下线和客观下线

  1. 名词说明
  • 主观下线:某个哨兵节点单方面的认为一个Redis节点不可用了
  • 客观下线:大部分哨兵节点都认为一个Redis节点不可用时,那么此节点就被认为是客观下线
  1. 原理
  • 哨兵进程会使用PING命令的方式来检测各个主库和从库的网络连接情况,用来判断实例状态

  • 如果哨兵发现主库或者从库响应超时(down-after-millisecond),那么哨兵会判定其为"主观下线"

  • 如果该节点是主节点,那么哨兵会进一步判断是否需要对其进行故障切换,这时候会发送命令(SENTINEL is-master-down-by-addr)询问其他哨兵节点是否认为该主节点是主观下线,当达到指定数量(quorum)时,哨兵就会认为此主节点是客观下线

  • 如果没有足够数量的哨兵同意主节点进入主观下线,主节点的主观下线状态就会被消除,若主节点重新向哨兵的PING命令返回有效回复,主节点的主观下线状态也会被消除

  • 哨兵误判:主库本身没有故障,但由于哨兵的误判,判断它为下线状态。一旦启动主从切换,后续的选举和通知操作都会带来额外的计算和通信开销。因此,为了不必要开销,我们要严格注意误判的情况。在哨兵集群中,判定主库是否处于下线状态,不是由一个哨兵来决定的,而是只有大多数哨兵认为主库已经"主观下线",主库才会标记为"客观下线"。这种判断机制为:少数服从多数。同时会触发故障转移

  • 关键属性

down-after-milliseconds:Redis Sentinel 配置文件中的一个属性,表示 Sentinel 发送 Ping 命令给实例之后,如果规定的时间内没有收到响应,就认为实例已经下线了,然后 Sentinel 就会开始判断实例的状态。这个属性的默认值为 30,000 毫秒 (30 秒)。可以通过修改配置文件的方式来更改该属性的值 SENTINEL is-master-down-by-addr:Redis Sentinel 提供的命令之一,用于向其它 Sentinel 节点询问某个主服务器是否已经下线。当一个 Sentinel 节点判定某个主服务器为主观下线后,会向其它 Sentinel 节点发送 SENTINEL is-master-down-by-addr 命令来确认这个主服务器是否已经客观下线,从而决定是否执行自动故障转移。这个命令需要提供主服务器的 IP 地址和端口号作为参数,响应内容则是一个字符串,表示主服务器的状态信息

quorum:哨兵模式中用来决定主节点是否被判定为客观下线的数量阈值。当有N个哨兵节点监控同一个主节点时,要求有至少N/2+1个哨兵节点判定该主节点为主观下线,此时才会被判定为客观下线,从而触发故障转移。这个数量阈值可以通过配置文件中的参数quorum来设置 SLAVEOF NO ONE:此命令是Redis用于将一个从节点转化为主节点的命令。执行该命令后,从节点会断开与其原来的主节点的连接,并开始独立工作,即成为一个新的独立的主节点 slaveof:此命令是Redis中用于配置主从复制关系的命令。它用于将一个Redis实例设置为另一个Redis实例的从服务器。这个命令在Redis的主从复制过程中是非常重要的,它让从服务器能够连接到主服务器,并且通过复制主服务器上的数据来保持与主服务器的数据一致性

6.哨兵模式优缺点

  1. 优点
  • 高可用性:哨兵模式可以自动检测节点的可用性,并实现主从节点的自动切换,从而提高了 Redis 的高可用性、健壮性

  • 灵活性:哨兵模式支持动态的添加、删除节点,可以根据需要扩展 Redis 的读写性能

  • 无需人工干预:哨兵模式不需要人工干预,可以自动实现故障转移和节点恢复

  1. 缺点
  • 延迟:哨兵模式需要检测节点的状态,判断主节点是否宕机,再进行故障转移,因此会增加一定的延迟

  • 配置复杂:哨兵模式需要配置多个哨兵节点,需要合理设置节点的数量和 quorum 值,对于不了解 Redis 的开发人员来说,配置较为复杂

  • 在线扩容复杂:Redis比较难支持在线扩容

  • 所有主从节点保存的都是全量数据,浪费内存空间,没有实现真正的分布式存储,数据量过大时,主从同步严重影响master性能

  • 故障转移时,在主节点选举结束之前,谁也不知道主从节点是谁,此时Redis会开启切换保护机制,禁止写操作,直到选举出新的主节点

3、RedisCluster模式

1.RedisCluster模式概述

  1. RedisCluster是Redis官方提供的分布式方案,适用于数据了特别大的场景,相对于哨兵模式,它具有以下优点
  • 高可用:多个主节点,每个主节点有对应多个从节点,主节点宕机RedisCluster机制会自动将某个从节点切换到主节点

  • 扩展性

    • 横向扩展:通过增加机器实现增加能力上限
    • 读写扩展:基于主从模式,通过读写分离,增加读写能力,避免单点故障
  • 分布式存储:RedisCluster采用分片技术将数据均匀分布到多个节点上,每个节点只保存部分数据,避免了单个节点存储数据过大的问题,提高了存储容量和性能

  • 自动数据迁移:RedisCluster支持自动数据迁移,当新增或删除节点时,会自动将数据迁移到其他节点上,保证数据均衡和数据完整性

  1. RedisCluster是什么
  • redis在3.0上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的数据。cluster模式为了解决Redis单节点容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,可受益于分布式集群高扩展性

  • RedisCluster是一种服务器Sharding技术,分片和路由都是在服务端实现,采用多主多从,每一个分区都是由一个Redis主节点和多个从节点组成,片区和片区之间是相互平行的。RedisCluster集群采用了P2P的网络拓扑架构,没有中心节点,所有节点通过Gossip协议通信,所有节点即存储数据也是控制节点

  • RedisCluster建议至少部署3个主节点和3个从节点,以保证数据的高可用性和负载均衡

  • 总结:RedisCluster是可以达到主从复制架构、读写分离、哨兵集群、高可用效果的集群架构

2.数据分布算法

  • 由于RedisCluster是将数据分布在不同的片区,所以需要数据分布算法,以下是几种数据分布算法的原理优缺点,RedisCluster采用的是HashSlot算法
  1. Hash寻址算法
  • 原理:对Key进行Hash运算,然后将值对主节点数量取模,最后得到的数值就是此数据应存储到的主节点

  • 优点:简单、快速、高效、适用于大规模快速查询

  • 缺点:

    • 数据分布不均匀:如果使用简单的Hash寻址算法,可能会出现数据倾斜的情况,导致某些节点负载过高,而某些节点负载过轻

    • 数据迁移困难:使用Hash寻址算法进行数据分布后,如果需要添加或删除节点,就需要重新计算每个数据对应的节点,然后将其迁移到新节点上,这个过程非常繁琐,而且需要暂停对集群的写操作

    • 扩展性受限:使用Hash寻址算法进行数据分布时,如果需要增加节点,那么需要将所有的数据重新计算分配,这样就会限制集群的扩展性

    • 大量缓存重建问题:主节点如果宕机,那么Hash运算时根据现有存活节点进行取模,得到的数值与原有存储数据时的数值不匹配,请求走不到原有路由节点上,从而导致大量的key瞬间全部失效

  1. 一致性Hash算法
  • 原理:将所有的主节点进行Hash运算,再将Hash值映射到一个固定的Hash环上面。然后对Key进行Hash运算,将此Hash值与圆环上的各个点进行对比,将Hash值落在圆环上后进行顺时针旋转去寻找距离自己最近的一个节点,数据的存储与读取都在此节点进行

  • 优点:保证任何一个主节点宕机,只会影响在之前那个主节点上的数据,此前的主节点宕机,查询时这部分数据会丢失,写入时沿着顺时针去到下一个主节点

  • 缺点

    • 数据丢失过大:在节点比较少的情况下, 丢失的数据量还是非常庞大的

    • 缓存热点问题:如果某些数据被频繁地访问,会导致热点数据集中在某个节点上,造成负载不均

    • 数据倾斜问题:节点数量较少时,由于数据分布不均,可能会导致某个节点负载过重,影响系统的性能。这是因为Hash函数的输出值在Hash环上并不是均匀分布的,而是有规律的。一致性哈希算法通过引入虚拟节点来解决这个问题,将每个物理节点映射到多个虚拟节点上,使得数据更加均匀地分布在环上

    • 一致性问题:由于节点的添加或删除会影响哈希值的计算,可能会导致数据分布不均,这个问题可以通过一些技术手段来解决,例如虚拟节点、数据复制等

  1. 带虚拟节点一致性Hash算法
  • 业界为了解决一致性Hash算法的问题,在一致性Hash算法的基础上增加了虚拟节点机制
  • 原理
    • 出现数据倾斜的原因就是因为节点的数量较少,那么我们可以通过对主节点Hash运算后,固定到Hash环上面,然后对该主节点进行多次Hash运算计算出几个虚拟节点,当Key进行Hash运算后如果顺时针找到虚拟节点,那么虚拟节点会映射到他真正的主节点,从而达到数据分散的效果
    • 实际运用中,通常将虚拟节点设置为32个以上,此时即便节点很少也可以做到尽可能的数据均匀分布
  • 举例
    • Hash("Neuronet") -> 虚拟节点1
    • 虚拟节点1 -> 主节点1

  1. HashSlot算法
  • Redis使用此算法的原因

    • RedisCluster需要支持动态扩容和缩容,在一致性hash算法中,增加或删除一个节点会导致整个哈希环重新分布,这样会导致大量的数据迁移和节点负载的不均衡,而使用HashSlot算法则可以避免这个问题
    • 此算法可以避免数据倾斜问题和虚拟节点带来的计算负担
  • 原理

    • 在HashSlot算法中,取值范围是0~16383。Redis将整个key空间分成了16384个槽,也就是16384个slot,每个主节点负责一部分槽
    • 客户端根据Key计算CRC16值,将值对16384取模,找到对应的槽,然后根据槽对应的主节点进行数据访问
    • Redis中的每个主节点都对应一部分槽位。增加一个主节点时,只需要将其他主节点的槽位分配一部分到新槽位。删除一个主节点时,就将此主节点的槽位移动到其他的主节点上去。移动Hash槽的成本是非常低的
    • HashSlot算法还可以将不同的数据类型映射到不同的槽中,以达到更好的负载均衡效果。同时,Redis还支持将相邻的多个槽划分到同一个节点,以便在某些场景下提高数据读取的效率
    • 如果想确保一些Key总是被分配到同一个节点,那么您可以使用哈希标签(Hash Tag)功能来强制让这些键映射到同一个槽位。哈希标签是在 RedisKey 上使用大括号 "{}" 的一种特殊语法
      • 例如:set mykey1:{100}和set mykey2:{100},它们的hash tag都是{100},那么它们就会被存储在同一个hash slot中
  • 16384槽位由来

    • CRC16算法产生的Hash值有16bit,该算法可以产生65536个值。值是分布在0~65535之间,那么其实在做取模运算时,我们是可以取模65536的,但是Redsi作者采取了16384

    • 作者的回答是:Redis集群节点数量如果超过1000个那么会造成网络拥堵,所以建议节点数量不超过1000个,那么1000个节点使用16384个槽位完全够用了。如果使用65536个槽位会导致主节点之间交互心跳包时,浪费带宽。槽位数量过少不够用,过多浪费带宽,所以作者通过实测计算得出一个16384的值,不多不少刚刚好

3.RedisCluster读写分离

  • 默认情况下RedisCluster读写都是通过主节点进行处理,不支持从节点读写。RedisCluster的核心理念是从节点是作为热备以及主节点宕机时从节点进行故障转移,从而实现高可用
  • Redis之所以需要读写分离,是为了横向扩展从节点去达到更高的读并发。而在RedisCluster中,主节点本身就可以任意横向扩展,如果想要更高的读写并发,那么对主节点进行横向扩展即可
  • 想要在RedisCluster中实现读写分离代价是比较高的,比如可以修改JedisClient源码,或是使用第三方RedisProxy工具

4.RedisCluster通信协议

4.1.Gossip通信协议概述

  • HashSlot算法解决的是数据读写问题,那么当集群节点数量发生变化、Slot迁移、主从切换等这些操作的信息需要同步给所有主节点,Gossip协议就是用来维护集群同步状态

  • RedisCluster是完全去中心化的,也就是没有一个统一的管理中心去通知所有节点进行同步,每个节点都是一个中心,RedisCluster采取的方案是使用Gossip协议来进行通信,节点之间通讯的目的是为了维护节点之间的元数据信息

    • 节点信息:每个节点的ID、IP地址和端口号等信息
    • 描述集群拓扑的槽位分配:槽位的范围以及哪些节点负责存储哪些槽位
    • 故障转移相关信息:哪个节点是主节点,哪个节点是从节点,如果主节点失效时从节点将如何晋升为主节点等信息
    • 其他集群相关的配置信息,如集群名称、是否开启了动态更新等
  • Gossip:中文名称是流言协议,在此协议中包含了多种消息类型,即ping、pong、meet、fail等。原理就是节点之间不断的进行通信交换信息,一段时间后所有节点就都有了整个集群的完整信息,最终所有节点的状态都会达成一致。Gossip中除了fail消息是立即全网感知,其他消息都具有一定延迟,所以是最终一致性

  • 节点之间通信流程

    • 新节点向集群中的任意一个节点发送 meet 命令,并将自己的 IP 和端口号信息附在命令中

    • 收到 meet 命令的节点会将新节点的信息记录下来,并向新节点返回 pong 命令,表示已经收到了新节点的请求

    • 新节点收到 pong 命令后,将收到 pong 命令的节点加入到自身的节点列表中,并向此节点发送 ping 命令,等待其返回 pong 命令

    • 当新节点收到足够数量的 pong 命令后,就会将自己加入到集群中,并向其他节点发送自己的拓扑结构信息。这个拓扑结构信息包括节点自身的信息以及其他节点的信息,以及每个节点的 hash slot 分配信息等

    • 集群中的其他节点收到新节点的拓扑结构信息后,会将其添加到自己的节点列表中,并向新节点返回 pong 命令

    • 新节点收到其他节点的 pong 命令后,就可以和其他节点进行通信,并开始接收和处理请求了。

    • 每个Redis节点会开放两个端口号,一个是自身的运行端口号,另外一个就是运行端口号+10000,此端口号用于节点之间通信

    • 节点之间建立TCP通道:每个节点都会与集群中的其他节点建立TCP连接,用于节点之间的通信。使用ping/pong消息保持节点之间的心跳连接

    • 节点之间进行握手:每个节点在连接到其他节点后,在固定的时间间隔内,会随机选择的若干个节点发送ping消息,通知对方自己还在运行,并请求对方发送关于自己和其他节点的信息。如果节点长时间没有响应,则被认为已经失效,会向其他节点广播 Fail 消息,以便其他节点更新该节点的状态信息

    • 节点响应:接收到ping消息的节点,会向发送ping消息的节点响应PONG命令,进行版本号和槽分配等元数据信息的交换,以确保集群中的所有节点拥有相同的元数据信息。并更新自己的局部视图

    • 节点之间进行数据同步:当某个节点的主节点进行数据修改时,会将修改信息发送给所有从节点。从节点收到信息后,会进行数据同步,确保数据的一致性

    • 节点之间进行故障转移:每个节点都会定期向其他节点发送PING命令,以确认对方是否存活。如果一个节点在一定时间内没有响应PING命令,其他节点就会将其标记为下线节点,并进行主从切换等操作

4.2.Gossip优缺点

  • Gossip优点

    • 分布式高效:Gossip协议是一种去中心化的协议,节点之间相互交流信息,每个节点都可以通过传播信息来实现全局一致性,不需要中央控制节点,使得节点加入或退出集群更加高效

    • 可伸缩性:Gossip协议可以很好地适应不同规模的系统,当节点数目增加时,节点间通信的成本是对数级别的

    • 容错性:Gossip协议具有一定的容错能力,由于每个节点可以通过交互信息来更新状态,因此即使一部分节点失效,其他节点仍然可以更新状态,保持整个集群的一致性

    • 自适应性:Gossip协议在传输信息时会根据实时情况进行调整,根据反馈信息和可靠性要求,自动选择合适的节点进行信息交流,从而提高了信息传输的效率和可靠性

    • 低延迟:Gossip协议采用分散的信息传播方式,信息可以在整个网络中快速地传播,从而使得系统的响应速度更快

  • Gossip缺点

    • 延迟问题:由于Gossip协议的传播速度相对较慢,因此可能存在节点状态更新的延迟问题。特别是在网络拓扑结构较为复杂或节点数量较大时,这种延迟问题会更加突出

    • 带宽开销:由于Gossip协议的信息需要在节点之间不断传播,因此可能会产生较大的网络带宽开销。特别是在节点数量较大时,这种开销会更加严重

    • 数据一致性问题:由于Gossip协议是基于随机的节点通信机制实现的,因此可能会出现数据不一致的情况。特别是在节点状态频繁变化时,这种问题会更加明显

    • 安全性问题:Gossip协议需要在节点之间传递敏感信息,因此存在安全性问题。特别是在没有适当的加密和认证机制时,这种问题会更加严重

4.3.Gossip消息分类

  • ping:每个节点会按照固定时间频繁的给其他节点发送ping消息,集群节点互相通过ping交换元数据

    • ping消息的发送是非常频繁的,并且需要携带元数据,所以会加重网络负担
    • 每个节点没秒执行10此ping消息,每次会随机选择5个最久没有通信的其他节点去通信
    • 如果发现某个节点通信延迟时间达到了 cluster_node_timeout/2,那么立即发送ping,避免数据交互延迟过长
    • cluster_node_timeout值是可以调节的,如果调节的比较大, 发送ping的频率就会降低
    • 发送 ping 消息时,会携带自己的信息,并随机选择 1/10 的其他节点,将它们的信息一起发送给目标节点进行交换。这样可以保证每个节点都能够获得全局信息,从而更好地协调整个集群的状态
    • 每个 ping 消息至少会包含 3 个其他节点的信息,最多包含总节点数减 2 个其他节点的信息。这是为了防止某些节点信息过于集中,导致部分节点信息丢失或者信息不全。同时,也可以保证每个节点都能够获取足够的信息,从而更好地对整个集群进行管理和控制
  • pong:此消息是作为meet和ping的元数据响应消息

  • meet:新节点使用此消息通知任意一个集群节点,集群节点会响应pong消息邀请加入到集群

  • fail:某个节点判断另外一个节点宕机之后,会广播fail消息,通知其他节点此节点宕机的消息,其他节点接收到消息后标记此节点下线,四种消息中只有fail消息是立即全网感知,其他消息都具有一定的延迟

5.RedisCluster客户端

  • 当客户端需要与 Redis Cluster 进行交互时,有两种主要的方式:基于重定向的方式和智能代理的方式

5.1.基于重定向的方式

  • 连接RedisCluster集群时,可以使用Redis自带的redis-cli客户端,支持重定向,他有两种方式手动重定向和自动重定向

  • 这种模式下,客户端将请求发送到集群中的某个节点,如果这个节点不是数据所在的节点,那么这个节点会将请求重定向到正确的节点上。在这个过程中,客户端和 Redis 集群节点之间使用 Redis 协议进行通信

  • 默认情况下,如果节点响应MOVED,那么就意味着节点槽位错误,那么需要手动重新选择。如果是使用redis-cli客户端连接,那么也可以在连接时,指定 -c 属性,那么当响应MOVED时会自动重新连接到正确槽位节点

  • 这种模式下,一般而言都最少需要重定向一次,当数据分布发生变化时,可能还需要重定向多次。重定向的网络开销是比较大的,所以一般推荐使用基于智能代理的方式来连接RedisCluster

5.2.基于智能代理的方式

  • 智能代理的方式是近些年来才逐渐被 Redis Cluster 推崇的交互方式,其中Jedis的扩展客户端JedisCluster就是一个不错的选择,其他的还有Redisson、Lettuce

  • JedisCluster它的设计理念是为了提供更好的性能、可靠性和易用性。JedisCluster在Jedis的基础上增加了自动重定向、连接池等功能,并进行了性能优化和bug修复。同时,它也保留了Jedis的API和用法,使得用户可以很容易地迁移到Smart Jedis上来

  • JedisCluster实现原理

    • 在初始化时,JedisCluster客户端会获取Redis Cluster中所有主节点的信息,包括节点IP地址、端口号和节点的槽位范围等信息,并将这些信息存储在本地的节点列表中

    • JedisCluster客户端在本地创建一个【槽位/节点映射表】,用来缓存集群中槽位与节点之间的映射关系。这个表的初始化过程如下:

      • 遍历所有主节点,获取每个节点的槽位范围
      • 遍历每个节点的槽位范围,将这些槽位与对应的节点信息存储在【槽位/节点映射表】中
    • 当客户端发起请求时,JedisCluster客户端会先通过CRC16算法计算出请求对应的槽位

    • JedisCluster客户端会在本地的【槽位/节点映射表】中查找对应的节点,如果能够找到,则直接访问该节点并进行读写操作,避免了根据错误节点返回的MOVE指令进行重定向的开销

    • 如果发生了数据迁移导致某个请求返回MOVE指令时,客户端会根据MOVE指令中返回的新节点信息,同步更新本地的【槽位/节点映射表】,以保证后续的请求能够直接通过该表进行节点定位,提升效率

    • 为了避免出现网络异常等情况导致本地的【槽位/节点映射表】过期,JedisCluster客户端会定时从Redis Cluster中获取最新的节点信息,并更新本地的节点列表和【槽位/节点映射表】

    • 通过这种方式,JedisCluster客户端能够实现高效的请求路由和自动重定向,同时减少了重定向的开销

6.RedisCluster扩容|缩容

  • 扩容

    • 新节点向集群中的一个已知节点发送 meet 命令请求加入集群
    • 已知节点向集群中其他节点发送 meet 命令,将新节点加入集群
    • 新节点加入集群后,会通过 ping/pong 报文与其他节点建立心跳连接,同步集群信息,包括集群节点的数量、槽位信息等
    • 当新节点成为集群的一员时,集群中的数据迁移将开始,数据迁移过程中对客户端是透明的。具体的数据迁移过程如下
      • 集群会从所有原节点中选取一部分槽位的数据迁移到新节点上,具体的迁移槽位和数量取决于集群中的数据分布情况
      • 在数据迁移期间,如果有客户端访问迁移的槽位,集群会返回一个 ASK 错误,告知客户端要访问的槽位已经迁移到了新节点上,同时告知客户端新节点的地址,客户端会根据返回的地址重新发起请求
      • 当所有数据迁移完成后,集群会向客户端返回MOVED错误,告知客户端对应的槽位已经迁移到了新节点上
      • 客户端收到MOVED错误后,会更新本地的【槽位/节点映射表】,以便后续请求可以直接定位到新节点
  • 缩容

    • 集群会从所有原节点中选取一部分槽位的数据迁移到其他节点上,具体的迁移槽位和数量取决于集群中的数据分布情况
    • 当所有数据迁移完成后,集群会将要删除的节点标记为 FAIL 状态,并向其他节点发送 forget 命令,通知其他节点忘记该节点
    • 当集群中的某个节点在 cluster-node-timeout 时间内没有收到要删除节点的任何消息时,就会将该节点从集群中删除
  • ASK与PONG的区别

    • ASK 消息是指当某个节点在进行槽位迁移时,如果有客户端请求的数据位于正在迁移的槽位上,该节点会返回一个 ASK 消息给客户端,告诉客户端该数据已经被迁移到了哪个节点上,并让客户端去请求正确的节点。因此,ASK 消息是在数据请求过程中被主动发送的

    • PONG 消息则是在 Redis Cluster 节点间进行心跳检测时使用的。节点间通过相互发送 PING/PONG 消息来保持连接和同步信息,PONG 消息用于回复对方的 PING 消息,以保证节点之间的连接正常。因此,PONG 消息是在节点间进行通信时被动发送的

7.RedisCluster主备切换

  • 判断节点宕机
    • 如果一个节点认为另一个节点宕机,那么是 subjective pfail,即主观宕机
    • 如果超过一半节点都认为另外一个节点宕机了,那就是fail,即客观宕机
    • 在 cluster-node-timeout 时间内,一个节点一直没有返回 pong,那么就认为它是 subjective pfail
    • 如果一个节点认为某个节点 subjective pfail 了,那么会在 ping 消息中发送给其他节点,如果超过半数的节点都认为该节点宕机了,那么就变成 fail
  • 从节点过滤
    • 对于宕机的 master 节点,从其所有的 slave 节点中,选择一个切换成 master 节点
    • 检查每个 slave 节点与 master 节点断开连接的时机,如果超过了 cluster-node-timeout * cluster-slave-validity-factor,那么这个节点就没有资格切换成 master 节点,直接被过滤
  • 从节点选举
    • 每个从节点,根据自己对 master 节点复制数据的 offset,来设置一个选举优先级,offset 越大的从节点,选举优先级越靠前,优先进行选举
    • 所有的 master 节点开始 slave 选举投票,给要进行选举的从节点进行投票,如果大部分 master 节点 (N/2 + 1) 都投票给了某个从节点,那么选举通过,那个从节点可以切换成 master 节点
    • 从节点执行主备切换,从节点切换为主节点

四.RedisCluster-Windows搭建

1.概述

  • Redis官网的最新版本是7.0,但是并没有适配Windows版本。windows版本一直都是微软在进行维护,不过不会是最新版本,是基于Redis的稳定版本制作,本文编写时GitHub上Windows版本最新版为5.0.14.1
  • Redis官方网址:redis.io/download/

image.png

  • 微软Github网址:github.com/tporadowski…
  • 建议使用5.0.14,因为5.0.14.1搭建集群时,从节点会报以下错误,具体原因未知,如果有知道的看官老爷可以评论区告知,感谢!!!

image.png image.png

2.实战

  • 第一步:Github下载5.0.14版本Redis,由于Github有时需要科学上网才能进入,并且下载比较慢,所以下面是百度云盘相关资料地址,包括集群搭建其他资料,各位看官老爷需要的自取即可

image.png image.png

  • 第二步:创建一个文件夹,将下载好的压缩包解压到一个文件夹中,并复制5份,以端口号命名,代表6个节点,最少6个节点,三主三从

image.png image.png

  • 第三步:修改每个文件夹中的redis.windows.conf文件,修改以下几个地方,注意每个节点配置文件端口需要按照节点自己的端口号进行配置,其他参数各位可以视情况进行修改。可以修改一份后拷贝到每个文件夹中,然后修改两处端口号即可
# 是否开启保护模式,yes时外部访问redis需要配置bind ip或访问密码,线上建议开启
protected-mode no

# 每个redis的端口都需要不同
port 6379

# 是否开启集群
cluster-enabled yes

# 该参数指定了集群配置文件的位置。每个节点在运行过程中,会维护一份集群配置文件
# 每当集群信息发生变化时比如增删节点,集群内所有节点会将最新信息更新到该配置文件
# 当节点重启后,会重新读取该配置文件,获取集群信息,可以方便的重新加入到集群中
# 此集群配置文件redis节点会自行维护,注意修改端口号
cluster-config-file nodes-6379.conf

# 集群节点不可用的最大时间,单位是毫秒,如果主节点在指定时间内不可达,那么会进行故障转移
cluster-node-timeout 15000

# 云服务器上部署需指定公网ip,或者Linux系统中的Docker运行时需要指定宿主机Ip
# cluster-announce-ip 公网ip地址

# 开启AOF持久化
appendonly yes
# AOF持久化文件名称
appendfilename "appendonly.aof"

# 设置节点密码,注意所有节点密码必须一致
requirepass 123456

# 主节点连接密码,用于保护集群,保持与requirepass一致即可
masterauth 123456

image.png

  • 第四步:使用命令根据配置文件启动所有节点,此时只是启动所有节点,但是没有启动集群
redis-server.exe redis.windows.conf

image.png

  • 也可以编写启动脚本,在每个节点文件夹里面放置一个此bat文件,双击脚本即可启动节点

image.png image.png

  • 第五步:在任意一个集群节点目录下执行命令启动集群
    • --cluster-replicas 1:一个主节点有一个从节点
    • -a:此属性指定集群节点密码,如果没有配置密码则不需要此属性
    • 注意:第一次需要执行创建集群命令,创建成功后续如果再想打开集群,那么启动所有节点即可,无需再次执行创建集群命令
redis-cli --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 --cluster-replicas 1 -a 123456
  • 此处会询问我们是否接受他分配的节点主从关系以及HashSlot,我们输入yes即可

image.png image.png

  • 第六步:测试
    • 注意:在创建集群之前一定不能连接节点,否则会创建集群失败
# 连接集群节点,可以连接启动的任意节点 -a 是指定节点密码
redis-cli.exe -c -h 127.0.0.1 -p 6379 -a 123456

# 查看集群信息
cluster info

# 查看当前Redis节点
info replication

# 查看当前节点槽位
cluster nodes

image.png

  • keys命令只能查当前节点,但是get、set命令可以操作所有节点

image.png

五.RedisCluster-Linux搭建

1.概述

  • 本文以windows中使用Linux虚拟机,并在虚拟机中使用Docker,Docker通过容器编排搭建Redis集群,如果使用云服务器那么部分配置需要修改,请另行百度
  • 需要先在windows上安装Linux系统,并在Linux系统中安装Docker,如果已安装请略过,如果未安装请参考本人另外一篇文章进行安装
  • 文章地址:juejin.cn/post/722061…

2.实战

  • 第一步:先下载一个SSH连接工具并连接到Linux,也可以使用IDEA连接,傻瓜式安装即可
  • FinalShell下载地址:www.hostbuf.com/t/988.html

image.png image.png image.png

  • 第二步:创建节点配置文件夹
# 返回上一级
cd ..
# 进入home目录
cd home
# 创建RedisCluster文件夹
mkdir RedisCluster
# 进入RedisCluster文件夹
cd RedisCluster
# 创建六个文件夹并创建出6个配置文件
mkdir 7901 7902 7903 7904 7905 7906
# 进入到创建好的7901文件夹中
ls
cd 7901

image.png

  • 第三步:创建节点配置文件
# 1.创建并编辑配置文件
vi redis.conf
# 2.点击i进入编辑状态,然后将下面的配置文件粘贴到redis.conf文件中,注意修改端口号
# 3.点击Esc退出输入
# 4.输入命令保存并退出注意有冒号::wq
# 5.执行 ls 查看是否创建成功
# 是否开启保护模式,yes时外部访问redis需要配置bind ip或访问密码,线上建议开启
protected-mode no

# 主节点连接密码,用于保护集群,保持与requirepass一致即可
masterauth 123456

# 每个redis的端口都需要不同
port 7901

# 是否开启集群
cluster-enabled yes

# 该参数指定了集群配置文件的位置。每个节点在运行过程中,会维护一份集群配置文件
# 每当集群信息发生变化时比如增删节点,集群内所有节点会将最新信息更新到该配置文件
# 当节点重启后,会重新读取该配置文件,获取集群信息,可以方便的重新加入到集群中
# 此集群配置文件redis节点会自行维护,注意修改端口号
cluster-config-file nodes-7901.conf

# 集群节点不可用的最大时间,单位是毫秒,如果主节点在指定时间内不可达,那么会进行故障转移
cluster-node-timeout 15000

# 云服务器上部署需指定公网ip,此处设置Linux服务器Ip
cluster-announce-ip 192.168.2.79
# 集群节点映射端口
cluster-announce-port 7901
# 集群节点通信端口
cluster-announce-bus-port 17901

# 开启AOF持久化
appendonly yes
# AOF持久化文件名称
appendfilename "appendonly.aof"

# 设置节点密码,注意所有节点密码必须一致
requirepass 123456

# Redis后台运行,在docker中无需开启
daemonize no

image.png image.png

  • 第四步:拷贝配置文件到其他节点文件夹中
[root@localhost 7901]# cp redis.conf ../7902
[root@localhost 7901]# cp redis.conf ../7903
[root@localhost 7901]# cp redis.conf ../7904
[root@localhost 7901]# cp redis.conf ../7905
[root@localhost 7901]# cp redis.conf ../7906

image.png

  • 第五步:修改所有节点配置文件
# 进入到其他五个节点文件夹中修改配置的端口
vi redis.conf
# 点击Esc 输入 :wq保存并退出
# 返回上一级编辑指定文件夹:vi ../7902/redis.conf 

image.png image.png

  • 第六步:编写docker-compose.yml文件
    • 注意:要在RedisCluster文件夹下创建

image.png image.png

version: "3"

# 定义服务可以多个
services:
  redis-cluster:
    image: redis:latest
    command: redis-cli --cluster create  192.168.2.79:7901 192.168.2.79:7902 192.168.2.79:7903 192.168.2.79:7904 192.168.2.79:7905 192.168.2.79:7906 --cluster-replicas 1 --cluster-yes -a 123456
    depends_on:
      - redis-7901
      - redis-7902
      - redis-7903
      - redis-7904
      - redis-7905
      - redis-7906                  
  redis-7901: # 服务名称
    image: redis:latest # 创建容器时所需的镜像
    container_name: redis-7901 # 容器名称
    restart: always # 容器总是重新启动
    ports:
      - 7901:7901
      - 17901:17901
    volumes: # 数据卷,目录挂载
      - ./etc_rc.local:/etc/rc.local
      - ./7901/redis.conf:/etc/redis/redis.conf
      - ./7901/data:/data
    command: ["redis-server", "/etc/redis/redis.conf"] # 覆盖容器启动后默认执行的命令

  redis-7902: # 服务名称
    image: redis:latest # 创建容器时所需的镜像
    container_name: redis-7902 # 容器名称
    restart: always # 容器总是重新启动
    ports:
      - 7902:7902
      - 17902:17902
    volumes: # 数据卷,目录挂载
      - ./etc_rc.local:/etc/rc.local
      - ./7902/redis.conf:/etc/redis/redis.conf
      - ./7902/data:/data
    command: ["redis-server", "/etc/redis/redis.conf"] # 覆盖容器启动后默认执行的命令

  redis-7903: # 服务名称
    image: redis:latest # 创建容器时所需的镜像
    container_name: redis-7903 # 容器名称
    restart: always # 容器总是重新启动
    ports:
      - 7903:7903
      - 17903:17903
    volumes: # 数据卷,目录挂载
      - ./etc_rc.local:/etc/rc.local
      - ./7903/redis.conf:/etc/redis/redis.conf
      - ./7903/data:/data
    command: ["redis-server", "/etc/redis/redis.conf"] # 覆盖容器启动后默认执行的命令

  redis-7904: # 服务名称
    image: redis:latest # 创建容器时所需的镜像
    container_name: redis-7904 # 容器名称
    restart: always # 容器总是重新启动
    ports:
      - 7904:7904
      - 17904:17904
    volumes: # 数据卷,目录挂载
      - ./etc_rc.local:/etc/rc.local
      - ./7904/redis.conf:/etc/redis/redis.conf
      - ./7904/data:/data
    command: ["redis-server", "/etc/redis/redis.conf"] # 覆盖容器启动后默认执行的命令

  redis-7905: # 服务名称
    image: redis:latest # 创建容器时所需的镜像
    container_name: redis-7905 # 容器名称
    restart: always # 容器总是重新启动
    ports:
      - 7905:7905
      - 17905:17905
    volumes: # 数据卷,目录挂载
      - ./etc_rc.local:/etc/rc.local
      - ./7905/redis.conf:/etc/redis/redis.conf
      - ./7905/data:/data
    command: ["redis-server", "/etc/redis/redis.conf"] # 覆盖容器启动后默认执行的命令

  redis-7906: # 服务名称
    image: redis:latest # 创建容器时所需的镜像
    container_name: redis-7906 # 容器名称
    restart: always # 容器总是重新启动
    ports:
      - 7906:7906
      - 17906:17906
    volumes: # 数据卷,目录挂载
      - ./etc_rc.local:/etc/rc.local
      - ./7906/redis.conf:/etc/redis/redis.conf
      - ./7906/data:/data
    command: ["redis-server", "/etc/redis/redis.conf"] # 覆盖容器启动后默认执行的命令

  • 第七步:在RedisCluster文件下执行命令构建集群
docker-compose up -d

image.png

  • 第八步:测试
# 进入7901容器
docker exec -it redis-7901 bash

# 连接7901节点
redis-cli -c -p 7901 -a 123456

# 查看集群信息
cluster info

# 查看当前Redis节点
info replication

# 查看当前节点槽位
cluster nodes

  • 注意:要保证节点STATUS都是"Up XX seconds"才算成功

image.png image.png image.png

六.Java操作RedisCluster

1.概述

  • Redis目前Java中最火热的三个客户端都是可以操作RedisCluster:Jedis、Redisson、Lettuce

  • SpringBoot高版本中将spring-boot-starter-data-redis默认客户端从Jedis替换到了Lettuce,所以如果我们想要使用JedisCluster,那么需要移除Lettuce依赖,并引入Jedis依赖

  • 本文以SpringBoot中的JedisCluster集成进入RedisTemplate为例,演示Java当中如何使用JedisCluster操作RedisCluster

    • RedisTemplate: Spring对Redis操作的一层封装,高版本中底层是通过Lettuce实现的
  • 核心配置对象:SpringBoot项目会自动读取配置并为我们创建,所以我们只需要使用即可

    • JedisPoolConfig:用于配置连接池,包括最大连接数、最大空闲连接数、连接超时时间等等。连接池的作用是避免频繁创建和关闭连接,提高连接复用率和性能

    • RedisClusterConfiguration:用于配置Redis Cluster各个节点的信息,包括节点的IP地址和端口号

    • JedisConnectionFactory:连接工厂,它继承自RedisConnectionFactory,可以通过传入JedisPoolConfig和RedisClusterConfiguration来创建连接

    • RedisTemplate:它是一个泛型类,可以指定键和值的类型。通过JedisConnectionFactory创建出RedisTemplate Bean后,就可以通过它来进行各种Redis操作,如get、set、incr、zadd等等

    • 默认情况下,RedisTemplate使用的是JdkSerializationRedisSerializer进行序列化,这种序列化方式不易读取并且会出现乱码。为了解决这个问题,可以使用其他序列化方式,如Jackson和Fastjson

2.实战

  • 第一步:创建一个Maven工程

image.png

  • 第二步:导入依赖
    <!--SpringBoot父依赖-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.2</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <!--排除默认的lettuce-core客户端依赖-->
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--引入Jedis客户端依赖-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.6.1</version>
        </dependency>
        <!--jackson依赖-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.12.7.1</version>
        </dependency>
        <!--SpringBoot测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
  • 第三步:创建配置文件
server:
  port: 8080
spring:
  redis:
    timeout: 5000 # 超时时间
    database: 0 # 默认连接那个库
    cluster: # 集群节点Ip+Port,多个以逗号隔开
      nodes:
        - 127.0.0.1:6379
        - 127.0.0.1:6380
        - 127.0.0.1:6381
        - 127.0.0.1:6382
        - 127.0.0.1:6383
        - 127.0.0.1:6384
      max-redirects: 3 # 重定向最大次数
    jedis:
      pool: # jedis连接池配置
        max-active: 8 # 连接池中最大的活跃连接数,即同时能从连接池中获取的最大连接数
        max-wait: -1 # 当连接池中没有可用连接时,调用者最大阻塞等待时间(单位为毫秒),超过这个时间后将抛出异常,-1为无限等待
        max-idle: 8 # 连接池中最大的空闲连接数,即连接池中最多能保持多少个空闲连接,超过这个数目的空闲连接将被释放
        min-idle: 0 # 接池中最小的空闲连接数,即连接池中保持的最少的空闲连接数,如果空闲连接数小于这个数目且总连接数小于 max-active,连接池就会创建新的连接
    password: 123456 # 配置连接密码

image.png

  • 第四步:自定义创建RedisTemplate实例化对象
package cn.neuronet.config;


import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
    public class RedisConfiguration extends CachingConfigurerSupport {

        @Autowired
        private RedisConnectionFactory factory;

        @Bean
        public RedisTemplate<String, Object> redisTemplate() {
            // 1.创建一个key是String,value是Object类型的RedisTemplate,使用时类型必须对应
            RedisTemplate<String, Object> template = new RedisTemplate<>();

            // 2.JedisConnectionFactory会使用RedisClusterConfiguration和JedisPoolConfig对象来创建JedisCluster连接池
            template.setConnectionFactory(factory);

            // 3.创建一个Jackson2JsonRedisSerializer对象,指定需要序列化和反序列化的对象类型为Object类型,替换默认的jdkSerializeable序列化
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
            // 4.创建ObjectMapper对象,用于设置访问属性和默认类型
            ObjectMapper om = new ObjectMapper();
            // 5.设置所有访问属性可见,同时将对象的类型信息一起序列化到JSON串中
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            // 6.将ObjectMapper对象设置到Jackson2JsonRedisSerializer中
            jackson2JsonRedisSerializer.setObjectMapper(om);
            // 7.用于序列化Redis中的String类型的数据。它是RedisTemplate的默认key序列化器,将Rediskey从String类型序列化为字节数组,以便于存储到Redis中
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

            // 8.String数据类型的Key采用String的序列化方式
            template.setKeySerializer(stringRedisSerializer);
            // 9.Hash数据类型的key也采用String的序列化方式
            template.setHashKeySerializer(stringRedisSerializer);
            // 10.String数据类型的value序列化方式采用jackson
            template.setValueSerializer(jackson2JsonRedisSerializer);
            // 11.Hash数据类型的value序列化方式采用jackson
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();
            return template;
        }
    }
  • 第五步:测试
package cn.neuronet;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

/**
* @Author: Neuronet
* @Date: 2023-04-23
* @Description: RedisCluster集群测试
* @Version:1.0
*/
@SpringBootTest
    public class RedisClusterTest {
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;

        @Test
        public void test() throws Exception{
            redisTemplate.opsForValue().set("neuronet", "稀土掘金");
            Object neuronetStr = redisTemplate.opsForValue().get("neuronet");
            System.out.println(neuronetStr);
        }

    }

image.png

  • 第七步:由于原生RedisTemplate方法在开发时会感觉到使用不便,所以通常来说项目中都会对RedisTemplate再次进行封装,此工具类百度文章很多,此处就不进行代码的粘贴,各位看官老爷自行百度RedisUtils查询即可,感谢阅读!!!