Redis——集群-阿里云开发者社区

开发者社区> 李子捌> 正文

Redis——集群

简介: Redis集群是Redis提供的分布式数据库方案,集群通过分片(sharding)进行数据共享。
+关注继续查看

1、简介

Redis集群是Redis提供的分布式数据库方案,集群通过分片(sharding)进行数据共享,Redis集群主要实现了以下目标:

  • 在1000个节点的时候仍能表现得很好并且可扩展性是线性的。
  • 没有合并操作(多个节点不存在相同的键),这样在 Redis 的数据模型中最典型的大数据值中也能有很好的表现。
  • 写入安全,那些与大多数节点相连的客户端所做的写入操作,系统尝试全部都保存下来。但是Redis无法保证数据完全不丢失,异步同步的主从复制无论如何都会存在数据丢失的情况。
  • 可用性,主节点不可用,从节点能替换主节点工作。

关于Redis集群的学习,如果没有任何经验的弟兄们建议先看下这三篇文章(中文系列):

Redis集群教程

http://redis.cn/topics/cluster-tutorial.html

Redis集群规范

http://redis.cn/topics/cluster-spec.html

Redis3主3从伪集群部署

https://blog.csdn.net/qq_41125219/article/details/118686281


下文内容依赖下图三主三从结构开展:

image

资源清单:

节点

IP

槽(slot)范围

Master[0]

192.168.211.107:6319

Slots 0 - 5460

Master[1]

192.168.211.107:6329

Slots 5461 - 10922

Master[2]

192.168.211.107:6339

Slots 10923 - 16383

Slave[0]

192.168.211.107:6369

Slave[1]

192.168.211.107:6349

Slave[2]

192.168.211.107:6359

image



2、集群内部

Redis 集群没有使用一致性hash, 而是引入了?哈希槽的概念。Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,这种结构很容易添加或者删除节点。集群的每个节点负责一部分hash槽,比如上面资源清单的集群有3个节点,其槽分配如下所示:

  • 节点 Master[0] 包含 0 到 5460 号哈希槽
  • 节点 Master[1] 包含5461 到 10922 号哈希槽
  • 节点 Master[2] 包含10923到 16383 号哈希槽

深入学习Redis集群之前,需要了解集群中Redis实例的内部结构。当某个Redis服务节点通过cluster_enabled配置为yes开启集群模式之后,Redis服务节点不仅会继续使用单机模式下的服务器组件,还会增加custerState、clusterNode、custerLink等结构用于存储集群模式下的特殊数据。

如下三个数据承载对象一定要认真看,尤其是结构中的注释,看完之后集群大体上怎么工作的,心里就有数了,嘿嘿嘿;

2.1 clsuterNode

clsuterNode用于存储节点信息,比如节点的名字、IP地址、端口信息和配置纪元等等,以下代码列出部分非常重要的属性:

typedef struct clsuterNode {


? ? // 创建时间

? ? mstime_t ctime;

? ??

? ? // 节点名字,由40位随机16进制的字符组成(与sentinel中讲的服务器运行id相同)

? ? char name[REDIS_CLUSTER_NAMELEN];

? ??

? ? // 节点标识,可以标识节点的角色和状态

? ? // 角色 -> 主节点或从节点 例如:REDIS_NODE_MASTER(主节点) REDIS_NODE_SLAVE(从节点)

? ? // 状态 -> 在线或下线 例如:REDIS_NODE_PFAIL(疑似下线) REDIS_NODE_FAIL(下线)?

? ? int flags;

? ??

? ? // 节点配置纪元,用于故障转移,与sentinel中用法类似

? ? // clusterState中的代表集群的配置纪元

? ? unit64_t configEpoch;

? ??

? ? // 节点IP地址

? ? char ip[REDIS_IP_STR_LEN];

? ??

? ? // 节点端口

? ? int port;

? ??

? ? // 连接节点的信息

? ? clusterLink *link;

? ??

? ? // 一个2048字节的二进制位数组

? ? // 位数组索引值可能为0或1

? ? // 数组索引i位置值为0,代表节点不负责处理槽i

? ? // 数组索引i位置值为1,代表节点负责处理槽i

? ? unsigned char slots[16384/8];

? ??

? ? // 记录当前节点处理槽的数量总和

? ? int numslots;

? ??

? ? // 如果当前节点是从节点

? ? // 指向当前从节点的主节点

? ? struct clusterNode *slaveof;

? ??

? ? // 如果当前节点是主节点

? ? // 正在复制当前主节点的从节点数量

? ? int numslaves;

? ??

? ? // 数组——记录正在复制当前主节点的所有从节点

? ? struct clusterNode **slaves;

? ??

} clsuterNode;

