Redis集群

Redis集群搭建与工作原理

前言

如果你想快速搭建集群,百度中有大量的新手资料。本博客主要介绍集群的底层实现细节,比较枯燥,还请读者细品、仔细品。

RedisCluster

集群是Redis分布式数据库的解决方案,具备高可用、高性能的特点,在生产中得到了广泛的应用。

1.搭建集群

一个Redis集群通常由多个节点(node)组成,所以我们首先要启动节点并将多个节点连接起来组成一个集群。

1.1 启动节点

Redis服务器会在启动时检查配置文件(redis.conf)中的cluster-enable配置项值来决定是否开启集群模式,流程如下:
在这里插入图片描述
集群模式:集群模式会继续使用单机模式下的一些组件

  • 节点继续使用文件事件处理器来处理和返回命令
  • 节点继续使用时间处理器,在时间处理器中调用集群特有的函数(向其他节点发送gossip协议、检查节点是否下线等)
  • 继续使用数据库来保存键值对
  • 继续使用RDB和AOF来持久化
  • 继续使用复制模块来进行复制工作

1.2 连接节点

我们可以通过cluster meet 命令,让服务器节点与指定节点握手,握手成功后,就会将ip和port指定的节点加入到当前集群中,如下图:

cluster meet <ip> <port>

在这里插入图片描述

1.2.1 cluster meet命令的实现

客户端 ->cluster meet B ->A -->hadle ->B
①:节点A为节点B创建一个ClusterNode结构,并添加到ClusterState. nodes中。
②:节点A根据指定的Ip和端口向节点B发送一条meet消息
③:节点B收到后,会为节点A创建一个ClusterNode结构,并添加到自己ClusterStates.nodes中
④: 节点B向节点A发送一条Pong消息
⑤: 节点A收到后,就确定B已经收到meet消息
⑥: 节点A向节点B发送一条Ping消息
⑦: 节点B收到A的Ping消息,就确定A已经收到了自己Pong消息,握手完成。
在这里插入图片描述

1.3 槽指派

集群的整个数据库被分为16384个槽,每个节点都可以处理0个或者最多16384个槽。我们可以通过cluster addslots 命令,将一个或者多个槽指派给指定节点负责。

  cluster addslots <slot> [slot ...]

1.3.1 记录节点的槽指派信息

节点负责的槽位信息该如何去保存呢?二进制位数组,每一位记录一个槽的指派信息,如果对应位为1,则代表当前节点负责该槽位。

public class ClusterNode {
    //..........
    /**
     * 二进制位数组,保存该节点负责的槽位信息
     * 如果二进制为1,则代表该节点负责该槽位
     */
    char[] slots;
    /**
     *  节点负责处理槽的数量
     */
    int numslots;
}
  • slots:二进制位数组,包含16384个二进制位
    在这里插入图片描述
  • numslots: 记录节点负责的槽的数量

1.3.2 传播节点的槽指派信息

如何将自己负责的槽位信息告诉其它节点呢?将二进制位数组(slots)发送给其他节点。
节点A通过消息从节点B那接收到节点B的slots数组时,节点A在自己的ClusterState.nodes字典中找到节点B对应的ClusterNode结构,并对结构中的slots数组进行保存或者更新。
在这里插入图片描述

1.3.3 记录集群所有槽的指派信息

ClusterNode.slots只记录自己负责槽位的信息,那么如何保存整个集群的槽信息呢?ClusterNode数组
ClusterNodes.slots: 自己负责的槽位信息
ClusterState.slots:整个集群的槽位信息

	class ClusterState {
	    /**
	     * 记录槽的指派信息,大小16384
	     */
	    ClusterNode[] slots;
	}

在这里插入图片描述

1.3.4 addSlots的实现

清楚如何保存槽位信息,那么addslots命令的实现呢?更新ClusterNodes.slots和ClusterState.slots,然后将更新结果通知给其他节点。
在这里插入图片描述

2.集群

集群启动后,当我们发生一条命令时,集群是怎样执行的呢?先根据key找到对应的槽位,再找到负责槽位的机器

