导读
前面文章【一、深入理解redis之需要掌握的知识点 】中,我们对redis需要学习的内容框架进行了一个梳理。
【二、redis中String和List两种数据类型和应用场景 】、【二、redis中Hash、Set、SortedSet应用场景 】两篇文章我们对redis中String、List、Hash、Set、SortedSet五种数据类型做了一下讲解,并且对他们各自的应用场景进行了介绍。
【三、redis数据存储之跳跃表(SKIP LIST) 】深入学习了支撑SortedSet排序背后的数据结构,跳跃表;
【四、redis持久化之RDB与AOF 】学习了redis中的两种持久化策略:RDB(快照)和AOF(追加日志);
【五、redis集群进化过程 】文章中我们学习了redis集群的进化过程,包括解决单点故障问题和性能瓶颈问题等。
【六、redis中AKF问题解决方案 】讲解了Redis使用及集群进化过程中AKF问题的解决方案。
【七、redis中CAP问题解决方案-Paxos理论过半通过 】我们讲解了Redis使用过程中出现CAP问题的解决方案,以及初步认识Paxos分布式一致性协议。
【八、redis中布式锁的实现及原理 】讲解了redis中布式锁的理论、原理及解决方案。
【九、redis缓存的回收策略-LRU算法】我们讲解了redis中如何配置缓存回收策略及回收策略的几种模式,另外还要讲解回收策略的执行过程和redis中实际使用的回收策略。
【十、redis中的哨兵模式(Sentinel)】中讲解了Redis中的哨兵模式,包括如何配置哨兵模式、哨兵模式下的客观下线和主观下线及故障转移策略等。
【十一、redis中的事物】讲解了redis中的事物,包括如何开启事物,如何使用事物做日常操作等。
从【十二、redis集群(①初识)】开始,我们学习redis集群,包括redis集群的使用、搭建及redis集群内部原理等。
【十二、redis集群(①初识)】中主要先初识redis集群,了解他的优势及模型等;【十二、redis集群(②相遇)】文章中我们主要学习,如何搭建redis集群、如何使用redis集群、redis集群中数据的键是如何分配的以及redis集群中每个节点的属性。
本章我们将要开始深入了解redis集群节点的拓扑结构以及节点之间数据交互及重定向等。如果大家在工作、学习、面试中针对redis还有什么疑问或者其他问题,可以评论区告诉我。为了保证可以连续不间断地获取最新的技术分析及讲解,建议关注本博客【不吃_花椒】。
集群拓扑结构->
Redis 集群是一个网状结构,每个节点都通过 TCP 连接跟其他每个节点连接。在一个有 N 个节点的集群中,每个节点都有 N-1 个流出的 TCP 连接,和 N-1 个流入的连接
。 这些 TCP 连接会永久保持,并不是按需创建的。
节点握手
节点总是在集群连接端口接受连接,甚至会回复接收到的 ping 包,即使发送 ping 包的节点是不可信的。 然而如果某个节点不被认为是在集群中,那么所有它发出的数据包都会被丢弃掉。
只有在两种方式下,一个节点才会认为另一个节点是集群中的一部分:
当一个节点使用 MEET 消息介绍自己
。一个 meet 消息跟一个 PING 消息完全一样,但它会强制让接收者接受发送者为集群中的一部分。只有在系统管理员使用以下命令要求的时候,节点才会发送 MEET 消息给其他节点;一个已被信任的节点能通过传播gossip消息让另一个节点被注册为集群中的一部分
。也就是说,如果 A 知道 B,B 知道 C,那么 B 会向A 发送 C 的gossip消息。A 收到后就会把 C 当作是网络中的一部分,并且尝试连接 C。这意味着,只要我们往任何连接图中加入节点,它们最终会自动形成一个完全连接图。从根本上来说,这表示集群能自动发现其他节点,但前提是有一个由系统管理员强制创建的信任关系。这个机制能防止不同的 Redis 集群因为 IP 地址变更或者其他网络事件而意外混合起来,从而使集群更具健壮性。当节点的网络连接断掉时,它会积极尝试连接所有其他已知节点。
MOVED 重定向
一个 Redis 客户端可以自由地向集群中的任意节点(包括从节点)发送查询。接收的节点会分析查询,如果这个命令是集群可以执行的(就是查询中只涉及一个键),那么节点会找这个键所属的哈希槽对应的节点。
如果刚好这个节点就是对应这个哈希槽,那么这个查询就直接被节点处理掉。否则这个节点会查看它内部的 哈希槽 -> 节点ID 映射,然后给客户端返回一个 MOVED 错误
。
注意,当集群是稳定的时候,所有客户端最终都会得到一份哈希槽 -> 节点的映射表
,这样能使得集群效率非常高:客户端直接定位目标节点,不用重定向、或代理或发生其他单点故障(single point of failure entities)。
一个客户端也应该能处理本文后面将提到的 -ASK 重定向错误。
集群在线重配置(live reconfiguration)
Redis 集群支持在集群运行过程中添加或移除节点。实际上,添加或移除节点都被抽象为同一个操作,那就是把哈希槽从一个节点移到另一个节点。
- 向集群添加一个新节点,就是把一个空节点加入到集群中并把某些哈希槽从已存在的节点移到新节点上。
- 从集群中移除一个节点,就是把该节点上的哈希槽移到其他已存在的节点上。
- 所以实现这个的核心是能把哈希槽移来移去。从实际角度看,哈希槽就只是一堆键,所以 Redis
集群在重组碎片(reshard)时做的就是把键从一个节点移到另一个节点。
为了理解这是怎么工作的,我们需要介绍 CLUSTER
的子命令,这些命令是用来操作 Redis 集群节点上的哈希槽转换表(slots translation table)。
以下是可用的子命令:
- 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
①ADDSLOTS 和 DELSLOTS,就是简单地用来给一个 Redis 节点指派(assign)或移除哈希槽。 在哈希槽被指派后,节点会将这个消息通过 gossip 协议向整个集群传播。ADDSLOTS 命令通常是用于在一个集群刚建立的时候快速给所有节点指派哈希槽。
②当 SETSLOT 子命令使用 NODE 形式的时候,用来给指定 ID 的节点指派哈希槽。 除此之外哈希槽能通过两个特殊的状态来设定,MIGRATING 和 IMPORTING:
- 当一个槽被设置为 MIGRATING,原来持有该哈希槽的节点仍会接受所有跟这个哈希槽有关的请求,但只有当查询的键还存在原节点时,原节点会处理该请求,否则这个查询会通过一个 -ASK 重定向(-ASK redirection)转发到迁移的目标节点。
- 当一个槽被设置为 IMPORTING,只有在接受到 ASKING 命令之后节点才会接受所有查询这个哈希槽的请求。如果客户端一直没有发送ASKING 命令,那么查询都会通过 -MOVED 重定向错误转发到真正处理这个哈希槽的节点那里。
③这么讲可能显得有点奇怪,现在我们用实例让它更清晰些。假设我们有两个 Redis 节点,称为 A 和 B。我们想要把哈希槽 8 从 节点A 移到 节点B,所以我们发送了这样的命令:
- 我们向 节点B 发送:CLUSTER SETSLOT 8 IMPORTING A
- 我们向 节点A 发送:CLUSTER SETSLOT 8 MIGRATING B
其他所有节点在每次被询问到的一个键是属于哈希槽 8 的时候,都会把客户端引向节点”A”。具体如下:
- 所有关于已存在的键的查询都由节点”A”处理。
- 所有关于不存在于节点 A 的键都由节点”B”处理。
这种方式让我们可以不用在节点 A 中创建新的键。同时,一个叫做 redis-trib
的特殊客户端,它也是 Redis 集群的配置程序(configuration utility),会确保把已存在的键从节点 A 移到节点 B。这通过以下命令实现:
CLUSTER GETKEYSINSLOT slot count
上面这个命令会返回指定的哈希槽中 count
个键。对于每个返回的键,redis-trib 向节点 A 发送一个 MIGRATE 命令,这样会以原子性
的方式(在移动键的过程中两个节点都被锁住,以免出现竞争状况)把指定的键从节点 A 移到节点 B。以下是 MIGRATE 的工作原理:
MIGRATE target_host target_port key target_database id timeout
执行 MIGRATE 命令的节点会连接到目标节点,把序列化后的 key 发送过去,一旦收到 OK 回复就会从它自己的数据集中删除老的 key。所以从一个外部客户端看来,在某个时间点,一个 key 要不就存在于节点 A 中要不就存在于节点 B 中。
在 Redis 集群中,不需要指定一个除了 0 号之外的数据库,但 MIGRATE 命令能用于其他跟 Redis 集群无关的的任务,所以它是一个足够通用的命令。MIGRATE 命令被优化了,使得即使在移动像长列表这样的复杂键仍然能做到快速。 不过当在重配置一个拥有很多键且键的数据量都很大的集群的时候,这个过程就并不那么好了,对于使用数据库的应用程序来说就会有延时这个限制。
ASK 重定向
在前面的章节中,我们简短地提到了 ASK 重定向(ASK redirection),为什么我们不能单纯地使用 MOVED 重定向呢?因为当我们使用 MOVED 的时候,意味着我们认为哈希槽永久地被另一个不同的节点处理,并且希望接下来的所有查询都尝试发到这个指定的节点上去。而 ASK 意味着我们只要下一个查询发送到指定节点上去。
这个命令是必要的,因为下一个关于哈希槽 8 的查询需要的键或许还在节点 A 中,所以我们希望客户端尝试在节点 A 中查找,如果需要的话也在节点 B 中查找。 由于这是发生在 16384 个槽的其中一个槽,所以对于集群的性能影响是在可接受的范围。
然而我们需要强制客户端的行为,以确保客户端会在尝试 A 中查找后去尝试在 B 中查找。如果客户端在发送查询前发送了 ASKING 命令,那么节点 B 只会接受被设为 IMPORTING 的槽的查询。 本质上来说,ASKING 命令在客户端设置了一个一次性标识(one-time flag),强制一个节点可以执行一次关于带有 IMPORTING 状态的槽的查询。
所以从客户端看来,ASK 重定向的完整语义如下:
- 如果接受到 ASK 重定向,那么把查询的对象调整为指定的节点。
- 先发送 ASKING 命令,再开始发送查询。
- 现在不要更新本地客户端的映射表把哈希槽 8 映射到节点 B。
一旦完成了哈希槽 8 的转移,节点 A 会发送一个 MOVED 消息,客户端也许会永久地把哈希槽 8 映射到新的 ip:端口号 上。 注意,即使客户端出现bug,过早地执行这个映射更新,也是没有问题的,因为它不会在查询前发送 ASKING 命令,节点 B 会用 MOVED 重定向错误把客户端重定向到节点 A 上。
如需了解更多更详细内容也可关注本人CSDN博客:不吃_花椒
后续redis中将要讲解的内容梳理
往期文章
Redis
二、redis中String和List两种数据类型和应用场景
二、redis中基础数据类型Hash、Set、SortedSet及其应用场景
Java集合
三、JDK1.7和1.8HashMap数据结构及源码分析-续
Java-IO体系
七、IDEA的maven项目的netty包的导入(其他jar同)
十一、JAVA中ServerSocket调用Linux系统内核
十四、使用Selector(多路复用器)实现Netty中Reactor单线程模型
十五、使用Selector(多路复用器)实现Netty中Reactor主从模型
十七、IO进化过程之EVENT(EPOLL-事件驱动异步模型)
如需了解更多更详细内容也可关注本人CSDN博客:不吃_花椒