上述代码中可能不太好理解的是slots[16384/8],其实可以简单的理解为一个16384大小的数组,数组索引下标处如果为1表示当前槽属于当前clusterNode处理,如果为0表示不属于当前clusterNode处理。clusterNode能够通过slots来识别,当前节点处理负责处理哪些槽。

初始clsuterNode或者未分配槽的集群中的clsuterNode的slots如下所示:

image

假设集群如上面我给出的资源清单,此时代表Master[0]的clusterNode的slots如下所示:

image

2.2 clusterLink

clusterLink是clsuterNode中的一个属性,用于存储连接节点所需的相关信息,比如套接字描述符、输入输出缓冲区等待,以下代码列出部分非常重要的属性:

typedef struct clusterState {


? ? // 连接创建时间

? ? mstime_t ctime;

? ?

? ? // TCP 套接字描述符

? ? int fd;

? ??

? ? // 输出缓冲区,需要发送给其他节点的消息缓存在这里

? ? sds sndbuf;

? ??

? ? // 输入缓冲区,接收打其他节点的消息缓存在这里

? ? sds rcvbuf;

? ??

? ? // 与当前clsuterNode节点代表的节点建立连接的其他节点保存在这里

? ? struct clusterNode *node;

} clusterState;

2.3 custerState

每个节点都会有一个custerState结构,这个结构中存储了当前集群的全部数据,比如集群状态、集群中的所有节点信息(主节点、从节点)等等,以下代码列出部分非常重要的属性:

typedef struct clusterState {


? ? // 当前节点指针,指向一个clusterNode

? ? clusterNode *myself;

? ??

? ? // 集群当前配置纪元,用于故障转移,与sentinel中用法类似

? ? unit64_t currentEpoch;

? ??

? ? // 集群状态 在线/下线

? ? int state;

? ??

? ? // 集群中处理着槽的节点数量总和

? ? int size;

? ??

? ? // 集群节点字典,所有clusterNode包括自己

? ? dict *node;

? ??

? ? // 集群中所有槽的指派信息

? ? clsuterNode *slots[16384];

? ??

? ? // 用于槽的重新分配——记录当前节点正在从其他节点导入的槽

? ? clusterNode *importing_slots_from[16384];

? ??

? ? // 用于槽的重新分配——记录当前节点正在迁移至其他节点的槽

? ? clusterNode *migrating_slots_to[16384];

? ??

? ? // ...

? ??

} clusterState;

在custerState有三个结构需要认真了解的,第一个是slots数组,clusterState中的slots数组与clsuterNode中的slots数组是不一样的,在clusterNode中slots数组记录的是当前clusterNode所负责的槽,而clusterState中的slots数组记录的是整个集群的每个槽由哪个clsuterNode负责,因此集群正常工作的时候clusterState的slots数组每个索引指向负责该槽的clusterNode,集群槽未分配之前指向null。

如图展示资源清单中的集群clusterState中的slots数组与clsuterNode中的slots数组:

image

Redis集群中使用两个slots数组的原因是出于性能的考虑:

  • 当我们需要获取整个集群中clusterNode分别负责什么槽时,只需要查询clusterState中的slots数组即可。如果没有clusterState的slots数组,则需要遍历所有的clusterNode结构,这样显然要慢一些
  • 此外clusterNode中的slots数组也有存在的必要,因为集群中任意一个节点之间需要知道彼此负责的槽,此时节点之间只需要互相传输clusterNode中的slots数组结构就行。

第二个需要认真了解的结构是node字典,该结构虽然简单,但是node字典中存储了所有的clusterNode,这也是Redis集群中的单个节点获取其他主节点、从节点信息的主要位置,因此我们也需要注意一下。

第三个需要认真了解的结构是importing_slots_from[16384]数组和migrating_slots_to[16384],这两个数组在集群重新分片时需要使用,需要重点了解,后面再说吧,这里说的话顺序不太对。

3、集群工作

3.1 槽(slot)如何指派?