2.1 集群命令

2.1.1 计算槽位

Redis采用CRC16算法来计算key对应的槽位,我们可以通过cluster keyslot key 命令来查看key对应的槽位信息。

	cluster keysolt key

2.1.2 判断槽位是否由当前节点负责

检查ClusterState.slots[i]是否等于ClusterState.myself,如果等于,则说明槽i由当前节点负责,执行命令;如果不等于,Redis会返货MOVED错误。

2.1.3 Moved错误

当节点发现键所在的槽位不由自己负责时,就会给客户端返回一个MOVED错误。在集群模式下,MOVED错误会被隐藏。

redis-cli -h -p  //单机模式
redis-cli -c -h -p //集群模式

2.2 节点数据库

集群模式下,节点只能使用0号数据库,而单机模式下没有这一限制。
在这里插入图片描述

2.2.1 槽&键

Redis节点会用跳跃表来保存槽和键之间的关系,跳跃表中每个节点的分值代表槽号,key对应数据库键。通过跳跃链表,我们可以很方便的返回某个槽对应的数据库键,这就是cluster getkeyinslot 命令实现的原理。

cluster getkeyinslot <slot> <count> //返回最多count个属于槽slot的数据库键

在这里插入图片描述

2.3 重新分片

有时候我们需要进行数据重分片,将已经指派给某个节点的槽改为指派给另一个节点。

2.3.1 重分片原理

Redis集群重分片是由redis-trib负责管理执行的,其执行过如下:
①:向目标节点发送Cluster setslot importing <source_id>命令,让目标节点准备从源节点导入属于槽slot的键值对
②:向源节点发送Cluster setslot migrate <target_id>命令,让源节点准备将属于槽slot的数据迁移之目标节点
③:向源节点发送Cluster getkeyinslot 命令,获取属于槽slot的键值对
④: 对于步骤3获得的每个键,redis-trib向源节点发送一个migrate <target_ip> <target_port> <key_name> 0 <time_out>命令,将键原子的从源节点迁移至目标节点
⑤:重复执行步骤3和步骤4,直到源节点保存的所有属于槽slot的键值对都被迁移至目标节点
⑥:将槽迁移的信息通知给集群中任意一节点,最后整个集群都会知道。
在这里插入图片描述

2.3.2 setslot importing

如何记录槽的迁移信息呢?每个节点提供一个迁移节点数组和一个导入节点数组。发送setslot importing的作用就是将目标节点的ClusterState.importingSlotFrom[slot]设置为源节点的ClusterNode;

 class ClusterState {
       /**
        * 当前节点正从其他节点导入的槽,大小16384
        * importingSlotFrom[slot] !=null:当前正从importingSlotFrom[slot]                          导入槽slot的数据
        */
      ClusterNode[] importingSlotFrom;
}

2.3.3 setslot migrating

Redis的migratingSlotTo记录了当前节点迁移到其他节点的信息。

 class ClusterState {
  /**
   * 当前节点正迁至其他节点的槽,大小16384
   *  migratingSlotTo[slot] !=null:当前节点正将槽[slot]迁移至 importingSlotFrom[slot]对应的节点
   */
   ClusterNode[] migratingSlotTo;
}

2.3.4 重分片过程

在这里插入图片描述

2.4 ASK错误

在槽迁移过程中,一部分键保存在源节点中、一部分键保存在目标节点中。此时可能客户端请求处理的key正在迁移中,如果该key已经被迁移,那么服务器将会向客户端返回一个ASK错误。如果key还未被迁移,则直接执行命令。 在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.4.1 Asking命令

当客户端请求的key正在被迁移且已经迁移至目标节点时,已经通过ASK错误,获取到目标节点的信息,为什么不直接执行命令而是先执行一个ASK命令? 因为这个槽还不归目标节点负责,此时直接请求,会返回Moved异常。
ASKING命令的作用:打开客户端REDIS_ASKING标识
REDIS_ASKING标识作用:通常情况下如果该节点不负责该槽位,会直接返回Moved异常;但是如果该节点的migratingSlotTo显示该槽正在迁移且客户端打开了REDIS_ASKING标识,则会破例执行一,这个标识会在执行命令后移除。
在这里插入图片描述

