{"msg":"操作成功","code":200,"data":{"createBy":"admin","createTime":"2020-10-17 15:41:04","updateBy":"admin","updateTime":"2026-03-03 16:37:54","remark":null,"id":37,"articleTitle":"Redis（四）之集群","articleUrl":"redis_cluster","articleThumbnail":"https://www.asumimoe.com/imgfiles/20220908/828bf19b43f2452d8540393410257d30.png","articleFlag":"1","draftStatus":"1","reprintStatement":"0","articleSummary":"集群，即Redis Cluster，是Redis 3.0开始引入的分布式存储方案。集群由多个节点(Node)组成，Redis的数据分布在这些节点中。集群中的节点分为主节点和从节点：只有主节点负责读写请求和集群信息的维护；从节点只进行主节点数据和状态信息的复制。","articleContent":"## 一、集群的作用\n\n集群，即Redis Cluster，是Redis 3.0开始引入的分布式存储方案。\n\n集群由多个节点(Node)组成，Redis的数据分布在这些节点中。集群中的节点分为主节点和从节点：只有主节点负责读写请求和集群信息的维护；从节点只进行主节点数据和状态信息的复制。\n\n集群的作用，可以归纳为两点：\n\n1. 数据分区：数据分区(或称数据分片)是集群最核心的功能。\n\n   集群将数据分散到多个节点，一方面突破了Redis单机内存大小的限制，存储容量大大增加；另一方面每个主节点都可以对外提供读服务和写服务，大大提高了集群的响应能力。\n\n   Redis单机内存大小受限问题，在介绍持久化和主从复制时都有提及；例如，如果单机内存太大，bgsave和bgrewriteaof的fork操作可能导致主进程阻塞，主从环境下主机切换时可能导致从节点长时间无法提供服务，全量复制阶段主节点的复制缓冲区可能溢出……。\n\n2. 高可用：集群支持主从复制和主节点的自动故障转移（与哨兵类似）；当任一节点发生故障时，集群仍然可以对外提供服务。\n\n## 二、集群的搭建\n\n方便起见，所有的节点布置在同一服务器上，以端口进行区分。三个主节点端口：7000/7001/7002，三个从节点端口：8000/8001/8002。\n\n### 1.执行Redis命令搭建集群\n\n集群搭建的步骤可以分为四步\n\n1）启动节点：将节点以集群的模式启动，此时节点是独立的，并没有建立联系；\n\n2）节点握手：让独立的节点连成一个网络；\n\n3）分配槽：将16384个槽分配给主节点；\n\n4）指定主从关系：为从节点指定主节点。\n\n#### （1）启动节点\n\n启动节点仍然需要redis-server命令，但需要使用集群模式启动。下面是7000节点的配置文件（其余节点只需要修改相应端口号即可）。\n\n```shell\n#redis-7000.conf\nport 7000\nbind 127.0.0.1\ndatabases 16\nsave 900 1\ncluster-enabled yes\ncluster-config-file \"node-7000.conf\"\nlogfile \"log-7000.log\"\ndbfilename \"dump-7000.rdb\"\ndaemonize yes\n```\n\n其中cluster-anabled与cluster-config-file是与集群相关的配置。\n\n**cluster-enabled yes**：Redis实例可以分为单机模式(standalone)和集群模式(cluster)；cluster-enabled yes可以启动集群模式。在单机模式下启动的Redis实例，如果执行info  server命令，可以发现redis_mode一项为standalone，集群模式下的redis_mode则为cluster\n\n**cluster-config-file**：该参数指定了**集群配置文件**的位置。每个节点在运行过程中，会维护一份集群配置文件；每当集群信息发生变化时（如增减节点），集群内所有节点会将最新信息更新到该配置文件；当节点重启后，会重新读取该配置文件，获取集群信息，可以方便的重新加入到集群中。**也就是说，当Redis节点以集群模式启动时，会首先寻找是否有集群配置文件，如果有则使用文件中的配置启动，如果没有，则初始化配置并将配置保存到文件中。**集群配置文件由Redis节点维护，不需要人工修改。\n\n使用redis-server将节点分别启动。节点启动后，可以查看节点情况：\n\n```shell\nredis-cli -p 7000 cluster nodes\nab1b40365384a0d14f40150ae8e40964bc07c9a2 :7000@17000 myself,master - 0 0 0 connected\n```\n\n其中返回值第一项表示节点id，由40个16进制字符串组成，节点id与之前提到的runId不同：Redis每次启动runId都会重新创建，但是节点id只在集群初始化时创建一次，然后保存到集群配置文件中，以后节点重新启动时会直接在集群配置文件中读取。\n\n#### （2）节点握手\n\n节点启动以后是相互独立的，并不知道其他节点存在；需要进行节点握手，将独立的节点组成一个网络。\n\n节点握手使用cluster meet {ip} {port}命令实现，例如在7000节点中执行cluster meet  192.168.72.128  7001，可以完成7000节点和7001节点的握手；注意ip使用的是局域网ip而不是localhost或127.0.0.1，是为了其他机器上的节点或客户端也可以访问。此时再使用cluster nodes查看：\n\n```shell\n127.0.0.1:7000> cluster meet 127.0.0.1 7001\nOK\n127.0.0.1:7000> cluster nodes\nd270bd47463927eb640b29b583d5643bab5c991b 127.0.0.1:7001@17001 master - 0 1602920766038 0 connected\nab1b40365384a0d14f40150ae8e40964bc07c9a2 127.0.0.1:7000@17000 myself,master - 0 0 1 connected\n```\n\n同理将其他节点都加入集群，完成握手：\n\n```shell\n127.0.0.1:7000> cluster meet 127.0.0.1 7002\nOK\n127.0.0.1:7000> cluster meet 127.0.0.1 8000\nOK\n127.0.0.1:7000> cluster meet 127.0.0.1 8001\nOK\n127.0.0.1:7000> cluster meet 127.0.0.1 8002\nOK\n127.0.0.1:7000> cluster nodes\ne254e52be922ca080f478555d12aad5295f05f2d 127.0.0.1:7002@17002 master - 0 1602920874000 0 connected\n04e946ae46ee5a8e35756bc1c6a49522feaed543 127.0.0.1:8001@18001 master - 0 1602920873923 4 connected\nab1b40365384a0d14f40150ae8e40964bc07c9a2 127.0.0.1:7000@17000 myself,master - 0 1602920873000 1 connected\nd270bd47463927eb640b29b583d5643bab5c991b 127.0.0.1:7001@17001 master - 0 1602920873000 2 connected\n6d8061fab34e3b1cb2371ca56f61c3e0b0b1b085 127.0.0.1:8002@18002 master - 0 1602920874932 5 connected\na9a2323132d4931be2705d1e32a1ad6001613db5 127.0.0.1:8000@18000 master - 0 1602920872000 3 connected\n```\n\n每个节点都可以感知到其他节点，可以在其他节点使用cluster nodes验证。\n\n#### （3）分配槽\n\n在Redis集群中，借助槽实现数据分区，具体原理后文会介绍。**集群有16384个槽，槽是数据管理和迁移的基本单位。当数据库中的16384个槽都分配了节点时，集群处于上线状态（ok）；如果有任意一个槽没有分配节点，则集群处于下线状态（fail）。**\n\ncluster info 可以查看分配槽之前的状态为fail:\n\n```shell\n127.0.0.1:7000> cluster info\ncluster_state:fail\ncluster_slots_assigned:0\n```\n\n分配槽使用cluster addslots命令，执行下面的命令将槽（0-16383）全部分配完毕：\n\n```shell\nredis-cli -p 7000 cluster addslots {0..5461}\nredis-cli -p 7001 cluster addslots {5462..10922}\nredis-cli -p 7002 cluster addslots {10923..16383}\n```\n\n分配完槽之后集群装台为ok状态：\n\n```shell\n127.0.0.1:7000> cluster info\ncluster_state:ok\ncluster_slots_assigned:16384\n```\n\n#### （4）指定主从关系\n\n集群中指定主从关系不再使用slaveof命令，而是使用cluster replicate命令；参数使用节点id。通过cluster nodes获取几个主节点的id后，执行下面的命令为每个从节点指定主节点：\n\n```shell\nredis-cli -p 8000 cluster replicate ab1b40365384a0d14f40150ae8e40964bc07c9a2\nredis-cli -p 8001 cluster replicate d270bd47463927eb640b29b583d5643bab5c991b\nredis-cli -p 8002 cluster replicate e254e52be922ca080f478555d12aad5295f05f2d\n```\n\n再次查看集群状态，可以查看到主从关系已经建立：\n\n```shell\n127.0.0.1:7000> cluster nodes\ne254e52be922ca080f478555d12aad5295f05f2d 127.0.0.1:7002@17002 master - 0 1602921499053 0 connected 10923-16383\n04e946ae46ee5a8e35756bc1c6a49522feaed543 127.0.0.1:8001@18001 slave d270bd47463927eb640b29b583d5643bab5c991b 0 1602921500063 4 connected\nab1b40365384a0d14f40150ae8e40964bc07c9a2 127.0.0.1:7000@17000 myself,master - 0 1602921497000 1 connected 0-5461\nd270bd47463927eb640b29b583d5643bab5c991b 127.0.0.1:7001@17001 master - 0 1602921498000 2 connected 5462-10922\n6d8061fab34e3b1cb2371ca56f61c3e0b0b1b085 127.0.0.1:8002@18002 slave e254e52be922ca080f478555d12aad5295f05f2d 0 1602921497035 5 connected\na9a2323132d4931be2705d1e32a1ad6001613db5 127.0.0.1:8000@18000 slave ab1b40365384a0d14f40150ae8e40964bc07c9a2 0 1602921498045 3 connected\n```\n\n### 2.使用Ruby脚本搭建集群\n\n在redis安装目录下的src目录中可以看到redis-trib.rb文件，这是一个Ruby脚本，可以实现自动化的集群搭建。\n\n#### （1）安装Ruby环境\n\n```bash\nyum install ruby -y #安装ruby环境\ngem install redis #gem是ruby的包管理工具，该命令可以安装ruby-redis依赖\n```\n\n#### （2）启动节点\n\n与第一种方式启动节点的方法相同。\n\n#### （3）搭建集群\n\n```bash\n./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:8000 127.0.0.1:8001 127.0.0.1:8002\n```\n\n其中：--replicas=1表示每个主节点有一个从节点；后面的多个{ip:port}表示节点地址，前面的做主节点，后面的做从节点。使用redis-trib.rb搭建集群时，要求槽内不能包含任何槽和数据。**（有些redis版本可能已经不再用redis-trib.rb，而是使用redis-cli）**\n\n执行创建命令后，脚本会给出创建集群的计划，包括哪个是主节点，哪个是从节点，以及如何分配槽。输入yes执行该计划\n\n```bash\nredis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:8000 127.0.0.1:8001 127.0.0.1:8002 --cluster-replicas 1\n>>> Performing hash slots allocation on 6 nodes...\nMaster[0] -> Slots 0 - 5460\nMaster[1] -> Slots 5461 - 10922\nMaster[2] -> Slots 10923 - 16383\nAdding replica 127.0.0.1:8001 to 127.0.0.1:7000\nAdding replica 127.0.0.1:8002 to 127.0.0.1:7001\nAdding replica 127.0.0.1:8000 to 127.0.0.1:7002\n>>> Trying to optimize slaves allocation for anti-affinity\n[WARNING] Some slaves are in the same host as their master\nM: 6c0479678225eae705f929af135c34eb6997a342 127.0.0.1:7000\n   slots:[0-5460] (5461 slots) master\nM: 9bcc47f14fe1f70b285b8f53450784937194e3ce 127.0.0.1:7001\n   slots:[5461-10922] (5462 slots) master\nM: e38169f52e92dac45ed18114849d408ef36a673e 127.0.0.1:7002\n   slots:[10923-16383] (5461 slots) master\nS: b87aba32ff70e672cc60b796a43bd5bb0802602a 127.0.0.1:8000\n   replicates 9bcc47f14fe1f70b285b8f53450784937194e3ce\nS: 55a09d40467497d49e0a18323dc72e7be5af9873 127.0.0.1:8001\n   replicates e38169f52e92dac45ed18114849d408ef36a673e\nS: 7fb27c2982a7b91f37945bad19b866fc2565d82b 127.0.0.1:8002\n   replicates 6c0479678225eae705f929af135c34eb6997a342\nCan I set the above configuration? (type 'yes' to accept): yes\n>>> Nodes configuration updated\n>>> Assign a different config epoch to each node\n>>> Sending CLUSTER MEET messages to join the cluster\nWaiting for the cluster to join\n....\n>>> Performing Cluster Check (using node 127.0.0.1:7000)\nM: 6c0479678225eae705f929af135c34eb6997a342 127.0.0.1:7000\n   slots:[0-5460] (5461 slots) master\n   1 additional replica(s)\nS: b87aba32ff70e672cc60b796a43bd5bb0802602a 127.0.0.1:8000\n   slots: (0 slots) slave\n   replicates 9bcc47f14fe1f70b285b8f53450784937194e3ce\nS: 7fb27c2982a7b91f37945bad19b866fc2565d82b 127.0.0.1:8002\n   slots: (0 slots) slave\n   replicates 6c0479678225eae705f929af135c34eb6997a342\nM: e38169f52e92dac45ed18114849d408ef36a673e 127.0.0.1:7002\n   slots:[10923-16383] (5461 slots) master\n   1 additional replica(s)\nS: 55a09d40467497d49e0a18323dc72e7be5af9873 127.0.0.1:8001\n   slots: (0 slots) slave\n   replicates e38169f52e92dac45ed18114849d408ef36a673e\nM: 9bcc47f14fe1f70b285b8f53450784937194e3ce 127.0.0.1:7001\n   slots:[5461-10922] (5462 slots) master\n   1 additional replica(s)\n[OK] All nodes agree about slots configuration.\n>>> Check for open slots...\n>>> Check slots coverage...\n[OK] All 16384 slots covered.\n```\n\n### 3.集群方案设计\n\n设计集群方案时，至少要考虑以下因素：\n\n（1）高可用要求：根据故障转移的原理，至少需要3个主节点才能完成故障转移，且3个主节点不应在同一台物理机上；每个主节点至少需要1个从节点，且主从节点不应在一台物理机上；因此高可用集群至少包含6个节点。\n\n（2）数据量和访问量：估算应用需要的数据量和总访问量(考虑业务发展，留有冗余)，结合每个主节点的容量和能承受的访问量(可以通过benchmark得到较准确估计)，计算需要的主节点数量。\n\n（3）节点数量限制：Redis官方给出的节点数量限制为1000，主要是考虑节点间通信带来的消耗。在实际应用中应尽量避免大集群；如果节点数量不足以满足应用对Redis数据量和访问量的要求，可以考虑：(1)业务分割，大集群分为多个小集群；(2)减少不必要的数据；(3)调整数据过期策略等。\n\n（4）适度冗余：Redis可以在不影响集群服务的情况下增加节点，因此节点数量适当冗余即可，不用太大。\n\n## 三、集群的基本原理\n\n集群最核心的功能是数据分区，因此首先介绍数据的分区规则；然后介绍集群实现的细节：通信机制和数据结构；最后以cluster meet(节点握手)、cluster addslots(槽分配)为例，说明节点是如何利用上述数据结构和通信机制实现集群命令的。\n\n### 1.数据分区方案\n\n数据分区有顺序分区、哈希分区等，其中哈希分区由于其天然的随机性，使用广泛；集群的分区方案便是哈希分区的一种。\n\n哈希分区的基本思路是：对数据的特征值（如key）进行哈希，然后根据哈希值决定数据落在哪个节点。常见的哈希分区包括：哈希取余分区、一致性哈希分区、带虚拟节点的一致性哈希分区等。\n\n衡量数据分区方法好坏的标准有很多，其中比较重要的两个因素是(1)数据分布是否均匀(2)增加或删减节点对数据分布的影响。由于哈希的随机性，哈希分区基本可以保证数据分布均匀；因此在比较哈希分区方案时，重点要看增减节点对数据分布的影响。\n\n#### （1）哈希取余分区\n\n哈希取余分区思路非常简单：计算key的hash值，然后对节点数量进行取余，从而决定数据映射到哪个节点上。该方案最大的问题是，当新增或删减节点时，节点数量发生变化，系统中所有的数据都需要重新计算映射关系，引发大规模数据迁移。\n\n#### （2）一致性哈希分区\n\n一致性哈希算法将整个哈希值空间组织成一个虚拟的圆环，如下图所示，范围为0-2^32-1；对于每个数据，根据key计算hash值，确定数据在环上的位置，然后从此位置沿环顺时针行走，找到的第一台服务器就是其应该映射到的服务器。\n\n![img](https://www.asumimoe.com/imgfiles/20260303/0c4a2e8d8cb743929acad1e0b4fc11ec.png)\n\n与哈希取余分区相比，一致性哈希分区将增减节点的影响限制在相邻节点。以上图为例，如果在node1和node2之间增加node5，则只有node2中的一部分数据会迁移到node5；如果去掉node2，则原node2中的数据只会迁移到node4中，只有node4会受影响。\n\n一致性哈希分区的主要问题在于，当节点数量较少时，增加或删减节点，对单个节点的影响可能很大，造成数据的严重不平衡。还是以上图为例，如果去掉node2，node4中的数据由总数据的1/4左右变为1/2左右，与其他节点相比负载过高。\n\n#### （3）带虚拟节点的一致性哈希分区\n\n该方案在一致性哈希分区的基础上，引入了虚拟节点的概念。Redis集群使用的便是该方案，其中的虚拟节点称为槽（slot）。槽是介于数据和实际节点之间的虚拟概念；每个实际节点包含一定数量的槽，每个槽包含哈希值在一定范围内的数据。引入槽以后，数据的映射关系由数据hash->实际节点，变成了数据hash->槽->实际节点。\n\n**在使用了槽的一致性哈希分区中，槽是数据管理和迁移的基本单位。槽解耦了数据和实际节点之间的关系，增加或删除节点对系统的影响很小。**仍以上图为例，系统中有4个实际节点，假设为其分配16个槽(0-15)；  槽0-3位于node1，4-7位于node2，以此类推。如果此时删除node2，只需要将槽4-7重新分配即可，例如槽4-5分配给node1，槽6分配给node3，槽7分配给node4；可以看出删除node2后，数据在其他节点的分布仍然较为均衡。\n\n槽的数量一般远小于2^32，远大于实际节点的数量；在Redis集群中，槽的数量为16384。\n\n下面这张图很好的总结了Redis集群将数据映射到实际节点的过程：\n\n![img](https://www.asumimoe.com/imgfiles/20260303/42f6a2bcb0574eb09194564dc3001ecf.png)\n\n- Redis对数据的特征值（一般是key）计算哈希值，使用的算法是CRC16。\n\n- 根据哈希值，计算数据属于哪个槽。\n\n- 根据槽与节点的映射关系，计算数据属于哪个节点。\n\n### 2.节点通信机制\n\n#### 两个端口\n\n在哨兵系统中，节点分为数据节点和哨兵节点：前者存储数据，后者实现额外的控制功能。在集群中，没有数据节点与非数据节点之分：所有的节点都存储数据，也都参与集群状态的维护。为此，集群中的每个节点，都提供了两个TCP端口：\n\n- 普通端口：即我们在前面指定的端口(7000等)。普通端口主要用于为客户端提供服务（与单机节点类似）；但在节点间数据迁移时也会使用。\n- 集群端口：端口号是普通端口+10000（10000是固定值，无法改变），如7000节点的集群端口为17000。集群端口只用于节点之间的通信，如搭建集群、增减节点、故障转移等操作时节点间的通信；不要使用客户端连接集群接口。为了保证集群可以正常工作，在配置防火墙时，要同时开启普通端口和集群端口。\n\n#### Gossip协议\n\n节点间通信，按照通信协议可以分为几种类型：单对单、广播、Gossip协议等。重点是广播和Gossip的对比。\n\n广播是指向集群内所有节点发送消息；优点是集群的收敛速度快(集群收敛是指集群内所有节点获得的集群信息是一致的)，缺点是每条消息都要发送给所有节点，CPU、带宽等消耗较大。\n\nGossip协议的特点是：在节点数量有限的网络中，每个节点都“随机”的与部分节点通信（并不是真正的随机，而是根据特定的规则选择通信的节点），经过一番杂乱无章的通信，每个节点的状态很快会达到一致。Gossip协议的优点有负载(比广播)低、去中心化、容错性高(因为通信有冗余)等；缺点主要是集群的收敛速度慢。\n\n#### 消息类型\n\n集群中的节点采用固定频率（每秒10次）的定时任务进行通信相关的工作：判断是否需要发送消息及消息类型、确定接收节点、发送消息等。如果集群状态发生了变化，如增减节点、槽状态变更，通过节点间的通信，所有节点会很快得知整个集群的状态，使集群收敛。\n\n节点间发送的消息主要分为5种：meet消息、ping消息、pong消息、fail消息、publish消息。不同的消息类型，通信协议、发送的频率和时机、接收节点的选择等是不同的。\n\n- MEET消息：在节点握手阶段，当节点收到客户端的CLUSTER MEET命令时，会向新加入的节点发送MEET消息，请求新节点加入到当前集群；新节点收到MEET消息后会回复一个PONG消息。\n- PING消息：集群里每个节点每秒钟会选择部分节点发送PING消息，接收者收到消息后会回复一个PONG消息。PING消息的内容是自身节点和部分其他节点的状态信息；作用是彼此交换信息，以及检测节点是否在线。PING消息使用Gossip协议发送，接收节点的选择兼顾了收敛速度和带宽成本，具体规则如下：(1)随机找5个节点，在其中选择最久没有通信的1个节点(2)扫描节点列表，选择最近一次收到PONG消息时间大于cluster_node_timeout/2的所有节点，防止这些节点长时间未更新。\n- PONG消息：PONG消息封装了自身状态数据。可以分为两种：第一种是在接到MEET/PING消息后回复的PONG消息；第二种是指节点向集群广播PONG消息，这样其他节点可以获知该节点的最新信息，例如故障恢复后新的主节点会广播PONG消息。\n- FAIL消息：当一个主节点判断另一个主节点进入FAIL状态时，会向集群广播这一FAIL消息；接收节点会将这一FAIL消息保存起来，便于后续的判断。\n- PUBLISH消息：节点收到PUBLISH命令后，会先执行该命令，然后向集群广播这一消息，接收节点也会执行该PUBLISH命令。\n\n转载自：[https://www.cnblogs.com/kismetv/p/9853040.html](https://www.cnblogs.com/kismetv/p/9853040.html)","categoryId":2,"viewCount":1090,"categoryName":"中间件","author":"球接子","authorAvatar":null,"tagIds":[1,14,17],"tagNames":["Redis","中间件","数据库"]}}