Redis集群一共16384个槽,如上资源清单我们在三主三从的集群中,每个主节点负责自己相应的槽,而在上面的三主三从部署的过程中并未看到我指定槽给对应的主节点,这是因为Redis集群自己内部给我们划分了槽,但是如果我们想自己指派槽该如何整呢?

我们可以向节点发送如下命令,将一个或多个槽指派给当前节点负责:

CLUSTER ADDSLOTS

比如我们想把0和1槽指派给Master[0],我们只需要想Master[0]节点发送如下命令即可:

CLUSTER ADDSLOTS 0 1

当节点被指派了槽后,会将clusterNode的slots数组更新,节点会将自己负责处理的槽也就是slots数组通过消息发送给集群中的其他节点,其他节点在接收当消息后会更新对应clusterNode的slots数组以及clusterState的solts数组。

3.2 ADDSLOTS 在Redis集群内部是如何实现的呢?

这个其实也比较简单,当我们向Redis集群中的某个节点发送CLUSTER ADDSLOTS命令时,当前节点首先会通过clusterState中的slots数组来确认指派给当前节点的槽是否没有指派给其他节点,如果已经指派了,那么会直接抛出异常,返回错误给指派的客户端。如果指派给当前节点的所有槽都未指派给其他节点,那么当前节点会将这些槽指派给自己。

指派主要有三个步骤:

  1. 更新clusterState的slots数组,将指定槽slots[i]指向当前clusterNode
  2. 更新clusterNode的slots数组,将指定槽slots[i]处的值更新为1
  3. 向集群中的其他节点发送消息,将clusterNode的slots数组发送给其他节点,其他节点接收到消息后也更新对应的clusterState的slots数组和clusterNode的slots数组

3.3 集群这么多节点,客户端怎么知道请求哪个节点?

在了解这个问题之前先要知道一个点,Redis集群是怎么计算当前这个键属于哪个槽的呢?根据官网的介绍,Redis其实并未使用一致性hash算法,而是将每个请求的key通过CRC16校验后对16384取模来决定放置到哪个槽中。

HASH_SLOT = CRC16(key) mod 16384

此时,当客户端连接向某个节点发送请求时,当前接收到命令的节点首先会通过算法计算出当前key所属的槽i,计算完后当前节点会判断clusterState的槽i是否由自己负责,如果恰好由自己负责那么当前节点就会之间响应客户端的请求,如果不由当前节点负责,则会经历如下步骤:

  1. 节点向客户端返回MOVED重定向错误,MOVED重定向错误中会将计算好的正确处理该key的clusterNode的ip和port返回给客户端
  2. 客户端接收到节点返回的MOVED重定向错误时,会根据ip和port将命令转发给正确的节点,整个处理过程对程序员来说透明,由Redis集群的服务端和客户端共同负责完成。

3.4 如果我想将已经分配给A节点的槽重新分配给B节点,怎么整?

这个问题其实涵括了很多问题,比如移除Redis集群中的某些节点,增加节点等都可以概括为把哈希槽从一个节点移动到另外一个节点。并且Redis集群非常牛逼的一点也在这里,它支持在线(不停机)的分配,也就是官方说集群在线重配置(live reconfiguration )。

在将实现之前先来看下CLUSTER的指令,指令会了操作就会了:

  • CLUSTER ADDSLOTS slot1 [slot2] … [slotN]
  • CLUSTER DELSLOTS slot1 [slot2] … [slotN]
  • CLUSTER SETSLOT slot NODE node
  • CLUSTER SETSLOT slot MIGRATING node
  • CLUSTER SETSLOT slot IMPORTING node

CLUSTER 用于槽分配的指令主要有如上这些,ADDSLOTS 和DELSLOTS主要用于槽的快速指派和快速删除,通常我们在集群刚刚建立的时候进行快速分配的时候才使用。CLUSTER SETSLOT slot NODE node也用于直接给指定的节点指派槽。如果集群已经建立我们通常使用最后两个来重分配,其代表的含义如下所示:

  • 当一个槽被设置为 MIGRATING,原来持有该哈希槽的节点仍会接受所有跟这个哈希槽有关的请求,但只有当查询的键还存在原节点时,原节点会处理该请求,否则这个查询会通过一个 -ASK 重定向(-ASK redirection)转发到迁移的目标节点。
  • 当一个槽被设置为 IMPORTING,只有在接受到 ASKING 命令之后节点才会接受所有查询这个哈希槽的请求。如果客户端一直没有发送 ASKING 命令,那么查询都会通过 -MOVED 重定向错误转发到真正处理这个哈希槽的节点那里。