2.4.2 ASK VS Moved

  • Moved代表该槽的负责权不在当前节点,客户端在接收到关于槽的Moved错误之后,客户端之后遇到关于该槽的命令请求时,都可以将命令请求发送至Moved错误指向的节点。
  • ASK错误只是两个节点在迁移槽时的临时措施,客户端在收到某个槽的ASK错误后,只会将下次该槽的请求转发到对应的节点,而不会对该槽之后的请求造成影响。
### Redis 集群的搭建与配置 #### 一、Redis 集群简介 Redis 是一种高性能的键值存储系统,支持多种数据结构操作。通过集群模式可以实现分布式存储和高可用性。Redis 集群允许多个 Redis 实例协同工作,提供更高的吞吐量和更强的数据持久化能力。 --- #### 二、环境准备 在开始之前,需确认以下条件已满足: - 所有服务器的操作系统版本一致(如 CentOS 7 或 Windows),并安装了相同版本的 Redis 软件。 - 已关闭防火墙或开放必要的端口(默认 Redis 使用 6379 及其衍生端口)。 - 每台服务器上至少有两个 Redis 实例运行,分别作为主节点和从节点[^1]。 --- #### 三、具体步骤 ##### 1. 下载并解压 Redis 文件 下载指定版本的 Redis 压缩包(如 Redis 6.2.5 或更高版本),将其解压到目标路径下。例如,在 Linux 中执行以下命令: ```bash wget http://download.redis.io/releases/redis-6.2.5.tar.gz tar -zxvf redis-6.2.5.tar.gz cd redis-6.2.5 make ``` ##### 2. 创建多个实例目录 为每个 Redis 实例创建独立的工作目录,并复制 `redis.conf` 至对应文件夹中。例如: ```bash mkdir -p /service/redis/{6379,6380} cp redis.conf /service/redis/6379/ cp redis.conf /service/redis/6380/ ``` ##### 3. 修改配置文件 编辑每个实例下的 `redis.conf` 文件,设置不同的监听端口号和其他必要参数。以下是关键配置项: - 设置绑定 IP 地址:`bind 0.0.0.0` - 关闭保护模式:`protected-mode no` - 开启集群功能:`cluster-enabled yes` - 指定集群配置文件位置:`cluster-config-file nodes-{port}.conf` - 设定日志级别:`loglevel notice` ##### 4. 启动 Redis 实例 依次启动各个 Redis 实例。例如: ```bash redis-server /service/redis/6379/redis.conf redis-server /service/redis/6380/redis.conf ``` 如果是在多台物理机上部署,则需要远程登录每台机器重复上述过程[^2]。 ##### 5. 构建集群拓扑 利用 `redis-cli` 的集群管理工具完成初始化操作。假设当前存在六个节点分布在三台主机上,则可输入如下指令构建集群关系: ```bash redis-cli --cluster create \ 192.168.x.y:6379 192.168.x.z:6379 ... \ --replicas 1 ``` 其中 `--replicas` 参数表示每个主节点分配几个副本[^4]。 ##### 6. 验证集群状态 最后可以通过以下方式验证集群是否正常运作: ```bash redis-cli -c -h {任意IP} -p {任一口号} CLUSTER INFO CLUSTER NODES PING SET key value GET key ``` --- #### 四、注意事项 - 如果使用 Docker 容器来部署 Redis 集群,请确保容器间网络互通良好。 - 对于 Windows 平台上的开发测试场景,可通过批处理脚本来简化服务启动流程[^3]。 --- #### 五、示例代码片段 下面展示了一个简单的 Python 程序用于连接至 Redis 集群并向其中写入一条记录: ```python import redis r = redis.StrictRedisCluster(startup_nodes=[{"host": "127.0.0.1", "port": "6379"}], decode_responses=True) r.set('foo', 'bar') print(r.get('foo')) ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值