上面这两句话是不是感觉不太看的懂,这是官方的描述,不太懂的话我来给你通俗的描述,整个流程大致如下步骤:

  1. redis-trib(集群管理软件redis-trib会负责Redis集群的槽分配工作),向目标节点(槽导入节点)发送CLUSTER SETSLOT slot IMPORTING node命令,目标节点会做好从源节点(槽导出节点)导入槽的准备工作。
  2. redis-trib随即向源节点发送CLUSTER SETSLOT slot MIGRATING node命令,源节点会做好槽导出准备工作
  3. redis-trib随即向源节点发送CLUSTER GETKEYSINSLOT slot count命令,源节点接收命令后会返回属于槽slot的键,最多返回count个键
  4. redis-trib会根据源节点返回的键向源节点依次发送MIGRATE ip port key 0 timeout命令,如果key在源节点中,将会迁移至目标节点。
  5. 迁移完成之后,redis-trib会向集群中的某个节点发送CLUSTER SETSLOT slot NODE node命令,节点接收到命令后会更新clusterNode和clusterState结构,然后节点通过消息传播槽的指派信息,至此集群槽迁移工作完成,且集群中的其他节点也更新了新的槽分配信息。

3.5 如果客户端访问的key所属的槽正在迁移怎么办?

优秀的你总会想到这种并发情况,牛皮呀!大佬们!

image

这个问题官方也考虑了,还记得我们在聊clusterState结构的时候么?importing_slots_from和migrating_slots_to就是用来处理这个问题的。

typedef struct clusterState {


? ? // ...

? ??

? ? // 用于槽的重新分配——记录当前节点正在从其他节点导入的槽

? ? clusterNode *importing_slots_from[16384];

? ??

? ? // 用于槽的重新分配——记录当前节点正在迁移至其他节点的槽

? ? clusterNode *migrating_slots_to[16384];

? ??

? ? // ...

? ??

} clusterState;

  • 当节点正在导出某个槽,则会在clusterState中的migrating_slots_to数组对应的下标处设置其指向对应的clusterNode,这个clusterNode会指向导入的节点。
  • 当节点正在导入某个槽,则会在clusterState中的importing_slots_from数组对应的下标处设置其指向对应的clusterNode,这个clusterNode会指向导出的节点。

有了上述两个相互数组,就能判断当前槽是否在迁移了,而且从哪里迁移来,要迁移到哪里去?搞笑不就是这么简单……

此时,回到问题中,如果客户端请求的key刚好属于正在迁移的槽。那么接收到命令的节点首先会尝试在自己的数据库中查找键key,如果这个槽还没迁移完成,且当前key刚好也还没迁移完成,那就直接响应客户端的请求就行。如果该key已经不在了,此时节点会去查询migrating_slots_to数组对应的索引槽,如果索引处的值不为null,而是指向了某个clusterNode结构,那说明这个key已经被迁移到这个clusterNode了。这个时候节点不会继续在处理指令,而是返回ASKING命令,这个命令也会携带导入槽clusterNode对应的ip和port。客户端在接收到ASKING命令之后就需要将请求转向正确的节点了,不过这里有一点需要注意的地方(因此我放个表情包在这里,方便读者注意)。

image

前面说了,当节点发现当前槽不属于自己处理时会返回MOVED指令,那么在迁移中的槽时怎么处理的呢?这个Redis集群是这个玩的。

节点发现槽正在迁移则向客户端返回ASKING命令,客户端会接收到ASKING命令,其中包含了槽迁入的clusterNode的节点ip和port。那么客户端首先会向迁入的clusterNode发送一条ASKING命令,这个命令必须要发目的是告诉当前节点,你要破例处理这次请求,因为这个槽已经迁移到你这里了,你不能直接拒绝我(因此如果Redis未接收到ASKING命令,会直接查询节点的clusterState,而正在迁移中的槽还没有更新到clusterState中,那么只能直接返回MOVED,这样不就会一直循环很多次……),接收到ASKING命令的节点会强制执行一次这个请求(只执行一次,下次再来需要重新提前发送ASKING命令)。

4、集群故障

Redis集群故障比较简单,这个和sentinel中主节点宕机或者在指定最长时间内未响应,重新在从节点中选举新的主节点的方式其实差不多。当然前提是Redis集群中的每个主节点,我们提前设置了从节点,要不就嘿嘿嘿……没戏。其大致步骤如下:

  1. 正常工作的集群,每个节点之间会定期向其他节点发送PING命令,如果接收命令的节点未在规定时间内返回PONG消息 ,当前节点会将接收命令的节点的clusterNode的flags设置为REDIS_NODE_PFAIL,PFAIL并不是下线,而是疑似下线。
  2. 集群节点会通过发送消息的方式来告知其他节点,集群中各个节点的状态信息
  3. 如果集群中半数以上负责处理槽的主节点都将某个主节点设置为疑似下线,那么这个节点将会被标记位下线状态,节点会将接收命令的节点的clusterNode的flags设置为REDIS_NODE_FAIL,FAIL表示已下线
  4. 集群节点通过发送消息的方式来告知其他节点,集群中各个节点的状态信息,此时下线节点的从节点在发现自己的主节点已经被标记为下线状态了,那么是时候挺身而出了
  5. 下线主节点的从节点,会选举出一个从节点作为最新的主节点,执行被选中的节点指向SLAVEOF no one成为新的主节点
  6. 新的主节点会撤销掉原主节点的槽指派,并将这些槽指派修改为自己,也就是修改clusterNode结构和clusterState结构
  7. 新的主节点向集群广播一条PONG指令,其他节点将会知道有新的主节点产生,并更新clusterNode结构和clusterState结构
  8. 新的主节点如果会向原主节点剩余的从节点发送新的SLAVEOF指令,使其成为自己的从节点
  9. 最后新的主节点将会负责原主节点的槽的响应工作

这里我写得非常模糊,如果需要细致挖掘的一定要看这篇文章:

http://redis.cn/topics/cluster-spec.html

或者可以看下黄健宏老师的《Redis设计与实现》这本书写得挺好,我也参考了很多内容。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
7834 0
使用NAT网关轻松为单台云服务器设置多个公网IP
在应用中,有时会遇到用户询问如何使单台云服务器具备多个公网IP的问题。 具体如何操作呢,有了NAT网关这个也不是难题。
25944 0
使用OpenApi弹性释放和设置云服务器ECS释放
云服务器ECS的一个重要特性就是按需创建资源。您可以在业务高峰期按需弹性的自定义规则进行资源创建,在完成业务计算的时候释放资源。本篇将提供几个Tips帮助您更加容易和自动化的完成云服务器的释放和弹性设置。
11660 0
windows server 2008阿里云ECS服务器安全设置
最近我们Sinesafe安全公司在为客户使用阿里云ecs服务器做安全的过程中,发现服务器基础安全性都没有做。为了为站长们提供更加有效的安全基础解决方案,我们Sinesafe将对阿里云服务器win2008 系统进行基础安全部署实战过程! 比较重要的几部分 1.
8167 0
阿里云服务器安全组设置内网互通的方法
虽然0.0.0.0/0使用非常方便,但是发现很多同学使用它来做内网互通,这是有安全风险的,实例有可能会在经典网络被内网IP访问到。下面介绍一下四种安全的内网互联设置方法。 购买前请先:领取阿里云幸运券,有很多优惠,可到下文中领取。
11716 0
腾讯云服务器 设置ngxin + fastdfs +tomcat 开机自启动
在tomcat中新建一个可以启动的 .sh 脚本文件 /usr/local/tomcat7/bin/ export JAVA_HOME=/usr/local/java/jdk7 export PATH=$JAVA_HOME/bin/:$PATH export CLASSPATH=.
4496 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
6425 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,云吞铺子总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系统盘、创建快照、配置安全组等操作如何登录ECS云服务器控制台? 1、先登录到阿里云ECS服务器控制台 2、点击顶部的“控制台” 3、通过左侧栏,切换到“云服务器ECS”即可,如下图所示 通过ECS控制台的远程连接来登录到云服务器 阿里云ECS云服务器自带远程连接功能,使用该功能可以登录到云服务器,简单且方便,如下图:点击“远程连接”,第一次连接会自动生成6位数字密码,输入密码即可登录到云服务器上。
21019 0
+关注
李子捌
简介: CSDN优秀作者、华为云专家 领域: Java框架、并发编程、分布式、微服务、Redis、HarmonyOS、中间件等技术
240
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载
http://www.vxiaotou.com